pyth-crosschain/solana/modules/token_bridge/program/src/api/transfer.rs

344 lines
9.6 KiB
Rust

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<Signer<AccountInfo<'b>>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
pub from: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
pub mint: Mut<Data<'b, SplMint, { AccountState::Initialized }>>,
pub custody: Mut<CustodyAccount<'b, { AccountState::MaybeInitialized }>>,
// 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<Info<'b>>,
/// Account to store the posted message
pub message: Signer<Mut<Info<'b>>>,
/// Emitter of the VAA
pub emitter: EmitterAccount<'b>,
/// Tracker for the emitter sequence
pub sequence: Mut<Info<'b>>,
/// Account to collect tx fee
pub fee_collector: Mut<Info<'b>>,
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<Signer<AccountInfo<'b>>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
pub from: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
pub from_owner: Signer<Info<'b>>,
pub mint: Mut<WrappedMint<'b, { AccountState::Initialized }>>,
pub wrapped_meta: WrappedTokenMeta<'b, { AccountState::Initialized }>,
pub authority_signer: AuthoritySigner<'b>,
/// CPI Context
pub bridge: Mut<Info<'b>>,
/// Account to store the posted message
pub message: Signer<Mut<Info<'b>>>,
/// Emitter of the VAA
pub emitter: EmitterAccount<'b>,
/// Tracker for the emitter sequence
pub sequence: Mut<Info<'b>>,
/// Account to collect tx fee
pub fee_collector: Mut<Info<'b>>,
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(())
}