From ff0b4766aebaffcfeab26b1e5122f823e298170b Mon Sep 17 00:00:00 2001 From: Hendrik Hofstadt Date: Fri, 7 Aug 2020 16:10:30 +0200 Subject: [PATCH] add instruction constructors --- docs/solana_program.md | 28 ++--- solana/bridge/src/error.rs | 3 + solana/bridge/src/error_program.rs | 1 + solana/bridge/src/instruction.rs | 157 +++++++++++++++++++++++++++-- solana/bridge/src/processor.rs | 54 ++++------ solana/bridge/src/state.rs | 15 ++- solana/bridge/src/vaa.rs | 13 ++- 7 files changed, 203 insertions(+), 68 deletions(-) diff --git a/docs/solana_program.md b/docs/solana_program.md index 6ac6117b0..36b55f408 100644 --- a/docs/solana_program.md +++ b/docs/solana_program.md @@ -28,13 +28,11 @@ Parameters: | ----- | -------- | ------------------- | ------ | --------- | ----- | ------- | | 0 | sys | SystemProgram | | | ️ | | | 1 | token_program | SplToken | | | ️ | | -| 2 | clock | Sysvar | | | ️ | ✅ | -| 3 | sender | TokenAccount | | ✅ | | | -| 4 | bridge | BridgeConfig | | | | | -| 5 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ | -| 6 | token | WrappedAsset | | ✅ | | ✅ | -| 7 | payer | Account | ✅ | | | | -| 8-n | sender_owner | Account | ✅ | | | | +| 2 | token_account | TokenAccount | | ✅ | | | +| 3 | bridge | BridgeConfig | | | | | +| 4 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ | +| 5 | token | WrappedAsset | | ✅ | | ✅ | +| 6 | payer | Account | ✅ | | | | #### TransferOutNative @@ -47,14 +45,12 @@ The transfer proposal will be tracked at a new account `proposal` where a VAA wi | ----- | --------------- | ------------------- | ------ | --------- | ----- | ------- | | 0 | sys | SystemProgram | | | ️ | | | 1 | token_program | SplToken | | | ️ | | -| 2 | clock | Sysvar | | | ️ | ✅ | -| 3 | sender | TokenAccount | | ✅ | | | -| 4 | bridge | BridgeConfig | | | | | -| 5 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ | -| 6 | token | Mint | | ✅ | | | +| 2 | token_account | TokenAccount | | ✅ | | | +| 3 | bridge | BridgeConfig | | | | | +| 4 | proposal | TransferOutProposal | | ✅ | ✅ | ✅ | +| 5 | token | Mint | | ✅ | | | +| 6 | payer | Account | ✅ | | | | | 7 | custody_account | TokenAccount | | ✅ | opt | ✅ | -| 8 | payer | Account | ✅ | | | | -| 9-n | sender_owner | Account | ✅ | | | | #### EvictTransferOut @@ -108,9 +104,8 @@ followed by: | Index | Name | Type | signer | writeable | empty | derived | | ----- | ------------ | ------------ | ------ | --------- | ----- | ------- | | 6 | token_program | SplToken | | | ️ | | -| 7 | token | WrappedAsset | | | opt | ✅ | +| 7 | token | WrappedAsset | | | opt | ✅ | | 8 | destination | TokenAccount | | ✅ | opt | | -| 9 | sender | Account | ✅ | | | | ##### Transfer: Ethereum (wrapped) -> Solana (native) @@ -125,7 +120,6 @@ followed by: | Index | Name | Type | signer | writeable | empty | derived | | ----- | ------------ | ------------------- | ------ | --------- | ----- | ------- | -| 6 | token_program | SplToken | | | ️ | | | 7 | out_proposal | TransferOutProposal | | ✅ | | ✅ | | 8 | sender | Account | ✅ | | | | diff --git a/solana/bridge/src/error.rs b/solana/bridge/src/error.rs index 575ebeac5..d888459d8 100644 --- a/solana/bridge/src/error.rs +++ b/solana/bridge/src/error.rs @@ -91,6 +91,9 @@ pub enum Error { /// Invalid transfer with src=dst #[error("SameChainTransfer")] SameChainTransfer, + /// VAA is longer than the maximum size + #[error("VAATooLong")] + VAATooLong, } impl From for ProgramError { diff --git a/solana/bridge/src/error_program.rs b/solana/bridge/src/error_program.rs index e43509588..a3d497072 100644 --- a/solana/bridge/src/error_program.rs +++ b/solana/bridge/src/error_program.rs @@ -35,6 +35,7 @@ impl PrintProgramError for Error { Error::ExpectedTransferOutProposal => info!("Error: ExpectedTransferOutProposal"), Error::VAAProposalMismatch => info!("Error: VAAProposalMismatch"), Error::SameChainTransfer => info!("Error: SameChainTransfer"), + Error::VAATooLong => info!("Error: VAATooLong"), } } } diff --git a/solana/bridge/src/instruction.rs b/solana/bridge/src/instruction.rs index d6151bf34..f1b336db5 100644 --- a/solana/bridge/src/instruction.rs +++ b/solana/bridge/src/instruction.rs @@ -10,21 +10,23 @@ use solana_sdk::{ pubkey::Pubkey, }; +use crate::error::Error::VAATooLong; use crate::instruction::BridgeInstruction::Initialize; -use crate::state::{AssetMeta, BridgeConfig}; +use crate::state::{AssetMeta, Bridge, BridgeConfig}; use crate::syscalls::RawKey; +use crate::vaa::{VAABody, VAA}; /// chain id of this chain pub const CHAIN_ID_SOLANA: u8 = 1; /// size of a VAA in bytes -const VAA_SIZE: usize = 100; +const VAA_SIZE: usize = 200; /// size of a foreign address in bytes const FOREIGN_ADDRESS_SIZE: usize = 32; -/// validator payment approval -pub type VAA_BODY = [u8; VAA_SIZE]; +/// length-prefixed serialized validator payment approval data +pub type VAAData = [u8; VAA_SIZE]; /// X and Y point of P for guardians pub type GuardianKey = [u8; 64]; /// address on a foreign chain @@ -50,6 +52,8 @@ pub struct TransferOutPayload { pub asset: AssetMeta, /// address on the foreign chain to transfer to pub target: ForeignAddress, + /// unique nonce of the transfer + pub nonce: u32, } /// Instructions supported by the SwapInfo program. @@ -91,7 +95,7 @@ pub enum BridgeInstruction { /// Submits a VAA signed by `guardian` on a valid `proposal`. /// See docs for accounts - PostVAA(VAA_BODY), + 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. @@ -143,7 +147,7 @@ impl BridgeInstruction { output[0] = 2; #[allow(clippy::cast_ptr_alignment)] let value = - unsafe { &mut *(&mut output[size_of::()] as *mut u8 as *mut VAA_BODY) }; + unsafe { &mut *(&mut output[size_of::()] as *mut u8 as *mut VAAData) }; *value = payload; } Self::EvictTransferOut() => { @@ -161,7 +165,6 @@ impl BridgeInstruction { pub fn initialize( program_id: &Pubkey, sender: &Pubkey, - bridge: &Pubkey, initial_guardian: RawKey, config: &BridgeConfig, ) -> Result { @@ -171,9 +174,15 @@ pub fn initialize( }) .serialize()?; + let bridge_key = Bridge::derive_bridge_id(program_id)?; + let guardian_set_key = Bridge::derive_guardian_set_id(program_id, &bridge_key, 0)?; + let accounts = vec![ + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::clock::id(), false), + AccountMeta::new(bridge_key, false), + AccountMeta::new(guardian_set_key, false), AccountMeta::new(*sender, true), - AccountMeta::new(*bridge, false), ]; Ok(Instruction { @@ -183,6 +192,138 @@ pub fn initialize( }) } +/// Creates an 'TransferOut' instruction. +pub fn transfer_out( + program_id: &Pubkey, + payer: &Pubkey, + token_account: &Pubkey, + token_mint: &Pubkey, + t: &TransferOutPayload, +) -> Result { + let data = BridgeInstruction::TransferOut(*t).serialize()?; + + let bridge_key = Bridge::derive_bridge_id(program_id)?; + let transfer_key = Bridge::derive_transfer_id( + program_id, + &bridge_key, + t.asset.chain, + t.asset.address, + t.chain_id, + t.target, + token_account.to_bytes(), + t.nonce, + )?; + + let mut accounts = vec![ + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new(*token_account, false), + AccountMeta::new(bridge_key, false), + AccountMeta::new(transfer_key, false), + AccountMeta::new(*token_mint, 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)); + } + + Ok(Instruction { + program_id: *program_id, + accounts, + data, + }) +} + +/// Creates a 'PostVAA' instruction. +pub fn post_vaa( + program_id: &Pubkey, + payer: &Pubkey, + v: &[u8], +) -> Result { + // VAA must be <= VAA_SIZE-1 to allow for the length prefix + if v.len() > VAA_SIZE - 1 { + return Err(VAATooLong.into()); + } + // Convert data to length-prefixed on-chain format + let mut vaa_data: Vec = vec![]; + vaa_data.push(v.len() as u8); + vaa_data.append(&mut v.to_vec()); + + let mut vaa_chain: [u8; 200] = [0; 200]; + vaa_chain.copy_from_slice(vaa_data.as_slice()); + + let data = BridgeInstruction::PostVAA(vaa_chain).serialize()?; + + // Parse VAA + let vaa = VAA::deserialize(&v[..])?; + + let bridge_key = Bridge::derive_bridge_id(program_id)?; + let guardian_set_key = + Bridge::derive_guardian_set_id(program_id, &bridge_key, vaa.guardian_set_index)?; + let claim_key = Bridge::derive_claim_id(program_id, &bridge_key, &vaa.body_hash()?)?; + + let mut accounts = vec![ + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), + AccountMeta::new_readonly(solana_sdk::sysvar::clock::id(), false), + AccountMeta::new(bridge_key, false), + AccountMeta::new(guardian_set_key, false), + AccountMeta::new(claim_key, false), + AccountMeta::new(*payer, true), + ]; + + match vaa.payload.unwrap() { + VAABody::UpdateGuardianSet(u) => { + let guardian_set_key = + Bridge::derive_guardian_set_id(program_id, &bridge_key, u.new_index)?; + accounts.push(AccountMeta::new(guardian_set_key, 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.address, + )?; + 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)); + } + } + } + + 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::() { diff --git a/solana/bridge/src/processor.rs b/solana/bridge/src/processor.rs index 07177943d..d2bfcd868 100644 --- a/solana/bridge/src/processor.rs +++ b/solana/bridge/src/processor.rs @@ -1,13 +1,11 @@ //! Program instruction processing logic #![cfg(feature = "program")] -use std::io::Write; use std::mem::size_of; use std::slice::Iter; use num_traits::AsPrimitive; use primitive_types::U256; -use sha3::Digest; use solana_sdk::clock::Clock; #[cfg(not(target_arch = "bpf"))] use solana_sdk::instruction::Instruction; @@ -24,7 +22,7 @@ use spl_token::state::Mint; use crate::error::Error; use crate::instruction::BridgeInstruction::*; -use crate::instruction::{BridgeInstruction, TransferOutPayload, CHAIN_ID_SOLANA}; +use crate::instruction::{BridgeInstruction, TransferOutPayload, VAAData, CHAIN_ID_SOLANA}; use crate::state::*; use crate::syscalls::RawKey; use crate::vaa::{BodyTransfer, BodyUpdateGuardianSet, VAABody, VAA}; @@ -59,13 +57,9 @@ impl Bridge { let vaa_data = &vaa_body[..len]; let vaa = VAA::deserialize(vaa_data)?; - let mut k = sha3::Keccak256::default(); - if let Err(_) = k.write(vaa_data) { - return Err(Error::ParseFailed.into()); - }; - let hash = k.finalize(); + let hash = vaa.body_hash()?; - Self::process_vaa(program_id, accounts, &vaa_body, &vaa, hash.as_ref()) + Self::process_vaa(program_id, accounts, vaa_body, &vaa, &hash) } _ => panic!(""), } @@ -88,7 +82,7 @@ impl Bridge { let clock = Clock::from_account_info(clock_info)?; // Create bridge account - let bridge_seed = Bridge::derive_bridge_seeds(program_id); + let bridge_seed = Bridge::derive_bridge_seeds(); Bridge::check_and_create_account::( program_id, accounts, @@ -142,7 +136,6 @@ impl Bridge { let account_info_iter = &mut accounts.iter(); next_account_info(account_info_iter)?; // System program next_account_info(account_info_iter)?; // Token program - let clock_info = next_account_info(account_info_iter)?; let sender_account_info = next_account_info(account_info_iter)?; let bridge_info = next_account_info(account_info_iter)?; let transfer_info = next_account_info(account_info_iter)?; @@ -150,7 +143,6 @@ impl Bridge { let payer_info = next_account_info(account_info_iter)?; let authority_info = next_account_info(account_info_iter)?; - let clock = Clock::from_account_info(clock_info)?; let sender = Bridge::token_account_deserialize(sender_account_info)?; let bridge = Bridge::bridge_deserialize(bridge_info)?; let mint = Bridge::mint_deserialize(mint_info)?; @@ -184,7 +176,7 @@ impl Bridge { t.chain_id, t.target, sender.owner.to_bytes(), - clock.slot.as_(), //TODO use nonce + t.nonce, ); Bridge::check_and_create_account::( program_id, @@ -229,16 +221,13 @@ impl Bridge { let account_info_iter = &mut accounts.iter(); next_account_info(account_info_iter)?; // System program next_account_info(account_info_iter)?; // Token program - let clock_info = next_account_info(account_info_iter)?; let sender_account_info = next_account_info(account_info_iter)?; let bridge_info = next_account_info(account_info_iter)?; let transfer_info = next_account_info(account_info_iter)?; let mint_info = next_account_info(account_info_iter)?; - let custody_info = next_account_info(account_info_iter)?; let payer_info = next_account_info(account_info_iter)?; - let sender_info = next_account_info(account_info_iter)?; + let custody_info = next_account_info(account_info_iter)?; - let clock = Clock::from_account_info(clock_info)?; let sender = Bridge::token_account_deserialize(sender_account_info)?; let bridge = Bridge::bridge_deserialize(bridge_info)?; let mint = Bridge::mint_deserialize(mint_info)?; @@ -260,8 +249,8 @@ impl Bridge { t.asset.address, t.chain_id, t.target, - sender.owner.to_bytes(), - clock.slot.as_(), //TODO use nonce + sender_account_info.key.to_bytes(), + t.nonce, ); Bridge::check_and_create_account::( program_id, @@ -310,7 +299,7 @@ impl Bridge { &bridge.config.token_program, sender_account_info.key, custody_info.key, - sender_info.key, + bridge_info.key, t.amount, )?; @@ -333,7 +322,7 @@ impl Bridge { pub fn process_vaa( program_id: &Pubkey, accounts: &[AccountInfo], - vaa_data: &[u8; 100], + vaa_data: VAAData, vaa: &VAA, hash: &[u8; 32], ) -> ProgramResult { @@ -508,6 +497,7 @@ impl Bridge { bridge: &mut Bridge, b: &BodyTransfer, ) -> ProgramResult { + next_account_info(account_info_iter)?; // Token program let mint_info = next_account_info(account_info_iter)?; let destination_info = next_account_info(account_info_iter)?; @@ -578,7 +568,7 @@ impl Bridge { bridge_info: &AccountInfo, vaa: &VAA, b: &BodyTransfer, - vaa_data: &[u8; 100], + vaa_data: VAAData, ) -> ProgramResult { let proposal_info = next_account_info(account_info_iter)?; @@ -603,7 +593,7 @@ impl Bridge { } // Set vaa - proposal.vaa = *vaa_data; + proposal.vaa = vaa_data; proposal.vaa_time = vaa.timestamp; Ok(()) @@ -671,7 +661,7 @@ impl Bridge { all_signers.as_slice(), amount, )?; - invoke_signed(&ix, accounts, &[]) + invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]]) } /// Mint a wrapped asset to account @@ -691,7 +681,7 @@ impl Bridge { &[], amount, )?; - invoke_signed(&ix, accounts, &[&[&bridge.to_bytes()[..32]][..]]) + invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]]) } /// Transfer tokens from a caller @@ -703,19 +693,15 @@ impl Bridge { authority: &Pubkey, amount: U256, ) -> Result<(), ProgramError> { - let all_signers: Vec<&Pubkey> = accounts - .iter() - .filter_map(|item| if item.is_signer { Some(item.key) } else { None }) - .collect(); let ix = spl_token::instruction::transfer( token_program_id, source, destination, authority, - all_signers.as_slice(), + &[], amount, )?; - invoke_signed(&ix, accounts, &[]) + invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]]) } /// Transfer tokens from a custody account @@ -735,7 +721,7 @@ impl Bridge { &[], amount, )?; - invoke_signed(&ix, accounts, &[&[&bridge.to_bytes()[..32]][..]]) + invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]]) } /// Create a new account @@ -756,7 +742,7 @@ impl Bridge { &Self::derive_custody_seeds(bridge, mint), )?; let ix = spl_token::instruction::initialize_account(token_program, account, mint, bridge)?; - invoke_signed(&ix, accounts, &[&[&bridge.to_bytes()[..32]][..]]) + invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]]) } /// Create a mint for a wrapped asset @@ -784,7 +770,7 @@ impl Bridge { U256::from(0), 8, )?; - invoke_signed(&ix, accounts, &[&[&bridge.to_bytes()[..32]][..]]) + invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]]) } /// Check that a key was derived correctly and create account diff --git a/solana/bridge/src/state.rs b/solana/bridge/src/state.rs index 2404e7b6b..f4015f91f 100644 --- a/solana/bridge/src/state.rs +++ b/solana/bridge/src/state.rs @@ -2,15 +2,14 @@ use std::mem::size_of; +use primitive_types::U256; use solana_sdk::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; - -use crate::instruction::{ForeignAddress, VAA_BODY}; +use zerocopy::AsBytes; use crate::error::Error; +use crate::instruction::{ForeignAddress, VAAData}; use crate::syscalls::RawKey; use crate::vaa::BodyTransfer; -use primitive_types::U256; -use zerocopy::AsBytes; /// fee rate as a ratio #[repr(C)] @@ -58,7 +57,7 @@ pub struct TransferOutProposal { /// asset that is being transferred pub asset: AssetMeta, /// vaa to unlock the tokens on the foreign chain - pub vaa: VAA_BODY, + pub vaa: VAAData, /// time the vaa was submitted pub vaa_time: u32, @@ -244,8 +243,8 @@ impl Bridge { } /// Calculates derived seeds for a bridge - pub fn derive_bridge_seeds(program_id: &Pubkey) -> Vec> { - vec![program_id.to_bytes().to_vec()] + pub fn derive_bridge_seeds() -> Vec> { + vec!["bridge".as_bytes().to_vec()] } /// Calculates derived seeds for a custody account @@ -268,7 +267,7 @@ impl Bridge { /// Calculates a derived address for this program pub fn derive_bridge_id(program_id: &Pubkey) -> Result { - Self::derive_key(program_id, &Self::derive_bridge_seeds(program_id)) + Self::derive_key(program_id, &Self::derive_bridge_seeds()) } /// Calculates a derived address for a custody account diff --git a/solana/bridge/src/vaa.rs b/solana/bridge/src/vaa.rs index dc935b27f..312047f7e 100644 --- a/solana/bridge/src/vaa.rs +++ b/solana/bridge/src/vaa.rs @@ -55,6 +55,18 @@ impl VAA { sol_verify_schnorr(&schnorr_input) } + pub fn body_hash(&self) -> Result<[u8; 32], Error> { + let body_bytes = self.signature_body()?; + + let mut k = sha3::Keccak256::default(); + if let Err(_) = k.write(body_bytes.as_slice()) { + return Err(Error::ParseFailed.into()); + }; + let hash = k.finalize(); + + return Ok(hash.into()); + } + pub fn serialize(&self) -> Result, Error> { let mut v = Cursor::new(Vec::new()); @@ -252,7 +264,6 @@ impl BodyTransfer { #[cfg(test)] mod tests { - use hex; use primitive_types::U256;