|
|
@@ -13,7 +13,10 @@ use crate::{
|
|
|
WrappedMint,
|
|
|
WrappedTokenMeta,
|
|
|
},
|
|
|
- messages::PayloadTransfer,
|
|
|
+ messages::{
|
|
|
+ PayloadTransfer,
|
|
|
+ PayloadTransferWithPayload,
|
|
|
+ },
|
|
|
types::*,
|
|
|
TokenBridgeError,
|
|
|
TokenBridgeError::{
|
|
|
@@ -68,6 +71,8 @@ use std::ops::{
|
|
|
DerefMut,
|
|
|
};
|
|
|
|
|
|
+pub type TransferNativeWithPayload<'b> = TransferNative<'b>;
|
|
|
+
|
|
|
#[derive(FromAccounts)]
|
|
|
pub struct TransferNative<'b> {
|
|
|
pub payer: Mut<Signer<AccountInfo<'b>>>,
|
|
|
@@ -134,6 +139,113 @@ pub fn transfer_native(
|
|
|
return Err(InvalidChain.into());
|
|
|
}
|
|
|
|
|
|
+ let (amount, fee) = verify_and_execute_native_transfers(ctx, accs, data.amount, data.fee)?;
|
|
|
+
|
|
|
+ // Post message
|
|
|
+ let payload = PayloadTransfer {
|
|
|
+ amount: U256::from(amount),
|
|
|
+ token_address: accs.mint.info().key.to_bytes(),
|
|
|
+ token_chain: CHAIN_ID_SOLANA,
|
|
|
+ to: data.target_address,
|
|
|
+ to_chain: data.target_chain,
|
|
|
+ fee: U256::from(fee),
|
|
|
+ };
|
|
|
+ let params = (
|
|
|
+ bridge::instruction::Instruction::PostMessage,
|
|
|
+ PostMessageData {
|
|
|
+ nonce: data.nonce,
|
|
|
+ payload: payload.try_to_vec()?,
|
|
|
+ consistency_level: ConsistencyLevel::Finalized,
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ 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(BorshDeserialize, BorshSerialize, Default)]
|
|
|
+pub struct TransferNativeWithPayloadData {
|
|
|
+ pub nonce: u32,
|
|
|
+ pub amount: u64,
|
|
|
+ pub fee: u64,
|
|
|
+ pub target_address: Address,
|
|
|
+ pub target_chain: ChainID,
|
|
|
+ pub payload: Vec<u8>,
|
|
|
+}
|
|
|
+
|
|
|
+pub fn transfer_native_with_payload(
|
|
|
+ ctx: &ExecutionContext,
|
|
|
+ accs: &mut TransferNative,
|
|
|
+ data: TransferNativeWithPayloadData,
|
|
|
+) -> Result<()> {
|
|
|
+ // Prevent transferring to the same chain.
|
|
|
+ if data.target_chain == CHAIN_ID_SOLANA {
|
|
|
+ return Err(InvalidChain.into());
|
|
|
+ }
|
|
|
+
|
|
|
+ let (amount, fee) = verify_and_execute_native_transfers(ctx, accs, data.amount, data.fee)?;
|
|
|
+
|
|
|
+ // Post message
|
|
|
+ let payload = PayloadTransferWithPayload {
|
|
|
+ amount: U256::from(amount),
|
|
|
+ token_address: accs.mint.info().key.to_bytes(),
|
|
|
+ token_chain: CHAIN_ID_SOLANA,
|
|
|
+ to: data.target_address,
|
|
|
+ to_chain: data.target_chain,
|
|
|
+ fee: U256::from(fee),
|
|
|
+ payload: data.payload,
|
|
|
+ };
|
|
|
+ let params = (
|
|
|
+ bridge::instruction::Instruction::PostMessage,
|
|
|
+ PostMessageData {
|
|
|
+ nonce: data.nonce,
|
|
|
+ payload: payload.try_to_vec()?,
|
|
|
+ consistency_level: ConsistencyLevel::Finalized,
|
|
|
+ },
|
|
|
+ );
|
|
|
+
|
|
|
+ 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(())
|
|
|
+}
|
|
|
+
|
|
|
+pub fn verify_and_execute_native_transfers(
|
|
|
+ ctx: &ExecutionContext,
|
|
|
+ accs: &mut TransferNative,
|
|
|
+ raw_amount: u64,
|
|
|
+ raw_fee: u64,
|
|
|
+) -> Result<(u64, u64)> {
|
|
|
// Verify that the custody account is derived correctly
|
|
|
let derivation_data: CustodyAccountDerivationData = (&*accs).into();
|
|
|
accs.custody
|
|
|
@@ -145,7 +257,7 @@ pub fn transfer_native(
|
|
|
}
|
|
|
|
|
|
// Fee must be less than amount
|
|
|
- if data.fee > data.amount {
|
|
|
+ if raw_fee > raw_amount {
|
|
|
return Err(InvalidFee.into());
|
|
|
}
|
|
|
|
|
|
@@ -171,8 +283,8 @@ pub fn transfer_native(
|
|
|
|
|
|
let trunc_divisor = 10u64.pow(8.max(accs.mint.decimals as u32) - 8);
|
|
|
// Truncate to 8 decimals
|
|
|
- let amount: u64 = data.amount / trunc_divisor;
|
|
|
- let fee: u64 = data.fee / trunc_divisor;
|
|
|
+ let amount: u64 = raw_amount / trunc_divisor;
|
|
|
+ let fee: u64 = raw_fee / trunc_divisor;
|
|
|
// Untruncate the amount to drop the remainder so we don't "burn" user's funds.
|
|
|
let amount_trunc: u64 = amount * trunc_divisor;
|
|
|
|
|
|
@@ -195,42 +307,7 @@ pub fn transfer_native(
|
|
|
);
|
|
|
invoke(&transfer_ix, ctx.accounts)?;
|
|
|
|
|
|
- // Post message
|
|
|
- let payload = PayloadTransfer {
|
|
|
- amount: U256::from(amount),
|
|
|
- token_address: accs.mint.info().key.to_bytes(),
|
|
|
- token_chain: CHAIN_ID_SOLANA,
|
|
|
- to: data.target_address,
|
|
|
- to_chain: data.target_chain,
|
|
|
- fee: U256::from(fee),
|
|
|
- };
|
|
|
- let params = (
|
|
|
- bridge::instruction::Instruction::PostMessage,
|
|
|
- PostMessageData {
|
|
|
- nonce: data.nonce,
|
|
|
- payload: payload.try_to_vec()?,
|
|
|
- consistency_level: ConsistencyLevel::Finalized,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- 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(())
|
|
|
+ Ok((amount, fee))
|
|
|
}
|
|
|
|
|
|
#[derive(FromAccounts)]
|
|
|
@@ -263,6 +340,8 @@ pub struct TransferWrapped<'b> {
|
|
|
pub clock: Sysvar<'b, Clock>,
|
|
|
}
|
|
|
|
|
|
+pub type TransferWrappedWithPayload<'b> = TransferWrapped<'b>;
|
|
|
+
|
|
|
impl<'a> From<&TransferWrapped<'a>> for WrappedDerivationData {
|
|
|
fn from(accs: &TransferWrapped<'a>) -> Self {
|
|
|
WrappedDerivationData {
|
|
|
@@ -302,54 +381,77 @@ pub fn transfer_wrapped(
|
|
|
return Err(InvalidChain.into());
|
|
|
}
|
|
|
|
|
|
- // Verify that the from account is owned by the from_owner
|
|
|
- if &accs.from.owner != accs.from_owner.key {
|
|
|
- return Err(WrongAccountOwner.into());
|
|
|
- }
|
|
|
+ verify_and_execute_wrapped_transfers(ctx, accs, data.amount, data.fee)?;
|
|
|
|
|
|
- // Verify mints
|
|
|
- if accs.mint.info().key != &accs.from.mint {
|
|
|
- return Err(TokenBridgeError::InvalidMint.into());
|
|
|
- }
|
|
|
+ // 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::Finalized,
|
|
|
+ },
|
|
|
+ );
|
|
|
|
|
|
- // Fee must be less than amount
|
|
|
- if data.fee > data.amount {
|
|
|
- return Err(InvalidFee.into());
|
|
|
- }
|
|
|
+ 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)?;
|
|
|
|
|
|
- // Verify that meta is correct
|
|
|
- let derivation_data: WrappedMetaDerivationData = (&*accs).into();
|
|
|
- accs.wrapped_meta
|
|
|
- .verify_derivation(ctx.program_id, &derivation_data)?;
|
|
|
+ Ok(())
|
|
|
+}
|
|
|
|
|
|
- // 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)?;
|
|
|
+#[derive(BorshDeserialize, BorshSerialize, Default)]
|
|
|
+pub struct TransferWrappedWithPayloadData {
|
|
|
+ pub nonce: u32,
|
|
|
+ pub amount: u64,
|
|
|
+ pub fee: u64,
|
|
|
+ pub target_address: Address,
|
|
|
+ pub target_chain: ChainID,
|
|
|
+ pub payload: Vec<u8>,
|
|
|
+}
|
|
|
|
|
|
- // Pay fee
|
|
|
- let transfer_ix = solana_program::system_instruction::transfer(
|
|
|
- accs.payer.key,
|
|
|
- accs.fee_collector.key,
|
|
|
- accs.bridge.config.fee,
|
|
|
- );
|
|
|
+pub fn transfer_wrapped_with_payload(
|
|
|
+ ctx: &ExecutionContext,
|
|
|
+ accs: &mut TransferWrapped,
|
|
|
+ data: TransferWrappedWithPayloadData,
|
|
|
+) -> Result<()> {
|
|
|
+ // Prevent transferring to the same chain.
|
|
|
+ if data.target_chain == CHAIN_ID_SOLANA {
|
|
|
+ return Err(InvalidChain.into());
|
|
|
+ }
|
|
|
|
|
|
- invoke(&transfer_ix, ctx.accounts)?;
|
|
|
+ verify_and_execute_wrapped_transfers(ctx, accs, data.amount, data.fee)?;
|
|
|
|
|
|
// Post message
|
|
|
- let payload = PayloadTransfer {
|
|
|
+ let payload = PayloadTransferWithPayload {
|
|
|
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),
|
|
|
+ payload: data.payload,
|
|
|
};
|
|
|
let params = (
|
|
|
bridge::instruction::Instruction::PostMessage,
|
|
|
@@ -379,3 +481,52 @@ pub fn transfer_wrapped(
|
|
|
|
|
|
Ok(())
|
|
|
}
|
|
|
+
|
|
|
+pub fn verify_and_execute_wrapped_transfers(
|
|
|
+ ctx: &ExecutionContext,
|
|
|
+ accs: &mut TransferWrapped,
|
|
|
+ amount: u64,
|
|
|
+ fee: u64,
|
|
|
+) -> 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());
|
|
|
+ }
|
|
|
+
|
|
|
+ // Fee must be less than amount
|
|
|
+ if fee > amount {
|
|
|
+ return Err(InvalidFee.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,
|
|
|
+ &[],
|
|
|
+ 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,
|
|
|
+ accs.bridge.config.fee,
|
|
|
+ );
|
|
|
+
|
|
|
+ invoke(&transfer_ix, ctx.accounts)?;
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+}
|