diff --git a/artifacts/program_methods/cross_zone_outbox.bin b/artifacts/program_methods/cross_zone_outbox.bin index b89f8d0f..e87cd663 100644 Binary files a/artifacts/program_methods/cross_zone_outbox.bin and b/artifacts/program_methods/cross_zone_outbox.bin differ diff --git a/artifacts/program_methods/ping_sender.bin b/artifacts/program_methods/ping_sender.bin new file mode 100644 index 00000000..c2480bc3 Binary files /dev/null and b/artifacts/program_methods/ping_sender.bin differ diff --git a/lee/state_machine/src/program.rs b/lee/state_machine/src/program.rs index 8b71448b..e5ee73d6 100644 --- a/lee/state_machine/src/program.rs +++ b/lee/state_machine/src/program.rs @@ -13,7 +13,8 @@ use crate::{ AUTHENTICATED_TRANSFER_ELF, AUTHENTICATED_TRANSFER_ID, BRIDGE_ELF, BRIDGE_ID, CLOCK_ELF, CLOCK_ID, CROSS_ZONE_INBOX_ELF, CROSS_ZONE_INBOX_ID, CROSS_ZONE_OUTBOX_ELF, CROSS_ZONE_OUTBOX_ID, FAUCET_ELF, FAUCET_ID, PINATA_ELF, PINATA_ID, PING_RECEIVER_ELF, - PING_RECEIVER_ID, TOKEN_ELF, TOKEN_ID, VAULT_ELF, VAULT_ID, + PING_RECEIVER_ID, PING_SENDER_ELF, PING_SENDER_ID, TOKEN_ELF, TOKEN_ID, VAULT_ELF, + VAULT_ID, }, }; @@ -198,6 +199,14 @@ impl Program { elf: PING_RECEIVER_ELF.to_vec(), } } + + #[must_use] + pub fn ping_sender() -> Self { + Self { + id: PING_SENDER_ID, + elf: PING_SENDER_ELF.to_vec(), + } + } } // TODO: Testnet only. Refactor to prevent compilation on mainnet. diff --git a/program_methods/guest/src/bin/cross_zone_outbox.rs b/program_methods/guest/src/bin/cross_zone_outbox.rs index 84805807..432e8d9c 100644 --- a/program_methods/guest/src/bin/cross_zone_outbox.rs +++ b/program_methods/guest/src/bin/cross_zone_outbox.rs @@ -20,13 +20,20 @@ fn main() { "Outbox is only callable through a chain call from a user program" ); - let (target_zone, target_program_id, payload, ordinal) = match instruction { + let (target_zone, target_program_id, target_accounts, payload, ordinal) = match instruction { Instruction::Emit { target_zone, target_program_id, + target_accounts, payload, ordinal, - } => (target_zone, target_program_id, payload, ordinal), + } => ( + target_zone, + target_program_id, + target_accounts, + payload, + ordinal, + ), }; let [outbox] = @@ -42,6 +49,7 @@ fn main() { post_account.data = OutboxRecord { target_zone, target_program_id, + target_accounts, payload, } .to_bytes() diff --git a/program_methods/guest/src/bin/ping_sender.rs b/program_methods/guest/src/bin/ping_sender.rs new file mode 100644 index 00000000..d0ad04f5 --- /dev/null +++ b/program_methods/guest/src/bin/ping_sender.rs @@ -0,0 +1,59 @@ +use cross_zone_outbox_core::Instruction as OutboxInstruction; +use lee_core::{ + account::AccountWithMetadata, + program::{AccountPostState, ChainedCall, ProgramInput, ProgramOutput, read_lee_inputs}, +}; +use ping_core::SenderInstruction; + +fn main() { + let ( + ProgramInput { + self_program_id, + caller_program_id, + pre_states, + instruction, + }, + instruction_words, + ) = read_lee_inputs::(); + + assert!( + caller_program_id.is_none(), + "ping_sender is only invoked as a top-level user transaction" + ); + + let SenderInstruction::Send { + outbox_program_id, + target_zone, + target_program_id, + target_accounts, + payload, + ordinal, + } = instruction; + + // The single account is the outbox PDA the chained call writes into; the + // outbox claims it, so ping_sender forwards it unchanged. + let [outbox] = + <[AccountWithMetadata; 1]>::try_from(pre_states).expect("Send requires exactly 1 account"); + + let call = ChainedCall::new( + outbox_program_id, + vec![outbox.clone()], + &OutboxInstruction::Emit { + target_zone, + target_program_id, + target_accounts, + payload, + ordinal, + }, + ); + + ProgramOutput::new( + self_program_id, + caller_program_id, + instruction_words, + vec![outbox.clone()], + vec![AccountPostState::new(outbox.account)], + ) + .with_chained_calls(vec![call]) + .write(); +} diff --git a/programs/cross_zone_outbox/core/src/lib.rs b/programs/cross_zone_outbox/core/src/lib.rs index 528cf769..b3449598 100644 --- a/programs/cross_zone_outbox/core/src/lib.rs +++ b/programs/cross_zone_outbox/core/src/lib.rs @@ -19,6 +19,10 @@ pub enum Instruction { Emit { target_zone: ZoneId, target_program_id: ProgramId, + /// Accounts the destination inbox must hand to the target program's + /// chained call. The emitter specifies them; the watcher forwards them + /// verbatim so the inbox stays target-agnostic. + target_accounts: Vec<[u8; 32]>, payload: Vec, ordinal: u32, }, @@ -31,6 +35,7 @@ pub enum Instruction { pub struct OutboxRecord { pub target_zone: ZoneId, pub target_program_id: ProgramId, + pub target_accounts: Vec<[u8; 32]>, pub payload: Vec, } diff --git a/programs/ping/core/src/lib.rs b/programs/ping/core/src/lib.rs index f6b5b963..268e5b98 100644 --- a/programs/ping/core/src/lib.rs +++ b/programs/ping/core/src/lib.rs @@ -12,6 +12,19 @@ pub enum ReceiverInstruction { Record { payload: Vec }, } +/// Instruction to `ping_sender`: forwarded verbatim into `cross_zone_outbox::Instruction::Emit`. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum SenderInstruction { + Send { + outbox_program_id: ProgramId, + target_zone: [u8; 32], + target_program_id: ProgramId, + target_accounts: Vec<[u8; 32]>, + payload: Vec, + ordinal: u32, + }, +} + /// The account a `ping_receiver` records the latest delivered payload into. #[must_use] pub fn ping_record_pda(receiver_id: ProgramId) -> AccountId {