diff --git a/solana/Dockerfile b/solana/Dockerfile index 1892e69dc..11d512c00 100644 --- a/solana/Dockerfile +++ b/solana/Dockerfile @@ -7,6 +7,7 @@ COPY Cargo.lock Cargo.lock COPY Cargo.toml Cargo.toml COPY modules modules COPY programs programs +COPY rust-toolchain rust-toolchain ENV RUST_BACKTRACE=1 diff --git a/solana/programs/example-native-token-transfers/tests/broadcast.rs b/solana/programs/example-native-token-transfers/tests/broadcast.rs index 7732ac0e1..321ba69b7 100644 --- a/solana/programs/example-native-token-transfers/tests/broadcast.rs +++ b/solana/programs/example-native-token-transfers/tests/broadcast.rs @@ -13,6 +13,7 @@ use wormhole_anchor_sdk::wormhole::PostedVaa; use crate::common::query::GetAccountDataAnchor; use crate::common::setup::{setup, OTHER_TRANSCEIVER}; +use crate::sdk::accounts::{good_ntt, NTTAccounts}; use crate::sdk::transceivers::wormhole::instructions::broadcast_id::{broadcast_id, BroadcastId}; use crate::{ common::submit::Submittable, @@ -24,12 +25,12 @@ pub mod sdk; #[tokio::test] async fn test_broadcast_peer() { - let (mut ctx, test_data) = setup(Mode::Locking).await; + let (mut ctx, _test_data) = setup(Mode::Locking).await; let wh_message = Keypair::new(); broadcast_peer( - &test_data.ntt, + &good_ntt, BroadcastPeer { payer: ctx.payer.pubkey(), wormhole_message: wh_message.pubkey(), @@ -60,7 +61,7 @@ async fn test_broadcast_id() { let wh_message = Keypair::new(); broadcast_id( - &test_data.ntt, + &good_ntt, BroadcastId { payer: ctx.payer.pubkey(), wormhole_message: wh_message.pubkey(), @@ -78,7 +79,7 @@ async fn test_broadcast_id() { assert_eq!( *msg.data(), WormholeTransceiverInfo { - manager_address: test_data.ntt.program.to_bytes(), + manager_address: good_ntt.program().to_bytes(), manager_mode: Mode::Locking, token_address: test_data.mint.to_bytes(), token_decimals: 9, diff --git a/solana/programs/example-native-token-transfers/tests/cancel_flow.rs b/solana/programs/example-native-token-transfers/tests/cancel_flow.rs index 98b257789..5116b2d3d 100644 --- a/solana/programs/example-native-token-transfers/tests/cancel_flow.rs +++ b/solana/programs/example-native-token-transfers/tests/cancel_flow.rs @@ -2,38 +2,40 @@ #![feature(type_changing_struct_update)] use anchor_lang::prelude::*; -use common::setup::{TestData, OTHER_CHAIN, OTHER_MANAGER, OTHER_TRANSCEIVER, THIS_CHAIN}; +use common::setup::{TestData, OTHER_CHAIN}; use example_native_token_transfers::{ - error::NTTError, instructions::{RedeemArgs, TransferArgs}, queue::{inbox::InboxRateLimit, outbox::OutboxRateLimit}, }; use ntt_messages::{ chain_id::ChainId, mode::Mode, ntt::NativeTokenTransfer, ntt_manager::NttManagerMessage, - transceiver::TransceiverMessage, transceivers::wormhole::WormholeTransceiver, - trimmed_amount::TrimmedAmount, }; -use sdk::transceivers::wormhole::instructions::receive_message::ReceiveMessage; -use solana_program::instruction::InstructionError; +use sdk::{ + accounts::{good_ntt, NTTAccounts}, + transceivers::wormhole::instructions::receive_message::ReceiveMessage, +}; use solana_program_test::*; -use solana_sdk::{signature::Keypair, signer::Signer, transaction::TransactionError}; -use wormhole_sdk::{Address, Vaa}; +use solana_sdk::{signature::Keypair, signer::Signer}; +use wormhole_sdk::Address; use crate::{ - common::submit::Submittable, - sdk::instructions::transfer::{approve_token_authority, transfer}, -}; -use crate::{ - common::{query::GetAccountDataAnchor, setup::setup}, + common::{ + query::GetAccountDataAnchor, + setup::{setup, OTHER_TRANSCEIVER}, + utils::make_transfer_message, + }, sdk::{ instructions::{ - post_vaa::post_vaa, redeem::{redeem, Redeem}, transfer::Transfer, }, transceivers::wormhole::instructions::receive_message::receive_message, }, }; +use crate::{ + common::{submit::Submittable, utils::post_vaa_helper}, + sdk::instructions::transfer::{approve_token_authority, transfer}, +}; pub mod common; pub mod sdk; @@ -47,7 +49,7 @@ fn init_transfer_accs_args( ) -> (Transfer, TransferArgs) { let accs = Transfer { payer: ctx.payer.pubkey(), - peer: test_data.ntt.peer(OTHER_CHAIN), + peer: good_ntt.peer(OTHER_CHAIN), mint: test_data.mint, from: test_data.user_token_account, from_authority: test_data.user.pubkey(), @@ -72,99 +74,43 @@ fn init_redeem_accs( ) -> Redeem { Redeem { payer: ctx.payer.pubkey(), - peer: test_data.ntt.peer(chain_id), - transceiver: test_data.ntt.program, - transceiver_message: test_data - .ntt - .transceiver_message(chain_id, ntt_manager_message.id), - inbox_item: test_data.ntt.inbox_item(chain_id, ntt_manager_message), - inbox_rate_limit: test_data.ntt.inbox_rate_limit(chain_id), + peer: good_ntt.peer(chain_id), + transceiver: good_ntt.program(), + transceiver_message: good_ntt.transceiver_message(chain_id, ntt_manager_message.id), + inbox_item: good_ntt.inbox_item(chain_id, ntt_manager_message), + inbox_rate_limit: good_ntt.inbox_rate_limit(chain_id), mint: test_data.mint, } } fn init_receive_message_accs( ctx: &mut ProgramTestContext, - test_data: &TestData, vaa: Pubkey, chain_id: u16, id: [u8; 32], ) -> ReceiveMessage { ReceiveMessage { payer: ctx.payer.pubkey(), - peer: test_data.ntt.transceiver_peer(chain_id), + peer: good_ntt.transceiver_peer(chain_id), vaa, chain_id, id, } } -async fn post_transfer_vaa( - ctx: &mut ProgramTestContext, - test_data: &TestData, - id: [u8; 32], - amount: u64, - // TODO: this is used for a negative testing of the recipient ntt_manager - // address. this should not be done in the cancel flow tests, but instead a - // dedicated receive transfer test suite - recipient_ntt_manager: Option<&Pubkey>, - recipient: &Keypair, -) -> (Pubkey, NttManagerMessage) { - let ntt_manager_message = NttManagerMessage { - id, - sender: [4u8; 32], - payload: NativeTokenTransfer { - amount: TrimmedAmount { - amount, - decimals: 9, - }, - source_token: [3u8; 32], - to_chain: ChainId { id: THIS_CHAIN }, - to: recipient.pubkey().to_bytes(), - }, - }; - - let transceiver_message: TransceiverMessage = - TransceiverMessage::new( - OTHER_MANAGER, - recipient_ntt_manager - .map(|k| k.to_bytes()) - .unwrap_or_else(|| test_data.ntt.program.to_bytes()), - ntt_manager_message.clone(), - vec![], - ); - - let vaa = Vaa { - version: 1, - guardian_set_index: 0, - signatures: vec![], - timestamp: 123232, - nonce: 0, - emitter_chain: OTHER_CHAIN.into(), - emitter_address: Address(OTHER_TRANSCEIVER), - sequence: 0, - consistency_level: 0, - payload: transceiver_message, - }; - - let posted_vaa = post_vaa(&test_data.ntt.wormhole, ctx, vaa).await; - - (posted_vaa, ntt_manager_message) -} - -async fn outbound_capacity(ctx: &mut ProgramTestContext, test_data: &TestData) -> u64 { +async fn outbound_capacity(ctx: &mut ProgramTestContext) -> u64 { let clock: Clock = ctx.banks_client.get_sysvar().await.unwrap(); let rate_limit: OutboxRateLimit = ctx - .get_account_data_anchor(test_data.ntt.outbox_rate_limit()) + .get_account_data_anchor(good_ntt.outbox_rate_limit()) .await; rate_limit.rate_limit.capacity_at(clock.unix_timestamp) } -async fn inbound_capacity(ctx: &mut ProgramTestContext, test_data: &TestData) -> u64 { +async fn inbound_capacity(ctx: &mut ProgramTestContext) -> u64 { let clock: Clock = ctx.banks_client.get_sysvar().await.unwrap(); let rate_limit: InboxRateLimit = ctx - .get_account_data_anchor(test_data.ntt.inbox_rate_limit(OTHER_CHAIN)) + .get_account_data_anchor(good_ntt.inbox_rate_limit(OTHER_CHAIN)) .await; rate_limit.rate_limit.capacity_at(clock.unix_timestamp) @@ -175,39 +121,55 @@ async fn test_cancel() { let recipient = Keypair::new(); let (mut ctx, test_data) = setup(Mode::Locking).await; - let (vaa0, msg0) = - post_transfer_vaa(&mut ctx, &test_data, [0u8; 32], 1000, None, &recipient).await; - let (vaa1, msg1) = - post_transfer_vaa(&mut ctx, &test_data, [1u8; 32], 2000, None, &recipient).await; + let msg0 = make_transfer_message(&good_ntt, [0u8; 32], 1000, &recipient.pubkey()); + let msg1 = make_transfer_message(&good_ntt, [1u8; 32], 2000, &recipient.pubkey()); + let vaa0 = post_vaa_helper( + &good_ntt, + OTHER_CHAIN.into(), + Address(OTHER_TRANSCEIVER), + msg0.clone(), + &mut ctx, + ) + .await; + let vaa1 = post_vaa_helper( + &good_ntt, + OTHER_CHAIN.into(), + Address(OTHER_TRANSCEIVER), + msg1.clone(), + &mut ctx, + ) + .await; - let inbound_limit_before = inbound_capacity(&mut ctx, &test_data).await; - let outbound_limit_before = outbound_capacity(&mut ctx, &test_data).await; + let inbound_limit_before = inbound_capacity(&mut ctx).await; + let outbound_limit_before = outbound_capacity(&mut ctx).await; receive_message( - &test_data.ntt, - init_receive_message_accs(&mut ctx, &test_data, vaa0, OTHER_CHAIN, [0u8; 32]), + &good_ntt, + init_receive_message_accs(&mut ctx, vaa0, OTHER_CHAIN, [0u8; 32]), ) .submit(&mut ctx) .await .unwrap(); redeem( - &test_data.ntt, - init_redeem_accs(&mut ctx, &test_data, OTHER_CHAIN, msg0), + &good_ntt, + init_redeem_accs( + &mut ctx, + &test_data, + OTHER_CHAIN, + msg0.ntt_manager_payload.clone(), + ), RedeemArgs {}, ) .submit(&mut ctx) .await .unwrap(); - assert_eq!( - outbound_limit_before, - outbound_capacity(&mut ctx, &test_data).await - ); + assert_eq!(outbound_limit_before, outbound_capacity(&mut ctx).await); assert_eq!( inbound_limit_before - 1000, - inbound_capacity(&mut ctx, &test_data).await + inbound_capacity(&mut ctx).await ); let outbox_item = Keypair::new(); @@ -216,7 +178,7 @@ async fn test_cancel() { init_transfer_accs_args(&mut ctx, &test_data, outbox_item.pubkey(), 7000, true); approve_token_authority( - &test_data.ntt, + &good_ntt, &test_data.user_token_account, &test_data.user.pubkey(), &args, @@ -224,33 +186,35 @@ async fn test_cancel() { .submit_with_signers(&[&test_data.user], &mut ctx) .await .unwrap(); - transfer(&test_data.ntt, accs, args, Mode::Locking) + transfer(&good_ntt, accs, args, Mode::Locking) .submit_with_signers(&[&outbox_item], &mut ctx) .await .unwrap(); assert_eq!( outbound_limit_before - 7000, - outbound_capacity(&mut ctx, &test_data).await + outbound_capacity(&mut ctx).await ); // fully replenished - assert_eq!( - inbound_limit_before, - inbound_capacity(&mut ctx, &test_data).await - ); + assert_eq!(inbound_limit_before, inbound_capacity(&mut ctx).await); receive_message( - &test_data.ntt, - init_receive_message_accs(&mut ctx, &test_data, vaa1, OTHER_CHAIN, [1u8; 32]), + &good_ntt, + init_receive_message_accs(&mut ctx, vaa1, OTHER_CHAIN, [1u8; 32]), ) .submit(&mut ctx) .await .unwrap(); redeem( - &test_data.ntt, - init_redeem_accs(&mut ctx, &test_data, OTHER_CHAIN, msg1), + &good_ntt, + init_redeem_accs( + &mut ctx, + &test_data, + OTHER_CHAIN, + msg1.ntt_manager_payload.clone(), + ), RedeemArgs {}, ) .submit(&mut ctx) @@ -259,53 +223,11 @@ async fn test_cancel() { assert_eq!( outbound_limit_before - 5000, - outbound_capacity(&mut ctx, &test_data).await + outbound_capacity(&mut ctx).await ); assert_eq!( inbound_limit_before - 2000, - inbound_capacity(&mut ctx, &test_data).await - ); -} - -// TODO: this should not live in this file, move to a dedicated receive test suite -#[tokio::test] -async fn test_wrong_recipient_ntt_manager() { - let recipient = Keypair::new(); - let (mut ctx, test_data) = setup(Mode::Locking).await; - - let (vaa0, msg0) = post_transfer_vaa( - &mut ctx, - &test_data, - [0u8; 32], - 1000, - Some(&Pubkey::default()), - &recipient, - ) - .await; - - receive_message( - &test_data.ntt, - init_receive_message_accs(&mut ctx, &test_data, vaa0, OTHER_CHAIN, [0u8; 32]), - ) - .submit(&mut ctx) - .await - .unwrap(); - - let err = redeem( - &test_data.ntt, - init_redeem_accs(&mut ctx, &test_data, OTHER_CHAIN, msg0), - RedeemArgs {}, - ) - .submit(&mut ctx) - .await - .unwrap_err(); - - assert_eq!( - err.unwrap(), - TransactionError::InstructionError( - 0, - InstructionError::Custom(NTTError::InvalidRecipientNttManager.into()) - ) + inbound_capacity(&mut ctx).await ); } diff --git a/solana/programs/example-native-token-transfers/tests/common/mod.rs b/solana/programs/example-native-token-transfers/tests/common/mod.rs index 5c1f8f0ca..ecf9020a8 100644 --- a/solana/programs/example-native-token-transfers/tests/common/mod.rs +++ b/solana/programs/example-native-token-transfers/tests/common/mod.rs @@ -3,3 +3,4 @@ pub mod account_json_utils; pub mod query; pub mod setup; pub mod submit; +pub mod utils; diff --git a/solana/programs/example-native-token-transfers/tests/common/setup.rs b/solana/programs/example-native-token-transfers/tests/common/setup.rs index 8a5cec2a7..ea27b3359 100644 --- a/solana/programs/example-native-token-transfers/tests/common/setup.rs +++ b/solana/programs/example-native-token-transfers/tests/common/setup.rs @@ -22,7 +22,7 @@ use spl_token::instruction::AuthorityType; use wormhole_anchor_sdk::wormhole::{BridgeData, FeeCollector}; use crate::sdk::{ - accounts::{Governance, Wormhole, NTT}, + accounts::{good_ntt, Governance, NTTAccounts}, instructions::{ admin::{register_transceiver, set_peer, RegisterTransceiver, SetPeer}, initialize::{initialize, Initialize}, @@ -42,19 +42,24 @@ pub const OUTBOUND_LIMIT: u64 = 10000; pub const INBOUND_LIMIT: u64 = 50000; pub const OTHER_TRANSCEIVER: [u8; 32] = [7u8; 32]; +pub const ANOTHER_TRANSCEIVER: [u8; 32] = [8u8; 32]; pub const OTHER_MANAGER: [u8; 32] = [9u8; 32]; +pub const ANOTHER_MANAGER: [u8; 32] = [5u8; 32]; pub const THIS_CHAIN: u16 = 1; pub const OTHER_CHAIN: u16 = 2; +pub const ANOTHER_CHAIN: u16 = 3; pub struct TestData { - pub ntt: NTT, pub governance: Governance, pub program_owner: Keypair, pub mint_authority: Keypair, pub mint: Pubkey, + pub bad_mint_authority: Keypair, + pub bad_mint: Pubkey, pub user: Keypair, pub user_token_account: Pubkey, + pub bad_user_token_account: Pubkey, } pub async fn setup_with_extra_accounts( @@ -137,7 +142,7 @@ pub async fn setup_ntt(ctx: &mut ProgramTestContext, test_data: &TestData, mode: spl_token::instruction::set_authority( &spl_token::ID, &test_data.mint, - Some(&test_data.ntt.token_authority()), + Some(&good_ntt.token_authority()), AuthorityType::MintTokens, &test_data.mint_authority.pubkey(), &[], @@ -149,7 +154,7 @@ pub async fn setup_ntt(ctx: &mut ProgramTestContext, test_data: &TestData, mode: } initialize( - &test_data.ntt, + &good_ntt, Initialize { payer: ctx.payer.pubkey(), deployer: test_data.program_owner.pubkey(), @@ -167,7 +172,7 @@ pub async fn setup_ntt(ctx: &mut ProgramTestContext, test_data: &TestData, mode: .unwrap(); register_transceiver( - &test_data.ntt, + &good_ntt, RegisterTransceiver { payer: ctx.payer.pubkey(), owner: test_data.program_owner.pubkey(), @@ -179,7 +184,7 @@ pub async fn setup_ntt(ctx: &mut ProgramTestContext, test_data: &TestData, mode: .unwrap(); set_transceiver_peer( - &test_data.ntt, + &good_ntt, SetTransceiverPeer { payer: ctx.payer.pubkey(), owner: test_data.program_owner.pubkey(), @@ -194,11 +199,10 @@ pub async fn setup_ntt(ctx: &mut ProgramTestContext, test_data: &TestData, mode: .unwrap(); set_peer( - &test_data.ntt, + &good_ntt, SetPeer { payer: ctx.payer.pubkey(), owner: test_data.program_owner.pubkey(), - mint: test_data.mint, }, SetPeerArgs { chain_id: ChainId { id: OTHER_CHAIN }, @@ -210,6 +214,23 @@ pub async fn setup_ntt(ctx: &mut ProgramTestContext, test_data: &TestData, mode: .submit_with_signers(&[&test_data.program_owner], ctx) .await .unwrap(); + + set_peer( + &good_ntt, + SetPeer { + payer: ctx.payer.pubkey(), + owner: test_data.program_owner.pubkey(), + }, + SetPeerArgs { + chain_id: ChainId { id: ANOTHER_CHAIN }, + address: ANOTHER_MANAGER, + limit: INBOUND_LIMIT, + token_decimals: 7, + }, + ) + .submit_with_signers(&[&test_data.program_owner], ctx) + .await + .unwrap(); } pub async fn setup_accounts(ctx: &mut ProgramTestContext, program_owner: Keypair) -> TestData { @@ -217,6 +238,9 @@ pub async fn setup_accounts(ctx: &mut ProgramTestContext, program_owner: Keypair let mint = Keypair::new(); let mint_authority = Keypair::new(); + let bad_mint = Keypair::new(); + let bad_mint_authority = Keypair::new(); + let user = Keypair::new(); let payer = ctx.payer.pubkey(); @@ -226,6 +250,12 @@ pub async fn setup_accounts(ctx: &mut ProgramTestContext, program_owner: Keypair .await .unwrap(); + create_mint(ctx, &bad_mint, &bad_mint_authority.pubkey(), 9) + .await + .submit(ctx) + .await + .unwrap(); + // create associated token account for user let user_token_account = get_associated_token_address_with_program_id(&user.pubkey(), &mint.pubkey(), &Token::id()); @@ -240,6 +270,22 @@ pub async fn setup_accounts(ctx: &mut ProgramTestContext, program_owner: Keypair .await .unwrap(); + let bad_user_token_account = get_associated_token_address_with_program_id( + &user.pubkey(), + &bad_mint.pubkey(), + &Token::id(), + ); + + spl_associated_token_account::instruction::create_associated_token_account( + &payer, + &user.pubkey(), + &bad_mint.pubkey(), + &Token::id(), + ) + .submit(ctx) + .await + .unwrap(); + spl_token::instruction::mint_to( &Token::id(), &mint.pubkey(), @@ -253,21 +299,31 @@ pub async fn setup_accounts(ctx: &mut ProgramTestContext, program_owner: Keypair .await .unwrap(); + spl_token::instruction::mint_to( + &Token::id(), + &bad_mint.pubkey(), + &bad_user_token_account, + &bad_mint_authority.pubkey(), + &[], + MINT_AMOUNT, + ) + .unwrap() + .submit_with_signers(&[&bad_mint_authority], ctx) + .await + .unwrap(); + TestData { - ntt: NTT { - program: example_native_token_transfers::ID, - wormhole: Wormhole { - program: wormhole_anchor_sdk::wormhole::program::ID, - }, - }, governance: Governance { program: wormhole_governance::ID, }, program_owner, mint_authority, mint: mint.pubkey(), + bad_mint_authority, + bad_mint: bad_mint.pubkey(), user, user_token_account, + bad_user_token_account, } } @@ -401,6 +457,8 @@ pub fn add_program_upgradeable( }; let program_file = find_file(&format!("{program_name}.so")); + + #[allow(clippy::panic)] match (prefer_bpf(), program_file, process_instruction) { // If SBF is preferred (i.e., `test-sbf` is invoked) and a BPF shared object exists, // use that as the program data. diff --git a/solana/programs/example-native-token-transfers/tests/common/utils.rs b/solana/programs/example-native-token-transfers/tests/common/utils.rs new file mode 100644 index 000000000..eb493e1f3 --- /dev/null +++ b/solana/programs/example-native-token-transfers/tests/common/utils.rs @@ -0,0 +1,71 @@ +use std::sync::atomic::AtomicU64; + +use anchor_lang::AnchorSerialize; +use ntt_messages::{ + chain_id::ChainId, ntt::NativeTokenTransfer, ntt_manager::NttManagerMessage, + transceiver::TransceiverMessage, transceivers::wormhole::WormholeTransceiver, + trimmed_amount::TrimmedAmount, +}; +use solana_program::pubkey::Pubkey; +use solana_program_test::ProgramTestContext; +use wormhole_sdk::{Address, Chain, Vaa}; + +use crate::sdk::accounts::NTT; + +use super::setup::{OTHER_MANAGER, THIS_CHAIN}; +use crate::sdk::instructions::post_vaa::post_vaa; + +pub fn make_transfer_message( + ntt: &NTT, + id: [u8; 32], + amount: u64, + recipient: &Pubkey, +) -> TransceiverMessage { + let ntt_manager_message = NttManagerMessage { + id, + sender: [4u8; 32], + payload: NativeTokenTransfer { + amount: TrimmedAmount { + amount, + decimals: 9, + }, + source_token: [3u8; 32], + to_chain: ChainId { id: THIS_CHAIN }, + to: recipient.to_bytes(), + }, + }; + + TransceiverMessage::new( + OTHER_MANAGER, + ntt.program().to_bytes(), + ntt_manager_message.clone(), + vec![], + ) +} + +pub async fn post_vaa_helper( + ntt: &NTT, + emitter_chain: Chain, + emitter_address: Address, + msg: A, + ctx: &mut ProgramTestContext, +) -> Pubkey { + static I: AtomicU64 = AtomicU64::new(0); + + let sequence = I.fetch_add(1, std::sync::atomic::Ordering::Acquire); + + let vaa = Vaa { + version: 1, + guardian_set_index: 0, + signatures: vec![], + timestamp: 123232, + nonce: 0, + emitter_chain, + emitter_address, + sequence, + consistency_level: 0, + payload: msg, + }; + + post_vaa(&ntt.wormhole(), ctx, vaa).await +} diff --git a/solana/programs/example-native-token-transfers/tests/governance.rs b/solana/programs/example-native-token-transfers/tests/governance.rs index 1c36ab344..6ce44d482 100644 --- a/solana/programs/example-native-token-transfers/tests/governance.rs +++ b/solana/programs/example-native-token-transfers/tests/governance.rs @@ -4,6 +4,7 @@ use std::sync::atomic::AtomicU64; use anchor_lang::{prelude::*, InstructionData}; +use common::setup::TestData; use example_native_token_transfers::config::Config; use ntt_messages::mode::Mode; use sdk::accounts::{Governance, Wormhole}; @@ -22,9 +23,12 @@ use wormhole_solana_utils::cpi::bpf_loader_upgradeable; use crate::{ common::{query::GetAccountDataAnchor, setup::setup, submit::Submittable}, - sdk::instructions::{ - admin::{set_paused, SetPaused}, - post_vaa::post_vaa, + sdk::{ + accounts::{good_ntt, NTTAccounts}, + instructions::{ + admin::{set_paused, SetPaused}, + post_vaa::post_vaa, + }, }, }; @@ -59,30 +63,32 @@ async fn post_governance_vaa( (post_vaa(wormhole, ctx, vaa.clone()).await, vaa) } -#[tokio::test] -async fn test_governance() { - let (mut ctx, test_data) = setup(Mode::Locking).await; - +/// Helper function to transfer ownership to the governance program. +/// Returns the VAA that was used to claim ownership. +async fn transfer_ownership_to_gov_program( + ctx: &mut ProgramTestContext, + test_data: &TestData, +) -> (Vaa, Instruction) { let governance_pda = test_data.governance.governance(); // step 1. transfer ownership to governance let ix = example_native_token_transfers::instruction::TransferOwnership; let accs = example_native_token_transfers::accounts::TransferOwnership { - config: test_data.ntt.config(), + config: good_ntt.config(), owner: test_data.program_owner.pubkey(), new_owner: governance_pda, - upgrade_lock: test_data.ntt.upgrade_lock(), - program_data: test_data.ntt.program_data(), + upgrade_lock: good_ntt.upgrade_lock(), + program_data: good_ntt.program_data(), bpf_loader_upgradeable_program: bpf_loader_upgradeable::id(), }; Instruction { - program_id: test_data.ntt.program, + program_id: good_ntt.program(), accounts: accs.to_account_metas(None), data: ix.data(), } - .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .submit_with_signers(&[&test_data.program_owner], ctx) .await .unwrap(); @@ -90,42 +96,50 @@ async fn test_governance() { let inner_ix_data = example_native_token_transfers::instruction::ClaimOwnership {}; let inner_ix_accs = example_native_token_transfers::accounts::ClaimOwnership { new_owner: OWNER, - config: test_data.ntt.config(), - upgrade_lock: test_data.ntt.upgrade_lock(), - program_data: test_data.ntt.program_data(), + config: good_ntt.config(), + upgrade_lock: good_ntt.upgrade_lock(), + program_data: good_ntt.program_data(), bpf_loader_upgradeable_program: bpf_loader_upgradeable::id(), }; let inner_ix: Instruction = Instruction { - program_id: test_data.ntt.program, + program_id: good_ntt.program(), accounts: inner_ix_accs.to_account_metas(None), data: inner_ix_data.data(), }; - wrap_governance( - &mut ctx, + let vaa = wrap_governance( + ctx, &test_data.governance, - &test_data.ntt.wormhole, - inner_ix, + &good_ntt.wormhole(), + inner_ix.clone(), None, None, ) .await .unwrap(); - // step 3. set paused + (vaa, inner_ix) +} + +#[tokio::test] +async fn test_governance() { + let (mut ctx, test_data) = setup(Mode::Locking).await; + + transfer_ownership_to_gov_program(&mut ctx, &test_data).await; + wrap_governance( &mut ctx, &test_data.governance, - &test_data.ntt.wormhole, - set_paused(&test_data.ntt, SetPaused { owner: OWNER }, true), + &good_ntt.wormhole(), + set_paused(&good_ntt, SetPaused { owner: OWNER }, true), None, None, ) .await .unwrap(); - let config_account: Config = ctx.get_account_data_anchor(test_data.ntt.config()).await; + let config_account: Config = ctx.get_account_data_anchor(good_ntt.config()).await; assert!(config_account.paused); } @@ -136,8 +150,8 @@ async fn test_governance_bad_emitter() { let err = wrap_governance( &mut ctx, &test_data.governance, - &test_data.ntt.wormhole, - set_paused(&test_data.ntt, SetPaused { owner: OWNER }, true), + &good_ntt.wormhole(), + set_paused(&good_ntt, SetPaused { owner: OWNER }, true), Some(Address::default()), None, ) @@ -157,61 +171,12 @@ async fn test_governance_bad_emitter() { async fn test_governance_replay() { let (mut ctx, test_data) = setup(Mode::Locking).await; - let governance_pda = test_data.governance.governance(); - - // step 1. transfer ownership to governance - let ix = example_native_token_transfers::instruction::TransferOwnership; - - let accs = example_native_token_transfers::accounts::TransferOwnership { - config: test_data.ntt.config(), - owner: test_data.program_owner.pubkey(), - new_owner: governance_pda, - upgrade_lock: test_data.ntt.upgrade_lock(), - program_data: test_data.ntt.program_data(), - bpf_loader_upgradeable_program: bpf_loader_upgradeable::id(), - }; - - Instruction { - program_id: test_data.ntt.program, - accounts: accs.to_account_metas(None), - data: ix.data(), - } - .submit_with_signers(&[&test_data.program_owner], &mut ctx) - .await - .unwrap(); - - // step 2. claim ownership - let inner_ix_data = example_native_token_transfers::instruction::ClaimOwnership {}; - let inner_ix_accs = example_native_token_transfers::accounts::ClaimOwnership { - new_owner: OWNER, - config: test_data.ntt.config(), - upgrade_lock: test_data.ntt.upgrade_lock(), - program_data: test_data.ntt.program_data(), - bpf_loader_upgradeable_program: bpf_loader_upgradeable::id(), - }; - - let inner_ix: Instruction = Instruction { - program_id: test_data.ntt.program, - accounts: inner_ix_accs.to_account_metas(None), - data: inner_ix_data.data(), - }; - - let vaa = wrap_governance( - &mut ctx, - &test_data.governance, - &test_data.ntt.wormhole, - inner_ix.clone(), - None, - None, - ) - .await - .unwrap(); + let (vaa, inner_ix) = transfer_ownership_to_gov_program(&mut ctx, &test_data).await; - // step 3. replay let err = wrap_governance( &mut ctx, &test_data.governance, - &test_data.ntt.wormhole, + &good_ntt.wormhole(), inner_ix, None, Some(vaa), diff --git a/solana/programs/example-native-token-transfers/tests/lib.rs b/solana/programs/example-native-token-transfers/tests/lib.rs new file mode 100644 index 000000000..7a3c7c60b --- /dev/null +++ b/solana/programs/example-native-token-transfers/tests/lib.rs @@ -0,0 +1,3 @@ +#![feature(type_changing_struct_update)] +pub mod common; +pub mod sdk; diff --git a/solana/programs/example-native-token-transfers/tests/receive.rs b/solana/programs/example-native-token-transfers/tests/receive.rs new file mode 100644 index 000000000..51eb2afbb --- /dev/null +++ b/solana/programs/example-native-token-transfers/tests/receive.rs @@ -0,0 +1,369 @@ +#![cfg(feature = "test-sbf")] +#![feature(type_changing_struct_update)] + +use anchor_lang::prelude::*; +use anchor_spl::token::{Token, TokenAccount}; +use common::{ + setup::{TestData, OTHER_CHAIN}, + utils::make_transfer_message, +}; +use example_native_token_transfers::{ + error::NTTError, + instructions::{RedeemArgs, ReleaseInboundArgs}, +}; +use ntt_messages::{mode::Mode, ntt::NativeTokenTransfer, ntt_manager::NttManagerMessage}; +use sdk::{ + accounts::NTTAccounts, transceivers::wormhole::instructions::receive_message::ReceiveMessage, +}; +use solana_program::instruction::InstructionError; +use solana_program_test::*; +use solana_sdk::{signature::Keypair, signer::Signer, transaction::TransactionError}; +use spl_associated_token_account::get_associated_token_address_with_program_id; +use wormhole_sdk::Address; + +use crate::{ + common::{ + query::GetAccountDataAnchor, + setup::{setup, OTHER_TRANSCEIVER}, + }, + sdk::{ + accounts::good_ntt, + instructions::redeem::{redeem, Redeem}, + transceivers::wormhole::instructions::receive_message::receive_message, + }, +}; +use crate::{ + common::{submit::Submittable, utils::post_vaa_helper}, + sdk::instructions::release_inbound::{release_inbound_unlock, ReleaseInbound}, +}; + +pub mod common; +pub mod sdk; + +fn init_redeem_accs( + ctx: &mut ProgramTestContext, + test_data: &TestData, + chain_id: u16, + ntt_manager_message: NttManagerMessage, +) -> Redeem { + Redeem { + payer: ctx.payer.pubkey(), + peer: good_ntt.peer(chain_id), + transceiver: good_ntt.program(), + transceiver_message: good_ntt.transceiver_message(chain_id, ntt_manager_message.id), + inbox_item: good_ntt.inbox_item(chain_id, ntt_manager_message), + inbox_rate_limit: good_ntt.inbox_rate_limit(chain_id), + mint: test_data.mint, + } +} + +fn init_receive_message_accs( + ctx: &mut ProgramTestContext, + vaa: Pubkey, + chain_id: u16, + id: [u8; 32], +) -> ReceiveMessage { + ReceiveMessage { + payer: ctx.payer.pubkey(), + peer: good_ntt.transceiver_peer(chain_id), + vaa, + chain_id, + id, + } +} + +#[tokio::test] +async fn test_receive() { + let recipient = Keypair::new(); + let (mut ctx, test_data) = setup(Mode::Locking).await; + + // transfer tokens to custody account + spl_token::instruction::transfer_checked( + &Token::id(), + &test_data.user_token_account, + &test_data.mint, + &good_ntt.custody(&test_data.mint), + &test_data.user.pubkey(), + &[], + 1000, + 9, + ) + .unwrap() + .submit_with_signers(&[&test_data.user], &mut ctx) + .await + .unwrap(); + + spl_associated_token_account::instruction::create_associated_token_account( + &ctx.payer.pubkey(), + &recipient.pubkey(), + &test_data.mint, + &Token::id(), + ) + .submit(&mut ctx) + .await + .unwrap(); + + let recipient_token_account = get_associated_token_address_with_program_id( + &recipient.pubkey(), + &test_data.mint, + &Token::id(), + ); + + let msg = make_transfer_message(&good_ntt, [0u8; 32], 1000, &recipient.pubkey()); + + let vaa0 = post_vaa_helper( + &good_ntt, + OTHER_CHAIN.into(), + Address(OTHER_TRANSCEIVER), + msg.clone(), + &mut ctx, + ) + .await; + + receive_message( + &good_ntt, + init_receive_message_accs(&mut ctx, vaa0, OTHER_CHAIN, [0u8; 32]), + ) + .submit(&mut ctx) + .await + .unwrap(); + + redeem( + &good_ntt, + init_redeem_accs( + &mut ctx, + &test_data, + OTHER_CHAIN, + msg.ntt_manager_payload.clone(), + ), + RedeemArgs {}, + ) + .submit(&mut ctx) + .await + .unwrap(); + + let token_account: TokenAccount = ctx.get_account_data_anchor(recipient_token_account).await; + + assert_eq!(token_account.amount, 0); + + release_inbound_unlock( + &good_ntt, + ReleaseInbound { + payer: ctx.payer.pubkey(), + inbox_item: good_ntt.inbox_item(OTHER_CHAIN, msg.ntt_manager_payload.clone()), + mint: test_data.mint, + recipient: recipient_token_account, + }, + ReleaseInboundArgs { + revert_on_delay: false, + }, + ) + .submit(&mut ctx) + .await + .unwrap(); + + let token_account: TokenAccount = ctx.get_account_data_anchor(recipient_token_account).await; + assert_eq!(token_account.amount, 1000); + + // let's make sure we can't redeem again. + let err = release_inbound_unlock( + &good_ntt, + ReleaseInbound { + payer: ctx.payer.pubkey(), + inbox_item: good_ntt.inbox_item(OTHER_CHAIN, msg.ntt_manager_payload.clone()), + mint: test_data.mint, + recipient: recipient_token_account, + }, + ReleaseInboundArgs { + revert_on_delay: false, + }, + ) + .submit(&mut ctx) + .await + .unwrap_err(); + + assert_eq!( + err.unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(NTTError::TransferAlreadyRedeemed.into()) + ) + ); +} + +#[tokio::test] +async fn test_double_receive() { + let recipient = Keypair::new(); + let (mut ctx, _test_data) = setup(Mode::Locking).await; + + let msg = make_transfer_message(&good_ntt, [0u8; 32], 1000, &recipient.pubkey()); + + let vaa0 = post_vaa_helper( + &good_ntt, + OTHER_CHAIN.into(), + Address(OTHER_TRANSCEIVER), + msg.clone(), + &mut ctx, + ) + .await; + let vaa1 = post_vaa_helper( + &good_ntt, + OTHER_CHAIN.into(), + Address(OTHER_TRANSCEIVER), + msg, + &mut ctx, + ) + .await; + + receive_message( + &good_ntt, + init_receive_message_accs(&mut ctx, vaa0, OTHER_CHAIN, [0u8; 32]), + ) + .submit(&mut ctx) + .await + .unwrap(); + + let err = receive_message( + &good_ntt, + init_receive_message_accs(&mut ctx, vaa1, OTHER_CHAIN, [0u8; 32]), + ) + .submit(&mut ctx) + .await + .unwrap_err(); + + assert_eq!( + err.unwrap(), + TransactionError::InstructionError(0, InstructionError::Custom(0)) + ); +} + +#[tokio::test] +async fn test_wrong_recipient_ntt_manager() { + let recipient = Keypair::new(); + let (mut ctx, test_data) = setup(Mode::Locking).await; + + let mut msg = make_transfer_message(&good_ntt, [0u8; 32], 1000, &recipient.pubkey()); + + msg.recipient_ntt_manager = Pubkey::new_unique().to_bytes(); + + let vaa0 = post_vaa_helper( + &good_ntt, + OTHER_CHAIN.into(), + Address(OTHER_TRANSCEIVER), + msg.clone(), + &mut ctx, + ) + .await; + + receive_message( + &good_ntt, + init_receive_message_accs(&mut ctx, vaa0, OTHER_CHAIN, [0u8; 32]), + ) + .submit(&mut ctx) + .await + .unwrap(); + + let err = redeem( + &good_ntt, + init_redeem_accs( + &mut ctx, + &test_data, + OTHER_CHAIN, + msg.ntt_manager_payload.clone(), + ), + RedeemArgs {}, + ) + .submit(&mut ctx) + .await + .unwrap_err(); + + assert_eq!( + err.unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(NTTError::InvalidRecipientNttManager.into()) + ) + ); +} + +#[tokio::test] +async fn test_wrong_transceiver_peer() { + let recipient = Keypair::new(); + let (mut ctx, _test_data) = setup(Mode::Locking).await; + + let msg = make_transfer_message(&good_ntt, [0u8; 32], 1000, &recipient.pubkey()); + + let vaa0 = post_vaa_helper( + &good_ntt, + OTHER_CHAIN.into(), + Address(Pubkey::new_unique().to_bytes()), // not the expected transceiver + msg.clone(), + &mut ctx, + ) + .await; + + let err = receive_message( + &good_ntt, + init_receive_message_accs(&mut ctx, vaa0, OTHER_CHAIN, [0u8; 32]), + ) + .submit(&mut ctx) + .await + .unwrap_err(); + + assert_eq!( + err.unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(NTTError::InvalidTransceiverPeer.into()) + ) + ); +} + +#[tokio::test] +async fn test_wrong_manager_peer() { + let recipient = Keypair::new(); + let (mut ctx, test_data) = setup(Mode::Locking).await; + + let mut msg = make_transfer_message(&good_ntt, [0u8; 32], 1000, &recipient.pubkey()); + + msg.source_ntt_manager = Pubkey::new_unique().to_bytes(); // not the expected source manager + + let vaa0 = post_vaa_helper( + &good_ntt, + OTHER_CHAIN.into(), + Address(OTHER_TRANSCEIVER), + msg.clone(), + &mut ctx, + ) + .await; + + receive_message( + &good_ntt, + init_receive_message_accs(&mut ctx, vaa0, OTHER_CHAIN, [0u8; 32]), + ) + .submit(&mut ctx) + .await + .unwrap(); + + let err = redeem( + &good_ntt, + init_redeem_accs( + &mut ctx, + &test_data, + OTHER_CHAIN, + msg.ntt_manager_payload.clone(), + ), + RedeemArgs {}, + ) + .submit(&mut ctx) + .await + .unwrap_err(); + + assert_eq!( + err.unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(NTTError::InvalidNttManagerPeer.into()) + ) + ); +} diff --git a/solana/programs/example-native-token-transfers/tests/sdk/accounts.rs b/solana/programs/example-native-token-transfers/tests/sdk/accounts.rs index 8c255773e..c18b3a95e 100644 --- a/solana/programs/example-native-token-transfers/tests/sdk/accounts.rs +++ b/solana/programs/example-native-token-transfers/tests/sdk/accounts.rs @@ -66,32 +66,39 @@ impl Governance { } } -pub struct NTT { - pub program: Pubkey, - pub wormhole: Wormhole, -} +pub type NTT = dyn NTTAccounts; + +pub trait NTTAccounts { + fn program(&self) -> Pubkey { + example_native_token_transfers::ID + } + + fn wormhole(&self) -> Wormhole { + Wormhole { + program: wormhole_anchor_sdk::wormhole::program::ID, + } + } -impl NTT { - pub fn config(&self) -> Pubkey { - let (config, _) = Pubkey::find_program_address(&[Config::SEED_PREFIX], &self.program); + fn config(&self) -> Pubkey { + let (config, _) = Pubkey::find_program_address(&[Config::SEED_PREFIX], &self.program()); config } - pub fn outbox_rate_limit(&self) -> Pubkey { + fn outbox_rate_limit(&self) -> Pubkey { let (outbox_rate_limit, _) = - Pubkey::find_program_address(&[OutboxRateLimit::SEED_PREFIX], &self.program); + Pubkey::find_program_address(&[OutboxRateLimit::SEED_PREFIX], &self.program()); outbox_rate_limit } - pub fn inbox_rate_limit(&self, chain: u16) -> Pubkey { + fn inbox_rate_limit(&self, chain: u16) -> Pubkey { let (inbox_rate_limit, _) = Pubkey::find_program_address( &[InboxRateLimit::SEED_PREFIX, &chain.to_be_bytes()], - &self.program, + &self.program(), ); inbox_rate_limit } - pub fn session_authority(&self, sender: &Pubkey, args: &TransferArgs) -> Pubkey { + fn session_authority(&self, sender: &Pubkey, args: &TransferArgs) -> Pubkey { let TransferArgs { amount, recipient_chain, @@ -100,23 +107,19 @@ impl NTT { } = args; let mut hasher = Keccak256::new(); - hasher.update(&amount.to_be_bytes()); - hasher.update(&recipient_chain.id.to_be_bytes()); - hasher.update(&recipient_address); - hasher.update(&[*should_queue as u8]); + hasher.update(amount.to_be_bytes()); + hasher.update(recipient_chain.id.to_be_bytes()); + hasher.update(recipient_address); + hasher.update([*should_queue as u8]); let (session_authority, _) = Pubkey::find_program_address( - &[ - SESSION_AUTHORITY_SEED.as_ref(), - sender.as_ref(), - &hasher.finalize(), - ], - &self.program, + &[SESSION_AUTHORITY_SEED, sender.as_ref(), &hasher.finalize()], + &self.program(), ); session_authority } - pub fn inbox_item( + fn inbox_item( &self, chain: u16, ntt_manager_message: NttManagerMessage, @@ -127,61 +130,67 @@ impl NTT { let (inbox_item, _) = Pubkey::find_program_address( &[InboxItem::SEED_PREFIX, &hasher.finalize()], - &self.program, + &self.program(), ); inbox_item } - pub fn token_authority(&self) -> Pubkey { + fn token_authority(&self) -> Pubkey { let (token_authority, _) = - Pubkey::find_program_address(&[TOKEN_AUTHORITY_SEED.as_ref()], &self.program); + Pubkey::find_program_address(&[TOKEN_AUTHORITY_SEED], &self.program()); token_authority } - pub fn registered_transceiver(&self, transceiver: &Pubkey) -> Pubkey { + fn registered_transceiver(&self, transceiver: &Pubkey) -> Pubkey { let (registered_transceiver, _) = Pubkey::find_program_address( &[RegisteredTransceiver::SEED_PREFIX, transceiver.as_ref()], - &self.program, + &self.program(), ); registered_transceiver } - pub fn emitter(&self) -> Pubkey { - let (emitter, _) = Pubkey::find_program_address(&[b"emitter".as_ref()], &self.program); + fn emitter(&self) -> Pubkey { + let (emitter, _) = Pubkey::find_program_address(&[b"emitter".as_ref()], &self.program()); emitter } - pub fn wormhole_message(&self, outbox_item: &Pubkey) -> Pubkey { + fn wormhole_message(&self, outbox_item: &Pubkey) -> Pubkey { let (wormhole_message, _) = Pubkey::find_program_address( &[b"message".as_ref(), outbox_item.as_ref()], - &self.program, + &self.program(), ); wormhole_message } - pub fn peer(&self, chain: u16) -> Pubkey { - let (peer, _) = - Pubkey::find_program_address(&[b"peer".as_ref(), &chain.to_be_bytes()], &self.program); + fn wormhole_sequence(&self) -> Pubkey { + self.wormhole().sequence(&self.emitter()) + } + + fn peer(&self, chain: u16) -> Pubkey { + let (peer, _) = Pubkey::find_program_address( + &[b"peer".as_ref(), &chain.to_be_bytes()], + &self.program(), + ); peer } - pub fn transceiver_peer(&self, chain: u16) -> Pubkey { + fn transceiver_peer(&self, chain: u16) -> Pubkey { let (peer, _) = Pubkey::find_program_address( &[b"transceiver_peer".as_ref(), &chain.to_be_bytes()], - &self.program, + &self.program(), ); peer } - pub fn transceiver_message(&self, chain: u16, id: [u8; 32]) -> Pubkey { + fn transceiver_message(&self, chain: u16, id: [u8; 32]) -> Pubkey { let (transceiver_message, _) = Pubkey::find_program_address( &[b"transceiver_message".as_ref(), &chain.to_be_bytes(), &id], - &self.program, + &self.program(), ); transceiver_message } - pub fn custody(&self, mint: &Pubkey) -> Pubkey { + fn custody(&self, mint: &Pubkey) -> Pubkey { anchor_spl::associated_token::get_associated_token_address_with_program_id( &self.token_authority(), mint, @@ -189,18 +198,23 @@ impl NTT { ) } - pub fn wormhole_sequence(&self) -> Pubkey { - self.wormhole.sequence(&self.emitter()) - } - - pub fn program_data(&self) -> Pubkey { + fn program_data(&self) -> Pubkey { let (addr, _) = - Pubkey::find_program_address(&[self.program.as_ref()], &bpf_loader_upgradeable::id()); + Pubkey::find_program_address(&[self.program().as_ref()], &bpf_loader_upgradeable::id()); addr } - pub fn upgrade_lock(&self) -> Pubkey { - let (addr, _) = Pubkey::find_program_address(&[b"upgrade_lock"], &self.program); + fn upgrade_lock(&self) -> Pubkey { + let (addr, _) = Pubkey::find_program_address(&[b"upgrade_lock"], &self.program()); addr } } + +/// This implements the account derivations correctly. For negative tests, other +/// implementations will implement them incorrectly. +pub struct GoodNTT {} + +#[allow(non_upper_case_globals)] +pub const good_ntt: GoodNTT = GoodNTT {}; + +impl NTTAccounts for GoodNTT {} diff --git a/solana/programs/example-native-token-transfers/tests/sdk/instructions/admin.rs b/solana/programs/example-native-token-transfers/tests/sdk/instructions/admin.rs index 2f860b439..620450482 100644 --- a/solana/programs/example-native-token-transfers/tests/sdk/instructions/admin.rs +++ b/solana/programs/example-native-token-transfers/tests/sdk/instructions/admin.rs @@ -7,7 +7,6 @@ use crate::sdk::accounts::NTT; pub struct SetPeer { pub payer: Pubkey, pub owner: Pubkey, - pub mint: Pubkey, } pub fn set_peer(ntt: &NTT, accounts: SetPeer, args: SetPeerArgs) -> Instruction { @@ -68,7 +67,7 @@ pub fn register_transceiver(ntt: &NTT, accounts: RegisterTransceiver) -> Instruc }; Instruction { - program_id: ntt.program, + program_id: ntt.program(), accounts: accounts.to_account_metas(None), data: data.data(), } diff --git a/solana/programs/example-native-token-transfers/tests/sdk/instructions/mod.rs b/solana/programs/example-native-token-transfers/tests/sdk/instructions/mod.rs index b97075c76..dc9fbe6bd 100644 --- a/solana/programs/example-native-token-transfers/tests/sdk/instructions/mod.rs +++ b/solana/programs/example-native-token-transfers/tests/sdk/instructions/mod.rs @@ -2,4 +2,5 @@ pub mod admin; pub mod initialize; pub mod post_vaa; pub mod redeem; +pub mod release_inbound; pub mod transfer; diff --git a/solana/programs/example-native-token-transfers/tests/sdk/instructions/release_inbound.rs b/solana/programs/example-native-token-transfers/tests/sdk/instructions/release_inbound.rs new file mode 100644 index 000000000..68c792316 --- /dev/null +++ b/solana/programs/example-native-token-transfers/tests/sdk/instructions/release_inbound.rs @@ -0,0 +1,40 @@ +use anchor_lang::{prelude::*, InstructionData}; +use anchor_spl::token::Token; +use example_native_token_transfers::{accounts::NotPausedConfig, instructions::ReleaseInboundArgs}; +use solana_sdk::instruction::Instruction; + +use crate::sdk::accounts::NTT; + +pub struct ReleaseInbound { + pub payer: Pubkey, + pub inbox_item: Pubkey, + pub mint: Pubkey, + pub recipient: Pubkey, +} + +pub fn release_inbound_unlock( + ntt: &NTT, + release_inbound: ReleaseInbound, + args: ReleaseInboundArgs, +) -> Instruction { + let data = example_native_token_transfers::instruction::ReleaseInboundUnlock { args }; + let accounts = example_native_token_transfers::accounts::ReleaseInboundUnlock { + common: example_native_token_transfers::accounts::ReleaseInbound { + payer: release_inbound.payer, + config: NotPausedConfig { + config: ntt.config(), + }, + inbox_item: release_inbound.inbox_item, + recipient: release_inbound.recipient, + token_authority: ntt.token_authority(), + mint: release_inbound.mint, + token_program: Token::id(), + }, + custody: ntt.custody(&release_inbound.mint), + }; + Instruction { + program_id: example_native_token_transfers::ID, + accounts: accounts.to_account_metas(None), + data: data.data(), + } +} diff --git a/solana/programs/example-native-token-transfers/tests/sdk/transceivers/wormhole/accounts/wormhole.rs b/solana/programs/example-native-token-transfers/tests/sdk/transceivers/wormhole/accounts/wormhole.rs index 664b6baf2..985786036 100644 --- a/solana/programs/example-native-token-transfers/tests/sdk/transceivers/wormhole/accounts/wormhole.rs +++ b/solana/programs/example-native-token-transfers/tests/sdk/transceivers/wormhole/accounts/wormhole.rs @@ -6,10 +6,10 @@ use crate::sdk::accounts::NTT; pub fn wormhole_accounts(ntt: &NTT) -> WormholeAccounts { WormholeAccounts { - bridge: ntt.wormhole.bridge(), - fee_collector: ntt.wormhole.fee_collector(), + bridge: ntt.wormhole().bridge(), + fee_collector: ntt.wormhole().fee_collector(), sequence: ntt.wormhole_sequence(), - program: ntt.wormhole.program, + program: ntt.wormhole().program, system_program: System::id(), clock: Clock::id(), rent: Rent::id(), diff --git a/solana/programs/example-native-token-transfers/tests/sdk/transceivers/wormhole/instructions/release_outbound.rs b/solana/programs/example-native-token-transfers/tests/sdk/transceivers/wormhole/instructions/release_outbound.rs index 515a1c1b8..871b5655a 100644 --- a/solana/programs/example-native-token-transfers/tests/sdk/transceivers/wormhole/instructions/release_outbound.rs +++ b/solana/programs/example-native-token-transfers/tests/sdk/transceivers/wormhole/instructions/release_outbound.rs @@ -25,7 +25,7 @@ pub fn release_outbound( outbox_item: release_outbound.outbox_item, wormhole_message: ntt.wormhole_message(&release_outbound.outbox_item), emitter: ntt.emitter(), - transceiver: ntt.registered_transceiver(&ntt.program), + transceiver: ntt.registered_transceiver(&ntt.program()), wormhole: wormhole_accounts(ntt), }; Instruction { diff --git a/solana/programs/example-native-token-transfers/tests/transfer.rs b/solana/programs/example-native-token-transfers/tests/transfer.rs index ca8032799..e932cadc5 100644 --- a/solana/programs/example-native-token-transfers/tests/transfer.rs +++ b/solana/programs/example-native-token-transfers/tests/transfer.rs @@ -1,7 +1,7 @@ #![cfg(feature = "test-sbf")] #![feature(type_changing_struct_update)] -use anchor_lang::prelude::{Clock, Pubkey}; +use anchor_lang::prelude::{Clock, ErrorCode, Pubkey}; use anchor_spl::token::{Mint, TokenAccount}; use common::setup::{TestData, OTHER_CHAIN}; use example_native_token_transfers::{ @@ -16,6 +16,7 @@ use ntt_messages::{ transceiver::TransceiverMessage, transceivers::wormhole::WormholeTransceiver, trimmed_amount::TrimmedAmount, }; +use sdk::accounts::NTT; use solana_program_test::*; use solana_sdk::{ instruction::InstructionError, signature::Keypair, signer::Signer, @@ -24,8 +25,14 @@ use solana_sdk::{ use wormhole_anchor_sdk::wormhole::PostedVaa; use crate::{ - common::{query::GetAccountDataAnchor, setup::OUTBOUND_LIMIT}, - sdk::instructions::transfer::Transfer, + common::{ + query::GetAccountDataAnchor, + setup::{ANOTHER_CHAIN, OUTBOUND_LIMIT}, + }, + sdk::{ + accounts::{good_ntt, NTTAccounts}, + instructions::transfer::Transfer, + }, }; use crate::{ common::{setup::OTHER_MANAGER, submit::Submittable}, @@ -56,6 +63,7 @@ use crate::common::setup::setup; /// It sets the accounts up properly, so for negative testing we just modify the /// result. fn init_accs_args( + ntt: &NTT, ctx: &mut ProgramTestContext, test_data: &TestData, outbox_item: Pubkey, @@ -67,7 +75,7 @@ fn init_accs_args( mint: test_data.mint, from: test_data.user_token_account, from_authority: test_data.user.pubkey(), - peer: test_data.ntt.peer(OTHER_CHAIN), + peer: ntt.peer(OTHER_CHAIN), outbox_item, }; @@ -100,10 +108,10 @@ async fn test_transfer(ctx: &mut ProgramTestContext, test_data: &TestData, mode: let clock: Clock = ctx.banks_client.get_sysvar().await.unwrap(); - let (accs, args) = init_accs_args(ctx, test_data, outbox_item.pubkey(), 154, false); + let (accs, args) = init_accs_args(&good_ntt, ctx, test_data, outbox_item.pubkey(), 154, false); approve_token_authority( - &test_data.ntt, + &good_ntt, &test_data.user_token_account, &test_data.user.pubkey(), &args, @@ -111,7 +119,7 @@ async fn test_transfer(ctx: &mut ProgramTestContext, test_data: &TestData, mode: .submit_with_signers(&[&test_data.user], ctx) .await .unwrap(); - transfer(&test_data.ntt, accs, args, mode) + transfer(&good_ntt, accs, args, mode) .submit_with_signers(&[&outbox_item], ctx) .await .unwrap(); @@ -135,7 +143,7 @@ async fn test_transfer(ctx: &mut ProgramTestContext, test_data: &TestData, mode: ); release_outbound( - &test_data.ntt, + &good_ntt, ReleaseOutbound { payer: ctx.payer.pubkey(), outbox_item: outbox_item.pubkey(), @@ -160,7 +168,7 @@ async fn test_transfer(ctx: &mut ProgramTestContext, test_data: &TestData, mode: outbox_item_account_after, ); - let wh_message = test_data.ntt.wormhole_message(&outbox_item.pubkey()); + let wh_message = good_ntt.wormhole_message(&outbox_item.pubkey()); // NOTE: technically this is not a PostedVAA but a PostedMessage, but the // sdk does not export that type, so we parse it as a PostedVAA instead. @@ -201,7 +209,14 @@ async fn test_burn_mode_burns_tokens() { let outbox_item = Keypair::new(); - let (accs, args) = init_accs_args(&mut ctx, &test_data, outbox_item.pubkey(), 105, false); + let (accs, args) = init_accs_args( + &good_ntt, + &mut ctx, + &test_data, + outbox_item.pubkey(), + 105, + false, + ); let mint_before: Mint = ctx.get_account_data_anchor(test_data.mint).await; @@ -210,7 +225,7 @@ async fn test_burn_mode_burns_tokens() { .await; approve_token_authority( - &test_data.ntt, + &good_ntt, &test_data.user_token_account, &test_data.user.pubkey(), &args, @@ -218,7 +233,7 @@ async fn test_burn_mode_burns_tokens() { .submit_with_signers(&[&test_data.user], &mut ctx) .await .unwrap(); - transfer(&test_data.ntt, accs, args, Mode::Burning) + transfer(&good_ntt, accs, args, Mode::Burning) .submit_with_signers(&[&outbox_item], &mut ctx) .await .unwrap(); @@ -245,20 +260,27 @@ async fn locking_mode_locks_tokens() { let outbox_item = Keypair::new(); - let (accs, args) = init_accs_args(&mut ctx, &test_data, outbox_item.pubkey(), 1050, false); + let (accs, args) = init_accs_args( + &good_ntt, + &mut ctx, + &test_data, + outbox_item.pubkey(), + 1050, + false, + ); let token_account_before: TokenAccount = ctx .get_account_data_anchor(test_data.user_token_account) .await; let custody_account_before: TokenAccount = ctx - .get_account_data_anchor(test_data.ntt.custody(&test_data.mint)) + .get_account_data_anchor(good_ntt.custody(&test_data.mint)) .await; let mint_before: Mint = ctx.get_account_data_anchor(test_data.mint).await; approve_token_authority( - &test_data.ntt, + &good_ntt, &test_data.user_token_account, &test_data.user.pubkey(), &args, @@ -266,7 +288,7 @@ async fn locking_mode_locks_tokens() { .submit_with_signers(&[&test_data.user], &mut ctx) .await .unwrap(); - transfer(&test_data.ntt, accs, args, Mode::Locking) + transfer(&good_ntt, accs, args, Mode::Locking) .submit_with_signers(&[&outbox_item], &mut ctx) .await .unwrap(); @@ -276,7 +298,7 @@ async fn locking_mode_locks_tokens() { .await; let custody_account_after: TokenAccount = ctx - .get_account_data_anchor(test_data.ntt.custody(&test_data.mint)) + .get_account_data_anchor(good_ntt.custody(&test_data.mint)) .await; let mint_after: Mint = ctx.get_account_data_anchor(test_data.mint).await; @@ -297,6 +319,98 @@ async fn locking_mode_locks_tokens() { assert_eq!(mint_before.supply, mint_after.supply); } +#[tokio::test] +async fn test_bad_mint() { + let (mut ctx, test_data) = setup(Mode::Locking).await; + + let outbox_item = Keypair::new(); + + let (mut accs, args) = init_accs_args( + &good_ntt, + &mut ctx, + &test_data, + outbox_item.pubkey(), + 1050, + false, + ); + + // use the wrong mint here + accs.mint = test_data.bad_mint; + + approve_token_authority( + &good_ntt, + &test_data.bad_user_token_account, + &test_data.user.pubkey(), + &args, + ) + .submit_with_signers(&[&test_data.user], &mut ctx) + .await + .unwrap(); + + let err = transfer(&good_ntt, accs, args, Mode::Locking) + .submit_with_signers(&[&outbox_item], &mut ctx) + .await + .unwrap_err(); + + assert_eq!( + err.unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(ErrorCode::ConstraintAddress.into()) + ) + ); +} + +#[tokio::test] +async fn test_invalid_peer() { + // in this test we send to 'OTHER_CHAIN' but use the peer account for + // 'ANOTHER_CHAIN'. + struct BadNTT {} + + impl NTTAccounts for BadNTT { + fn peer(&self, _chain_id: u16) -> Pubkey { + // return 'ANOTHER_CHAIN' peer account + good_ntt.peer(ANOTHER_CHAIN) + } + } + + let (mut ctx, test_data) = setup(Mode::Locking).await; + + let outbox_item = Keypair::new(); + + let (accs, args) = init_accs_args( + &BadNTT {}, + &mut ctx, + &test_data, + outbox_item.pubkey(), + 1050, + false, + ); + + approve_token_authority( + &good_ntt, + &test_data.bad_user_token_account, + &test_data.user.pubkey(), + &args, + ) + .submit_with_signers(&[&test_data.user], &mut ctx) + .await + .unwrap(); + + let err = transfer(&BadNTT {}, accs, args, Mode::Locking) + .submit_with_signers(&[&outbox_item], &mut ctx) + .await + .unwrap_err(); + + assert_eq!( + err.unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(ErrorCode::ConstraintSeeds.into()) + ) + ); +} + #[tokio::test] async fn test_rate_limit() { let (mut ctx, test_data) = setup(Mode::Locking).await; @@ -304,14 +418,21 @@ async fn test_rate_limit() { let outbox_item = Keypair::new(); let clock: Clock = ctx.banks_client.get_sysvar().await.unwrap(); - let (accs, args) = init_accs_args(&mut ctx, &test_data, outbox_item.pubkey(), 100, false); + let (accs, args) = init_accs_args( + &good_ntt, + &mut ctx, + &test_data, + outbox_item.pubkey(), + 100, + false, + ); let outbound_limit_before: OutboxRateLimit = ctx - .get_account_data_anchor(test_data.ntt.outbox_rate_limit()) + .get_account_data_anchor(good_ntt.outbox_rate_limit()) .await; approve_token_authority( - &test_data.ntt, + &good_ntt, &test_data.user_token_account, &test_data.user.pubkey(), &args, @@ -319,13 +440,13 @@ async fn test_rate_limit() { .submit_with_signers(&[&test_data.user], &mut ctx) .await .unwrap(); - transfer(&test_data.ntt, accs, args, Mode::Locking) + transfer(&good_ntt, accs, args, Mode::Locking) .submit_with_signers(&[&outbox_item], &mut ctx) .await .unwrap(); let outbound_limit_after: OutboxRateLimit = ctx - .get_account_data_anchor(test_data.ntt.outbox_rate_limit()) + .get_account_data_anchor(good_ntt.outbox_rate_limit()) .await; assert_eq!( @@ -339,10 +460,17 @@ async fn test_transfer_wrong_mode() { let (mut ctx, test_data) = setup(Mode::Burning).await; let outbox_item = Keypair::new(); - let (accs, args) = init_accs_args(&mut ctx, &test_data, outbox_item.pubkey(), 100, false); + let (accs, args) = init_accs_args( + &good_ntt, + &mut ctx, + &test_data, + outbox_item.pubkey(), + 100, + false, + ); approve_token_authority( - &test_data.ntt, + &good_ntt, &test_data.user_token_account, &test_data.user.pubkey(), &args, @@ -351,7 +479,7 @@ async fn test_transfer_wrong_mode() { .await .unwrap(); // make sure we can't transfer in the wrong mode - let err = transfer(&test_data.ntt, accs.clone(), args.clone(), Mode::Locking) + let err = transfer(&good_ntt, accs.clone(), args.clone(), Mode::Locking) .submit_with_signers(&[&outbox_item], &mut ctx) .await .unwrap_err(); @@ -383,6 +511,7 @@ async fn test_large_tx_queue() { let too_much = OUTBOUND_LIMIT + 1000; let should_queue = true; let (accs, args) = init_accs_args( + &good_ntt, &mut ctx, &test_data, outbox_item.pubkey(), @@ -391,11 +520,11 @@ async fn test_large_tx_queue() { ); let outbound_limit_before: OutboxRateLimit = ctx - .get_account_data_anchor(test_data.ntt.outbox_rate_limit()) + .get_account_data_anchor(good_ntt.outbox_rate_limit()) .await; approve_token_authority( - &test_data.ntt, + &good_ntt, &test_data.user_token_account, &test_data.user.pubkey(), &args, @@ -403,13 +532,13 @@ async fn test_large_tx_queue() { .submit_with_signers(&[&test_data.user], &mut ctx) .await .unwrap(); - transfer(&test_data.ntt, accs, args, Mode::Locking) + transfer(&good_ntt, accs, args, Mode::Locking) .submit_with_signers(&[&outbox_item], &mut ctx) .await .unwrap(); let outbound_limit_after: OutboxRateLimit = ctx - .get_account_data_anchor(test_data.ntt.outbox_rate_limit()) + .get_account_data_anchor(good_ntt.outbox_rate_limit()) .await; assert_queued(&mut ctx, outbox_item.pubkey()).await; @@ -424,10 +553,17 @@ async fn test_cant_transfer_when_paused() { let outbox_item = Keypair::new(); - let (accs, args) = init_accs_args(&mut ctx, &test_data, outbox_item.pubkey(), 100, false); + let (accs, args) = init_accs_args( + &good_ntt, + &mut ctx, + &test_data, + outbox_item.pubkey(), + 100, + false, + ); set_paused( - &test_data.ntt, + &good_ntt, SetPaused { owner: test_data.program_owner.pubkey(), }, @@ -438,7 +574,7 @@ async fn test_cant_transfer_when_paused() { .unwrap(); approve_token_authority( - &test_data.ntt, + &good_ntt, &test_data.user_token_account, &test_data.user.pubkey(), &args, @@ -446,7 +582,7 @@ async fn test_cant_transfer_when_paused() { .submit_with_signers(&[&test_data.user], &mut ctx) .await .unwrap(); - let err = transfer(&test_data.ntt, accs.clone(), args.clone(), Mode::Locking) + let err = transfer(&good_ntt, accs.clone(), args.clone(), Mode::Locking) .submit_with_signers(&[&outbox_item], &mut ctx) .await .unwrap_err(); @@ -458,7 +594,7 @@ async fn test_cant_transfer_when_paused() { // make sure we can unpause set_paused( - &test_data.ntt, + &good_ntt, SetPaused { owner: test_data.program_owner.pubkey(), }, @@ -469,7 +605,7 @@ async fn test_cant_transfer_when_paused() { .unwrap(); approve_token_authority( - &test_data.ntt, + &good_ntt, &test_data.user_token_account, &test_data.user.pubkey(), &args, @@ -477,7 +613,7 @@ async fn test_cant_transfer_when_paused() { .submit_with_signers(&[&test_data.user], &mut ctx) .await .unwrap(); - transfer(&test_data.ntt, accs, args, Mode::Locking) + transfer(&good_ntt, accs, args, Mode::Locking) .submit_with_signers(&[&outbox_item], &mut ctx) .await .unwrap(); @@ -492,6 +628,7 @@ async fn test_large_tx_no_queue() { let too_much = OUTBOUND_LIMIT + 1000; let should_queue = false; let (accs, args) = init_accs_args( + &good_ntt, &mut ctx, &test_data, outbox_item.pubkey(), @@ -500,7 +637,7 @@ async fn test_large_tx_no_queue() { ); approve_token_authority( - &test_data.ntt, + &good_ntt, &test_data.user_token_account, &test_data.user.pubkey(), &args, @@ -508,7 +645,7 @@ async fn test_large_tx_no_queue() { .submit_with_signers(&[&test_data.user], &mut ctx) .await .unwrap(); - let err = transfer(&test_data.ntt, accs, args, Mode::Locking) + let err = transfer(&good_ntt, accs, args, Mode::Locking) .submit_with_signers(&[&outbox_item], &mut ctx) .await .unwrap_err(); @@ -529,10 +666,17 @@ async fn test_cant_release_queued() { let outbox_item = Keypair::new(); let too_much = OUTBOUND_LIMIT + 1000; - let (accs, args) = init_accs_args(&mut ctx, &test_data, outbox_item.pubkey(), too_much, true); + let (accs, args) = init_accs_args( + &good_ntt, + &mut ctx, + &test_data, + outbox_item.pubkey(), + too_much, + true, + ); approve_token_authority( - &test_data.ntt, + &good_ntt, &test_data.user_token_account, &test_data.user.pubkey(), &args, @@ -540,7 +684,7 @@ async fn test_cant_release_queued() { .submit_with_signers(&[&test_data.user], &mut ctx) .await .unwrap(); - transfer(&test_data.ntt, accs, args, Mode::Locking) + transfer(&good_ntt, accs, args, Mode::Locking) .submit_with_signers(&[&outbox_item], &mut ctx) .await .unwrap(); @@ -549,7 +693,7 @@ async fn test_cant_release_queued() { // check that 'revert_on_delay = true' returns correct error let err = release_outbound( - &test_data.ntt, + &good_ntt, ReleaseOutbound { payer: ctx.payer.pubkey(), outbox_item: outbox_item.pubkey(), @@ -572,7 +716,7 @@ async fn test_cant_release_queued() { // check that 'revert_on_delay = false' succeeds but does not release release_outbound( - &test_data.ntt, + &good_ntt, ReleaseOutbound { payer: ctx.payer.pubkey(), outbox_item: outbox_item.pubkey(), @@ -588,7 +732,7 @@ async fn test_cant_release_queued() { assert_queued(&mut ctx, outbox_item.pubkey()).await; // just to be safe, let's make sure the wormhole message account wasn't initialised - let wh_message = test_data.ntt.wormhole_message(&outbox_item.pubkey()); + let wh_message = good_ntt.wormhole_message(&outbox_item.pubkey()); assert!(ctx .banks_client .get_account(wh_message) @@ -603,10 +747,17 @@ async fn test_cant_release_twice() { let outbox_item = Keypair::new(); - let (accs, args) = init_accs_args(&mut ctx, &test_data, outbox_item.pubkey(), 100, false); + let (accs, args) = init_accs_args( + &good_ntt, + &mut ctx, + &test_data, + outbox_item.pubkey(), + 100, + false, + ); approve_token_authority( - &test_data.ntt, + &good_ntt, &test_data.user_token_account, &test_data.user.pubkey(), &args, @@ -614,13 +765,13 @@ async fn test_cant_release_twice() { .submit_with_signers(&[&test_data.user], &mut ctx) .await .unwrap(); - transfer(&test_data.ntt, accs, args, Mode::Locking) + transfer(&good_ntt, accs, args, Mode::Locking) .submit_with_signers(&[&outbox_item], &mut ctx) .await .unwrap(); release_outbound( - &test_data.ntt, + &good_ntt, ReleaseOutbound { payer: ctx.payer.pubkey(), outbox_item: outbox_item.pubkey(), @@ -635,7 +786,7 @@ async fn test_cant_release_twice() { // make sure we can't release again let err = release_outbound( - &test_data.ntt, + &good_ntt, ReleaseOutbound { payer: ctx.payer.pubkey(), outbox_item: outbox_item.pubkey(),