wormhole/solana/modules/token_bridge/program/src/api/complete_transfer.rs

276 lines
7.8 KiB
Rust

use crate::{
accounts::{
ConfigAccount,
CustodyAccount,
CustodyAccountDerivationData,
CustodySigner,
Endpoint,
EndpointDerivationData,
MintSigner,
WrappedDerivationData,
WrappedMetaDerivationData,
WrappedMint,
WrappedTokenMeta,
},
messages::PayloadTransfer,
types::*,
TokenBridgeError::*,
INVALID_VAAS,
};
use bridge::{
accounts::claim::{
self,
Claim,
},
PayloadMessage,
CHAIN_ID_SOLANA,
};
use solana_program::account_info::AccountInfo;
use solitaire::{
processors::seeded::{
invoke_seeded,
Seeded,
},
*,
};
#[derive(FromAccounts)]
pub struct CompleteNative<'b> {
pub payer: Mut<Signer<AccountInfo<'b>>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
pub vaa: PayloadMessage<'b, PayloadTransfer>,
pub claim: Mut<Claim<'b>>,
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
pub to: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
pub to_fees: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
pub custody: Mut<CustodyAccount<'b, { AccountState::Initialized }>>,
pub mint: Data<'b, SplMint, { AccountState::Initialized }>,
pub custody_signer: CustodySigner<'b>,
}
impl<'a> From<&CompleteNative<'a>> for EndpointDerivationData {
fn from(accs: &CompleteNative<'a>) -> Self {
EndpointDerivationData {
emitter_chain: accs.vaa.meta().emitter_chain,
emitter_address: accs.vaa.meta().emitter_address,
}
}
}
impl<'a> From<&CompleteNative<'a>> for CustodyAccountDerivationData {
fn from(accs: &CompleteNative<'a>) -> Self {
CustodyAccountDerivationData {
mint: *accs.mint.info().key,
}
}
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct CompleteNativeData {}
pub fn complete_native(
ctx: &ExecutionContext,
accs: &mut CompleteNative,
_data: CompleteNativeData,
) -> Result<()> {
// Verify the chain registration
let derivation_data: EndpointDerivationData = (&*accs).into();
accs.chain_registration
.verify_derivation(ctx.program_id, &derivation_data)?;
// 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.mint.info().key != accs.to.mint {
return Err(InvalidMint.into());
}
if *accs.mint.info().key != accs.to_fees.mint {
return Err(InvalidMint.into());
}
if *accs.mint.info().key != accs.custody.mint {
return Err(InvalidMint.into());
}
if *accs.custody_signer.key != accs.custody.owner {
return Err(WrongAccountOwner.into());
}
// Verify VAA
if accs.vaa.token_address != accs.mint.info().key.to_bytes() {
return Err(InvalidMint.into());
}
if accs.vaa.token_chain != 1 {
return Err(InvalidChain.into());
}
if accs.vaa.to_chain != CHAIN_ID_SOLANA {
return Err(InvalidChain.into());
}
if accs.vaa.to != accs.to.info().key.to_bytes() {
return Err(InvalidRecipient.into());
}
if INVALID_VAAS.contains(&&*accs.vaa.info().key.to_string()) {
return Err(InvalidVAA.into());
}
// Prevent vaa double signing
claim::consume(ctx, accs.payer.key, &mut accs.claim, &accs.vaa)?;
let mut amount = accs.vaa.amount.as_u64();
let mut fee = accs.vaa.fee.as_u64();
// Wormhole always caps transfers at 8 decimals; un-truncate if the local token has more
if accs.mint.decimals > 8 {
amount *= 10u64.pow((accs.mint.decimals - 8) as u32);
fee *= 10u64.pow((accs.mint.decimals - 8) as u32);
}
let token_amount = amount
.checked_sub(fee)
.ok_or(SolitaireError::InsufficientFunds)?;
// Transfer tokens
let transfer_ix = spl_token::instruction::transfer(
&spl_token::id(),
accs.custody.info().key,
accs.to.info().key,
accs.custody_signer.key,
&[],
token_amount,
)?;
invoke_seeded(&transfer_ix, ctx, &accs.custody_signer, None)?;
// Transfer fees
let transfer_ix = spl_token::instruction::transfer(
&spl_token::id(),
accs.custody.info().key,
accs.to_fees.info().key,
accs.custody_signer.key,
&[],
fee,
)?;
invoke_seeded(&transfer_ix, ctx, &accs.custody_signer, None)?;
Ok(())
}
#[derive(FromAccounts)]
pub struct CompleteWrapped<'b> {
pub payer: Mut<Signer<AccountInfo<'b>>>,
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
// Signed message for the transfer
pub vaa: PayloadMessage<'b, PayloadTransfer>,
pub claim: Mut<Claim<'b>>,
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
pub to: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
pub to_fees: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
pub mint: Mut<WrappedMint<'b, { AccountState::Initialized }>>,
pub wrapped_meta: WrappedTokenMeta<'b, { AccountState::Initialized }>,
pub mint_authority: MintSigner<'b>,
}
impl<'a> From<&CompleteWrapped<'a>> for EndpointDerivationData {
fn from(accs: &CompleteWrapped<'a>) -> Self {
EndpointDerivationData {
emitter_chain: accs.vaa.meta().emitter_chain,
emitter_address: accs.vaa.meta().emitter_address,
}
}
}
impl<'a> From<&CompleteWrapped<'a>> for WrappedDerivationData {
fn from(accs: &CompleteWrapped<'a>) -> Self {
WrappedDerivationData {
token_chain: accs.vaa.token_chain,
token_address: accs.vaa.token_address,
}
}
}
#[derive(BorshDeserialize, BorshSerialize, Default)]
pub struct CompleteWrappedData {}
pub fn complete_wrapped(
ctx: &ExecutionContext,
accs: &mut CompleteWrapped,
_data: CompleteWrappedData,
) -> Result<()> {
// Verify the chain registration
let derivation_data: EndpointDerivationData = (&*accs).into();
accs.chain_registration
.verify_derivation(ctx.program_id, &derivation_data)?;
// Verify mint
accs.wrapped_meta.verify_derivation(
ctx.program_id,
&WrappedMetaDerivationData {
mint_key: *accs.mint.info().key,
},
)?;
if accs.wrapped_meta.token_address != accs.vaa.token_address
|| accs.wrapped_meta.chain != accs.vaa.token_chain
{
return Err(InvalidMint.into());
}
// Verify mints
if *accs.mint.info().key != accs.to.mint {
return Err(InvalidMint.into());
}
if *accs.mint.info().key != accs.to_fees.mint {
return Err(InvalidMint.into());
}
// Verify VAA
if accs.vaa.to_chain != CHAIN_ID_SOLANA {
return Err(InvalidChain.into());
}
if accs.vaa.to != accs.to.info().key.to_bytes() {
return Err(InvalidRecipient.into());
}
if INVALID_VAAS.contains(&&*accs.vaa.info().key.to_string()) {
return Err(InvalidVAA.into());
}
claim::consume(ctx, accs.payer.key, &mut accs.claim, &accs.vaa)?;
let token_amount: u64 = accs
.vaa
.amount
.as_u64()
.checked_sub(accs.vaa.fee.as_u64())
.ok_or(SolitaireError::InsufficientFunds)?;
// Mint tokens
let mint_ix = spl_token::instruction::mint_to(
&spl_token::id(),
accs.mint.info().key,
accs.to.info().key,
accs.mint_authority.key,
&[],
token_amount,
)?;
invoke_seeded(&mint_ix, ctx, &accs.mint_authority, None)?;
// Mint fees
let mint_ix = spl_token::instruction::mint_to(
&spl_token::id(),
accs.mint.info().key,
accs.to_fees.info().key,
accs.mint_authority.key,
&[],
accs.vaa.fee.as_u64(),
)?;
invoke_seeded(&mint_ix, ctx, &accs.mint_authority, None)?;
Ok(())
}