use crate::{ accounts::{ AuthoritySigner, ConfigAccount, CustodyAccount, CustodyAccountDerivationData, CustodySigner, EmitterAccount, MintSigner, WrappedDerivationData, WrappedMetaDerivationData, WrappedMint, WrappedTokenMeta, }, messages::PayloadTransfer, types::*, TokenBridgeError, TokenBridgeError::WrongAccountOwner, }; use bridge::{ api::{ PostMessage, PostMessageData, }, types::ConsistencyLevel, vaa::SerializePayload, }; use primitive_types::U256; use solana_program::{ account_info::AccountInfo, instruction::{ AccountMeta, Instruction, }, program::{ invoke, invoke_signed, }, program_error::ProgramError, program_option::COption, pubkey::Pubkey, sysvar::clock::Clock, }; use solitaire::{ processors::seeded::{ invoke_seeded, Seeded, }, CreationLamports::Exempt, *, }; use spl_token::{ error::TokenError::OwnerMismatch, state::{ Account, Mint, }, }; use std::ops::{ Deref, DerefMut, }; #[derive(FromAccounts)] pub struct TransferNative<'b> { pub payer: Mut>>, pub config: ConfigAccount<'b, { AccountState::Initialized }>, pub from: Mut>, pub mint: Mut>, pub custody: Mut>, // This could allow someone to race someone else's tx if they do the approval in a separate tx. // Therefore the approval must be set in the same tx. pub authority_signer: AuthoritySigner<'b>, pub custody_signer: CustodySigner<'b>, /// CPI Context pub bridge: Mut>, /// Account to store the posted message pub message: Signer>>, /// Emitter of the VAA pub emitter: EmitterAccount<'b>, /// Tracker for the emitter sequence pub sequence: Mut>, /// Account to collect tx fee pub fee_collector: Mut>, pub clock: Sysvar<'b, Clock>, } impl<'a> From<&TransferNative<'a>> for CustodyAccountDerivationData { fn from(accs: &TransferNative<'a>) -> Self { CustodyAccountDerivationData { mint: *accs.mint.info().key, } } } impl<'b> InstructionContext<'b> for TransferNative<'b> { } #[derive(BorshDeserialize, BorshSerialize, Default)] pub struct TransferNativeData { pub nonce: u32, pub amount: u64, pub fee: u64, pub target_address: Address, pub target_chain: ChainID, } pub fn transfer_native( ctx: &ExecutionContext, accs: &mut TransferNative, data: TransferNativeData, ) -> Result<()> { // Verify that the custody account is derived correctly let derivation_data: CustodyAccountDerivationData = (&*accs).into(); accs.custody .verify_derivation(ctx.program_id, &derivation_data)?; // Verify mints if accs.from.mint != *accs.mint.info().key { return Err(TokenBridgeError::InvalidMint.into()); } // Verify that the token is not a wrapped token if let COption::Some(mint_authority) = accs.mint.mint_authority { if mint_authority == MintSigner::key(None, ctx.program_id) { return Err(TokenBridgeError::TokenNotNative.into()); } } if !accs.custody.is_initialized() { accs.custody .create(&(&*accs).into(), ctx, accs.payer.key, Exempt)?; let init_ix = spl_token::instruction::initialize_account( &spl_token::id(), accs.custody.info().key, accs.mint.info().key, accs.custody_signer.key, )?; invoke_signed(&init_ix, ctx.accounts, &[])?; } // Truncate to 8 decimals let amount: u64 = data.amount / (10u64.pow(8.max(accs.mint.decimals as u32) - 8)); // Transfer tokens let transfer_ix = spl_token::instruction::transfer( &spl_token::id(), accs.from.info().key, accs.custody.info().key, accs.authority_signer.key, &[], amount, )?; invoke_seeded(&transfer_ix, ctx, &accs.authority_signer, None)?; // Pay fee let transfer_ix = solana_program::system_instruction::transfer(accs.payer.key, accs.fee_collector.key, 1000); invoke(&transfer_ix, ctx.accounts)?; // Post message let payload = PayloadTransfer { amount: U256::from(amount), token_address: accs.mint.info().key.to_bytes(), token_chain: 1, to: data.target_address, to_chain: data.target_chain, fee: U256::from(data.fee), }; let params = ( bridge::instruction::Instruction::PostMessage, PostMessageData { nonce: data.nonce, payload: payload.try_to_vec()?, consistency_level: ConsistencyLevel::Confirmed, }, ); let ix = Instruction::new_with_bytes( accs.config.wormhole_bridge, params.try_to_vec()?.as_slice(), vec![ AccountMeta::new(*accs.bridge.key, false), AccountMeta::new(*accs.message.key, true), AccountMeta::new_readonly(*accs.emitter.key, true), AccountMeta::new(*accs.sequence.key, false), AccountMeta::new(*accs.payer.key, true), AccountMeta::new(*accs.fee_collector.key, false), AccountMeta::new_readonly(*accs.clock.info().key, false), AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false), ], ); invoke_seeded(&ix, ctx, &accs.emitter, None)?; Ok(()) } #[derive(FromAccounts)] pub struct TransferWrapped<'b> { pub payer: Mut>>, pub config: ConfigAccount<'b, { AccountState::Initialized }>, pub from: Mut>, pub from_owner: Signer>, pub mint: Mut>, pub wrapped_meta: WrappedTokenMeta<'b, { AccountState::Initialized }>, pub authority_signer: AuthoritySigner<'b>, /// CPI Context pub bridge: Mut>, /// Account to store the posted message pub message: Signer>>, /// Emitter of the VAA pub emitter: EmitterAccount<'b>, /// Tracker for the emitter sequence pub sequence: Mut>, /// Account to collect tx fee pub fee_collector: Mut>, pub clock: Sysvar<'b, Clock>, } impl<'a> From<&TransferWrapped<'a>> for WrappedDerivationData { fn from(accs: &TransferWrapped<'a>) -> Self { WrappedDerivationData { token_chain: 1, token_address: accs.mint.info().key.to_bytes(), } } } impl<'a> From<&TransferWrapped<'a>> for WrappedMetaDerivationData { fn from(accs: &TransferWrapped<'a>) -> Self { WrappedMetaDerivationData { mint_key: *accs.mint.info().key, } } } impl<'b> InstructionContext<'b> for TransferWrapped<'b> { } #[derive(BorshDeserialize, BorshSerialize, Default)] pub struct TransferWrappedData { pub nonce: u32, pub amount: u64, pub fee: u64, pub target_address: Address, pub target_chain: ChainID, } pub fn transfer_wrapped( ctx: &ExecutionContext, accs: &mut TransferWrapped, data: TransferWrappedData, ) -> Result<()> { // Verify that the from account is owned by the from_owner if &accs.from.owner != accs.from_owner.key { return Err(WrongAccountOwner.into()); } // Verify mints if accs.mint.info().key != &accs.from.mint { return Err(TokenBridgeError::InvalidMint.into()); } // Verify that meta is correct let derivation_data: WrappedMetaDerivationData = (&*accs).into(); accs.wrapped_meta .verify_derivation(ctx.program_id, &derivation_data)?; // Burn tokens let burn_ix = spl_token::instruction::burn( &spl_token::id(), accs.from.info().key, accs.mint.info().key, accs.authority_signer.key, &[], data.amount, )?; invoke_seeded(&burn_ix, ctx, &accs.authority_signer, None)?; // Pay fee let transfer_ix = solana_program::system_instruction::transfer(accs.payer.key, accs.fee_collector.key, 1000); invoke(&transfer_ix, ctx.accounts)?; // Post message let payload = PayloadTransfer { amount: U256::from(data.amount), token_address: accs.wrapped_meta.token_address, token_chain: accs.wrapped_meta.chain, to: data.target_address, to_chain: data.target_chain, fee: U256::from(data.fee), }; let params = ( bridge::instruction::Instruction::PostMessage, PostMessageData { nonce: data.nonce, payload: payload.try_to_vec()?, consistency_level: ConsistencyLevel::Confirmed, }, ); let ix = Instruction::new_with_bytes( accs.config.wormhole_bridge, params.try_to_vec()?.as_slice(), vec![ AccountMeta::new(*accs.bridge.key, false), AccountMeta::new(*accs.message.key, true), AccountMeta::new_readonly(*accs.emitter.key, true), AccountMeta::new(*accs.sequence.key, false), AccountMeta::new(*accs.payer.key, true), AccountMeta::new(*accs.fee_collector.key, false), AccountMeta::new_readonly(*accs.clock.info().key, false), AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false), ], ); invoke_seeded(&ix, ctx, &accs.emitter, None)?; Ok(()) }