#![expect( clippy::tests_outside_test_module, reason = "top-level test functions are conventional for integration tests" )] //! Single-zone state-machine tests for cross-zone delivery (ping demo) and the //! wrapped-token bridge (Demo 2). They drive the guests in isolation, no watcher //! or Bedrock: a hand-built `cross_zone_inbox::Dispatch` (as the watcher would //! inject) and the source `bridge_lock::Lock` (which escrows and chains //! `outbox::Emit`). Fast, so they pin guest logic before the e2e exercises the //! plumbing. Run with `RISC0_DEV_MODE=1`. use cross_zone_inbox_core::{ CrossZoneMessage, InboxConfig, Instruction as InboxInstruction, SeenShard, inbox_config_account_id, inbox_seen_shard_account_id, }; use cross_zone_marker_core::inbox_source_marker_account_id; use cross_zone_outbox_core::{OutboxRecord, outbox_pda}; use lee::{ AccountId, PrivateKey, PublicKey, PublicTransaction, V03State, ValidatedStateDiff, public_transaction::{Message, WitnessSet}, }; use lee_core::account::Account; use ping_core::{ ReceiverInstruction, outbox_bytes, ping_record_pda, read_outbox, receiver_config_account_id, sender_config_account_id, }; /// Serializes an instruction to the borsh bytes the guests read. macro_rules! bytes_of { ($instruction:expr) => { borsh::to_vec($instruction).expect("serialize instruction") }; } const INITIAL_BALANCE: u128 = 100; const LOCK_AMOUNT: u128 = 30; const RECIPIENT: [u8; 32] = [9; 32]; /// These tests drive the guest directly, so any fixed source-block hash does. const SRC_BLOCK_HASH: [u8; 32] = [7; 32]; /// State registering the cross-zone builtins these tests exercise. fn base_state() -> V03State { V03State::new().with_programs([ programs::cross_zone_inbox(), programs::cross_zone_outbox(), programs::ping_sender(), programs::ping_receiver(), programs::bridge_lock(), programs::wrapped_token(), ]) } /// Seeds the inbox config (inbox-owned), which is now just this zone's id. fn seed_inbox_config(state: &mut V03State, self_zone: [u8; 32]) { let inbox_id = programs::cross_zone_inbox().id(); let config = InboxConfig { self_zone }; *state = std::mem::replace(state, V03State::new()).with_public_accounts([( inbox_config_account_id(inbox_id), Account { program_owner: inbox_id.into(), balance: 0, data: config .to_bytes() .try_into() .expect("config fits in account data"), nonce: 0_u128.into(), }, )]); } /// Seeds the wrapped-token config pinning the inbox as minter and `sources` as the /// peer pairs it will mint for, matching what genesis seeds for a real zone. fn seed_wrapped_config( state: &mut V03State, authority: Option, sources: Vec<([u8; 32], lee_core::program::ProgramId)>, ) { seed_wrapped_config_with_governance(state, None, authority, sources); } /// The same, naming a program allowed to act for the authority through a chain. fn seed_wrapped_config_with_governance( state: &mut V03State, governance: Option, authority: Option, sources: Vec<([u8; 32], lee_core::program::ProgramId)>, ) { let wrapped_token_id = programs::wrapped_token().id(); let config = wrapped_token_core::WrappedTokenConfig { minter: programs::cross_zone_inbox().id(), governance, authority, sources, }; *state = std::mem::replace(state, V03State::new()).with_public_accounts([( wrapped_token_core::config_account_id(wrapped_token_id), Account { program_owner: wrapped_token_id.into(), data: config .to_bytes() .try_into() .expect("wrapped-token config fits in account data"), ..Default::default() }, )]); } /// Seeds the ping-receiver config pinning the inbox as deliverer and `sources` as /// the peer pairs it accepts a delivery from. fn seed_receiver_config( state: &mut V03State, authority: Option, sources: Vec<([u8; 32], lee_core::program::ProgramId)>, ) { seed_receiver_config_with_governance(state, None, authority, sources); } /// The same, naming a program allowed to act for the authority through a chain. fn seed_receiver_config_with_governance( state: &mut V03State, governance: Option, authority: Option, sources: Vec<([u8; 32], lee_core::program::ProgramId)>, ) { let receiver_id = programs::ping_receiver().id(); let config = ping_core::ReceiverConfig { deliverer: programs::cross_zone_inbox().id(), governance, authority, sources, }; *state = std::mem::replace(state, V03State::new()).with_public_accounts([( receiver_config_account_id(receiver_id), Account { program_owner: receiver_id.into(), data: config .to_bytes() .try_into() .expect("receiver config fits in account data"), ..Default::default() }, )]); } /// Seeds the ping-sender config account pinning the real outbox, matching what /// genesis seeds for a real zone. fn seed_ping_sender_config(state: &mut V03State) { let sender_id = programs::ping_sender().id(); *state = std::mem::replace(state, V03State::new()).with_public_accounts([( sender_config_account_id(sender_id), Account { program_owner: sender_id.into(), data: outbox_bytes(programs::cross_zone_outbox().id()) .to_vec() .try_into() .expect("outbox id fits in account data"), ..Default::default() }, )]); } /// Seeds the bridge-lock config account pinning the real outbox and the wrapped /// token, matching what genesis seeds for a real zone. fn seed_bridge_lock_config(state: &mut V03State) { let bridge_lock_id = programs::bridge_lock().id(); *state = std::mem::replace(state, V03State::new()).with_public_accounts([( bridge_lock_core::config_account_id(bridge_lock_id), Account { program_owner: bridge_lock_id.into(), data: bridge_lock_core::config_bytes( programs::cross_zone_outbox().id(), programs::wrapped_token().id(), ) .to_vec() .try_into() .expect("pinned ids fit in account data"), ..Default::default() }, )]); } /// The account list a dispatch declares, mirroring `cross_zone::build_inbox_dispatch_tx`: /// config, seen shard, source marker, then the target's own accounts. fn dispatch_accounts( inbox_id: lee_core::program::ProgramId, msg: &CrossZoneMessage, targets: Vec, ) -> Vec { let mut ids = vec![ inbox_config_account_id(inbox_id), inbox_seen_shard_account_id(inbox_id, &msg.src_zone, msg.src_block_id), inbox_source_marker_account_id(inbox_id, &msg.src_zone, msg.src_program_id), ]; ids.extend(targets); ids } /// Asserts the transaction fails at `block` with an error mentioning `expected`, /// so a refusal for an unrelated reason cannot keep a guard test green. fn rejects_at(state: &V03State, tx: &PublicTransaction, block: u64, expected: &str) { let Err(err) = ValidatedStateDiff::from_public_transaction(tx, state, block, 0) else { panic!("expected a rejection mentioning {expected}"); }; assert!( format!("{err:?}").contains(expected), "rejected for the wrong reason: {err:?}" ); } /// A top-level authority transaction: the instruction bytes over `accounts`, /// signed by `key` at `nonce`. fn signed_tx( program: lee_core::program::ProgramId, accounts: Vec, nonce: u128, instruction_data: Vec, key: &PrivateKey, ) -> PublicTransaction { let message = Message::new_preserialized(program, accounts, vec![nonce.into()], instruction_data); let witness = WitnessSet::for_message(&message, &[key]); PublicTransaction::new(message, witness) } /// An unsigned call through the governance proxy, delegating `delegated` (or /// nothing) on the chained call into `target`. fn via_proxy( proxy_id: lee_core::program::ProgramId, target: lee_core::program::ProgramId, config: AccountId, authority: AccountId, delegated: Option, instruction_data: Vec, ) -> PublicTransaction { let message = Message::try_new( proxy_id, vec![config, authority], vec![], (target, instruction_data, delegated), ) .expect("build proxy message"); PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])) } /// An authority instruction delivered through the inbox, as a peer would have to /// send it: the dispatch shape over the target's config and authority accounts. fn chained_via_inbox( target: lee_core::program::ProgramId, config_id: AccountId, authority: AccountId, instruction_data: Vec, ) -> PublicTransaction { let inbox_id = programs::cross_zone_inbox().id(); let msg = CrossZoneMessage { src_zone: [2; 32], src_block_id: 5, src_block_hash: SRC_BLOCK_HASH, src_tx_index: 0, src_program_id: programs::bridge_lock().id(), target_program_id: target, payload: instruction_data, l1_inclusion_witness: None, }; let message = Message::try_new( inbox_id, dispatch_accounts(inbox_id, &msg, vec![config_id, authority]), vec![], InboxInstruction::Dispatch(msg), ) .expect("build dispatch message"); PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])) } /// A `ping_sender::Send` carrying `payload` to `target_zone`, over the accounts /// given rather than the correct ones, so tests can vary them. fn send_tx(accounts: Vec, target_zone: [u8; 32], ordinal: u32) -> PublicTransaction { let receiver_id = programs::ping_receiver().id(); let payload = borsh::to_vec(&ReceiverInstruction::Record { payload: b"ping".to_vec(), }) .expect("serialize ping instruction"); let send = ping_core::SenderInstruction::Send { target_zone, target_program_id: receiver_id, target_accounts: vec![ receiver_config_account_id(receiver_id).into_value(), ping_record_pda(receiver_id).into_value(), ], payload, ordinal, }; let message = Message::try_new(programs::ping_sender().id(), accounts, vec![], send) .expect("build ping_sender message"); PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])) } /// The wrapped-token `Mint` the bridge forwards, serialized as the cross-zone /// payload (borsh bytes). fn mint_payload() -> Vec { mint_payload_of(LOCK_AMOUNT) } fn mint_payload_of(amount: u128) -> Vec { let mint = wrapped_token_core::Instruction::Mint { recipient: RECIPIENT, amount, }; borsh::to_vec(&mint).expect("serialize mint") } /// Runs a bridge mint of `amount` through the inbox, as the watcher would. fn dispatch_mint(amount: u128) -> Result { let inbox_id = programs::cross_zone_inbox().id(); let wrapped_token_id = programs::wrapped_token().id(); let self_zone = [1_u8; 32]; let src_zone = [2_u8; 32]; let src_block_id = 5; let mut state = base_state(); seed_inbox_config(&mut state, self_zone); seed_wrapped_config(&mut state, None, vec![(src_zone, [9_u32; 8])]); let msg = CrossZoneMessage { src_zone, src_block_id, src_block_hash: SRC_BLOCK_HASH, src_tx_index: 0, src_program_id: [9_u32; 8], target_program_id: wrapped_token_id, payload: mint_payload_of(amount), l1_inclusion_witness: None, }; let message = Message::try_new( inbox_id, dispatch_accounts( inbox_id, &msg, vec![ wrapped_token_core::config_account_id(wrapped_token_id), wrapped_token_core::holding_account_id(wrapped_token_id, &RECIPIENT), ], ), vec![], InboxInstruction::Dispatch(msg), ) .expect("build dispatch message"); let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])); ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) } /// One message must not be able to pin a holding near `u128::MAX`, which would /// make every later honest mint to that recipient overflow and fail for good. #[test] fn a_mint_above_the_cap_is_rejected() { assert!( dispatch_mint(wrapped_token_core::MAX_MINT_AMOUNT + 1).is_err(), "an amount over the per-mint cap must not execute" ); } #[test] fn a_mint_at_the_cap_is_accepted() { let diff = dispatch_mint(wrapped_token_core::MAX_MINT_AMOUNT) .expect("the cap itself is a legitimate amount"); let holding_id = wrapped_token_core::holding_account_id(programs::wrapped_token().id(), &RECIPIENT); let minted = wrapped_token_core::read_balance( &diff.public_diff()[&holding_id].data.clone().into_inner(), ); assert_eq!(minted, wrapped_token_core::MAX_MINT_AMOUNT); } /// Drives `cross_zone_inbox::Dispatch` directly through the state machine /// (no watcher) and asserts the message is delivered to `ping_receiver`, which /// records the payload into its own PDA. #[test] fn inbox_dispatch_delivers_payload_to_ping_receiver() { let inbox_id = programs::cross_zone_inbox().id(); let receiver_id = programs::ping_receiver().id(); let self_zone = [1_u8; 32]; let src_zone = [2_u8; 32]; let src_block_id = 5; let mut state = base_state(); seed_inbox_config(&mut state, self_zone); seed_receiver_config(&mut state, None, vec![(src_zone, [9_u32; 8])]); // The payload is the ping_receiver instruction, borsh-serialized into instruction_data bytes. let inner = b"hello-cross-zone".to_vec(); let payload = borsh::to_vec(&ReceiverInstruction::Record { payload: inner.clone(), }) .expect("serialize ping instruction"); let msg = CrossZoneMessage { src_zone, src_block_id, src_block_hash: SRC_BLOCK_HASH, src_tx_index: 0, src_program_id: [9_u32; 8], target_program_id: receiver_id, payload, l1_inclusion_witness: None, }; let record_id = ping_record_pda(receiver_id); let message = Message::try_new( inbox_id, dispatch_accounts( inbox_id, &msg, vec![receiver_config_account_id(receiver_id), record_id], ), vec![], InboxInstruction::Dispatch(msg), ) .expect("build dispatch message"); let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])); let diff = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) .expect("dispatch must validate and execute"); let record = diff .public_diff() .get(&record_id) .expect("ping record account must change") .clone(); assert_eq!( record.data.into_inner(), inner, "ping_receiver must record the delivered payload" ); } /// Drives `bridge_lock::Lock` and asserts it debits the holder, credits the /// escrow, and records the forwarded mint in the outbox PDA. #[test] fn lock_escrows_balance_and_emits_to_outbox() { let bridge_lock_id = programs::bridge_lock().id(); let wrapped_token_id = programs::wrapped_token().id(); let outbox_id = programs::cross_zone_outbox().id(); let zone_b = [2_u8; 32]; let ordinal = 0; let mut state = base_state(); let holder_key = PrivateKey::try_new([7; 32]).expect("valid key"); let holder_id = AccountId::from(&PublicKey::new_from_private_key(&holder_key)); state = state.with_public_accounts([( holder_id, Account { program_owner: bridge_lock_id.into(), balance: INITIAL_BALANCE, ..Default::default() }, )]); seed_bridge_lock_config(&mut state); let payload = mint_payload(); let escrow_id = bridge_lock_core::escrow_account_id(bridge_lock_id); let outbox_record_id = outbox_pda(outbox_id, bridge_lock_id, &zone_b, ordinal); let tx = lock_tx(&holder_key, holder_id, zone_b, ordinal, 0); let diff = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) .expect("lock must validate and execute"); let public_diff = diff.public_diff(); let holder_after = public_diff[&holder_id].balance; assert_eq!( holder_after, INITIAL_BALANCE - LOCK_AMOUNT, "holder debited" ); let escrow_after = public_diff[&escrow_id].balance; assert_eq!(escrow_after, LOCK_AMOUNT, "escrow credited"); let record = OutboxRecord::from_bytes(&public_diff[&outbox_record_id].data.clone().into_inner()) .expect("outbox PDA holds an OutboxRecord"); assert_eq!( record.emitter, bridge_lock_id, "the record names the program that emitted it" ); assert_eq!(record.target_zone, zone_b); assert_eq!(record.ordinal, ordinal); assert_eq!(record.target_program_id, wrapped_token_id); assert_eq!( record.payload, payload, "emitted payload is the wrapped mint" ); } /// A `bridge_lock::Lock` emitting to `(zone_b, ordinal)`, ready to run twice. fn lock_tx( holder_key: &PrivateKey, holder_id: AccountId, zone_b: [u8; 32], ordinal: u32, nonce: u128, ) -> PublicTransaction { let wrapped_token_id = programs::wrapped_token().id(); lock_tx_to( holder_key, holder_id, zone_b, ordinal, nonce, wrapped_token_id, mint_target_accounts(wrapped_token_id), ) } /// The mint's own account list: the wrapped-token config, then the recipient's /// holding. What `wrapped_token::Mint` requires on the destination zone. fn mint_target_accounts(wrapped_token_id: lee_core::program::ProgramId) -> Vec<[u8; 32]> { vec![ wrapped_token_core::config_account_id(wrapped_token_id).into_value(), wrapped_token_core::holding_account_id(wrapped_token_id, &RECIPIENT).into_value(), ] } /// The same lock aimed at `target_program_id` over `target_accounts`, so a test /// can vary what the destination would be asked to do. fn lock_tx_to( holder_key: &PrivateKey, holder_id: AccountId, zone_b: [u8; 32], ordinal: u32, nonce: u128, target_program_id: lee_core::program::ProgramId, target_accounts: Vec<[u8; 32]>, ) -> PublicTransaction { let bridge_lock_id = programs::bridge_lock().id(); let outbox_id = programs::cross_zone_outbox().id(); let lock = bridge_lock_core::Instruction::Lock { amount: LOCK_AMOUNT, target_zone: zone_b, target_program_id, target_accounts, payload: mint_payload(), ordinal, }; let message = Message::try_new( bridge_lock_id, vec![ bridge_lock_core::config_account_id(bridge_lock_id), holder_id, bridge_lock_core::escrow_account_id(bridge_lock_id), outbox_pda(outbox_id, bridge_lock_id, &zone_b, ordinal), ], vec![nonce.into()], lock, ) .expect("build lock message"); let witness = WitnessSet::for_message(&message, &[holder_key]); PublicTransaction::new(message, witness) } /// A slot holds one message for ever, so a second emission into it fails rather /// than replacing the record. Without this a later emitter silently destroys an /// earlier one, and for a bridge that means an escrow with no record of what it /// was for. #[test] fn a_second_emit_at_the_same_slot_is_rejected() { let zone_b = [2_u8; 32]; let ordinal = 0; let holder_key = PrivateKey::try_new([7; 32]).expect("valid key"); let holder_id = AccountId::from(&PublicKey::new_from_private_key(&holder_key)); let mut state = base_state().with_public_accounts([( holder_id, Account { program_owner: programs::bridge_lock().id().into(), balance: INITIAL_BALANCE, ..Default::default() }, )]); seed_bridge_lock_config(&mut state); let first = lock_tx(&holder_key, holder_id, zone_b, ordinal, 0); let diff = ValidatedStateDiff::from_public_transaction(&first, &state, 1, 0) .expect("the first lock executes"); state.apply_state_diff(diff); // Same slot, fresh nonce, so the only thing that can reject it is the slot // already holding a record. Matched on the guest's own message rather than // any error, or a future change that rejected it earlier for an unrelated // reason would keep this passing. let second = lock_tx(&holder_key, holder_id, zone_b, ordinal, 1); let Err(err) = ValidatedStateDiff::from_public_transaction(&second, &state, 2, 0) else { panic!("a second emission into a written slot must not execute"); }; assert!( format!("{err:?}").contains("Outbox slot already written"), "rejected for the wrong reason: {err:?}" ); // Control: the same second lock into a fresh ordinal executes, so the // refusal above is the slot and not the transaction's shape. let elsewhere = lock_tx(&holder_key, holder_id, zone_b, ordinal + 1, 1); ValidatedStateDiff::from_public_transaction(&elsewhere, &state, 2, 0) .expect("a lock into an unwritten slot executes"); } /// Two programs emitting to one zone and ordinal address two different slots, /// so neither can overwrite or block the other. #[test] fn two_emitters_share_an_ordinal_without_colliding() { let outbox_id = programs::cross_zone_outbox().id(); let sender_id = programs::ping_sender().id(); let bridge_lock_id = programs::bridge_lock().id(); let receiver_id = programs::ping_receiver().id(); let zone_b = [2_u8; 32]; let ordinal = 0; let holder_key = PrivateKey::try_new([7; 32]).expect("valid key"); let holder_id = AccountId::from(&PublicKey::new_from_private_key(&holder_key)); let mut state = base_state().with_public_accounts([( holder_id, Account { program_owner: bridge_lock_id.into(), balance: INITIAL_BALANCE, ..Default::default() }, )]); seed_ping_sender_config(&mut state); seed_bridge_lock_config(&mut state); let lock_slot = outbox_pda(outbox_id, bridge_lock_id, &zone_b, ordinal); let send_slot = outbox_pda(outbox_id, sender_id, &zone_b, ordinal); assert_ne!( lock_slot, send_slot, "the same zone and ordinal under two emitters are two slots" ); let lock = lock_tx(&holder_key, holder_id, zone_b, ordinal, 0); let diff = ValidatedStateDiff::from_public_transaction(&lock, &state, 1, 0) .expect("the lock executes"); state.apply_state_diff(diff); let send = send_tx( vec![sender_config_account_id(sender_id), send_slot], zone_b, ordinal, ); let send_diff = ValidatedStateDiff::from_public_transaction(&send, &state, 2, 0) .expect("the send executes into its own slot, not the lock's"); let record = OutboxRecord::from_bytes( &send_diff.public_diff()[&send_slot] .data .clone() .into_inner(), ) .expect("outbox PDA holds an OutboxRecord"); assert_eq!(record.emitter, sender_id); assert_eq!(record.target_program_id, receiver_id); // And the lock's own slot is untouched by it. let lock_record = OutboxRecord::from_bytes(&state.get_account_by_id(lock_slot).data.into_inner()) .expect("the lock's record survives"); assert_eq!(lock_record.emitter, bridge_lock_id); } /// A caller can no longer aim an emission at a program of their own and still /// succeed, leaving no record of it. With the program no longer an instruction /// field, the account is the only way left to try. #[test] fn a_send_into_a_foreign_outbox_slot_is_rejected() { let sender_id = programs::ping_sender().id(); let zone_b = [2_u8; 32]; let ordinal = 0; let mut state = base_state(); seed_ping_sender_config(&mut state); // A slot under some other program, which is what the caller would have to // pass to reach it. let foreign_slot = outbox_pda([3; 8], sender_id, &zone_b, ordinal); let send = send_tx( vec![sender_config_account_id(sender_id), foreign_slot], zone_b, ordinal, ); // Refused inside the pinned outbox, not by the sender: the chained call goes // there whatever account the caller passes, which is the point. let Err(err) = ValidatedStateDiff::from_public_transaction(&send, &state, 1, 0) else { panic!("a send into a slot outside the pinned outbox must not execute"); }; assert!( format!("{err:?}").contains("Account must be the outbox PDA"), "rejected for the wrong reason: {err:?}" ); } /// Nothing releases an escrow, so a message the destination will refuse is a /// burn: debited here, never minted there. The refusal has to come before the /// debit. #[test] fn a_lock_naming_another_target_program_is_rejected() { let bridge_lock_id = programs::bridge_lock().id(); let zone_b = [2_u8; 32]; let holder_key = PrivateKey::try_new([7; 32]).expect("valid key"); let holder_id = AccountId::from(&PublicKey::new_from_private_key(&holder_key)); let mut state = base_state().with_public_accounts([( holder_id, Account { program_owner: bridge_lock_id.into(), balance: INITIAL_BALANCE, ..Default::default() }, )]); seed_bridge_lock_config(&mut state); let elsewhere = programs::ping_receiver().id(); let lock = lock_tx_to( &holder_key, holder_id, zone_b, 0, 0, elsewhere, mint_target_accounts(elsewhere), ); let Err(err) = ValidatedStateDiff::from_public_transaction(&lock, &state, 1, 0) else { panic!("a lock aimed at another program must not execute"); }; assert!( format!("{err:?}").contains("only mints through the wrapped token it is pinned to"), "rejected for the wrong reason: {err:?}" ); assert_eq!( state.get_account_by_id(holder_id).balance, INITIAL_BALANCE, "a refused lock leaves the holder's balance alone" ); } /// The same burn by a different route: the right target program, the wrong /// accounts for it. `wrapped_token::Mint` fails its own address asserts on the /// destination, so the escrow has to be refused here instead. #[test] fn a_lock_naming_other_mint_accounts_is_rejected() { let bridge_lock_id = programs::bridge_lock().id(); let wrapped_token_id = programs::wrapped_token().id(); let zone_b = [2_u8; 32]; let holder_key = PrivateKey::try_new([7; 32]).expect("valid key"); let holder_id = AccountId::from(&PublicKey::new_from_private_key(&holder_key)); let mut state = base_state().with_public_accounts([( holder_id, Account { program_owner: bridge_lock_id.into(), balance: INITIAL_BALANCE, ..Default::default() }, )]); seed_bridge_lock_config(&mut state); // A holding under someone other than the payload's recipient: a mint the // destination would credit to the wrong account if it credited it at all. let other_holding = wrapped_token_core::holding_account_id(wrapped_token_id, &[4; 32]).into_value(); let lock = lock_tx_to( &holder_key, holder_id, zone_b, 0, 0, wrapped_token_id, vec![ wrapped_token_core::config_account_id(wrapped_token_id).into_value(), other_holding, ], ); let Err(err) = ValidatedStateDiff::from_public_transaction(&lock, &state, 1, 0) else { panic!("a lock over the wrong mint accounts must not execute"); }; assert!( format!("{err:?}").contains("target accounts must be the mint's config"), "rejected for the wrong reason: {err:?}" ); assert_eq!( state.get_account_by_id(holder_id).balance, INITIAL_BALANCE, "a refused lock leaves the holder's balance alone" ); } /// The config is read by address, so substituting another account for it fails /// rather than reading the pins out of whatever that account holds. Without the /// address check, 64 bytes a caller controls would re-pin both for one lock. #[test] fn a_lock_with_a_substituted_config_account_is_rejected() { let bridge_lock_id = programs::bridge_lock().id(); let wrapped_token_id = programs::wrapped_token().id(); let outbox_id = programs::cross_zone_outbox().id(); let zone_b = [2_u8; 32]; let ordinal = 0; let holder_key = PrivateKey::try_new([7; 32]).expect("valid key"); let holder_id = AccountId::from(&PublicKey::new_from_private_key(&holder_key)); // A bridge-lock-owned account holding pins of the caller's choosing, so only // the address check stands between it and being read as the config. let decoy_key = PrivateKey::try_new([8; 32]).expect("valid key"); let decoy_id = AccountId::from(&PublicKey::new_from_private_key(&decoy_key)); let mut state = base_state().with_public_accounts([ ( holder_id, Account { program_owner: bridge_lock_id.into(), balance: INITIAL_BALANCE, ..Default::default() }, ), ( decoy_id, Account { program_owner: bridge_lock_id.into(), data: bridge_lock_core::config_bytes([3; 8], [4; 8]) .to_vec() .try_into() .expect("pinned ids fit in account data"), ..Default::default() }, ), ]); seed_bridge_lock_config(&mut state); let lock = bridge_lock_core::Instruction::Lock { amount: LOCK_AMOUNT, target_zone: zone_b, target_program_id: wrapped_token_id, target_accounts: mint_target_accounts(wrapped_token_id), payload: mint_payload(), ordinal, }; let message = Message::try_new( bridge_lock_id, vec![ decoy_id, holder_id, bridge_lock_core::escrow_account_id(bridge_lock_id), outbox_pda(outbox_id, bridge_lock_id, &zone_b, ordinal), ], vec![0_u128.into()], lock, ) .expect("build lock message"); let tx = PublicTransaction::new( message.clone(), WitnessSet::for_message(&message, &[&holder_key]), ); let Err(err) = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) else { panic!("a lock over a substituted config account must not execute"); }; assert!( format!("{err:?}").contains("must be the bridge-lock config PDA"), "rejected for the wrong reason: {err:?}" ); } /// A bridge with no pin cannot fall back to caller-named programs: it stops /// locking. The state a zone reaches by skipping the genesis init. #[test] fn a_lock_before_the_pins_are_set_is_rejected() { let bridge_lock_id = programs::bridge_lock().id(); let zone_b = [2_u8; 32]; let holder_key = PrivateKey::try_new([7; 32]).expect("valid key"); let holder_id = AccountId::from(&PublicKey::new_from_private_key(&holder_key)); let state = base_state().with_public_accounts([( holder_id, Account { program_owner: bridge_lock_id.into(), balance: INITIAL_BALANCE, ..Default::default() }, )]); let lock = lock_tx(&holder_key, holder_id, zone_b, 0, 0); let Err(err) = ValidatedStateDiff::from_public_transaction(&lock, &state, 1, 0) else { panic!("a lock with nothing pinned must not execute"); }; assert!( format!("{err:?}").contains("config account holds an outbox and a mint target"), "rejected for the wrong reason: {err:?}" ); } /// Written once, on the same terms as the sender's: an identical re-init is the /// genesis replay, a different one would redirect every lock on the zone. #[test] fn the_bridge_pins_are_written_once_and_replayable() { let bridge_lock_id = programs::bridge_lock().id(); let config_id = bridge_lock_core::config_account_id(bridge_lock_id); let outbox_id = programs::cross_zone_outbox().id(); let wrapped_token_id = programs::wrapped_token().id(); let init = |outbox: lee_core::program::ProgramId, target: lee_core::program::ProgramId| { let message = Message::try_new( bridge_lock_id, vec![config_id], vec![], bridge_lock_core::Instruction::InitConfig { outbox_program_id: outbox, target_program_id: target, }, ) .expect("build InitConfig message"); PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])) }; let mut state = base_state(); let diff = ValidatedStateDiff::from_public_transaction( &init(outbox_id, wrapped_token_id), &state, 1, 0, ) .expect("the first init claims the config PDA"); state.apply_state_diff(diff); assert_eq!( bridge_lock_core::read_config(&state.get_account_by_id(config_id).data.into_inner()), Some((outbox_id, wrapped_token_id)), "the config pins both programs after genesis" ); ValidatedStateDiff::from_public_transaction(&init(outbox_id, wrapped_token_id), &state, 2, 0) .expect("replaying the identical init is a no-op, not a failure"); // Either half moving is a redirect: the outbox decides whether the emission is // recorded, the target where the value lands. for (outbox, target, what) in [ ([3; 8], wrapped_token_id, "outbox"), (outbox_id, [3; 8], "mint target"), ] { let Err(err) = ValidatedStateDiff::from_public_transaction(&init(outbox, target), &state, 3, 0) else { panic!("a re-init naming a different {what} must not execute"); }; assert!( format!("{err:?}").contains("already pins a different outbox or mint target"), "rejected for the wrong reason: {err:?}" ); } } /// An emitter with no pin cannot fall back to a caller-named outbox: it stops /// emitting. The state a zone reaches by skipping the genesis init. #[test] fn a_send_before_the_pin_is_set_is_rejected() { let sender_id = programs::ping_sender().id(); let outbox_id = programs::cross_zone_outbox().id(); let zone_b = [2_u8; 32]; let ordinal = 0; let state = base_state(); let slot = outbox_pda(outbox_id, sender_id, &zone_b, ordinal); let send = send_tx( vec![sender_config_account_id(sender_id), slot], zone_b, ordinal, ); let Err(err) = ValidatedStateDiff::from_public_transaction(&send, &state, 1, 0) else { panic!("a send with no outbox pinned must not execute"); }; assert!( format!("{err:?}").contains("config account holds an outbox program id"), "rejected for the wrong reason: {err:?}" ); } /// The config is read by address, so substituting another account for it fails /// rather than pinning the outbox to whatever that account happens to hold. #[test] fn a_send_with_a_substituted_config_account_is_rejected() { let sender_id = programs::ping_sender().id(); let outbox_id = programs::cross_zone_outbox().id(); let zone_b = [2_u8; 32]; let ordinal = 0; let mut state = base_state(); seed_ping_sender_config(&mut state); let slot = outbox_pda(outbox_id, sender_id, &zone_b, ordinal); let send = send_tx(vec![ping_record_pda(sender_id), slot], zone_b, ordinal); let Err(err) = ValidatedStateDiff::from_public_transaction(&send, &state, 1, 0) else { panic!("a send over a substituted config account must not execute"); }; assert!( format!("{err:?}").contains("must be the ping-sender config PDA"), "rejected for the wrong reason: {err:?}" ); } /// Written once: an identical re-init has to succeed, since genesis is replayed /// during multi-sequencer reconstruction, while one naming a different outbox has /// to fail, or anyone could redirect every emission on the zone after genesis. #[test] fn the_outbox_pin_is_written_once_and_replayable() { let sender_id = programs::ping_sender().id(); let config_id = sender_config_account_id(sender_id); // Unsigned and nonce-free, as genesis builds it: the config PDA has no signer. let init = |outbox: lee_core::program::ProgramId| { let message = Message::try_new( sender_id, vec![config_id], vec![], ping_core::SenderInstruction::InitConfig { outbox_program_id: outbox, }, ) .expect("build InitConfig message"); PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])) }; let mut state = base_state(); let outbox_id = programs::cross_zone_outbox().id(); let first = init(outbox_id); let diff = ValidatedStateDiff::from_public_transaction(&first, &state, 1, 0) .expect("the first init claims the config PDA"); state.apply_state_diff(diff); assert_eq!( read_outbox(&state.get_account_by_id(config_id).data.into_inner()), Some(outbox_id), "the config pins the outbox after genesis" ); ValidatedStateDiff::from_public_transaction(&init(outbox_id), &state, 2, 0) .expect("replaying the identical init is a no-op, not a failure"); let Err(err) = ValidatedStateDiff::from_public_transaction(&init([3; 8]), &state, 3, 0) else { panic!("a re-init naming a different outbox must not execute"); }; assert!( format!("{err:?}").contains("already pins a different outbox"), "rejected for the wrong reason: {err:?}" ); } /// The token's authority path, end to end: each guard refuses for its own /// reason, the signed path works more than once through the claimed account, and /// renouncing is one-way. The receiver battery mirrors this one. #[test] fn the_token_authority_path_holds() { let wrapped_token_id = programs::wrapped_token().id(); let config_id = wrapped_token_core::config_account_id(wrapped_token_id); let src_zone = [2_u8; 32]; let key = PrivateKey::try_new([7; 32]).expect("valid key"); let authority = AccountId::from(&PublicKey::new_from_private_key(&key)); let other_key = PrivateKey::try_new([8; 32]).expect("valid key"); let other = AccountId::from(&PublicKey::new_from_private_key(&other_key)); let update = |account: AccountId, signer: &PrivateKey, nonce: u128, sources: Vec<([u8; 32], lee_core::program::ProgramId)>| { signed_tx( wrapped_token_id, vec![config_id, account], nonce, bytes_of!(&wrapped_token_core::Instruction::UpdateSources { sources }), signer, ) }; let renounce = |account: AccountId, signer: &PrivateKey, nonce: u128| { signed_tx( wrapped_token_id, vec![config_id, account], nonce, bytes_of!(&wrapped_token_core::Instruction::RenounceAuthority), signer, ) }; let bridge_source = vec![(src_zone, programs::bridge_lock().id())]; // With no authority configured, nothing moves in either direction. let mut unset = base_state(); seed_wrapped_config(&mut unset, None, vec![]); rejects_at( &unset, &update(authority, &key, 0, bridge_source.clone()), 1, "fixed at genesis", ); rejects_at( &unset, &renounce(authority, &key, 0), 1, "already renounced", ); // With one configured: the wrong account, and the right account without its // own signature, are refused for their own reasons. let mut state = base_state(); seed_wrapped_config(&mut state, Some(authority), vec![]); rejects_at( &state, &update(other, &other_key, 0, bridge_source.clone()), 1, "second account must be the configured authority", ); rejects_at( &state, &renounce(other, &other_key, 0), 1, "second account must be the configured authority", ); rejects_at( &state, &update(authority, &other_key, 0, bridge_source.clone()), 1, "must authorize a source change", ); rejects_at( &state, &renounce(authority, &other_key, 0), 1, "must authorize renouncing it", ); // Substituting another account for the config is refused rather than read, // on both instructions. let substituted = |instruction_data: Vec| { signed_tx( wrapped_token_id, vec![ping_record_pda(wrapped_token_id), authority], 0, instruction_data, &key, ) }; rejects_at( &state, &substituted(bytes_of!(&wrapped_token_core::Instruction::UpdateSources { sources: bridge_source.clone(), })), 1, "must be the wrapped-token config PDA", ); rejects_at( &state, &substituted(bytes_of!( &wrapped_token_core::Instruction::RenounceAuthority )), 1, "must be the wrapped-token config PDA", ); // The authority itself works, and more than once: the first use claims the // account for the target, and the second runs on the claimed path. let diff = ValidatedStateDiff::from_public_transaction( &update(authority, &key, 0, bridge_source.clone()), &state, 1, 0, ) .expect("the configured authority changes sources"); state.apply_state_diff(diff); let cfg = wrapped_token_core::WrappedTokenConfig::from_bytes( &state.get_account_by_id(config_id).data.into_inner(), ) .expect("config decodes"); assert_eq!(cfg.sources, bridge_source, "the new source is authorized"); assert_eq!( state.get_account_by_id(authority).program_owner, wrapped_token_id.into(), "the first use claims the authority account for the target" ); let sender_source = vec![(src_zone, programs::ping_sender().id())]; let second = ValidatedStateDiff::from_public_transaction( &update(authority, &key, 1, sender_source.clone()), &state, 2, 0, ) .expect("the authority acts again"); state.apply_state_diff(second); let updated_cfg = wrapped_token_core::WrappedTokenConfig::from_bytes( &state.get_account_by_id(config_id).data.into_inner(), ) .expect("config decodes"); assert_eq!( updated_cfg.sources, sender_source, "the second change took effect" ); assert_eq!( updated_cfg.authority, Some(authority), "the authority is unchanged" ); // Renouncing is one-way: the sources freeze at their last value and nothing // moves afterwards, in either direction. let renounced = ValidatedStateDiff::from_public_transaction(&renounce(authority, &key, 2), &state, 3, 0) .expect("the authority renounces itself"); state.apply_state_diff(renounced); let renounced_cfg = wrapped_token_core::WrappedTokenConfig::from_bytes( &state.get_account_by_id(config_id).data.into_inner(), ) .expect("config decodes"); assert_eq!(renounced_cfg.authority, None, "the authority is gone"); assert_eq!( renounced_cfg.sources, sender_source, "renouncing leaves the sources it froze" ); assert_eq!( renounced_cfg.minter, programs::cross_zone_inbox().id(), "the minter is unchanged" ); rejects_at( &state, &update(authority, &key, 3, bridge_source), 4, "fixed at genesis", ); rejects_at( &state, &renounce(authority, &key, 3), 4, "already renounced", ); } /// `ping_receiver` authorizes its own sources too. It holds nothing worth /// stealing, but without this any program on any configured peer could overwrite /// the record, and a delivery would prove only that some peer sent it. #[test] fn a_delivery_from_an_unauthorized_source_does_not_reach_ping_receiver() { let inbox_id = programs::cross_zone_inbox().id(); let receiver_id = programs::ping_receiver().id(); let self_zone = [1_u8; 32]; let src_zone = [2_u8; 32]; let mut state = base_state(); seed_inbox_config(&mut state, self_zone); // Authorizes one source; the delivery comes from another. seed_receiver_config( &mut state, None, vec![(src_zone, programs::bridge_lock().id())], ); let payload = borsh::to_vec(&ReceiverInstruction::Record { payload: b"ping".to_vec(), }) .expect("serialize ping instruction"); let msg = CrossZoneMessage { src_zone, src_block_id: 5, src_block_hash: SRC_BLOCK_HASH, src_tx_index: 0, src_program_id: programs::ping_sender().id(), target_program_id: receiver_id, payload, l1_inclusion_witness: None, }; let message = Message::try_new( inbox_id, dispatch_accounts( inbox_id, &msg, vec![ receiver_config_account_id(receiver_id), ping_record_pda(receiver_id), ], ), vec![], InboxInstruction::Dispatch(msg), ) .expect("build dispatch message"); let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])); let Err(err) = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) else { panic!("an unauthorized source must not reach the receiver"); }; assert!( format!("{err:?}").contains("peer source this receiver authorizes"), "rejected for the wrong reason: {err:?}" ); } /// The inbox binds the marker to the message it is delivering. Without that the /// marker would be a field the dispatch could set freely, and a target checking it /// would be checking nothing. #[test] fn the_inbox_refuses_a_marker_that_does_not_match_the_message() { let inbox_id = programs::cross_zone_inbox().id(); let receiver_id = programs::ping_receiver().id(); let self_zone = [1_u8; 32]; let src_zone = [2_u8; 32]; let sender_id = programs::ping_sender().id(); let mut state = base_state(); seed_inbox_config(&mut state, self_zone); seed_receiver_config(&mut state, None, vec![(src_zone, sender_id)]); let payload = borsh::to_vec(&ReceiverInstruction::Record { payload: b"ping".to_vec(), }) .expect("serialize ping instruction"); let msg = CrossZoneMessage { src_zone, src_block_id: 5, src_block_hash: SRC_BLOCK_HASH, src_tx_index: 0, src_program_id: sender_id, target_program_id: receiver_id, payload, l1_inclusion_witness: None, }; // The message says ping_sender; the marker names bridge_lock, which the // receiver also would not accept. The inbox must refuse it first. let message = Message::try_new( inbox_id, vec![ inbox_config_account_id(inbox_id), inbox_seen_shard_account_id(inbox_id, &msg.src_zone, msg.src_block_id), inbox_source_marker_account_id(inbox_id, &src_zone, programs::bridge_lock().id()), receiver_config_account_id(receiver_id), ping_record_pda(receiver_id), ], vec![], InboxInstruction::Dispatch(msg), ) .expect("build dispatch message"); let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])); let Err(err) = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) else { panic!("a marker that does not match the message must not be delivered"); }; assert!( format!("{err:?}").contains("must be the source marker PDA for this message"), "rejected for the wrong reason: {err:?}" ); } /// The receiver's authority path is a mirror of the token's, and a mirror is /// exactly where a copy-paste slip hides. Same battery, run against it. #[test] fn the_receiver_authority_path_holds() { let receiver_id = programs::ping_receiver().id(); let config_id = receiver_config_account_id(receiver_id); let src_zone = [2_u8; 32]; let sender_id = programs::ping_sender().id(); let key = PrivateKey::try_new([7; 32]).expect("valid key"); let authority = AccountId::from(&PublicKey::new_from_private_key(&key)); let other_key = PrivateKey::try_new([8; 32]).expect("valid key"); let other = AccountId::from(&PublicKey::new_from_private_key(&other_key)); let update = |account: AccountId, signer: &PrivateKey, nonce: u128| { signed_tx( receiver_id, vec![config_id, account], nonce, bytes_of!(&ping_core::ReceiverInstruction::UpdateSources { sources: vec![(src_zone, sender_id)], }), signer, ) }; let renounce = |account: AccountId, signer: &PrivateKey, nonce: u128| { signed_tx( receiver_id, vec![config_id, account], nonce, bytes_of!(&ping_core::ReceiverInstruction::RenounceAuthority), signer, ) }; // The wrong account, and the right account without its own signature, are // both refused for their own reasons. let mut state = base_state(); seed_receiver_config(&mut state, Some(authority), vec![]); rejects_at( &state, &update(other, &other_key, 0), 1, "must be the configured authority", ); rejects_at( &state, &renounce(other, &other_key, 0), 1, "must be the configured authority", ); rejects_at( &state, &update(authority, &other_key, 0), 1, "must authorize a source change", ); rejects_at( &state, &renounce(authority, &other_key, 0), 1, "must authorize renouncing it", ); // The authority itself works, and renouncing is one-way. let diff = ValidatedStateDiff::from_public_transaction(&update(authority, &key, 0), &state, 1, 0) .expect("the configured authority changes sources"); state.apply_state_diff(diff); let cfg = ping_core::ReceiverConfig::from_bytes( &state.get_account_by_id(config_id).data.into_inner(), ) .expect("config decodes"); assert_eq!(cfg.sources, vec![(src_zone, sender_id)]); assert_eq!(cfg.deliverer, programs::cross_zone_inbox().id()); let renounce_diff = ValidatedStateDiff::from_public_transaction(&renounce(authority, &key, 1), &state, 2, 0) .expect("the authority renounces itself"); state.apply_state_diff(renounce_diff); let renounced_cfg = ping_core::ReceiverConfig::from_bytes( &state.get_account_by_id(config_id).data.into_inner(), ) .expect("config decodes"); assert_eq!(renounced_cfg.authority, None, "the authority is gone"); assert_eq!( renounced_cfg.sources, vec![(src_zone, sender_id)], "renouncing freezes the list it had" ); rejects_at(&state, &update(authority, &key, 2), 3, "fixed at genesis"); rejects_at( &state, &renounce(authority, &key, 2), 3, "already renounced", ); } /// The inbox cannot reach the authority instructions, named as governance or not: /// it prepends the source marker to every chained call, so the config never lands /// where these instructions read it. Worth pinning, because the inbox is the only /// program that chain-calls a target today, so this is what actually keeps a peer /// away from the source list. #[test] fn the_inbox_cannot_reach_the_authority_instructions() { let wrapped_token_id = programs::wrapped_token().id(); let inbox_id = programs::cross_zone_inbox().id(); let config_id = wrapped_token_core::config_account_id(wrapped_token_id); let self_zone = [1_u8; 32]; let src_zone = [2_u8; 32]; let key = PrivateKey::try_new([7; 32]).expect("valid key"); let authority = AccountId::from(&PublicKey::new_from_private_key(&key)); let update = || { chained_via_inbox( wrapped_token_id, config_id, authority, bytes_of!(&wrapped_token_core::Instruction::UpdateSources { sources: vec![(src_zone, programs::bridge_lock().id())], }), ) }; // No governance named: the chained call is refused. let mut closed = base_state(); seed_inbox_config(&mut closed, self_zone); seed_wrapped_config(&mut closed, Some(authority), vec![]); rejects_at( &closed, &update(), 1, "must be the wrapped-token config PDA", ); // Naming the inbox as governance changes nothing: the obstacle is structural, // not the caller check. The prepended marker sits at index 0, so with or // without the inbox named as governance the call dies on the config-address // check, before the caller check is even reached. let mut open = base_state(); seed_inbox_config(&mut open, self_zone); seed_wrapped_config_with_governance(&mut open, Some(inbox_id), Some(authority), vec![]); rejects_at(&open, &update(), 1, "must be the wrapped-token config PDA"); } /// A program-held authority acts through the governance program delegating its /// PDA on the chained call: the first use has the target claim the account, the /// second runs on the claimed path, and renouncing through it is as total as /// renouncing top-level. #[test] fn the_governance_path_holds() { let wrapped_token_id = programs::wrapped_token().id(); let proxy_id = test_programs::authority_proxy().id(); let config_id = wrapped_token_core::config_account_id(wrapped_token_id); let src_zone = [2_u8; 32]; let seed = lee_core::program::PdaSeed::new([3; 32]); let authority = AccountId::for_public_pda(&proxy_id, &seed); let mut state = base_state().with_programs([test_programs::authority_proxy()]); seed_wrapped_config_with_governance(&mut state, Some(proxy_id), Some(authority), vec![]); let update = |sources: Vec<([u8; 32], lee_core::program::ProgramId)>| { via_proxy( proxy_id, wrapped_token_id, config_id, authority, Some(seed), bytes_of!(&wrapped_token_core::Instruction::UpdateSources { sources }), ) }; let renounce = || { via_proxy( proxy_id, wrapped_token_id, config_id, authority, Some(seed), bytes_of!(&wrapped_token_core::Instruction::RenounceAuthority), ) }; let first = ValidatedStateDiff::from_public_transaction( &update(vec![(src_zone, programs::bridge_lock().id())]), &state, 1, 0, ) .expect("the governance path changes sources"); state.apply_state_diff(first); let cfg = wrapped_token_core::WrappedTokenConfig::from_bytes( &state.get_account_by_id(config_id).data.into_inner(), ) .expect("config decodes"); assert_eq!(cfg.sources, vec![(src_zone, programs::bridge_lock().id())]); assert_eq!( state.get_account_by_id(authority).program_owner, wrapped_token_id.into(), "the first use claims the delegated PDA for the target" ); let second = ValidatedStateDiff::from_public_transaction(&update(vec![]), &state, 2, 0) .expect("the governance path acts again"); state.apply_state_diff(second); let cleared_cfg = wrapped_token_core::WrappedTokenConfig::from_bytes( &state.get_account_by_id(config_id).data.into_inner(), ) .expect("config decodes"); assert!( cleared_cfg.sources.is_empty(), "the second change took effect" ); assert_eq!(cleared_cfg.authority, Some(authority)); let renounced = ValidatedStateDiff::from_public_transaction(&renounce(), &state, 3, 0) .expect("the governance path renounces"); state.apply_state_diff(renounced); let renounced_cfg = wrapped_token_core::WrappedTokenConfig::from_bytes( &state.get_account_by_id(config_id).data.into_inner(), ) .expect("config decodes"); assert_eq!(renounced_cfg.authority, None, "the authority is gone"); rejects_at( &state, &update(vec![(src_zone, programs::bridge_lock().id())]), 4, "fixed at genesis", ); rejects_at(&state, &renounce(), 4, "already renounced"); } /// Each governance-path guard fails on its own: a caller other than the /// configured governance program is refused with the delegation in order, no /// configured governance refuses every chained caller (on the token's update /// and on its three sibling handlers), and the governance program without /// delegating finds the authority unauthorized. #[test] fn the_governance_path_guards_hold() { let wrapped_token_id = programs::wrapped_token().id(); let receiver_id = programs::ping_receiver().id(); let proxy_id = test_programs::authority_proxy().id(); let config_id = wrapped_token_core::config_account_id(wrapped_token_id); let src_zone = [2_u8; 32]; let seed = lee_core::program::PdaSeed::new([3; 32]); let authority = AccountId::for_public_pda(&proxy_id, &seed); let call = |delegated: Option| { via_proxy( proxy_id, wrapped_token_id, config_id, authority, delegated, bytes_of!(&wrapped_token_core::Instruction::UpdateSources { sources: vec![(src_zone, programs::bridge_lock().id())], }), ) }; // A perfect call shape from a program that is not the configured governance. let mut other = base_state().with_programs([test_programs::authority_proxy()]); seed_wrapped_config_with_governance( &mut other, Some(programs::ping_sender().id()), Some(authority), vec![], ); rejects_at( &other, &call(Some(seed)), 1, "through the configured governance program", ); // No governance configured: every chained caller is refused. let mut closed = base_state().with_programs([test_programs::authority_proxy()]); seed_wrapped_config(&mut closed, Some(authority), vec![]); seed_receiver_config(&mut closed, Some(authority), vec![]); rejects_at( &closed, &call(Some(seed)), 1, "through the configured governance program", ); // The same pin guards the three sibling handlers, both renounces and the // receiver's update, each of which would otherwise accept the delegated // authority and succeed. for (target, config, instruction_data) in [ ( wrapped_token_id, config_id, bytes_of!(&wrapped_token_core::Instruction::RenounceAuthority), ), ( receiver_id, receiver_config_account_id(receiver_id), bytes_of!(&ping_core::ReceiverInstruction::UpdateSources { sources: vec![(src_zone, programs::ping_sender().id())], }), ), ( receiver_id, receiver_config_account_id(receiver_id), bytes_of!(&ping_core::ReceiverInstruction::RenounceAuthority), ), ] { rejects_at( &closed, &via_proxy( proxy_id, target, config, authority, Some(seed), instruction_data, ), 1, "through the configured governance program", ); } // The configured governance itself, but not delegating the authority. let mut undelegated = base_state().with_programs([test_programs::authority_proxy()]); seed_wrapped_config_with_governance(&mut undelegated, Some(proxy_id), Some(authority), vec![]); rejects_at( &undelegated, &call(None), 1, "must authorize a source change", ); } /// The receiver's governance path works the same way; without this its config /// never carries a governance in any test. #[test] fn the_receiver_governance_path_holds() { let receiver_id = programs::ping_receiver().id(); let proxy_id = test_programs::authority_proxy().id(); let config_id = receiver_config_account_id(receiver_id); let src_zone = [2_u8; 32]; let seed = lee_core::program::PdaSeed::new([3; 32]); let authority = AccountId::for_public_pda(&proxy_id, &seed); let mut state = base_state().with_programs([test_programs::authority_proxy()]); seed_receiver_config_with_governance(&mut state, Some(proxy_id), Some(authority), vec![]); let tx = via_proxy( proxy_id, receiver_id, config_id, authority, Some(seed), bytes_of!(&ping_core::ReceiverInstruction::UpdateSources { sources: vec![(src_zone, programs::ping_sender().id())], }), ); let diff = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) .expect("the receiver governance path changes sources"); state.apply_state_diff(diff); let cfg = ping_core::ReceiverConfig::from_bytes( &state.get_account_by_id(config_id).data.into_inner(), ) .expect("config decodes"); assert_eq!(cfg.sources, vec![(src_zone, programs::ping_sender().id())]); assert_eq!( state.get_account_by_id(authority).program_owner, receiver_id.into(), "the first use claims the delegated PDA for the receiver" ); } /// One authority seeds both targets at genesis, and the config doc promises that /// whichever target is used first owns the account while the other keeps /// working. Claim through the token, then act and renounce on the receiver. #[test] fn a_shared_authority_survives_the_first_claim() { let wrapped_token_id = programs::wrapped_token().id(); let receiver_id = programs::ping_receiver().id(); let proxy_id = test_programs::authority_proxy().id(); let token_config_id = wrapped_token_core::config_account_id(wrapped_token_id); let receiver_config_id = receiver_config_account_id(receiver_id); let src_zone = [2_u8; 32]; let seed = lee_core::program::PdaSeed::new([3; 32]); let authority = AccountId::for_public_pda(&proxy_id, &seed); let mut state = base_state().with_programs([test_programs::authority_proxy()]); seed_wrapped_config_with_governance(&mut state, Some(proxy_id), Some(authority), vec![]); seed_receiver_config_with_governance(&mut state, Some(proxy_id), Some(authority), vec![]); let token_update = via_proxy( proxy_id, wrapped_token_id, token_config_id, authority, Some(seed), bytes_of!(&wrapped_token_core::Instruction::UpdateSources { sources: vec![(src_zone, programs::bridge_lock().id())], }), ); let first = ValidatedStateDiff::from_public_transaction(&token_update, &state, 1, 0) .expect("the token claims the shared authority"); state.apply_state_diff(first); assert_eq!( state.get_account_by_id(authority).program_owner, wrapped_token_id.into(), "the first target to be used owns the account" ); let receiver_update = via_proxy( proxy_id, receiver_id, receiver_config_id, authority, Some(seed), bytes_of!(&ping_core::ReceiverInstruction::UpdateSources { sources: vec![(src_zone, programs::ping_sender().id())], }), ); let second = ValidatedStateDiff::from_public_transaction(&receiver_update, &state, 2, 0) .expect("the other target still acts on the token-owned authority"); state.apply_state_diff(second); let receiver_cfg = ping_core::ReceiverConfig::from_bytes( &state .get_account_by_id(receiver_config_id) .data .into_inner(), ) .expect("config decodes"); assert_eq!( receiver_cfg.sources, vec![(src_zone, programs::ping_sender().id())] ); assert_eq!( state.get_account_by_id(authority).program_owner, wrapped_token_id.into(), "the receiver never takes the account over" ); let receiver_renounce = via_proxy( proxy_id, receiver_id, receiver_config_id, authority, Some(seed), bytes_of!(&ping_core::ReceiverInstruction::RenounceAuthority), ); let third = ValidatedStateDiff::from_public_transaction(&receiver_renounce, &state, 3, 0) .expect("the other target renounces on the token-owned authority"); state.apply_state_diff(third); let renounced_cfg = ping_core::ReceiverConfig::from_bytes( &state .get_account_by_id(receiver_config_id) .data .into_inner(), ) .expect("config decodes"); assert_eq!(renounced_cfg.authority, None, "the receiver side is gone"); let token_cfg = wrapped_token_core::WrappedTokenConfig::from_bytes( &state.get_account_by_id(token_config_id).data.into_inner(), ) .expect("config decodes"); assert_eq!( token_cfg.authority, Some(authority), "renouncing one target leaves the other's grant alone" ); } /// An authority account with any history can never be claimed, so all four /// authority handlers refuse it, and say why rather than surfacing a rule number. #[test] fn an_authority_account_with_history_is_refused() { let wrapped_token_id = programs::wrapped_token().id(); let receiver_id = programs::ping_receiver().id(); let src_zone = [2_u8; 32]; let key = PrivateKey::try_new([7; 32]).expect("valid key"); let authority = AccountId::from(&PublicKey::new_from_private_key(&key)); let mut state = base_state(); seed_wrapped_config(&mut state, Some(authority), vec![]); seed_receiver_config(&mut state, Some(authority), vec![]); // Unowned but already used: exactly what one prior signature leaves behind. state = state.with_public_accounts([( authority, Account { nonce: 1_u128.into(), ..Default::default() }, )]); for (program, config_id, instruction_data) in [ ( wrapped_token_id, wrapped_token_core::config_account_id(wrapped_token_id), bytes_of!(&wrapped_token_core::Instruction::UpdateSources { sources: vec![(src_zone, programs::bridge_lock().id())], }), ), ( wrapped_token_id, wrapped_token_core::config_account_id(wrapped_token_id), bytes_of!(&wrapped_token_core::Instruction::RenounceAuthority), ), ( receiver_id, receiver_config_account_id(receiver_id), bytes_of!(&ping_core::ReceiverInstruction::UpdateSources { sources: vec![(src_zone, programs::ping_sender().id())], }), ), ( receiver_id, receiver_config_account_id(receiver_id), bytes_of!(&ping_core::ReceiverInstruction::RenounceAuthority), ), ] { rejects_at( &state, &signed_tx( program, vec![config_id, authority], 1, instruction_data, &key, ), 1, "must be untouched before its first use", ); } } /// The guards that survive a deletion otherwise: the receiver's config-address /// checks its substitution cases miss, and the three caller pins that are only /// reachable through the inbox. #[test] fn the_remaining_authority_guards_hold() { let wrapped_token_id = programs::wrapped_token().id(); let receiver_id = programs::ping_receiver().id(); let self_zone = [1_u8; 32]; let src_zone = [2_u8; 32]; let key = PrivateKey::try_new([7; 32]).expect("valid key"); let authority = AccountId::from(&PublicKey::new_from_private_key(&key)); let mut state = base_state(); seed_inbox_config(&mut state, self_zone); seed_wrapped_config(&mut state, Some(authority), vec![]); seed_receiver_config(&mut state, Some(authority), vec![]); // Config address, on both receiver instructions. for instruction_data in [ bytes_of!(&ping_core::ReceiverInstruction::UpdateSources { sources: vec![(src_zone, programs::ping_sender().id())], }), bytes_of!(&ping_core::ReceiverInstruction::RenounceAuthority), ] { rejects_at( &state, &signed_tx( receiver_id, vec![ping_record_pda(receiver_id), authority], 0, instruction_data, &key, ), 1, "must be the receiver config PDA", ); } // Reached through the inbox rather than top-level: the prepended marker sits // at index 0, so each call dies on the target's config-address check. The // caller pins themselves are exercised through the proxy in // the_governance_path_guards_hold, where the account list is well formed. for (target, config_id, instruction_data, expected) in [ ( wrapped_token_id, wrapped_token_core::config_account_id(wrapped_token_id), bytes_of!(&wrapped_token_core::Instruction::RenounceAuthority), "must be the wrapped-token config PDA", ), ( receiver_id, receiver_config_account_id(receiver_id), bytes_of!(&ping_core::ReceiverInstruction::RenounceAuthority), "must be the receiver config PDA", ), ( receiver_id, receiver_config_account_id(receiver_id), bytes_of!(&ping_core::ReceiverInstruction::UpdateSources { sources: vec![(src_zone, programs::ping_sender().id())], }), "must be the receiver config PDA", ), ] { rejects_at( &state, &chained_via_inbox(target, config_id, authority, instruction_data), 1, expected, ); } } /// A token that authorizes nothing mints for nobody. The state a zone reaches with /// no peers configured, where the config is still seeded so its PDA cannot be /// claimed by a first initializer. #[test] fn a_mint_is_refused_when_the_token_authorizes_no_source() { let inbox_id = programs::cross_zone_inbox().id(); let wrapped_token_id = programs::wrapped_token().id(); let self_zone = [1_u8; 32]; let src_zone = [2_u8; 32]; let mut state = base_state(); seed_inbox_config(&mut state, self_zone); seed_wrapped_config(&mut state, None, vec![]); let msg = CrossZoneMessage { src_zone, src_block_id: 5, src_block_hash: SRC_BLOCK_HASH, src_tx_index: 0, src_program_id: programs::bridge_lock().id(), target_program_id: wrapped_token_id, payload: mint_payload(), l1_inclusion_witness: None, }; let message = Message::try_new( inbox_id, dispatch_accounts( inbox_id, &msg, vec![ wrapped_token_core::config_account_id(wrapped_token_id), wrapped_token_core::holding_account_id(wrapped_token_id, &RECIPIENT), ], ), vec![], InboxInstruction::Dispatch(msg), ) .expect("build dispatch message"); let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])); let Err(err) = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) else { panic!("a token authorizing nothing must not mint"); }; assert!( format!("{err:?}").contains("peer source this token authorizes"), "rejected for the wrong reason: {err:?}" ); } /// The marker only means something because the caller is pinned to the inbox. /// Invoked directly, with the caller handing in the marker themselves, the mint /// must refuse before it ever looks at it. #[test] fn a_top_level_mint_is_refused() { let inbox_id = programs::cross_zone_inbox().id(); let wrapped_token_id = programs::wrapped_token().id(); let src_zone = [2_u8; 32]; let src_program_id = programs::bridge_lock().id(); let mut state = base_state(); seed_wrapped_config(&mut state, None, vec![(src_zone, src_program_id)]); let marker_id = inbox_source_marker_account_id(inbox_id, &src_zone, src_program_id); let message = Message::try_new( wrapped_token_id, vec![ marker_id, wrapped_token_core::config_account_id(wrapped_token_id), wrapped_token_core::holding_account_id(wrapped_token_id, &RECIPIENT), ], vec![], wrapped_token_core::Instruction::Mint { recipient: RECIPIENT, amount: LOCK_AMOUNT, }, ) .expect("build mint message"); let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])); let Err(err) = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) else { panic!("a directly invoked mint must not execute"); }; assert!( format!("{err:?}").contains("only callable by the authorized minter"), "rejected for the wrong reason: {err:?}" ); } /// Drives a hand-built `cross_zone_inbox::Dispatch` (as the watcher would inject) /// and asserts it chains into `wrapped_token::Mint`, crediting the recipient. #[test] fn inbox_dispatch_mints_wrapped_token() { let diff = dispatch_mint(LOCK_AMOUNT).expect("dispatch must validate and execute"); let holding_id = wrapped_token_core::holding_account_id(programs::wrapped_token().id(), &RECIPIENT); let minted = wrapped_token_core::read_balance( &diff.public_diff()[&holding_id].data.clone().into_inner(), ); assert_eq!( minted, LOCK_AMOUNT, "recipient holding minted the locked amount" ); } /// `ping_sender` lets its caller choose the target and payload freely, so any user /// on a peer can aim a `Mint` payload at `wrapped_token`. The inbox no longer /// refuses it; the token does, because the marker names `ping_sender` and the /// token authorized only the bridge. This is the check that replaced the central /// route table, so it must be the thing that rejects here. #[test] fn a_mint_from_an_unrouted_emitter_is_rejected() { let inbox_id = programs::cross_zone_inbox().id(); let wrapped_token_id = programs::wrapped_token().id(); let self_zone = [1_u8; 32]; let src_zone = [2_u8; 32]; let src_block_id = 5; let mut state = base_state(); // The config a bridging zone writes: the lock program may mint, nothing else. seed_inbox_config(&mut state, self_zone); seed_wrapped_config( &mut state, None, vec![(src_zone, programs::bridge_lock().id())], ); let msg = CrossZoneMessage { src_zone, src_block_id, src_block_hash: SRC_BLOCK_HASH, src_tx_index: 0, // The emitter a user can drive directly, aimed at the bridge's target. src_program_id: programs::ping_sender().id(), target_program_id: wrapped_token_id, payload: mint_payload(), l1_inclusion_witness: None, }; let wrapped_config_id = wrapped_token_core::config_account_id(wrapped_token_id); let holding_id = wrapped_token_core::holding_account_id(wrapped_token_id, &RECIPIENT); let message = Message::try_new( inbox_id, dispatch_accounts(inbox_id, &msg, vec![wrapped_config_id, holding_id]), vec![], InboxInstruction::Dispatch(msg), ) .expect("build dispatch message"); let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])); let Err(err) = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) else { panic!("a delivery from a source the token did not authorize must not mint"); }; assert!( format!("{err:?}").contains("peer source this token authorizes"), "rejected for the wrong reason: {err:?}" ); } /// The same target reached by the emitter the route names still works. Without /// this, the test above would pass equally against an inbox that rejected every /// delivery. #[test] fn a_mint_from_the_routed_emitter_is_accepted() { let inbox_id = programs::cross_zone_inbox().id(); let wrapped_token_id = programs::wrapped_token().id(); let bridge_lock_id = programs::bridge_lock().id(); let self_zone = [1_u8; 32]; let src_zone = [2_u8; 32]; let src_block_id = 5; let mut state = base_state(); seed_inbox_config(&mut state, self_zone); seed_wrapped_config( &mut state, None, vec![(src_zone, programs::bridge_lock().id())], ); let msg = CrossZoneMessage { src_zone, src_block_id, src_block_hash: SRC_BLOCK_HASH, src_tx_index: 0, src_program_id: bridge_lock_id, target_program_id: wrapped_token_id, payload: mint_payload(), l1_inclusion_witness: None, }; let wrapped_config_id = wrapped_token_core::config_account_id(wrapped_token_id); let holding_id = wrapped_token_core::holding_account_id(wrapped_token_id, &RECIPIENT); let message = Message::try_new( inbox_id, dispatch_accounts(inbox_id, &msg, vec![wrapped_config_id, holding_id]), vec![], InboxInstruction::Dispatch(msg), ) .expect("build dispatch message"); let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])); let diff = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) .expect("the routed emitter must still deliver"); let minted = wrapped_token_core::read_balance( &diff.public_diff()[&holding_id].data.clone().into_inner(), ); assert_eq!(minted, LOCK_AMOUNT); } /// A dispatch whose message key is already in the seen-shard is an idempotent /// no-op: the inbox makes no chained call, so the wrapped token is not minted a /// second time. This is the bridge's replay defense. #[test] fn mint_replay_rejected() { let inbox_id = programs::cross_zone_inbox().id(); let wrapped_token_id = programs::wrapped_token().id(); let self_zone = [1_u8; 32]; let src_zone = [2_u8; 32]; let src_block_id = 5; let src_tx_index = 0; let mut state = base_state(); seed_inbox_config(&mut state, self_zone); seed_wrapped_config(&mut state, None, vec![(src_zone, [9_u32; 8])]); // Seed the seen-shard as already holding this delivery, so the inbox takes // the replay no-op branch. The shard is inbox-owned (claimed on a prior // delivery) and bound to the same source block, so the guest leaves it // untouched. let seen_id = inbox_seen_shard_account_id(inbox_id, &src_zone, src_block_id); let mut shard = SeenShard::default(); shard.insert(SRC_BLOCK_HASH, src_tx_index); state = state.with_public_accounts([( seen_id, Account { program_owner: inbox_id.into(), balance: 0, data: shard .to_bytes() .try_into() .expect("shard fits in account data"), nonce: 0_u128.into(), }, )]); let msg = CrossZoneMessage { src_zone, src_block_id, src_block_hash: SRC_BLOCK_HASH, src_tx_index, src_program_id: [9_u32; 8], target_program_id: wrapped_token_id, payload: mint_payload(), l1_inclusion_witness: None, }; let wrapped_config_id = wrapped_token_core::config_account_id(wrapped_token_id); let holding_id = wrapped_token_core::holding_account_id(wrapped_token_id, &RECIPIENT); let message = Message::try_new( inbox_id, dispatch_accounts(inbox_id, &msg, vec![wrapped_config_id, holding_id]), vec![], InboxInstruction::Dispatch(msg), ) .expect("build dispatch message"); let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])); let diff = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0) .expect("a replayed dispatch is a valid no-op, not an error"); let public_diff = diff.public_diff(); // No mint: the holding is never credited on replay. let minted = public_diff.get(&holding_id).map_or(0, |account| { wrapped_token_core::read_balance(&account.data.clone().into_inner()) }); assert_eq!(minted, 0, "a replayed message must not mint again"); // The seen-shard is untouched by the no-op. if let Some(seen) = public_diff.get(&seen_id) { let shard_after = SeenShard::from_bytes(&seen.data.clone().into_inner()).expect("seen shard decodes"); assert_eq!(shard_after, shard, "replay must not modify the seen-shard"); } } /// A peer publishing two blocks at one block id gets at most one delivered from. /// /// Both resolve to the same shard account; the first binds it. Failing rather /// than no-opping is the point: a replay no-op would let a peer choose which of /// two messages at one coordinate the target program ever sees. #[test] fn a_delivery_from_a_second_block_at_the_same_id_is_refused() { let inbox_id = programs::cross_zone_inbox().id(); let receiver_id = programs::ping_receiver().id(); let self_zone = [1_u8; 32]; let src_zone = [2_u8; 32]; let src_block_id = 5; let other_block_hash = [8_u8; 32]; let mut state = base_state(); seed_inbox_config(&mut state, self_zone); seed_receiver_config(&mut state, None, vec![(src_zone, [9_u32; 8])]); // The shard as the first delivery left it: bound, holding transaction 0. let seen_id = inbox_seen_shard_account_id(inbox_id, &src_zone, src_block_id); let mut shard = SeenShard::default(); shard.insert(SRC_BLOCK_HASH, 0); state = state.with_public_accounts([( seen_id, Account { program_owner: inbox_id.into(), balance: 0, data: shard .to_bytes() .try_into() .expect("shard fits in account data"), nonce: 0_u128.into(), }, )]); let payload = borsh::to_vec(&ReceiverInstruction::Record { payload: b"from-the-other-block".to_vec(), }) .expect("serialize ping instruction"); // A different transaction index, so this is not a replay: only the source // block differs from what the shard is bound to. let msg = CrossZoneMessage { src_zone, src_block_id, src_block_hash: other_block_hash, src_tx_index: 1, src_program_id: [9_u32; 8], target_program_id: receiver_id, payload, l1_inclusion_witness: None, }; let record_id = ping_record_pda(receiver_id); let message = Message::try_new( inbox_id, dispatch_accounts( inbox_id, &msg, vec![receiver_config_account_id(receiver_id), record_id], ), vec![], InboxInstruction::Dispatch(msg), ) .expect("build dispatch message"); let tx = PublicTransaction::new(message, WitnessSet::from_raw_parts(vec![])); assert!( ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0).is_err(), "a delivery from a block the shard is not bound to must not execute" ); // Control: the same delivery naming the bound block executes, so the refusal // above is the binding and not the transaction's shape. let control_payload = borsh::to_vec(&ReceiverInstruction::Record { payload: b"from-the-bound-block".to_vec(), }) .expect("serialize ping instruction"); let control_msg = CrossZoneMessage { src_zone, src_block_id, src_block_hash: SRC_BLOCK_HASH, src_tx_index: 1, src_program_id: [9_u32; 8], target_program_id: receiver_id, payload: control_payload, l1_inclusion_witness: None, }; let control_message = Message::try_new( inbox_id, dispatch_accounts( inbox_id, &control_msg, vec![receiver_config_account_id(receiver_id), record_id], ), vec![], InboxInstruction::Dispatch(control_msg), ) .expect("build dispatch message"); let control_tx = PublicTransaction::new(control_message, WitnessSet::from_raw_parts(vec![])); let diff = ValidatedStateDiff::from_public_transaction(&control_tx, &state, 1, 0) .expect("a second delivery from the bound block executes"); let public_diff = diff.public_diff(); let seen_after = public_diff .get(&seen_id) .expect("the shard records the new delivery"); let shard_after = SeenShard::from_bytes(&seen_after.data.clone().into_inner()).expect("seen shard decodes"); assert!(shard_after.contains(0), "the first delivery is still there"); assert!(shard_after.contains(1), "and the second is recorded"); assert_eq!( shard_after.src_block_hash, SRC_BLOCK_HASH, "a shard stays bound to the block that claimed it" ); }