diff --git a/solana/bridge/src/entrypoint.rs b/solana/bridge/src/entrypoint.rs index 8741abdc..953a9843 100644 --- a/solana/bridge/src/entrypoint.rs +++ b/solana/bridge/src/entrypoint.rs @@ -3,8 +3,8 @@ #![cfg(not(feature = "no-entrypoint"))] use solana_program::{ - account_info::AccountInfo,entrypoint, entrypoint::ProgramResult, program_error::PrintProgramError, - pubkey::Pubkey, + account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, + program_error::PrintProgramError, pubkey::Pubkey, }; use crate::{error::Error, state::Bridge}; diff --git a/solana/bridge/src/instruction.rs b/solana/bridge/src/instruction.rs index c2ff06cf..ba605b8f 100644 --- a/solana/bridge/src/instruction.rs +++ b/solana/bridge/src/instruction.rs @@ -3,20 +3,20 @@ use std::mem::size_of; -use primitive_types::U256; use solana_program::{ instruction::{AccountMeta, Instruction}, program_error::ProgramError, pubkey::Pubkey, }; +use crate::instruction::BridgeInstruction::PublishMessage; use crate::{ - instruction::BridgeInstruction::{ - CreateWrapped, Initialize, PokeProposal, PostVAA, TransferOut, VerifySignatures, - }, - state::{AssetMeta, Bridge, BridgeConfig}, + instruction::BridgeInstruction::{Initialize, PostVAA, VerifySignatures}, + state::{Bridge, BridgeConfig}, vaa::{VAABody, VAA}, }; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use std::io::{Cursor, Read, Write}; /// chain id of this chain pub const CHAIN_ID_SOLANA: u8 = 1; @@ -24,6 +24,8 @@ pub const CHAIN_ID_SOLANA: u8 = 1; pub const MAX_LEN_GUARDIAN_KEYS: usize = 20; /// maximum size of a posted VAA pub const MAX_VAA_SIZE: usize = 1000; +/// maximum size of a posted VAA +pub const MAX_PAYLOAD_SIZE: usize = 400; /// size of a foreign address in bytes const FOREIGN_ADDRESS_SIZE: usize = 32; @@ -45,34 +47,21 @@ pub struct InitializePayload { pub config: BridgeConfig, } -#[repr(C)] -#[derive(Clone, Copy, Debug)] -pub struct TransferOutPayload { - /// amount to transfer - pub amount: U256, - /// chain id to transfer to - pub chain_id: u8, - /// Information about the asset to be transferred - pub asset: AssetMeta, - /// address on the foreign chain to transfer to - pub target: ForeignAddress, - /// unique nonce of the transfer +pub struct PublishMessagePayload { + /// unique nonce for this message pub nonce: u32, + /// message payload + pub payload: Vec, } -#[repr(C)] -#[derive(Clone, Copy, Debug)] -pub struct TransferOutPayloadRaw { - /// amount to transfer - pub amount: [u8; 32], - /// chain id to transfer to - pub chain_id: u8, - /// Information about the asset to be transferred - pub asset: AssetMeta, - /// address on the foreign chain to transfer to - pub target: ForeignAddress, - /// unique nonce of the transfer - pub nonce: u32, +impl Clone for PublishMessagePayload { + fn clone(&self) -> PublishMessagePayload { + let payload = self.payload.clone(); + return PublishMessagePayload { + payload, + nonce: self.nonce, + }; + } } #[derive(Clone, Copy, Debug)] @@ -98,50 +87,16 @@ pub enum BridgeInstruction { /// 4. `[signer]` The fee payer for new account creation Initialize(InitializePayload), - /// Burns or locks a (wrapped) asset `token` from `sender` on the Solana chain. - /// - /// Wrapped asset transfer out - /// 0. `[writable]` The from token account - /// 1. `[]` The System program. - /// 2. `[]` The spl token program. - /// 3. `[]` The clock SysVar - /// 4. `[derived]` The bridge config - /// 5. `[writable, derived, empty]` The new transfer out tracking account - /// 6. `[writable, derived]` The mint of the wrapped asset - /// 7. ..7+M '[signer]' M signer accounts (from token authority) - /// - /// Native token transfer out - /// 0. `[writable]` The from token account - /// 1. `[]` The System program. - /// 2. `[]` The spl token program. - /// 3. `[]` The clock SysVar - /// 4. `[derived]` The bridge config - /// 5. `[writable, derived, empty]` The new transfer out tracking account - /// 6. `[writable, derived]` The mint of the wrapped asset - /// 7. `[writable, derived]` The custody token account of the bridge - /// 8. ..8+M '[signer]' M signer accounts (from token authority) - TransferOut(TransferOutPayload), + /// Publishes a message over the Wormhole network. + /// See docs for accounts + PublishMessage(PublishMessagePayload), /// Submits a VAA signed by `guardian` on a valid `proposal`. /// See docs for accounts PostVAA(VAAData), - /// Deletes a `proposal` after the `VAA_EXPIRATION_TIME` is over to free up space on chain. - /// This returns the rent to the sender. - EvictTransferOut(), - - /// Deletes a `ExecutedVAA` after the `VAA_EXPIRATION_TIME` is over to free up space on chain. - /// This returns the rent to the sender. - EvictClaimedVAA(), - - /// Pokes a proposal with no valid VAAs attached so guardians reprocess it. - PokeProposal(), - /// Verifies signature instructions VerifySignatures(VerifySigPayload), - - /// Creates a new wrapped asset - CreateWrapped(AssetMeta), } impl BridgeInstruction { @@ -157,32 +112,32 @@ impl BridgeInstruction { Initialize(*payload) } 1 => { - let payload: &TransferOutPayloadRaw = unpack(input)?; - let amount = U256::from_big_endian(&payload.amount); + let mut payload_data = Cursor::new(input); - TransferOut(TransferOutPayload { - amount, - chain_id: payload.chain_id, - asset: payload.asset, - target: payload.target, - nonce: payload.nonce, - }) + let nonce = payload_data + .read_u32::() + .map_err(|_| ProgramError::InvalidArgument)?; + let mut message_payload: Vec = vec![]; + payload_data + .read(&mut message_payload) + .map_err(|_| ProgramError::InvalidArgument)?; + + let payload: PublishMessagePayload = PublishMessagePayload { + nonce, + payload: message_payload, + }; + + PublishMessage(payload) } 2 => { let payload: VAAData = input[1..].to_vec(); PostVAA(payload) } - 5 => PokeProposal(), - 6 => { + 3 => { let payload: &VerifySigPayload = unpack(input)?; VerifySignatures(*payload) } - 7 => { - let payload: &AssetMeta = unpack(input)?; - - CreateWrapped(*payload) - } _ => return Err(ProgramError::InvalidInstructionData), }) } @@ -196,65 +151,36 @@ impl BridgeInstruction { output.resize(size_of::() + 1, 0); output[0] = 0; #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { + let value = unsafe { &mut *(&mut output[size_of::()] as *mut u8 as *mut InitializePayload) }; *value = payload; } - Self::TransferOut(payload) => { - output.resize(size_of::() + 1, 0); - output[0] = 1; - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { - &mut *(&mut output[size_of::()] as *mut u8 as *mut TransferOutPayloadRaw) - }; + Self::PublishMessage(payload) => { + let mut v: Cursor> = Cursor::new(Vec::new()); + v.write_u8(1).map_err(|_| ProgramError::InvalidArgument)?; + v.write_u32::(payload.nonce) + .map_err(|_| ProgramError::InvalidArgument)?; + v.write(&payload.payload) + .map_err(|_| ProgramError::InvalidArgument)?; - let mut amount_bytes = [0u8; 32]; - payload.amount.to_big_endian(&mut amount_bytes); - - *value = TransferOutPayloadRaw { - amount: amount_bytes, - chain_id: payload.chain_id, - asset: payload.asset, - target: payload.target, - nonce: payload.nonce, - }; + output = v.into_inner(); } Self::PostVAA(payload) => { output.resize(1, 0); output[0] = 2; #[allow(clippy::cast_ptr_alignment)] - output.extend_from_slice(&payload); - } - Self::EvictTransferOut() => { - output.resize(1, 0); - output[0] = 3; - } - Self::EvictClaimedVAA() => { - output.resize(1, 0); - output[0] = 4; - } - Self::PokeProposal() => { - output.resize(1, 0); - output[0] = 5; + output.extend_from_slice(&payload); } Self::VerifySignatures(payload) => { output.resize(size_of::() + 1, 0); - output[0] = 6; + output[0] = 3; #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { + let value = unsafe { &mut *(&mut output[size_of::()] as *mut u8 as *mut VerifySigPayload) }; *value = payload; } - Self::CreateWrapped(payload) => { - output.resize(size_of::() + 1, 0); - output[0] = 7; - #[allow(clippy::cast_ptr_alignment)] - let value = - unsafe { &mut *(&mut output[size_of::()] as *mut u8 as *mut AssetMeta) }; - *value = payload; - } } Ok(output) } @@ -280,7 +206,7 @@ pub fn initialize( len_guardians: initial_guardian.len() as u8, initial_guardian: initial_g, }) - .serialize()?; + .serialize()?; let bridge_key = Bridge::derive_bridge_id(program_id)?; let guardian_set_key = Bridge::derive_guardian_set_id(program_id, &bridge_key, 0)?; @@ -300,48 +226,36 @@ pub fn initialize( }) } -/// Creates an 'TransferOut' instruction. +/// Creates an 'PublishMessage' instruction. #[cfg(not(target_arch = "bpf"))] -pub fn transfer_out( +pub fn publish_message( program_id: &Pubkey, payer: &Pubkey, - token_account: &Pubkey, - token_mint: &Pubkey, - t: &TransferOutPayload, + t: &PublishMessagePayload, ) -> Result { - let data = BridgeInstruction::TransferOut(*t).serialize()?; - let bridge_key = Bridge::derive_bridge_id(program_id)?; - let transfer_key = Bridge::derive_transfer_id( + + let message_key = Bridge::derive_message_id( program_id, &bridge_key, - t.asset.chain, - t.asset.address, - t.chain_id, - t.target, - token_account.to_bytes(), + CHAIN_ID_SOLANA, + payer.to_bytes(), t.nonce, + &t.payload, )?; - let mut accounts = vec![ + let accounts = vec![ AccountMeta::new_readonly(*program_id, false), AccountMeta::new_readonly(solana_program::system_program::id(), false), - AccountMeta::new_readonly(spl_token::id(), false), AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false), AccountMeta::new_readonly(solana_program::sysvar::clock::id(), false), AccountMeta::new_readonly(solana_program::sysvar::instructions::id(), false), - AccountMeta::new(*token_account, false), AccountMeta::new_readonly(bridge_key, false), - AccountMeta::new(transfer_key, false), - AccountMeta::new(*token_mint, false), + AccountMeta::new(message_key, false), AccountMeta::new(*payer, true), ]; - // If the token is a native solana token add a custody account - if t.asset.chain == CHAIN_ID_SOLANA { - let custody_key = Bridge::derive_custody_id(program_id, &bridge_key, token_mint)?; - accounts.push(AccountMeta::new(custody_key, false)); - } + let data = BridgeInstruction::PublishMessage(t.clone()).serialize()?; Ok(Instruction { program_id: *program_id, @@ -429,48 +343,26 @@ pub fn post_vaa( // Make program writeable accounts[0] = AccountMeta::new(*program_id, false); accounts.push(AccountMeta::new(u.buffer, false)); - let (programdata_address, _) = Pubkey::find_program_address(&[program_id.as_ref()], &solana_program::bpf_loader_upgradeable::id()); + let (programdata_address, _) = Pubkey::find_program_address( + &[program_id.as_ref()], + &solana_program::bpf_loader_upgradeable::id(), + ); accounts.push(AccountMeta::new(programdata_address, false)); - accounts.push(AccountMeta::new_readonly(solana_program::bpf_loader_upgradeable::id(), false)); + accounts.push(AccountMeta::new_readonly( + solana_program::bpf_loader_upgradeable::id(), + false, + )); } - VAABody::Transfer(t) => { - if t.source_chain == CHAIN_ID_SOLANA { - // Solana (any) -> Ethereum (any) - let transfer_key = Bridge::derive_transfer_id( - program_id, - &bridge_key, - t.asset.chain, - t.asset.address, - t.target_chain, - t.target_address, - t.source_address, - t.nonce, - )?; - accounts.push(AccountMeta::new(transfer_key, false)) - } else if t.asset.chain == CHAIN_ID_SOLANA { - // Foreign (wrapped) -> Solana (native) - let mint_key = Pubkey::new(&t.asset.address); - let custody_key = Bridge::derive_custody_id(program_id, &bridge_key, &mint_key)?; - accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); - accounts.push(AccountMeta::new(mint_key, false)); - accounts.push(AccountMeta::new(Pubkey::new(&t.target_address), false)); - accounts.push(AccountMeta::new(custody_key, false)); - } else { - // Foreign (native) -> Solana (wrapped) - let wrapped_key = Bridge::derive_wrapped_asset_id( - program_id, - &bridge_key, - t.asset.chain, - t.asset.decimals, - t.asset.address, - )?; - let wrapped_meta_key = - Bridge::derive_wrapped_meta_id(program_id, &bridge_key, &wrapped_key)?; - accounts.push(AccountMeta::new_readonly(spl_token::id(), false)); - accounts.push(AccountMeta::new(wrapped_key, false)); - accounts.push(AccountMeta::new(Pubkey::new(&t.target_address), false)); - accounts.push(AccountMeta::new(wrapped_meta_key, false)); - } + VAABody::Message(t) => { + let message_key = Bridge::derive_message_id( + program_id, + &bridge_key, + t.emitter_chain, + t.emitter_address, + t.nonce, + &t.data, + )?; + accounts.push(AccountMeta::new(message_key, false)) } } @@ -481,65 +373,12 @@ pub fn post_vaa( }) } -/// Creates a 'CreateWrapped' instruction. -pub fn create_wrapped( - program_id: &Pubkey, - payer: &Pubkey, - meta: AssetMeta, -) -> Result { - let data = BridgeInstruction::CreateWrapped(meta).serialize()?; - - let bridge_key = Bridge::derive_bridge_id(program_id)?; - let wrapped_mint_key = Bridge::derive_wrapped_asset_id( - program_id, - &bridge_key, - meta.chain, - meta.decimals, - meta.address, - )?; - let wrapped_meta_key = - Bridge::derive_wrapped_meta_id(program_id, &bridge_key, &wrapped_mint_key)?; - - let accounts = vec![ - AccountMeta::new_readonly(solana_program::system_program::id(), false), - AccountMeta::new_readonly(spl_token::id(), false), - AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false), - AccountMeta::new_readonly(bridge_key, false), - AccountMeta::new(*payer, true), - AccountMeta::new(wrapped_mint_key, false), - AccountMeta::new(wrapped_meta_key, false), - ]; - - Ok(Instruction { - program_id: *program_id, - accounts, - data, - }) -} - -/// Creates an 'PokeProposal' instruction. -#[cfg(not(target_arch = "bpf"))] -pub fn poke_proposal( - program_id: &Pubkey, - transfer_proposal: &Pubkey, -) -> Result { - let data = BridgeInstruction::PokeProposal().serialize()?; - - let accounts = vec![AccountMeta::new(*transfer_proposal, false)]; - - Ok(Instruction { - program_id: *program_id, - accounts, - data, - }) -} - /// Unpacks a reference from a bytes buffer. pub fn unpack(input: &[u8]) -> Result<&T, ProgramError> { if input.len() < size_of::() + size_of::() { return Err(ProgramError::InvalidInstructionData); } #[allow(clippy::cast_ptr_alignment)] - let val: &T = unsafe { &*(&input[1] as *const u8 as *const T) }; + let val: &T = unsafe { &*(&input[1] as *const u8 as *const T) }; Ok(val) }