From 1f30398e3061e1251deccdfc532c674ae8f9eb59 Mon Sep 17 00:00:00 2001 From: Hendrik Hofstadt Date: Thu, 15 Apr 2021 11:36:29 +0200 Subject: [PATCH] refactor new ix processing, remove old processors Change-Id: I682573bf8544c5a19b2b57c6660e9e86e9b12cf9 --- solana/bridge/src/processor.rs | 446 +++++++-------------------------- 1 file changed, 94 insertions(+), 352 deletions(-) diff --git a/solana/bridge/src/processor.rs b/solana/bridge/src/processor.rs index ddabdd791..6037a5d3e 100644 --- a/solana/bridge/src/processor.rs +++ b/solana/bridge/src/processor.rs @@ -1,42 +1,37 @@ //! Program instruction processing logic #![cfg(feature = "program")] -use std::{borrow::Borrow, cell::RefCell, io::Write, mem::size_of, slice::Iter}; +use std::{io::Write, mem::size_of, slice::Iter}; use byteorder::ByteOrder; use num_traits::AsPrimitive; -use primitive_types::U256; + use sha3::Digest; use solana_program::program::invoke_signed; use solana_program::{ account_info::{next_account_info, AccountInfo}, clock::Clock, entrypoint::ProgramResult, - hash::Hasher, - info, instruction::Instruction, program_error::ProgramError, pubkey::Pubkey, rent::Rent, - system_instruction::{create_account, SystemInstruction}, + system_instruction::{create_account}, sysvar::Sysvar, }; -use spl_token::{state::Mint}; +use crate::instruction::PublishMessagePayload; +use crate::vaa::{BodyContractUpgrade, BodyMessage}; use crate::{ error::Error, instruction::{ - BridgeInstruction, BridgeInstruction::*, TransferOutPayload, VAAData, VerifySigPayload, - CHAIN_ID_SOLANA, MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE, + BridgeInstruction, BridgeInstruction::*, VAAData, VerifySigPayload, CHAIN_ID_SOLANA, + MAX_LEN_GUARDIAN_KEYS, MAX_VAA_SIZE, }, state::*, - vaa::{BodyTransfer, BodyUpdateGuardianSet, VAABody, VAA}, + vaa::{BodyUpdateGuardianSet, VAABody, VAA}, }; -use solana_program::program_pack::Pack; -use std::borrow::BorrowMut; -use std::ops::Add; -use solana_program::fee_calculator::FeeCalculator; -use crate::vaa::BodyContractUpgrade; +use crate::governance::{BodyUpdateGuardianSet, BodyContractUpgrade}; /// SigInfo contains metadata about signers in a VerifySignature ix struct SigInfo { @@ -69,14 +64,10 @@ impl Bridge { payload.config, ) } - TransferOut(p) => { - msg!("Instruction: TransferOut"); + PublishMessage(p) => { + msg!("Instruction: PublishMessage"); - if p.asset.chain == CHAIN_ID_SOLANA { - Self::process_transfer_native_out(program_id, accounts, &p) - } else { - Self::process_transfer_out(program_id, accounts, &p) - } + Self::process_publish_message(program_id, accounts, &p) } PostVAA(vaa_body) => { msg!("Instruction: PostVAA"); @@ -84,21 +75,11 @@ impl Bridge { Self::process_vaa(program_id, accounts, vaa_body, &vaa) } - PokeProposal() => { - msg!("Instruction: PokeProposal"); - - Self::process_poke(program_id, accounts) - } VerifySignatures(p) => { msg!("Instruction: VerifySignatures"); Self::process_verify_signatures(program_id, accounts, &p) } - CreateWrapped(meta) => { - msg!("Instruction: CreateWrapped"); - Self::process_create_wrapped(program_id, accounts, &meta) - } - _ => panic!(""), } } @@ -168,23 +149,6 @@ impl Bridge { Ok(()) } - /// Transfers a wrapped asset out - pub fn process_poke(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { - let account_info_iter = &mut accounts.iter(); - let proposal_info = Self::next_account_info_with_owner(account_info_iter, program_id)?; - - let mut transfer_data = proposal_info.try_borrow_mut_data()?; - let mut proposal: &mut TransferOutProposal = Self::unpack(&mut transfer_data)?; - if proposal.vaa_time != 0 { - return Err(Error::VAAAlreadySubmitted.into()); - } - - // Increase poke counter - proposal.poke_counter += 1; - - Ok(()) - } - /// Processes signature verifications pub fn process_verify_signatures( program_id: &Pubkey, @@ -373,234 +337,76 @@ impl Bridge { Ok(()) } - /// Transfers a wrapped asset out - pub fn process_transfer_out( + /// Publish a message to the Wormhole protocol + pub fn process_publish_message( program_id: &Pubkey, accounts: &[AccountInfo], - t: &TransferOutPayload, + t: &PublishMessagePayload, ) -> ProgramResult { - msg!("wrapped transfer out"); + msg!("publishing message"); let account_info_iter = &mut accounts.iter(); next_account_info(account_info_iter)?; // Bridge program next_account_info(account_info_iter)?; // System program - next_account_info(account_info_iter)?; // Token program next_account_info(account_info_iter)?; // Rent sysvar let clock_info = next_account_info(account_info_iter)?; let instructions_info = next_account_info(account_info_iter)?; - let sender_account_info = next_account_info(account_info_iter)?; let bridge_info = Self::next_account_info_with_owner(account_info_iter, program_id)?; - let transfer_info = next_account_info(account_info_iter)?; - let mint_info = next_account_info(account_info_iter)?; + let message_info = next_account_info(account_info_iter)?; let payer_info = next_account_info(account_info_iter)?; + let emitter_info = next_account_info(account_info_iter)?; - let sender = Bridge::token_account_deserialize(sender_account_info)?; - let bridge_data = bridge_info.try_borrow_data()?; - let bridge: &Bridge = Self::unpack_immutable(&bridge_data)?; - let mint = Bridge::mint_deserialize(mint_info)?; let clock = Clock::from_account_info(clock_info)?; if *instructions_info.key != solana_program::sysvar::instructions::id() { return Err(Error::InvalidSysvar.into()); } + // The message emitter must be a signer + if !emitter_info.is_signer { + return Err(Error::EmitterNotSigner.into()); + } + // Fee handling let fee = Self::transfer_fee(); Self::check_fees(instructions_info, bridge_info, fee)?; - // Does the token belong to the mint - if sender.mint != *mint_info.key { - return Err(Error::TokenMintMismatch.into()); - } - - // Check that the mint is actually a wrapped asset belonging to *this* bridge instance - let expected_mint_address = Bridge::derive_wrapped_asset_id( - program_id, - bridge_info.key, - t.asset.chain, - t.asset.decimals, - t.asset.address, - )?; - if expected_mint_address != *mint_info.key { - return Err(Error::InvalidDerivedAccount.into()); - } - // Create transfer account - let transfer_seed = Bridge::derive_transfer_id_seeds( + let message_seed = Bridge::derive_message_seeds( bridge_info.key, - t.asset.chain, - t.asset.address, - t.chain_id, - t.target, - sender_account_info.key.to_bytes(), + CHAIN_ID_SOLANA, + emitter_info.key.to_bytes(), t.nonce, + &t.payload, ); - Bridge::check_and_create_account::( + Bridge::check_and_create_account::( program_id, accounts, - transfer_info.key, + message_info.key, payer_info, program_id, - &transfer_seed, + &message_seed, None, )?; - // Load transfer account - let mut transfer_data = transfer_info.try_borrow_mut_data()?; - let mut transfer: &mut TransferOutProposal = Self::unpack(&mut transfer_data)?; - - // Burn tokens - Bridge::wrapped_burn( - program_id, - accounts, - &bridge.config.token_program, - sender_account_info.key, - mint_info.key, - t.amount, - )?; + let mut message_data = message_info.try_borrow_mut_data()?; + let mut message: &mut PostedMessage = Self::unpack(&mut message_data)?; // Initialize transfer - transfer.nonce = t.nonce; - transfer.source_address = sender_account_info.key.to_bytes(); - transfer.foreign_address = t.target; - transfer.amount = t.amount; - transfer.to_chain_id = t.chain_id; - transfer.lockup_time = clock.unix_timestamp as u32; - - // Make sure decimals are correct - transfer.asset = AssetMeta { - chain: t.asset.chain, // Chain and address cannot be spoofed because the account is derived from it - address: t.asset.address, - decimals: mint.decimals, // We use the info from mint because it can be spoofed - }; - - Ok(()) - } - - /// Transfers a native token to a foreign chain - pub fn process_transfer_native_out( - program_id: &Pubkey, - accounts: &[AccountInfo], - t: &TransferOutPayload, - ) -> ProgramResult { - msg!("native transfer out"); - let account_info_iter = &mut accounts.iter(); - next_account_info(account_info_iter)?; // Bridge program - next_account_info(account_info_iter)?; // System program - next_account_info(account_info_iter)?; // Token program - next_account_info(account_info_iter)?; // Rent sysvar - let clock_info = next_account_info(account_info_iter)?; - let instructions_info = next_account_info(account_info_iter)?; - let sender_account_info = next_account_info(account_info_iter)?; - let bridge_info = Self::next_account_info_with_owner(account_info_iter, program_id)?; - let transfer_info = next_account_info(account_info_iter)?; - let mint_info = next_account_info(account_info_iter)?; - let payer_info = next_account_info(account_info_iter)?; - let custody_info = next_account_info(account_info_iter)?; - - let sender = Bridge::token_account_deserialize(sender_account_info)?; - let mint = Bridge::mint_deserialize(mint_info)?; - let bridge_data = bridge_info.try_borrow_data()?; - let bridge: &Bridge = Self::unpack_immutable(&bridge_data)?; - let clock = Clock::from_account_info(clock_info)?; - - let fee = Self::transfer_fee(); - Self::check_fees(instructions_info, bridge_info, fee)?; - - // Does the token belong to the mint - if sender.mint != *mint_info.key { - return Err(Error::TokenMintMismatch.into()); - } - - // Create transfer account - let transfer_seed = Bridge::derive_transfer_id_seeds( - bridge_info.key, - t.asset.chain, - t.asset.address, - t.chain_id, - t.target, - sender_account_info.key.to_bytes(), - t.nonce, - ); - Bridge::check_and_create_account::( - program_id, - accounts, - transfer_info.key, - payer_info, - program_id, - &transfer_seed, - None, - )?; - - // Load transfer account - let mut transfer_data = transfer_info.try_borrow_mut_data()?; - let mut transfer: &mut TransferOutProposal = Self::unpack(&mut transfer_data)?; - - // Check that custody account was derived correctly - let expected_custody_id = - Bridge::derive_custody_id(program_id, bridge_info.key, mint_info.key)?; - if expected_custody_id != *custody_info.key { - return Err(Error::InvalidDerivedAccount.into()); - } - - // Create the account if it does not exist - if custody_info.data_is_empty() { - Bridge::create_custody_account( - program_id, - accounts, - &bridge.config.token_program, - bridge_info.key, - custody_info.key, - mint_info.key, - payer_info, - None, - )?; - } - - let bridge_authority = Self::derive_bridge_id(program_id)?; - - // Check that the custody token account is owned by the derived key - let custody = Self::token_account_deserialize(custody_info)?; - if custody.owner != bridge_authority { - return Err(Error::WrongTokenAccountOwner.into()); - } - - // Check that the source is not the custody account - if custody_info.key == sender_account_info.key { - return Err(Error::WrongTokenAccountOwner.into()); - } - - msg!("transferring"); - // Transfer tokens to custody - This also checks that custody mint = mint - Bridge::token_transfer_caller( - program_id, - accounts, - &bridge.config.token_program, - sender_account_info.key, - custody_info.key, - &bridge_authority, - t.amount, - )?; - - // Initialize proposal - transfer.amount = t.amount; - transfer.to_chain_id = t.chain_id; - transfer.source_address = sender_account_info.key.to_bytes(); - transfer.foreign_address = t.target; - transfer.nonce = t.nonce; - transfer.lockup_time = clock.unix_timestamp as u32; - - // Don't use the user-given data as we don't check mint = AssetMeta.address - transfer.asset = AssetMeta { - chain: CHAIN_ID_SOLANA, - address: mint_info.key.to_bytes(), - decimals: mint.decimals, - }; + message.submission_time = clock.unix_timestamp as u32; + message.emitter_chain = CHAIN_ID_SOLANA; + message.emitter_address = emitter_info.key.to_bytes(); + message.nonce = t.nonce; + message.payload = t.payload; Ok(()) } /// Verify that a certain fee was sent to the bridge in the preceding instruction - pub fn check_fees(instructions_info: &AccountInfo, bridge_info: &AccountInfo, fee: u64) -> Result<(), ProgramError> { + pub fn check_fees( + instructions_info: &AccountInfo, + bridge_info: &AccountInfo, + fee: u64, + ) -> Result<(), ProgramError> { let current_instruction = solana_program::sysvar::instructions::load_current_index( &instructions_info.try_borrow_mut_data()?, ); @@ -663,9 +469,13 @@ impl Bridge { amount: u64, ) -> ProgramResult { let mut payer_balance = payer_account.try_borrow_mut_lamports()?; - **payer_balance = payer_balance.checked_sub(amount).ok_or(ProgramError::InsufficientFunds)?; + **payer_balance = payer_balance + .checked_sub(amount) + .ok_or(ProgramError::InsufficientFunds)?; let mut recipient_balance = recipient_account.try_borrow_mut_lamports()?; - **recipient_balance = recipient_balance.checked_add(amount).ok_or(ProgramError::InvalidArgument)?; + **recipient_balance = recipient_balance + .checked_add(amount) + .ok_or(ProgramError::InvalidArgument)?; Ok(()) } @@ -708,7 +518,9 @@ impl Bridge { } // Check that the guardian set is still active - if guardian_set.expiration_time != 0 && (guardian_set.expiration_time as i64) < clock.unix_timestamp { + if guardian_set.expiration_time != 0 + && (guardian_set.expiration_time as i64) < clock.unix_timestamp + { return Err(Error::GuardianSetExpired.into()); } @@ -726,11 +538,11 @@ impl Bridge { return Err(ProgramError::InvalidAccountData); } - let signature_count = (sig_state + let signature_count = sig_state .signatures .iter() .filter(|v| v.iter().filter(|v| **v != 0).count() != 0) - .count() as u8); + .count() as u8; // Check quorum // We're using a fixed point number transformation with 1 decimal to deal with rounding. // The cast to u16 exists to prevent issues where len_keys * 10 might overflow. @@ -738,86 +550,23 @@ impl Bridge { return Err(ProgramError::InvalidArgument); } - let mut evict_signatures = false; - let payload = vaa.payload.as_ref().ok_or(Error::InvalidVAAAction)?; - match payload { - VAABody::UpdateGuardianSet(v) => { - let mut bridge_data = bridge_info.try_borrow_mut_data()?; - let bridge: &mut Bridge = Self::unpack(&mut bridge_data)?; - Self::process_vaa_set_update( - program_id, - accounts, - account_info_iter, - &clock, - bridge_info, - payer_info, - bridge, - guardian_set, - &v, - ) - } - VAABody::Transfer(v) => { - if v.source_chain == CHAIN_ID_SOLANA { - Self::process_vaa_transfer_post( - program_id, - account_info_iter, - bridge_info, - vaa, - &v, - vaa_data, - sig_info.key, - ) - } else { - let bridge_data = bridge_info.try_borrow_data()?; - let bridge: &Bridge = Self::unpack_immutable(&bridge_data)?; - evict_signatures = true; - Self::process_vaa_transfer( - program_id, - accounts, - account_info_iter, - bridge_info, - bridge, - &v, - ) - } - } - VAABody::UpgradeContract(v) => { - if v.chain_id == CHAIN_ID_SOLANA { - evict_signatures = true; - Self::process_vaa_upgrade( - program_id, - accounts, - bridge_info, - v, - ) - } else { - return Err(Error::InvalidChain.into()); - } - } - }?; - - // Check and create claim - let claim_seeds = Bridge::derive_claim_seeds(bridge_info.key, vaa.signature_body()?); - // Will fail if the Claim exists i.e. the VAA has already been claimed - Bridge::check_and_create_account::( + // Process the message posting + Self::process_vaa_message_post( program_id, - accounts, - claim_info.key, - payer_info, - program_id, - &claim_seeds, - Some(bridge_info), + account_info_iter, + bridge_info, + vaa, + sig_info.key, )?; - // If the signatures are not needed anymore, evict them and reclaim rent. - // This should cover most of the costs of the guardian. - if evict_signatures { - Self::transfer_sol(sig_info, payer_info, sig_info.lamports())?; - } - // Refund tx fee if possible - if bridge_info.lamports().checked_sub(Self::MIN_BRIDGE_BALANCE).unwrap_or(0) >= Self::VAA_TX_FEE { + if bridge_info + .lamports() + .checked_sub(Self::MIN_BRIDGE_BALANCE) + .unwrap_or(0) + >= Self::VAA_TX_FEE + { Self::transfer_sol(bridge_info, payer_info, Self::VAA_TX_FEE)?; } @@ -832,7 +581,7 @@ impl Bridge { } /// Processes a Guardian set update - pub fn process_vaa_set_update( + pub fn process_governance_guardian_update( program_id: &Pubkey, accounts: &[AccountInfo], account_info_iter: &mut Iter, @@ -903,59 +652,47 @@ impl Bridge { } /// Processes a VAA post for data availability (for Solana -> foreign transfers) - pub fn process_vaa_transfer_post( + pub fn process_vaa_message_post( program_id: &Pubkey, account_info_iter: &mut Iter, bridge_info: &AccountInfo, vaa: &VAA, - b: &BodyTransfer, - vaa_data: VAAData, sig_account: &Pubkey, ) -> ProgramResult { - msg!("posting VAA"); - let proposal_info = Self::next_account_info_with_owner(account_info_iter, program_id)?; + msg!("posting VAA message"); + let message_account = Self::next_account_info_with_owner(account_info_iter, program_id)?; // Check whether the proposal was derived correctly - let expected_proposal = Bridge::derive_transfer_id( + let expected_message_address = Bridge::derive_message_id( program_id, bridge_info.key, - b.asset.chain, - b.asset.address, - b.target_chain, - b.target_address, - b.source_address, + b.emitter_chain, + b.emitter_address, b.nonce, + &b.data, )?; - if expected_proposal != *proposal_info.key { + if expected_message_address != *message_account.key { return Err(Error::InvalidDerivedAccount.into()); } - let mut transfer_data = proposal_info.try_borrow_mut_data()?; - let mut proposal: &mut TransferOutProposal = Self::unpack(&mut transfer_data)?; - if !proposal.matches_vaa(b) { - return Err(Error::VAAProposalMismatch.into()); + let mut message_data = message_account.try_borrow_mut_data()?; + let mut message: &mut PostedMessage = Self::unpack(&mut message_data)?; + if !message.matches_vaa(b) { + return Err(Error::VAAMessageMismatch.into()); } - if proposal.vaa_time != 0 { + if message.vaa_time != 0 { return Err(Error::VAAAlreadySubmitted.into()); } - if vaa_data.len() > MAX_VAA_SIZE { - return Err(Error::VAATooLong.into()); - } - // Set vaa - for i in 0..vaa_data.len() { - proposal.vaa[i] = vaa_data[i] - } - // Stop byte - proposal.vaa[vaa_data.len()] = 0xff; - proposal.vaa_time = vaa.timestamp; - proposal.signature_account = *sig_account; + message.vaa_version = vaa.version; + message.vaa_time = vaa.timestamp; + message.vaa_signature_account = *sig_account; Ok(()) } /// Processes a VAA contract upgrade - pub fn process_vaa_upgrade( + pub fn process_governance_upgrade( program_id: &Pubkey, accounts: &[AccountInfo], bridge_info: &AccountInfo, @@ -987,7 +724,7 @@ impl Bridge { } pub fn invoke_vec_seed<'a>( - program_id: &Pubkey, + _program_id: &Pubkey, instruction: &Instruction, account_infos: &[AccountInfo<'a>], seeds: &Vec>, @@ -998,9 +735,10 @@ impl Bridge { /// The amount of sol that needs to be held in the BridgeConfig account in order to make it /// exempt of rent payments. - const MIN_BRIDGE_BALANCE: u64 = (((solana_program::rent::ACCOUNT_STORAGE_OVERHEAD + size_of::() as u64) * - solana_program::rent::DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64 - * solana_program::rent::DEFAULT_EXEMPTION_THRESHOLD) as u64; + const MIN_BRIDGE_BALANCE: u64 = + (((solana_program::rent::ACCOUNT_STORAGE_OVERHEAD + size_of::() as u64) + * solana_program::rent::DEFAULT_LAMPORTS_PER_BYTE_YEAR) as f64 + * solana_program::rent::DEFAULT_EXEMPTION_THRESHOLD) as u64; /// Check that a key was derived correctly and create account pub fn check_and_create_account( @@ -1037,7 +775,11 @@ impl Bridge { Some(v) => { let bal = v.try_lamports()?; let rent = Rent::default().minimum_balance(size_of::()); - if bal.checked_sub(Self::MIN_BRIDGE_BALANCE).ok_or(ProgramError::InsufficientFunds)? >= rent { + if bal + .checked_sub(Self::MIN_BRIDGE_BALANCE) + .ok_or(ProgramError::InsufficientFunds)? + >= rent + { // Refund rent to payer Self::transfer_sol(v, payer, rent)?; } @@ -1049,7 +791,7 @@ impl Bridge { /// Create a new account fn create_account_raw( - program_id: &Pubkey, + _program_id: &Pubkey, accounts: &[AccountInfo], new_account: &Pubkey, payer: &Pubkey,