405 lines
12 KiB
Rust
405 lines
12 KiB
Rust
|
use crate::{
|
||
|
accounts::{
|
||
|
AuthoritySigner,
|
||
|
ConfigAccount,
|
||
|
CoreBridge,
|
||
|
CustodyAccount,
|
||
|
CustodyAccountDerivationData,
|
||
|
CustodySigner,
|
||
|
EmitterAccount,
|
||
|
MintSigner,
|
||
|
SplTokenMeta,
|
||
|
SplTokenMetaDerivationData,
|
||
|
WrappedDerivationData,
|
||
|
WrappedMetaDerivationData,
|
||
|
WrappedMint,
|
||
|
WrappedTokenMeta,
|
||
|
},
|
||
|
messages::PayloadTransfer,
|
||
|
types::*,
|
||
|
TokenBridgeError,
|
||
|
TokenBridgeError::{
|
||
|
InvalidMetadata,
|
||
|
TokenNotNFT,
|
||
|
WrongAccountOwner,
|
||
|
},
|
||
|
};
|
||
|
use bridge::{
|
||
|
accounts::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 spl_token_metadata::state::Metadata;
|
||
|
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 }>>,
|
||
|
/// SPL Metadata for the associated Mint
|
||
|
pub spl_metadata: SplTokenMeta<'b>,
|
||
|
|
||
|
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<CoreBridge<'b, { AccountState::Initialized }>>,
|
||
|
|
||
|
/// 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<'a> From<&TransferNative<'a>> for SplTokenMetaDerivationData {
|
||
|
fn from(accs: &TransferNative<'a>) -> Self {
|
||
|
SplTokenMetaDerivationData {
|
||
|
mint: *accs.mint.info().key,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<'b> InstructionContext<'b> for TransferNative<'b> {
|
||
|
}
|
||
|
|
||
|
#[derive(BorshDeserialize, BorshSerialize, Default)]
|
||
|
pub struct TransferNativeData {
|
||
|
pub nonce: u32,
|
||
|
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)?;
|
||
|
|
||
|
let derivation_data: SplTokenMetaDerivationData = (&*accs).into();
|
||
|
accs.spl_metadata
|
||
|
.verify_derivation(&spl_token_metadata::id(), &derivation_data)?;
|
||
|
|
||
|
// Verify mints
|
||
|
if accs.from.mint != *accs.mint.info().key {
|
||
|
return Err(TokenBridgeError::InvalidMint.into());
|
||
|
}
|
||
|
|
||
|
// Token must have metadata
|
||
|
if !accs.spl_metadata.data_is_empty() {
|
||
|
return Err(TokenNotNFT.into());
|
||
|
}
|
||
|
|
||
|
if *accs.spl_metadata.owner != spl_token_metadata::id() {
|
||
|
return Err(WrongAccountOwner.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, &[])?;
|
||
|
}
|
||
|
|
||
|
// Transfer tokens
|
||
|
let transfer_ix = spl_token::instruction::transfer(
|
||
|
&spl_token::id(),
|
||
|
accs.from.info().key,
|
||
|
accs.custody.info().key,
|
||
|
accs.authority_signer.key,
|
||
|
&[],
|
||
|
1,
|
||
|
)?;
|
||
|
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,
|
||
|
accs.bridge.config.fee,
|
||
|
);
|
||
|
invoke(&transfer_ix, ctx.accounts)?;
|
||
|
|
||
|
let metadata: Metadata =
|
||
|
Metadata::from_account_info(accs.spl_metadata.info()).ok_or(InvalidMetadata)?;
|
||
|
|
||
|
// Post message
|
||
|
let payload = PayloadTransfer {
|
||
|
token_address: accs.mint.info().key.to_bytes(),
|
||
|
token_chain: 1,
|
||
|
to: data.target_address,
|
||
|
to_chain: data.target_chain,
|
||
|
symbol: metadata.data.symbol,
|
||
|
name: metadata.data.name,
|
||
|
uri: metadata.data.uri,
|
||
|
token_id: U256::from(0), // TODO
|
||
|
};
|
||
|
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.info().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: MaybeMut<Signer<Info<'b>>>,
|
||
|
pub mint: Mut<WrappedMint<'b, { AccountState::Initialized }>>,
|
||
|
pub wrapped_meta: WrappedTokenMeta<'b, { AccountState::Initialized }>,
|
||
|
/// SPL Metadata for the associated Mint
|
||
|
pub spl_metadata: SplTokenMeta<'b>,
|
||
|
|
||
|
pub authority_signer: AuthoritySigner<'b>,
|
||
|
|
||
|
/// CPI Context
|
||
|
pub bridge: Mut<CoreBridge<'b, { AccountState::Initialized }>>,
|
||
|
|
||
|
/// 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 WrappedMetaDerivationData {
|
||
|
fn from(accs: &TransferWrapped<'a>) -> Self {
|
||
|
WrappedMetaDerivationData {
|
||
|
mint_key: *accs.mint.info().key,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<'a> From<&TransferWrapped<'a>> for SplTokenMetaDerivationData {
|
||
|
fn from(accs: &TransferWrapped<'a>) -> Self {
|
||
|
SplTokenMetaDerivationData {
|
||
|
mint: *accs.mint.info().key,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<'b> InstructionContext<'b> for TransferWrapped<'b> {
|
||
|
}
|
||
|
|
||
|
#[derive(BorshDeserialize, BorshSerialize, Default)]
|
||
|
pub struct TransferWrappedData {
|
||
|
pub nonce: u32,
|
||
|
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,
|
||
|
&[],
|
||
|
1,
|
||
|
)?;
|
||
|
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,
|
||
|
accs.bridge.config.fee,
|
||
|
);
|
||
|
|
||
|
invoke(&transfer_ix, ctx.accounts)?;
|
||
|
|
||
|
// Enfoce wrapped meta to be uninitialized.
|
||
|
let derivation_data: WrappedMetaDerivationData = (&*accs).into();
|
||
|
accs.wrapped_meta
|
||
|
.verify_derivation(ctx.program_id, &derivation_data)?;
|
||
|
|
||
|
// Token must have metadata
|
||
|
if !accs.spl_metadata.data_is_empty() {
|
||
|
return Err(TokenNotNFT.into());
|
||
|
}
|
||
|
|
||
|
let derivation_data: SplTokenMetaDerivationData = (&*accs).into();
|
||
|
accs.spl_metadata
|
||
|
.verify_derivation(&spl_token_metadata::id(), &derivation_data)?;
|
||
|
|
||
|
if *accs.spl_metadata.owner != spl_token_metadata::id() {
|
||
|
return Err(WrongAccountOwner.into());
|
||
|
}
|
||
|
|
||
|
let metadata: Metadata =
|
||
|
Metadata::from_account_info(accs.spl_metadata.info()).ok_or(InvalidMetadata)?;
|
||
|
|
||
|
// Post message
|
||
|
let payload = PayloadTransfer {
|
||
|
token_address: accs.wrapped_meta.token_address,
|
||
|
token_chain: accs.wrapped_meta.chain,
|
||
|
token_id: U256(accs.wrapped_meta.token_id),
|
||
|
to: data.target_address,
|
||
|
to_chain: data.target_chain,
|
||
|
symbol: metadata.data.symbol,
|
||
|
name: metadata.data.name,
|
||
|
uri: metadata.data.uri,
|
||
|
};
|
||
|
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.info().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(())
|
||
|
}
|