solana: Add "msg.sender" and remove fee from payload3 (#1279)
* solana: Add "msg.sender" and remove fee from payload3 * solana: update payload3 instruction to include the sender account * solana: allow sending payload 3s to program ids directly Co-authored-by: Csongor Kiss <ckiss@jumptrading.com>
This commit is contained in:
parent
58cd031ea8
commit
47318c2a03
|
@ -307,7 +307,6 @@ export async function transferNativeSol(
|
|||
WSOL_ADDRESS,
|
||||
nonce,
|
||||
amount.valueOf(),
|
||||
relayerFee.valueOf(),
|
||||
targetAddress,
|
||||
coalesceChainId(targetChain),
|
||||
payload
|
||||
|
@ -408,7 +407,6 @@ export async function transferFromSolana(
|
|||
mintAddress,
|
||||
nonce,
|
||||
amount.valueOf(),
|
||||
relayerFee.valueOf(),
|
||||
targetAddress,
|
||||
coalesceChainId(targetChain),
|
||||
payload
|
||||
|
@ -438,7 +436,6 @@ export async function transferFromSolana(
|
|||
originAddress as Uint8Array, // checked by throw
|
||||
nonce,
|
||||
amount.valueOf(),
|
||||
relayerFee.valueOf(),
|
||||
targetAddress,
|
||||
coalesceChainId(targetChain),
|
||||
payload
|
||||
|
|
|
@ -71,6 +71,12 @@ deploy/nft_bridge: nft_bridge-buffer-$(NETWORK).txt
|
|||
@echo Deployed nft bridge contract at:
|
||||
@cat $<
|
||||
|
||||
.PHONY: wasm
|
||||
## Build wasm
|
||||
wasm: $(SOURCE_FILES)
|
||||
DOCKER_BUILDKIT=1 docker build -f Dockerfile.wasm -o type=local,dest=$@ .
|
||||
cp -r $@/* ..
|
||||
|
||||
test:
|
||||
@echo "Running integration tests"
|
||||
DOCKER_BUILDKIT=1 docker build -f Dockerfile --target ci_tests --build-arg BRIDGE_ADDRESS=${bridge_ADDRESS_devnet} .
|
||||
|
|
|
@ -5,6 +5,7 @@ pub mod create_wrapped;
|
|||
pub mod governance;
|
||||
pub mod initialize;
|
||||
pub mod transfer;
|
||||
pub mod transfer_payload;
|
||||
|
||||
pub use attest::*;
|
||||
pub use complete_transfer::*;
|
||||
|
@ -13,3 +14,4 @@ pub use create_wrapped::*;
|
|||
pub use governance::*;
|
||||
pub use initialize::*;
|
||||
pub use transfer::*;
|
||||
pub use transfer_payload::*;
|
||||
|
|
|
@ -29,6 +29,66 @@ use solitaire::{
|
|||
*,
|
||||
};
|
||||
|
||||
use solana_program::pubkey::Pubkey;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Recipient
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct RedeemerAccount<'b>(pub MaybeMut<Signer<Info<'b>>>);
|
||||
|
||||
impl<'a, 'b: 'a, 'c> Peel<'a, 'b, 'c> for RedeemerAccount<'b> {
|
||||
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(RedeemerAccount(MaybeMut::peel(ctx)?))
|
||||
}
|
||||
|
||||
fn persist(&self, program_id: &Pubkey) -> Result<()> {
|
||||
MaybeMut::persist(&self.0, program_id)
|
||||
}
|
||||
}
|
||||
|
||||
// May or may not be a PDA, so we don't use [`Derive`], instead implement
|
||||
// [`Seeded`] directly.
|
||||
impl<'b> Seeded<()> for RedeemerAccount<'b> {
|
||||
fn seeds(_accs: ()) -> Vec<Vec<u8>> {
|
||||
vec![String::from("redeemer").as_bytes().to_vec()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a> Keyed<'a, 'b> for RedeemerAccount<'b> {
|
||||
fn info(&'a self) -> &Info<'b> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> RedeemerAccount<'b> {
|
||||
/// Transfer with payload can only be redeemed by the recipient. The idea is
|
||||
/// to target contracts which can then decide how to process the payload.
|
||||
///
|
||||
/// The actual recipient (the `to` field in the VAA) may be either a wallet
|
||||
/// or a program id. Since wallets can sign transactions directly, if the
|
||||
/// recipient is a wallet, then we just require the wallet to sign the
|
||||
/// redeem transaction. If, however, the recipient is a program, then
|
||||
/// program can only provide a PDA as a signer. In this case, we require the
|
||||
/// this to be a PDA derived from the recipient program id and the string
|
||||
/// "redeemer".
|
||||
///
|
||||
/// That is, the redeemer account either matches the `vaa.to` field directly
|
||||
/// (user wallets), or is a PDA derived from vaa.to and "sender" (contracts).
|
||||
///
|
||||
/// The `vaa.to` account must own the token account.
|
||||
fn verify_recipient_address(&self, recipient: &Pubkey) -> Result<()> {
|
||||
if recipient == self.info().key {
|
||||
return Ok(());
|
||||
} else {
|
||||
self.verify_derivation(recipient, ())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromAccounts)]
|
||||
pub struct CompleteNativeWithPayload<'b> {
|
||||
pub payer: Mut<Signer<AccountInfo<'b>>>,
|
||||
|
@ -38,17 +98,9 @@ pub struct CompleteNativeWithPayload<'b> {
|
|||
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
|
||||
|
||||
pub to: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
|
||||
/// Transfer with payload can only be redeemed by the recipient. The idea is
|
||||
/// to target contracts which can then decide how to process the payload.
|
||||
///
|
||||
/// The actual recipient (the `to` field above) is an associated token
|
||||
/// account of the target contract and not the contract itself, so we also need
|
||||
/// to take the target contract's address directly. This will be the owner
|
||||
/// of the associated token account. This ownership check cannot be
|
||||
/// expressed in Solitaire, so we have to check it explicitly in
|
||||
/// [`complete_native_with_payload`]
|
||||
/// We require that the contract is a signer of this transaction.
|
||||
pub to_owner: MaybeMut<Signer<Info<'b>>>,
|
||||
|
||||
/// See [`verify_recipient_address`]
|
||||
pub redeemer: RedeemerAccount<'b>,
|
||||
pub to_fees: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
|
||||
pub custody: Mut<CustodyAccount<'b, { AccountState::Initialized }>>,
|
||||
pub mint: Data<'b, SplMint, { AccountState::Initialized }>,
|
||||
|
@ -115,12 +167,14 @@ pub fn complete_native_with_payload(
|
|||
if accs.vaa.to_chain != CHAIN_ID_SOLANA {
|
||||
return Err(InvalidChain.into());
|
||||
}
|
||||
if accs.vaa.to != accs.to_owner.info().key.to_bytes() {
|
||||
return Err(InvalidRecipient.into());
|
||||
}
|
||||
|
||||
// VAA-specified recipient must be token account owner
|
||||
if *accs.to_owner.info().key != accs.to.owner {
|
||||
let recipient = Pubkey::try_from_slice(&accs.vaa.to)?;
|
||||
accs.redeemer.verify_recipient_address(&recipient)?;
|
||||
|
||||
// Token account owner must be either the VAA-specified recipient, or the
|
||||
// redeemer account (for regular wallets, these two are equal, for programs
|
||||
// the latter is a PDA)
|
||||
if recipient != accs.to.owner && *accs.redeemer.info().key != accs.to.owner {
|
||||
return Err(InvalidRecipient.into());
|
||||
}
|
||||
|
||||
|
@ -129,12 +183,10 @@ pub fn complete_native_with_payload(
|
|||
accs.vaa.claim(ctx, accs.payer.key)?;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Transfer tokens
|
||||
|
@ -144,18 +196,7 @@ pub fn complete_native_with_payload(
|
|||
accs.to.info().key,
|
||||
accs.custody_signer.key,
|
||||
&[],
|
||||
amount.checked_sub(fee).unwrap(),
|
||||
)?;
|
||||
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,
|
||||
amount,
|
||||
)?;
|
||||
invoke_seeded(&transfer_ix, ctx, &accs.custody_signer, None)?;
|
||||
|
||||
|
@ -173,17 +214,9 @@ pub struct CompleteWrappedWithPayload<'b> {
|
|||
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
|
||||
|
||||
pub to: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
|
||||
/// Transfer with payload can only be redeemed by the recipient. The idea is
|
||||
/// to target contracts which can then decide how to process the payload.
|
||||
///
|
||||
/// The actual recipient (the `to` field above) is an associated token
|
||||
/// account of the target contract and not the contract itself, so we also need
|
||||
/// to take the target contract's address directly. This will be the owner
|
||||
/// of the associated token account. This ownership check cannot be
|
||||
/// expressed in Solitaire, so we have to check it explicitly in
|
||||
/// [`complete_native_with_payload`]
|
||||
/// We require that the contract is a signer of this transaction.
|
||||
pub to_owner: MaybeMut<Signer<Info<'b>>>,
|
||||
|
||||
/// See [`verify_recipient_address`]
|
||||
pub redeemer: RedeemerAccount<'b>,
|
||||
pub to_fees: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
|
||||
pub mint: Mut<WrappedMint<'b, { AccountState::Initialized }>>,
|
||||
pub wrapped_meta: WrappedTokenMeta<'b, { AccountState::Initialized }>,
|
||||
|
@ -247,12 +280,14 @@ pub fn complete_wrapped_with_payload(
|
|||
if accs.vaa.to_chain != CHAIN_ID_SOLANA {
|
||||
return Err(InvalidChain.into());
|
||||
}
|
||||
if accs.vaa.to != accs.to_owner.info().key.to_bytes() {
|
||||
return Err(InvalidRecipient.into());
|
||||
}
|
||||
|
||||
// VAA-specified recipient must be token account owner
|
||||
if *accs.to_owner.info().key != accs.to.owner {
|
||||
let recipient = Pubkey::try_from_slice(&accs.vaa.to)?;
|
||||
accs.redeemer.verify_recipient_address(&recipient)?;
|
||||
|
||||
// Token account owner must be either the VAA-specified recipient, or the
|
||||
// redeemer account (for regular wallets, these two are equal, for programs
|
||||
// the latter is a PDA)
|
||||
if recipient != accs.to.owner && *accs.redeemer.info().key != accs.to.owner {
|
||||
return Err(InvalidRecipient.into());
|
||||
}
|
||||
|
||||
|
@ -266,22 +301,7 @@ pub fn complete_wrapped_with_payload(
|
|||
accs.to.info().key,
|
||||
accs.mint_authority.key,
|
||||
&[],
|
||||
accs.vaa
|
||||
.amount
|
||||
.as_u64()
|
||||
.checked_sub(accs.vaa.fee.as_u64())
|
||||
.unwrap(),
|
||||
)?;
|
||||
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(),
|
||||
accs.vaa.amount.as_u64(),
|
||||
)?;
|
||||
invoke_seeded(&mint_ix, ctx, &accs.mint_authority, None)?;
|
||||
|
||||
|
|
|
@ -13,10 +13,7 @@ use crate::{
|
|||
WrappedMint,
|
||||
WrappedTokenMeta,
|
||||
},
|
||||
messages::{
|
||||
PayloadTransfer,
|
||||
PayloadTransferWithPayload,
|
||||
},
|
||||
messages::PayloadTransfer,
|
||||
types::*,
|
||||
TokenBridgeError,
|
||||
TokenBridgeError::{
|
||||
|
@ -54,8 +51,6 @@ use solitaire::{
|
|||
*,
|
||||
};
|
||||
|
||||
pub type TransferNativeWithPayload<'b> = TransferNative<'b>;
|
||||
|
||||
#[derive(FromAccounts)]
|
||||
pub struct TransferNative<'b> {
|
||||
pub payer: Mut<Signer<AccountInfo<'b>>>,
|
||||
|
@ -119,7 +114,21 @@ pub fn transfer_native(
|
|||
return Err(InvalidChain.into());
|
||||
}
|
||||
|
||||
let (amount, fee) = verify_and_execute_native_transfers(ctx, accs, data.amount, data.fee)?;
|
||||
let derivation_data: CustodyAccountDerivationData = (&*accs).into();
|
||||
let (amount, fee) = verify_and_execute_native_transfers(
|
||||
ctx,
|
||||
&derivation_data,
|
||||
&accs.payer,
|
||||
&accs.from,
|
||||
&accs.mint,
|
||||
&accs.custody,
|
||||
&accs.authority_signer,
|
||||
&accs.custody_signer,
|
||||
&accs.bridge,
|
||||
&accs.fee_collector,
|
||||
data.amount,
|
||||
data.fee,
|
||||
)?;
|
||||
|
||||
// Post message
|
||||
let payload = PayloadTransfer {
|
||||
|
@ -159,80 +168,26 @@ pub fn transfer_native(
|
|||
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(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn verify_and_execute_native_transfers(
|
||||
ctx: &ExecutionContext,
|
||||
accs: &mut TransferNative,
|
||||
derivation_data: &CustodyAccountDerivationData,
|
||||
payer: &Mut<Signer<AccountInfo>>,
|
||||
from: &Mut<Data<SplAccount, { AccountState::Initialized }>>,
|
||||
mint: &Mut<Data<SplMint, { AccountState::Initialized }>>,
|
||||
custody: &Mut<CustodyAccount<{ AccountState::MaybeInitialized }>>,
|
||||
authority_signer: &AuthoritySigner,
|
||||
custody_signer: &CustodySigner,
|
||||
bridge: &Mut<CoreBridge<{ AccountState::Initialized }>>,
|
||||
fee_collector: &Mut<Info>,
|
||||
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
|
||||
.verify_derivation(ctx.program_id, &derivation_data)?;
|
||||
custody.verify_derivation(ctx.program_id, derivation_data)?;
|
||||
|
||||
// Verify mints
|
||||
if accs.from.mint != *accs.mint.info().key {
|
||||
if from.mint != *mint.info().key {
|
||||
return Err(TokenBridgeError::InvalidMint.into());
|
||||
}
|
||||
|
||||
|
@ -242,26 +197,25 @@ pub fn verify_and_execute_native_transfers(
|
|||
}
|
||||
|
||||
// Verify that the token is not a wrapped token
|
||||
if let COption::Some(mint_authority) = accs.mint.mint_authority {
|
||||
if let COption::Some(mint_authority) = 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)?;
|
||||
if !custody.is_initialized() {
|
||||
custody.create(derivation_data, ctx, 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,
|
||||
custody.info().key,
|
||||
mint.info().key,
|
||||
custody_signer.key,
|
||||
)?;
|
||||
invoke_signed(&init_ix, ctx.accounts, &[])?;
|
||||
}
|
||||
|
||||
let trunc_divisor = 10u64.pow(8.max(accs.mint.decimals as u32) - 8);
|
||||
let trunc_divisor = 10u64.pow(8.max(mint.decimals as u32) - 8);
|
||||
// Truncate to 8 decimals
|
||||
let amount: u64 = raw_amount / trunc_divisor;
|
||||
let fee: u64 = raw_fee / trunc_divisor;
|
||||
|
@ -271,19 +225,19 @@ pub fn verify_and_execute_native_transfers(
|
|||
// Transfer tokens
|
||||
let transfer_ix = spl_token::instruction::transfer(
|
||||
&spl_token::id(),
|
||||
accs.from.info().key,
|
||||
accs.custody.info().key,
|
||||
accs.authority_signer.key,
|
||||
from.info().key,
|
||||
custody.info().key,
|
||||
authority_signer.key,
|
||||
&[],
|
||||
amount_trunc,
|
||||
)?;
|
||||
invoke_seeded(&transfer_ix, ctx, &accs.authority_signer, None)?;
|
||||
invoke_seeded(&transfer_ix, ctx, authority_signer, None)?;
|
||||
|
||||
// Pay fee
|
||||
let transfer_ix = solana_program::system_instruction::transfer(
|
||||
accs.payer.key,
|
||||
accs.fee_collector.key,
|
||||
accs.bridge.config.fee,
|
||||
payer.key,
|
||||
fee_collector.key,
|
||||
bridge.config.fee,
|
||||
);
|
||||
invoke(&transfer_ix, ctx.accounts)?;
|
||||
|
||||
|
@ -320,8 +274,6 @@ 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 {
|
||||
|
@ -358,7 +310,21 @@ pub fn transfer_wrapped(
|
|||
return Err(InvalidChain.into());
|
||||
}
|
||||
|
||||
verify_and_execute_wrapped_transfers(ctx, accs, data.amount, data.fee)?;
|
||||
let derivation_data: WrappedMetaDerivationData = (&*accs).into();
|
||||
verify_and_execute_wrapped_transfers(
|
||||
ctx,
|
||||
&derivation_data,
|
||||
&accs.payer,
|
||||
&accs.from,
|
||||
&accs.from_owner,
|
||||
&accs.mint,
|
||||
&accs.wrapped_meta,
|
||||
&accs.authority_signer,
|
||||
&accs.bridge,
|
||||
&accs.fee_collector,
|
||||
data.amount,
|
||||
data.fee,
|
||||
)?;
|
||||
|
||||
// Post message
|
||||
let payload = PayloadTransfer {
|
||||
|
@ -398,80 +364,28 @@ pub fn transfer_wrapped(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[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>,
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
verify_and_execute_wrapped_transfers(ctx, accs, data.amount, data.fee)?;
|
||||
|
||||
// Post message
|
||||
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,
|
||||
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(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn verify_and_execute_wrapped_transfers(
|
||||
ctx: &ExecutionContext,
|
||||
accs: &mut TransferWrapped,
|
||||
derivation_data: &WrappedMetaDerivationData,
|
||||
payer: &Mut<Signer<AccountInfo>>,
|
||||
from: &Mut<Data<SplAccount, { AccountState::Initialized }>>,
|
||||
from_owner: &MaybeMut<Signer<Info>>,
|
||||
mint: &Mut<WrappedMint<{ AccountState::Initialized }>>,
|
||||
wrapped_meta: &WrappedTokenMeta<{ AccountState::Initialized }>,
|
||||
authority_signer: &AuthoritySigner,
|
||||
bridge: &Mut<CoreBridge<{ AccountState::Initialized }>>,
|
||||
fee_collector: &Mut<Info>,
|
||||
amount: u64,
|
||||
fee: u64,
|
||||
) -> Result<()> {
|
||||
// Verify that the from account is owned by the from_owner
|
||||
if &accs.from.owner != accs.from_owner.key {
|
||||
if &from.owner != from_owner.key {
|
||||
return Err(WrongAccountOwner.into());
|
||||
}
|
||||
|
||||
// Verify mints
|
||||
if accs.mint.info().key != &accs.from.mint {
|
||||
if mint.info().key != &from.mint {
|
||||
return Err(TokenBridgeError::InvalidMint.into());
|
||||
}
|
||||
|
||||
|
@ -481,26 +395,24 @@ pub fn verify_and_execute_wrapped_transfers(
|
|||
}
|
||||
|
||||
// Verify that meta is correct
|
||||
let derivation_data: WrappedMetaDerivationData = (&*accs).into();
|
||||
accs.wrapped_meta
|
||||
.verify_derivation(ctx.program_id, &derivation_data)?;
|
||||
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,
|
||||
from.info().key,
|
||||
mint.info().key,
|
||||
authority_signer.key,
|
||||
&[],
|
||||
amount,
|
||||
)?;
|
||||
invoke_seeded(&burn_ix, ctx, &accs.authority_signer, None)?;
|
||||
invoke_seeded(&burn_ix, ctx, authority_signer, None)?;
|
||||
|
||||
// Pay fee
|
||||
let transfer_ix = solana_program::system_instruction::transfer(
|
||||
accs.payer.key,
|
||||
accs.fee_collector.key,
|
||||
accs.bridge.config.fee,
|
||||
payer.key,
|
||||
fee_collector.key,
|
||||
bridge.config.fee,
|
||||
);
|
||||
|
||||
invoke(&transfer_ix, ctx.accounts)?;
|
||||
|
|
|
@ -0,0 +1,372 @@
|
|||
use crate::{
|
||||
accounts::{
|
||||
AuthoritySigner,
|
||||
ConfigAccount,
|
||||
CoreBridge,
|
||||
CustodyAccount,
|
||||
CustodyAccountDerivationData,
|
||||
CustodySigner,
|
||||
EmitterAccount,
|
||||
WrappedDerivationData,
|
||||
WrappedMetaDerivationData,
|
||||
WrappedMint,
|
||||
WrappedTokenMeta,
|
||||
},
|
||||
messages::PayloadTransferWithPayload,
|
||||
types::*,
|
||||
TokenBridgeError::InvalidChain,
|
||||
};
|
||||
use bridge::{
|
||||
api::PostMessageData,
|
||||
types::ConsistencyLevel,
|
||||
vaa::SerializePayload,
|
||||
CHAIN_ID_SOLANA,
|
||||
};
|
||||
use primitive_types::U256;
|
||||
use solana_program::{
|
||||
account_info::AccountInfo,
|
||||
instruction::{
|
||||
AccountMeta,
|
||||
Instruction,
|
||||
},
|
||||
pubkey::Pubkey,
|
||||
sysvar::clock::Clock,
|
||||
};
|
||||
use solitaire::{
|
||||
processors::seeded::invoke_seeded,
|
||||
*,
|
||||
};
|
||||
|
||||
use super::{
|
||||
verify_and_execute_native_transfers,
|
||||
verify_and_execute_wrapped_transfers,
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Sender
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct SenderAccount<'b>(pub MaybeMut<Signer<Info<'b>>>);
|
||||
|
||||
impl<'a, 'b: 'a, 'c> Peel<'a, 'b, 'c> for SenderAccount<'b> {
|
||||
fn peel<I>(ctx: &'c mut Context<'a, 'b, 'c, I>) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(SenderAccount(MaybeMut::peel(ctx)?))
|
||||
}
|
||||
|
||||
fn persist(&self, program_id: &Pubkey) -> Result<()> {
|
||||
MaybeMut::persist(&self.0, program_id)
|
||||
}
|
||||
}
|
||||
|
||||
// May or may not be a PDA, so we don't use [`Derive`], instead implement
|
||||
// [`Seeded`] directly.
|
||||
impl<'b> Seeded<()> for SenderAccount<'b> {
|
||||
fn seeds(_accs: ()) -> Vec<Vec<u8>> {
|
||||
vec![String::from("sender").as_bytes().to_vec()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b: 'a> Keyed<'a, 'b> for SenderAccount<'b> {
|
||||
fn info(&'a self) -> &Info<'b> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> SenderAccount<'b> {
|
||||
/// Transfers with payload also include the address of the account or contract
|
||||
/// that sent the transfer. Semantically this is identical to "msg.sender" on
|
||||
/// EVM chains, i.e. it is the address of the immediate caller of the token
|
||||
/// bridge transaction.
|
||||
/// Since on Solana, a transaction can have multiple different signers, getting
|
||||
/// this information is not so straightforward.
|
||||
/// The strategy we use to figure out the sender of the transaction is to
|
||||
/// require an additional signer ([`SenderAccount`]) for the transaction.
|
||||
/// If the transaction was sent by a user wallet directly, then this may just be
|
||||
/// the wallet's pubkey. If, however, the transaction was initiated by a
|
||||
/// program, then we require this to be a PDA derived from the sender program's
|
||||
/// id and the string "sender". In this case, the sender program must also
|
||||
/// attach its program id to the instruction data. If the PDA verification
|
||||
/// succeeds (thereby proving that [[`cpi_program_id`]] indeed signed the
|
||||
/// transaction), then the program's id is attached to the VAA as the sender,
|
||||
/// otherwise the transaction is rejected.
|
||||
///
|
||||
/// Note that a program may opt to forego the PDA derivation and instead just
|
||||
/// pass on the original wallet as the wallet account (or any other signer, as
|
||||
/// long as they don't provide their program_id in the instruction data). The
|
||||
/// sender address is provided as a means for protocols to verify on the
|
||||
/// receiving end that the message was emitted by a contract they trust, so
|
||||
/// foregoing this check is not advised. If the receiving contract needs to know
|
||||
/// the sender wallet's address too, then that information can be included in
|
||||
/// the additional payload, along with any other data that the protocol needs to
|
||||
/// send across. The legitimacy of the attached data can be verified by checking
|
||||
/// that the sender contract is a trusted one.
|
||||
///
|
||||
/// Also note that attaching the correct PDA as [[`SenderAccount`]] but missing the
|
||||
/// [[`cpi_program_id`]] field will result in a successful transaction, but in
|
||||
/// that case the PDA's address will directly be encoded into the payload
|
||||
/// instead of the sender program's id.
|
||||
fn derive_sender_address(&self, cpi_program_id: &Option<Pubkey>) -> Result<Address> {
|
||||
match cpi_program_id {
|
||||
Some(cpi_program_id) => {
|
||||
self.verify_derivation(cpi_program_id, ())?;
|
||||
Ok(cpi_program_id.to_bytes())
|
||||
}
|
||||
None => Ok(self.info().key.to_bytes()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Transfer wrapped with payload
|
||||
|
||||
#[derive(FromAccounts)]
|
||||
pub struct TransferNativeWithPayload<'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<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>,
|
||||
|
||||
/// See [`derive_sender_address`]
|
||||
pub sender: SenderAccount<'b>,
|
||||
}
|
||||
|
||||
impl<'a> From<&TransferNativeWithPayload<'a>> for CustodyAccountDerivationData {
|
||||
fn from(accs: &TransferNativeWithPayload<'a>) -> Self {
|
||||
CustodyAccountDerivationData {
|
||||
mint: *accs.mint.info().key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BorshDeserialize, BorshSerialize, Default)]
|
||||
pub struct TransferNativeWithPayloadData {
|
||||
pub nonce: u32,
|
||||
pub amount: u64,
|
||||
pub target_address: Address,
|
||||
pub target_chain: ChainID,
|
||||
pub payload: Vec<u8>,
|
||||
/// See [`derive_sender_address`]
|
||||
pub cpi_program_id: Option<Pubkey>,
|
||||
}
|
||||
|
||||
pub fn transfer_native_with_payload(
|
||||
ctx: &ExecutionContext,
|
||||
accs: &mut TransferNativeWithPayload,
|
||||
data: TransferNativeWithPayloadData,
|
||||
) -> Result<()> {
|
||||
// Prevent transferring to the same chain.
|
||||
if data.target_chain == CHAIN_ID_SOLANA {
|
||||
return Err(InvalidChain.into());
|
||||
}
|
||||
|
||||
let derivation_data: CustodyAccountDerivationData = (&*accs).into();
|
||||
let (amount, _fee) = verify_and_execute_native_transfers(
|
||||
ctx,
|
||||
&derivation_data,
|
||||
&accs.payer,
|
||||
&accs.from,
|
||||
&accs.mint,
|
||||
&accs.custody,
|
||||
&accs.authority_signer,
|
||||
&accs.custody_signer,
|
||||
&accs.bridge,
|
||||
&accs.fee_collector,
|
||||
data.amount,
|
||||
0,
|
||||
)?;
|
||||
|
||||
// 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,
|
||||
from_address: accs.sender.derive_sender_address(&data.cpi_program_id)?,
|
||||
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(())
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Transfer wrapped with payload
|
||||
|
||||
#[derive(FromAccounts)]
|
||||
pub struct TransferWrappedWithPayload<'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 }>,
|
||||
|
||||
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>,
|
||||
|
||||
/// See [`derive_sender_address`]
|
||||
pub sender: SenderAccount<'b>,
|
||||
}
|
||||
|
||||
impl<'a> From<&TransferWrappedWithPayload<'a>> for WrappedDerivationData {
|
||||
fn from(accs: &TransferWrappedWithPayload<'a>) -> Self {
|
||||
WrappedDerivationData {
|
||||
token_chain: 1,
|
||||
token_address: accs.mint.info().key.to_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&TransferWrappedWithPayload<'a>> for WrappedMetaDerivationData {
|
||||
fn from(accs: &TransferWrappedWithPayload<'a>) -> Self {
|
||||
WrappedMetaDerivationData {
|
||||
mint_key: *accs.mint.info().key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BorshDeserialize, BorshSerialize, Default)]
|
||||
pub struct TransferWrappedWithPayloadData {
|
||||
pub nonce: u32,
|
||||
pub amount: u64,
|
||||
pub target_address: Address,
|
||||
pub target_chain: ChainID,
|
||||
pub payload: Vec<u8>,
|
||||
/// See [`derive_sender_address`]
|
||||
pub cpi_program_id: Option<Pubkey>,
|
||||
}
|
||||
|
||||
pub fn transfer_wrapped_with_payload(
|
||||
ctx: &ExecutionContext,
|
||||
accs: &mut TransferWrappedWithPayload,
|
||||
data: TransferWrappedWithPayloadData,
|
||||
) -> Result<()> {
|
||||
// Prevent transferring to the same chain.
|
||||
if data.target_chain == CHAIN_ID_SOLANA {
|
||||
return Err(InvalidChain.into());
|
||||
}
|
||||
|
||||
let derivation_data: WrappedMetaDerivationData = (&*accs).into();
|
||||
verify_and_execute_wrapped_transfers(
|
||||
ctx,
|
||||
&derivation_data,
|
||||
&accs.payer,
|
||||
&accs.from,
|
||||
&accs.from_owner,
|
||||
&accs.mint,
|
||||
&accs.wrapped_meta,
|
||||
&accs.authority_signer,
|
||||
&accs.bridge,
|
||||
&accs.fee_collector,
|
||||
data.amount,
|
||||
0,
|
||||
)?;
|
||||
|
||||
// Post message
|
||||
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,
|
||||
from_address: accs.sender.derive_sender_address(&data.cpi_program_id)?,
|
||||
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(())
|
||||
}
|
|
@ -24,6 +24,7 @@ use crate::{
|
|||
AttestTokenData,
|
||||
CreateWrappedData,
|
||||
RegisterChainData,
|
||||
SenderAccount,
|
||||
TransferNativeData,
|
||||
TransferWrappedData,
|
||||
UpgradeContractData,
|
||||
|
@ -447,6 +448,26 @@ fn claimable_vaa(
|
|||
)
|
||||
}
|
||||
|
||||
/// Required accounts
|
||||
///
|
||||
/// | name | account | signer |
|
||||
/// |------------------+-------------------------------------------------------------------+--------|
|
||||
/// | payer | Pubkey | true |
|
||||
/// | config | PDA(program_id, \["config"\]) | false |
|
||||
/// | from | Pubkey | false |
|
||||
/// | mint | Pubkey | false |
|
||||
/// | custody | PDA(program_id, \[mint\]) | false |
|
||||
/// | authority_signer | PDA(program_id, \["authority_signer"\]) | false |
|
||||
/// | custody_signer | PDA(program_id, \["custody_signer"\]) | false |
|
||||
/// | bridge_config | PDA(bridge_id, \["Bridge"\]) | false |
|
||||
/// | message | Pubkey | true |
|
||||
/// | emitter | PDA(program_id, \["emitter"\]) | false |
|
||||
/// | sequence | PDA(bridge_id, \["Sequence", emitter\]) | false |
|
||||
/// | fee_collector | PDA(bridge_id, \["fee_collector"\]) | false |
|
||||
/// | rent | rent sysvar | false |
|
||||
/// | system_program | system program | false |
|
||||
/// | bridge_id | bridge_id program | false |
|
||||
/// | spl_token | spl_token program | false |
|
||||
pub fn transfer_native(
|
||||
program_id: Pubkey,
|
||||
bridge_id: Pubkey,
|
||||
|
@ -455,50 +476,6 @@ pub fn transfer_native(
|
|||
from: Pubkey,
|
||||
mint: Pubkey,
|
||||
data: TransferNativeData,
|
||||
) -> solitaire::Result<Instruction> {
|
||||
transfer_native_raw(
|
||||
program_id,
|
||||
bridge_id,
|
||||
payer,
|
||||
message_key,
|
||||
from,
|
||||
mint,
|
||||
(crate::instruction::Instruction::TransferNative, data).try_to_vec()?,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn transfer_native_with_payload(
|
||||
program_id: Pubkey,
|
||||
bridge_id: Pubkey,
|
||||
payer: Pubkey,
|
||||
message_key: Pubkey,
|
||||
from: Pubkey,
|
||||
mint: Pubkey,
|
||||
data: TransferNativeWithPayloadData,
|
||||
) -> solitaire::Result<Instruction> {
|
||||
transfer_native_raw(
|
||||
program_id,
|
||||
bridge_id,
|
||||
payer,
|
||||
message_key,
|
||||
from,
|
||||
mint,
|
||||
(
|
||||
crate::instruction::Instruction::TransferNativeWithPayload,
|
||||
data,
|
||||
)
|
||||
.try_to_vec()?,
|
||||
)
|
||||
}
|
||||
|
||||
fn transfer_native_raw(
|
||||
program_id: Pubkey,
|
||||
bridge_id: Pubkey,
|
||||
payer: Pubkey,
|
||||
message_key: Pubkey,
|
||||
from: Pubkey,
|
||||
mint: Pubkey,
|
||||
data: Vec<u8>,
|
||||
) -> solitaire::Result<Instruction> {
|
||||
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
|
||||
let custody_key = CustodyAccount::<'_, { AccountState::Initialized }>::key(
|
||||
|
@ -520,6 +497,8 @@ fn transfer_native_raw(
|
|||
);
|
||||
let fee_collector_key = FeeCollector::key(None, &bridge_id);
|
||||
|
||||
let instruction = crate::instruction::Instruction::TransferNative;
|
||||
|
||||
Ok(Instruction {
|
||||
program_id,
|
||||
accounts: vec![
|
||||
|
@ -543,10 +522,117 @@ fn transfer_native_raw(
|
|||
AccountMeta::new_readonly(bridge_id, false),
|
||||
AccountMeta::new_readonly(spl_token::id(), false),
|
||||
],
|
||||
data,
|
||||
data: (instruction, data).try_to_vec()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Required accounts
|
||||
///
|
||||
/// | name | account | signer |
|
||||
/// |------------------+------------------------------------------------------------------------+--------|
|
||||
/// | payer | Pubkey | true |
|
||||
/// | config | PDA(program_id, \["config"\]) | false |
|
||||
/// | from | Pubkey | false |
|
||||
/// | mint | Pubkey | false |
|
||||
/// | custody | PDA(program_id, \[mint\]) | false |
|
||||
/// | authority_signer | PDA(program_id, \["authority_signer"\]) | false |
|
||||
/// | custody_signer | PDA(program_id, \["custody_signer"\]) | false |
|
||||
/// | bridge_config | PDA(bridge_id, \["Bridge"\]) | false |
|
||||
/// | message | Pubkey | true |
|
||||
/// | emitter | PDA(program_id, \["emitter"\]) | false |
|
||||
/// | sequence | PDA(bridge_id, \["Sequence", emitter\]) | false |
|
||||
/// | fee_collector | PDA(bridge_id, \["fee_collector"\]) | false |
|
||||
/// | clock | clock sysvar | false |
|
||||
/// | sender | if Some(p) = data.cpi_program_id then PDA(p, \["sender"\]) else payer | true |
|
||||
/// | rent | rent sysvar | false |
|
||||
/// | system_program | system program | false |
|
||||
/// | bridge_id | bridge_id program | false |
|
||||
/// | spl_token | spl_token program | false |
|
||||
pub fn transfer_native_with_payload(
|
||||
program_id: Pubkey,
|
||||
bridge_id: Pubkey,
|
||||
payer: Pubkey,
|
||||
message_key: Pubkey,
|
||||
from: Pubkey,
|
||||
mint: Pubkey,
|
||||
data: TransferNativeWithPayloadData,
|
||||
) -> solitaire::Result<Instruction> {
|
||||
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
|
||||
let custody_key = CustodyAccount::<'_, { AccountState::Initialized }>::key(
|
||||
&CustodyAccountDerivationData { mint },
|
||||
&program_id,
|
||||
);
|
||||
|
||||
let authority_signer_key = AuthoritySigner::key(None, &program_id);
|
||||
let custody_signer_key = CustodySigner::key(None, &program_id);
|
||||
let emitter_key = EmitterAccount::key(None, &program_id);
|
||||
|
||||
// Bridge keys
|
||||
let bridge_config = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &bridge_id);
|
||||
let sequence_key = Sequence::key(
|
||||
&SequenceDerivationData {
|
||||
emitter_key: &emitter_key,
|
||||
},
|
||||
&bridge_id,
|
||||
);
|
||||
let fee_collector_key = FeeCollector::key(None, &bridge_id);
|
||||
|
||||
let sender = match data.cpi_program_id {
|
||||
Some(cpi_program_id) => SenderAccount::key((), &cpi_program_id),
|
||||
None => payer,
|
||||
};
|
||||
|
||||
let instruction = crate::instruction::Instruction::TransferNativeWithPayload;
|
||||
|
||||
Ok(Instruction {
|
||||
program_id,
|
||||
accounts: vec![
|
||||
AccountMeta::new(payer, true),
|
||||
AccountMeta::new_readonly(config_key, false),
|
||||
AccountMeta::new(from, false),
|
||||
AccountMeta::new(mint, false),
|
||||
AccountMeta::new(custody_key, false),
|
||||
AccountMeta::new_readonly(authority_signer_key, false),
|
||||
AccountMeta::new_readonly(custody_signer_key, false),
|
||||
AccountMeta::new(bridge_config, false),
|
||||
AccountMeta::new(message_key, true),
|
||||
AccountMeta::new_readonly(emitter_key, false),
|
||||
AccountMeta::new(sequence_key, false),
|
||||
AccountMeta::new(fee_collector_key, false),
|
||||
AccountMeta::new_readonly(solana_program::sysvar::clock::id(), false),
|
||||
AccountMeta::new(sender, true),
|
||||
// Dependencies
|
||||
AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
|
||||
AccountMeta::new_readonly(solana_program::system_program::id(), false),
|
||||
// Program
|
||||
AccountMeta::new_readonly(bridge_id, false),
|
||||
AccountMeta::new_readonly(spl_token::id(), false),
|
||||
],
|
||||
data: (instruction, data).try_to_vec()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Required accounts
|
||||
///
|
||||
/// | name | account | signer |
|
||||
/// |------------------+------------------------------------------------------------------------+--------|
|
||||
/// | payer | Pubkey | true |
|
||||
/// | config | PDA(program_id, \["config"\]) | false |
|
||||
/// | from | Pubkey | false |
|
||||
/// | from_owner | Pubkey | true |
|
||||
/// | wrapped_mint | PDA(program_id, \["wrapped", token_chain, token_address\]) | false |
|
||||
/// | wrapped_meta | PDA(program_id, \["meta", wrapped_mint\]) | false |
|
||||
/// | authority_signer | PDA(program_id, \["authority_signer"\]) | false |
|
||||
/// | bridge_config | PDA(bridge_id, \["Bridge"\]) | false |
|
||||
/// | message | Pubkey | true |
|
||||
/// | emitter | PDA(program_id, \["emitter"\]) | false |
|
||||
/// | sequence | PDA(bridge_id, \["Sequence", emitter\]) | false |
|
||||
/// | fee_collector | PDA(bridge_id, \["fee_collector"\]) | false |
|
||||
/// | clock | clock sysvar | false |
|
||||
/// | rent | rent sysvar | false |
|
||||
/// | system_program | system program | false |
|
||||
/// | bridge_id | bridge_id program | false |
|
||||
/// | spl_token | spl_token program | false |
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn transfer_wrapped(
|
||||
program_id: Pubkey,
|
||||
|
@ -558,60 +644,6 @@ pub fn transfer_wrapped(
|
|||
token_chain: u16,
|
||||
token_address: ForeignAddress,
|
||||
data: TransferWrappedData,
|
||||
) -> solitaire::Result<Instruction> {
|
||||
transfer_wrapped_raw(
|
||||
program_id,
|
||||
bridge_id,
|
||||
payer,
|
||||
message_key,
|
||||
from,
|
||||
from_owner,
|
||||
token_chain,
|
||||
token_address,
|
||||
(crate::instruction::Instruction::TransferWrapped, data).try_to_vec()?,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn transfer_wrapped_with_payload(
|
||||
program_id: Pubkey,
|
||||
bridge_id: Pubkey,
|
||||
payer: Pubkey,
|
||||
message_key: Pubkey,
|
||||
from: Pubkey,
|
||||
from_owner: Pubkey,
|
||||
token_chain: u16,
|
||||
token_address: ForeignAddress,
|
||||
data: TransferWrappedWithPayloadData,
|
||||
) -> solitaire::Result<Instruction> {
|
||||
transfer_wrapped_raw(
|
||||
program_id,
|
||||
bridge_id,
|
||||
payer,
|
||||
message_key,
|
||||
from,
|
||||
from_owner,
|
||||
token_chain,
|
||||
token_address,
|
||||
(
|
||||
crate::instruction::Instruction::TransferWrappedWithPayload,
|
||||
data,
|
||||
)
|
||||
.try_to_vec()?,
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn transfer_wrapped_raw(
|
||||
program_id: Pubkey,
|
||||
bridge_id: Pubkey,
|
||||
payer: Pubkey,
|
||||
message_key: Pubkey,
|
||||
from: Pubkey,
|
||||
from_owner: Pubkey,
|
||||
token_chain: u16,
|
||||
token_address: ForeignAddress,
|
||||
data: Vec<u8>,
|
||||
) -> solitaire::Result<Instruction> {
|
||||
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
|
||||
|
||||
|
@ -642,6 +674,8 @@ fn transfer_wrapped_raw(
|
|||
);
|
||||
let fee_collector_key = FeeCollector::key(None, &bridge_id);
|
||||
|
||||
let instruction = crate::instruction::Instruction::TransferWrapped;
|
||||
|
||||
Ok(Instruction {
|
||||
program_id,
|
||||
accounts: vec![
|
||||
|
@ -665,7 +699,105 @@ fn transfer_wrapped_raw(
|
|||
AccountMeta::new_readonly(bridge_id, false),
|
||||
AccountMeta::new_readonly(spl_token::id(), false),
|
||||
],
|
||||
data,
|
||||
data: (instruction, data).try_to_vec()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Required accounts
|
||||
///
|
||||
/// | name | account | signer |
|
||||
/// |------------------+------------------------------------------------------------------------+--------|
|
||||
/// | payer | Pubkey | true |
|
||||
/// | config | PDA(program_id, \["config"\]) | false |
|
||||
/// | from | Pubkey | false |
|
||||
/// | from_owner | Pubkey | true |
|
||||
/// | wrapped_mint | PDA(program_id, \["wrapped", token_chain, token_address\]) | false |
|
||||
/// | wrapped_meta | PDA(program_id, \["meta", wrapped_mint\]) | false |
|
||||
/// | authority_signer | PDA(program_id, \["authority_signer"\]) | false |
|
||||
/// | bridge_config | PDA(bridge_id, \["Bridge"\]) | false |
|
||||
/// | message | Pubkey | true |
|
||||
/// | emitter | PDA(program_id, \["emitter"\]) | false |
|
||||
/// | sequence | PDA(bridge_id, \["Sequence", emitter\]) | false |
|
||||
/// | fee_collector | PDA(bridge_id, \["fee_collector"\]) | false |
|
||||
/// | clock | clock sysvar | false |
|
||||
/// | sender | if Some(p) = data.cpi_program_id then PDA(p, \["sender"\]) else payer | true |
|
||||
/// | rent | rent sysvar | false |
|
||||
/// | system_program | system program | false |
|
||||
/// | bridge_id | bridge_id program | false |
|
||||
/// | spl_token | spl_token program | false |
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn transfer_wrapped_with_payload(
|
||||
program_id: Pubkey,
|
||||
bridge_id: Pubkey,
|
||||
payer: Pubkey,
|
||||
message_key: Pubkey,
|
||||
from: Pubkey,
|
||||
from_owner: Pubkey,
|
||||
token_chain: u16,
|
||||
token_address: ForeignAddress,
|
||||
data: TransferWrappedWithPayloadData,
|
||||
) -> solitaire::Result<Instruction> {
|
||||
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
|
||||
|
||||
let wrapped_mint_key = WrappedMint::<'_, { AccountState::Uninitialized }>::key(
|
||||
&WrappedDerivationData {
|
||||
token_chain,
|
||||
token_address,
|
||||
},
|
||||
&program_id,
|
||||
);
|
||||
let wrapped_meta_key = WrappedTokenMeta::<'_, { AccountState::Uninitialized }>::key(
|
||||
&WrappedMetaDerivationData {
|
||||
mint_key: wrapped_mint_key,
|
||||
},
|
||||
&program_id,
|
||||
);
|
||||
|
||||
let authority_signer = AuthoritySigner::key(None, &program_id);
|
||||
let emitter_key = EmitterAccount::key(None, &program_id);
|
||||
|
||||
// Bridge keys
|
||||
let bridge_config = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &bridge_id);
|
||||
let sequence_key = Sequence::key(
|
||||
&SequenceDerivationData {
|
||||
emitter_key: &emitter_key,
|
||||
},
|
||||
&bridge_id,
|
||||
);
|
||||
let fee_collector_key = FeeCollector::key(None, &bridge_id);
|
||||
|
||||
let sender = match data.cpi_program_id {
|
||||
Some(cpi_program_id) => SenderAccount::key((), &cpi_program_id),
|
||||
None => payer,
|
||||
};
|
||||
|
||||
let instruction = crate::instruction::Instruction::TransferWrappedWithPayload;
|
||||
|
||||
Ok(Instruction {
|
||||
program_id,
|
||||
accounts: vec![
|
||||
AccountMeta::new(payer, true),
|
||||
AccountMeta::new_readonly(config_key, false),
|
||||
AccountMeta::new(from, false),
|
||||
AccountMeta::new_readonly(from_owner, true),
|
||||
AccountMeta::new(wrapped_mint_key, false),
|
||||
AccountMeta::new_readonly(wrapped_meta_key, false),
|
||||
AccountMeta::new_readonly(authority_signer, false),
|
||||
AccountMeta::new(bridge_config, false),
|
||||
AccountMeta::new(message_key, true),
|
||||
AccountMeta::new_readonly(emitter_key, false),
|
||||
AccountMeta::new(sequence_key, false),
|
||||
AccountMeta::new(fee_collector_key, false),
|
||||
AccountMeta::new_readonly(solana_program::sysvar::clock::id(), false),
|
||||
AccountMeta::new(sender, true),
|
||||
// Dependencies
|
||||
AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
|
||||
AccountMeta::new_readonly(solana_program::system_program::id(), false),
|
||||
// Program
|
||||
AccountMeta::new_readonly(bridge_id, false),
|
||||
AccountMeta::new_readonly(spl_token::id(), false),
|
||||
],
|
||||
data: (instruction, data).try_to_vec()?,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -133,7 +133,9 @@ impl DeserializePayload for PayloadTransferWithPayload {
|
|||
|
||||
let mut fee_data: [u8; 32] = [0; 32];
|
||||
v.read_exact(&mut fee_data)?;
|
||||
let fee = U256::from_big_endian(&fee_data);
|
||||
|
||||
let mut from_address = Address::default();
|
||||
v.read_exact(&mut from_address)?;
|
||||
|
||||
let mut payload = vec![];
|
||||
v.read_to_end(&mut payload)?;
|
||||
|
@ -144,7 +146,7 @@ impl DeserializePayload for PayloadTransferWithPayload {
|
|||
token_chain,
|
||||
to,
|
||||
to_chain,
|
||||
fee,
|
||||
from_address,
|
||||
payload,
|
||||
})
|
||||
}
|
||||
|
@ -164,9 +166,7 @@ impl SerializePayload for PayloadTransferWithPayload {
|
|||
writer.write_all(&self.to)?;
|
||||
writer.write_u16::<BigEndian>(self.to_chain)?;
|
||||
|
||||
let mut fee_data: [u8; 32] = [0; 32];
|
||||
self.fee.to_big_endian(&mut fee_data);
|
||||
writer.write_all(&fee_data)?;
|
||||
writer.write_all(&self.from_address)?;
|
||||
|
||||
writer.write_all(self.payload.as_slice())?;
|
||||
|
||||
|
@ -186,8 +186,8 @@ pub struct PayloadTransferWithPayload {
|
|||
pub to: Address,
|
||||
/// Chain ID of the recipient
|
||||
pub to_chain: ChainID,
|
||||
/// Amount of tokens (big-endian uint256) that the user is willing to pay as relayer fee. Must be <= Amount.
|
||||
pub fee: U256,
|
||||
/// Sender of the transaction
|
||||
pub from_address: Address,
|
||||
/// Arbitrary payload
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
|
|
@ -129,7 +129,6 @@ pub fn transfer_native_with_payload_ix(
|
|||
mint: String,
|
||||
nonce: u32,
|
||||
amount: u64,
|
||||
fee: u64,
|
||||
target_address: Vec<u8>,
|
||||
target_chain: u16,
|
||||
payload: Vec<u8>,
|
||||
|
@ -154,10 +153,10 @@ pub fn transfer_native_with_payload_ix(
|
|||
TransferNativeWithPayloadData {
|
||||
nonce,
|
||||
amount,
|
||||
fee,
|
||||
target_address: target_addr,
|
||||
target_chain,
|
||||
payload,
|
||||
cpi_program_id: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -227,7 +226,6 @@ pub fn transfer_wrapped_with_payload_ix(
|
|||
token_address: Vec<u8>,
|
||||
nonce: u32,
|
||||
amount: u64,
|
||||
fee: u64,
|
||||
target_address: Vec<u8>,
|
||||
target_chain: u16,
|
||||
payload: Vec<u8>,
|
||||
|
@ -256,10 +254,10 @@ pub fn transfer_wrapped_with_payload_ix(
|
|||
TransferWrappedWithPayloadData {
|
||||
nonce,
|
||||
amount,
|
||||
fee,
|
||||
target_address: target_addr,
|
||||
target_chain,
|
||||
payload,
|
||||
cpi_program_id: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
|
Loading…
Reference in New Issue