From 2caa2011e7e73e7b55fa1a64dd7658539f9f50ed Mon Sep 17 00:00:00 2001 From: nvsriram Date: Fri, 7 Feb 2025 16:38:51 -0500 Subject: [PATCH 01/12] Add `set_threshold` ix --- .../src/error.rs | 2 ++ .../src/instructions/admin/mod.rs | 23 +++++++++++++++++++ .../example-native-token-transfers/src/lib.rs | 4 ++++ 3 files changed, 29 insertions(+) diff --git a/solana/programs/example-native-token-transfers/src/error.rs b/solana/programs/example-native-token-transfers/src/error.rs index 2156b584a..6fc037e32 100644 --- a/solana/programs/example-native-token-transfers/src/error.rs +++ b/solana/programs/example-native-token-transfers/src/error.rs @@ -61,6 +61,8 @@ pub enum NTTError { IncorrectRentPayer, #[msg("InvalidMultisig")] InvalidMultisig, + #[msg("ThresholdTooHigh")] + ThresholdTooHigh, } impl From for NTTError { diff --git a/solana/programs/example-native-token-transfers/src/instructions/admin/mod.rs b/solana/programs/example-native-token-transfers/src/instructions/admin/mod.rs index a8cb12c5e..290960420 100644 --- a/solana/programs/example-native-token-transfers/src/instructions/admin/mod.rs +++ b/solana/programs/example-native-token-transfers/src/instructions/admin/mod.rs @@ -3,6 +3,7 @@ use ntt_messages::chain_id::ChainId; use crate::{ config::Config, + error::NTTError, peer::NttManagerPeer, queue::{inbox::InboxRateLimit, outbox::OutboxRateLimit, rate_limit::RateLimitState}, registered_transceiver::RegisteredTransceiver, @@ -200,3 +201,25 @@ pub fn set_paused(ctx: Context, paused: bool) -> Result<()> { ctx.accounts.config.paused = paused; Ok(()) } + +// * Set Threshold +#[derive(Accounts)] +#[instruction(threshold: u8)] +pub struct SetThreshold<'info> { + pub owner: Signer<'info>, + + #[account( + mut, + has_one = owner, + constraint = threshold <= config.enabled_transceivers.len() @ NTTError::ThresholdTooHigh + )] + pub config: Account<'info, Config>, +} + +pub fn set_threshold(ctx: Context, threshold: u8) -> Result<()> { + if threshold == 0 { + return Err(NTTError::ZeroThreshold.into()); + } + ctx.accounts.config.threshold = threshold; + Ok(()) +} diff --git a/solana/programs/example-native-token-transfers/src/lib.rs b/solana/programs/example-native-token-transfers/src/lib.rs index f3aed6dfb..1d3e0e3e0 100644 --- a/solana/programs/example-native-token-transfers/src/lib.rs +++ b/solana/programs/example-native-token-transfers/src/lib.rs @@ -206,6 +206,10 @@ pub mod example_native_token_transfers { instructions::mark_outbox_item_as_released(ctx) } + pub fn set_threshold(ctx: Context, threshold: u8) -> Result<()> { + instructions::set_threshold(ctx, threshold) + } + // standalone transceiver stuff pub fn set_wormhole_peer( From d6da2c4320a02be71a738e3b9e0d4e053267009f Mon Sep 17 00:00:00 2001 From: nvsriram Date: Fri, 7 Feb 2025 16:39:22 -0500 Subject: [PATCH 02/12] Update comment about threshold --- .../src/instructions/initialize.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solana/programs/example-native-token-transfers/src/instructions/initialize.rs b/solana/programs/example-native-token-transfers/src/instructions/initialize.rs index dfa889bb9..f87896e3d 100644 --- a/solana/programs/example-native-token-transfers/src/instructions/initialize.rs +++ b/solana/programs/example-native-token-transfers/src/instructions/initialize.rs @@ -155,7 +155,7 @@ fn initialize_config_and_rate_limit( pending_owner: None, paused: false, next_transceiver_id: 0, - // NOTE: can't be changed for now + // NOTE: can be changed via `set_threshold` ix threshold: 1, enabled_transceivers: Bitmap::new(), custody: common.custody.key(), From 574b5bd72be850f1ea395cea83f9cffede2db5ae Mon Sep 17 00:00:00 2001 From: nvsriram Date: Fri, 7 Feb 2025 16:40:14 -0500 Subject: [PATCH 03/12] Add cargo tests --- .../tests/admin.rs | 61 +++++++++++++++++++ .../tests/sdk/instructions/admin.rs | 19 ++++++ 2 files changed, 80 insertions(+) create mode 100644 solana/programs/example-native-token-transfers/tests/admin.rs diff --git a/solana/programs/example-native-token-transfers/tests/admin.rs b/solana/programs/example-native-token-transfers/tests/admin.rs new file mode 100644 index 000000000..544fc9248 --- /dev/null +++ b/solana/programs/example-native-token-transfers/tests/admin.rs @@ -0,0 +1,61 @@ +#![cfg(feature = "test-sbf")] +#![feature(type_changing_struct_update)] + +use example_native_token_transfers::error::NTTError; +use ntt_messages::mode::Mode; +use solana_program_test::*; +use solana_sdk::{instruction::InstructionError, signer::Signer, transaction::TransactionError}; + +use crate::{ + common::{setup::setup, submit::Submittable}, + sdk::instructions::admin::{set_threshold, SetThreshold}, +}; + +pub mod common; +pub mod sdk; + +#[tokio::test] +async fn test_zero_threshold() { + let (mut ctx, test_data) = setup(Mode::Locking).await; + + let err = set_threshold( + &test_data.ntt, + SetThreshold { + owner: test_data.program_owner.pubkey(), + }, + 0, + ) + .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .await + .unwrap_err(); + assert_eq!( + err.unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(NTTError::ZeroThreshold.into()) + ) + ); +} + +#[tokio::test] +async fn test_threshold_too_high() { + let (mut ctx, test_data) = setup(Mode::Burning).await; + + let err = set_threshold( + &test_data.ntt, + SetThreshold { + owner: test_data.program_owner.pubkey(), + }, + 2, + ) + .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .await + .unwrap_err(); + assert_eq!( + err.unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(NTTError::ThresholdTooHigh.into()) + ) + ); +} 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..eb5b0840c 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 @@ -73,3 +73,22 @@ pub fn register_transceiver(ntt: &NTT, accounts: RegisterTransceiver) -> Instruc data: data.data(), } } + +pub struct SetThreshold { + pub owner: Pubkey, +} + +pub fn set_threshold(ntt: &NTT, accounts: SetThreshold, threshold: u8) -> Instruction { + let data = example_native_token_transfers::instruction::SetThreshold { threshold }; + + let accounts = example_native_token_transfers::accounts::SetThreshold { + config: ntt.config(), + owner: accounts.owner, + }; + + Instruction { + program_id: example_native_token_transfers::ID, + accounts: accounts.to_account_metas(None), + data: data.data(), + } +} From c56909cd52b13890543bf698b440544dde67ba17 Mon Sep 17 00:00:00 2001 From: nvsriram Date: Fri, 7 Feb 2025 16:46:14 -0500 Subject: [PATCH 04/12] Add NTT namespace TS helper --- solana/ts/lib/ntt.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/solana/ts/lib/ntt.ts b/solana/ts/lib/ntt.ts index 26d0f2d59..67ec69bff 100644 --- a/solana/ts/lib/ntt.ts +++ b/solana/ts/lib/ntt.ts @@ -1103,6 +1103,24 @@ export namespace NTT { .instruction(); } + export async function createSetThresholdInstruction( + program: Program>, + args: { + owner: PublicKey; + threshold: number; + }, + pdas?: Pdas + ) { + pdas = pdas ?? NTT.pdas(program.programId); + return program.methods + .setThreshold(args.threshold) + .accountsStrict({ + owner: args.owner, + config: pdas.configAccount(), + }) + .instruction(); + } + export async function createSetOutboundLimitInstruction( program: Program>, args: { From 853c69410c0180fca3f152a5476a73ebda1ffa26 Mon Sep 17 00:00:00 2001 From: nvsriram Date: Mon, 10 Feb 2025 17:57:00 -0500 Subject: [PATCH 05/12] Add deregister transceiver ix --- .../src/instructions/admin/mod.rs | 39 +++++++++++++++++++ .../example-native-token-transfers/src/lib.rs | 4 ++ 2 files changed, 43 insertions(+) diff --git a/solana/programs/example-native-token-transfers/src/instructions/admin/mod.rs b/solana/programs/example-native-token-transfers/src/instructions/admin/mod.rs index 290960420..6d35a50bd 100644 --- a/solana/programs/example-native-token-transfers/src/instructions/admin/mod.rs +++ b/solana/programs/example-native-token-transfers/src/instructions/admin/mod.rs @@ -124,6 +124,45 @@ pub fn register_transceiver(ctx: Context) -> Result<()> { Ok(()) } +#[derive(Accounts)] +pub struct DeregisterTransceiver<'info> { + #[account( + mut, + has_one = owner, + )] + pub config: Account<'info, Config>, + + #[account(mut)] + pub owner: Signer<'info>, + + #[account(executable)] + /// CHECK: transceiver is meant to be a transceiver program. Arguably a `Program` constraint could be + /// used here that wraps the Transceiver account type. + pub transceiver: UncheckedAccount<'info>, + + #[account( + seeds = [RegisteredTransceiver::SEED_PREFIX, transceiver.key().as_ref()], + bump, + constraint = config.enabled_transceivers.get(registered_transceiver.id)? @ NTTError::DisabledTransceiver, + )] + pub registered_transceiver: Account<'info, RegisteredTransceiver>, +} + +pub fn deregister_transceiver(ctx: Context) -> Result<()> { + ctx.accounts + .config + .enabled_transceivers + .set(ctx.accounts.registered_transceiver.id, false)?; + + // decrement threshold if too high + let num_enabled_transceivers = ctx.accounts.config.enabled_transceivers.len(); + if num_enabled_transceivers < ctx.accounts.config.threshold { + // threshold should be at least 1 + ctx.accounts.config.threshold = num_enabled_transceivers.max(1); + } + Ok(()) +} + // * Limit rate adjustment #[derive(Accounts)] diff --git a/solana/programs/example-native-token-transfers/src/lib.rs b/solana/programs/example-native-token-transfers/src/lib.rs index 1d3e0e3e0..c581a35dd 100644 --- a/solana/programs/example-native-token-transfers/src/lib.rs +++ b/solana/programs/example-native-token-transfers/src/lib.rs @@ -188,6 +188,10 @@ pub mod example_native_token_transfers { instructions::register_transceiver(ctx) } + pub fn deregister_transceiver(ctx: Context) -> Result<()> { + instructions::deregister_transceiver(ctx) + } + pub fn set_outbound_limit( ctx: Context, args: SetOutboundLimitArgs, From cac60a759439d45adeae75ddf7c6f827b70a7b1c Mon Sep 17 00:00:00 2001 From: nvsriram Date: Tue, 11 Feb 2025 14:51:13 -0500 Subject: [PATCH 06/12] Add reregister transceiver test --- .../tests/admin.rs | 106 +++++++++++++++++- .../tests/sdk/instructions/admin.rs | 22 ++++ 2 files changed, 125 insertions(+), 3 deletions(-) diff --git a/solana/programs/example-native-token-transfers/tests/admin.rs b/solana/programs/example-native-token-transfers/tests/admin.rs index 544fc9248..cc59c5fb4 100644 --- a/solana/programs/example-native-token-transfers/tests/admin.rs +++ b/solana/programs/example-native-token-transfers/tests/admin.rs @@ -1,19 +1,119 @@ #![cfg(feature = "test-sbf")] #![feature(type_changing_struct_update)] -use example_native_token_transfers::error::NTTError; +use example_native_token_transfers::{config::Config, error::NTTError}; use ntt_messages::mode::Mode; use solana_program_test::*; use solana_sdk::{instruction::InstructionError, signer::Signer, transaction::TransactionError}; use crate::{ - common::{setup::setup, submit::Submittable}, - sdk::instructions::admin::{set_threshold, SetThreshold}, + common::{ + query::GetAccountDataAnchor, + setup::{setup, TestData}, + submit::Submittable, + }, + sdk::instructions::admin::{ + deregister_transceiver, register_transceiver, set_threshold, DeregisterTransceiver, + RegisterTransceiver, SetThreshold, + }, }; pub mod common; pub mod sdk; +async fn assert_threshold( + ctx: &mut ProgramTestContext, + test_data: &TestData, + expected_threshold: u8, +) { + let config_account: Config = ctx.get_account_data_anchor(test_data.ntt.config()).await; + assert_eq!(config_account.threshold, expected_threshold); +} + +#[tokio::test] +async fn test_reregister_all_transceivers() { + let (mut ctx, test_data) = setup(Mode::Locking).await; + + // register ntt_transceiver + register_transceiver( + &test_data.ntt, + RegisterTransceiver { + payer: ctx.payer.pubkey(), + owner: test_data.program_owner.pubkey(), + transceiver: ntt_transceiver::ID, + }, + ) + .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .await + .unwrap(); + + // set threshold to 2 + set_threshold( + &test_data.ntt, + SetThreshold { + owner: test_data.program_owner.pubkey(), + }, + 2, + ) + .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .await + .unwrap(); + + // deregister ntt_transceiver + deregister_transceiver( + &test_data.ntt, + DeregisterTransceiver { + owner: test_data.program_owner.pubkey(), + transceiver: ntt_transceiver::ID, + }, + ) + .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .await + .unwrap(); + assert_threshold(&mut ctx, &test_data, 1).await; + + // deregister baked-in transceiver + deregister_transceiver( + &test_data.ntt, + DeregisterTransceiver { + owner: test_data.program_owner.pubkey(), + transceiver: example_native_token_transfers::ID, + }, + ) + .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .await + .unwrap(); + assert_threshold(&mut ctx, &test_data, 1).await; + + // reregister ntt_transceiver + register_transceiver( + &test_data.ntt, + RegisterTransceiver { + payer: ctx.payer.pubkey(), + owner: test_data.program_owner.pubkey(), + transceiver: ntt_transceiver::ID, + }, + ) + .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .await + .unwrap(); + assert_threshold(&mut ctx, &test_data, 1).await; + + // reregister baked-in transceiver + register_transceiver( + &test_data.ntt, + RegisterTransceiver { + payer: ctx.payer.pubkey(), + owner: test_data.program_owner.pubkey(), + transceiver: example_native_token_transfers::ID, + }, + ) + .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .await + .unwrap(); + assert_threshold(&mut ctx, &test_data, 1).await; +} + #[tokio::test] async fn test_zero_threshold() { let (mut ctx, test_data) = setup(Mode::Locking).await; 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 eb5b0840c..6ebf892ed 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 @@ -74,6 +74,28 @@ pub fn register_transceiver(ntt: &NTT, accounts: RegisterTransceiver) -> Instruc } } +pub struct DeregisterTransceiver { + pub owner: Pubkey, + pub transceiver: Pubkey, +} + +pub fn deregister_transceiver(ntt: &NTT, accounts: DeregisterTransceiver) -> Instruction { + let data = example_native_token_transfers::instruction::DeregisterTransceiver {}; + + let accounts = example_native_token_transfers::accounts::DeregisterTransceiver { + config: ntt.config(), + owner: accounts.owner, + transceiver: accounts.transceiver, + registered_transceiver: ntt.registered_transceiver(&accounts.transceiver), + }; + + Instruction { + program_id: ntt.program, + accounts: accounts.to_account_metas(None), + data: data.data(), + } +} + pub struct SetThreshold { pub owner: Pubkey, } From 344fbcd99f3f492b55751a78b03050877f7ab57c Mon Sep 17 00:00:00 2001 From: nvsriram Date: Tue, 11 Feb 2025 16:35:40 -0500 Subject: [PATCH 07/12] Update reregister transceiver test using wormhole program id and governance program id --- .../tests/admin.rs | 92 +++++++++++-------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/solana/programs/example-native-token-transfers/tests/admin.rs b/solana/programs/example-native-token-transfers/tests/admin.rs index cc59c5fb4..368cf15af 100644 --- a/solana/programs/example-native-token-transfers/tests/admin.rs +++ b/solana/programs/example-native-token-transfers/tests/admin.rs @@ -34,43 +34,55 @@ async fn assert_threshold( async fn test_reregister_all_transceivers() { let (mut ctx, test_data) = setup(Mode::Locking).await; - // register ntt_transceiver - register_transceiver( - &test_data.ntt, - RegisterTransceiver { - payer: ctx.payer.pubkey(), - owner: test_data.program_owner.pubkey(), - transceiver: ntt_transceiver::ID, - }, - ) - .submit_with_signers(&[&test_data.program_owner], &mut ctx) - .await - .unwrap(); + // Transceivers are expected to be executable which requires them to be added on setup + // Thus, we pass all available executable program IDs as dummy_transceivers + let dummy_transceivers = vec![ + wormhole_anchor_sdk::wormhole::program::ID, + wormhole_governance::ID, + ]; + let num_dummy_transceivers: u8 = dummy_transceivers.len().try_into().unwrap(); + + // register dummy transceivers + for transceiver in &dummy_transceivers { + register_transceiver( + &test_data.ntt, + RegisterTransceiver { + payer: ctx.payer.pubkey(), + owner: test_data.program_owner.pubkey(), + transceiver: *transceiver, + }, + ) + .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .await + .unwrap(); + } - // set threshold to 2 + // set threshold = 1 (for baked-in transceiver) + num_dummy_transceivers set_threshold( &test_data.ntt, SetThreshold { owner: test_data.program_owner.pubkey(), }, - 2, + 1 + num_dummy_transceivers, ) .submit_with_signers(&[&test_data.program_owner], &mut ctx) .await .unwrap(); - // deregister ntt_transceiver - deregister_transceiver( - &test_data.ntt, - DeregisterTransceiver { - owner: test_data.program_owner.pubkey(), - transceiver: ntt_transceiver::ID, - }, - ) - .submit_with_signers(&[&test_data.program_owner], &mut ctx) - .await - .unwrap(); - assert_threshold(&mut ctx, &test_data, 1).await; + // deregister dummy transceivers + for (idx, transceiver) in dummy_transceivers.iter().enumerate() { + deregister_transceiver( + &test_data.ntt, + DeregisterTransceiver { + owner: test_data.program_owner.pubkey(), + transceiver: *transceiver, + }, + ) + .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .await + .unwrap(); + assert_threshold(&mut ctx, &test_data, num_dummy_transceivers - idx as u8).await; + } // deregister baked-in transceiver deregister_transceiver( @@ -85,19 +97,21 @@ async fn test_reregister_all_transceivers() { .unwrap(); assert_threshold(&mut ctx, &test_data, 1).await; - // reregister ntt_transceiver - register_transceiver( - &test_data.ntt, - RegisterTransceiver { - payer: ctx.payer.pubkey(), - owner: test_data.program_owner.pubkey(), - transceiver: ntt_transceiver::ID, - }, - ) - .submit_with_signers(&[&test_data.program_owner], &mut ctx) - .await - .unwrap(); - assert_threshold(&mut ctx, &test_data, 1).await; + // reregister dummy transceiver + for transceiver in &dummy_transceivers { + register_transceiver( + &test_data.ntt, + RegisterTransceiver { + payer: ctx.payer.pubkey(), + owner: test_data.program_owner.pubkey(), + transceiver: *transceiver, + }, + ) + .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .await + .unwrap(); + assert_threshold(&mut ctx, &test_data, 1).await; + } // reregister baked-in transceiver register_transceiver( From 2c18f6341139fceb2506a0f2fce6655e06a8cbcb Mon Sep 17 00:00:00 2001 From: nvsriram Date: Tue, 11 Feb 2025 16:48:21 -0500 Subject: [PATCH 08/12] Add `createDeregisterTransceiverIx` helper to `SolanaNtt` class --- solana/ts/sdk/ntt.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/solana/ts/sdk/ntt.ts b/solana/ts/sdk/ntt.ts index 5a4577de0..62d78b532 100644 --- a/solana/ts/sdk/ntt.ts +++ b/solana/ts/sdk/ntt.ts @@ -759,6 +759,28 @@ export class SolanaNtt .instruction(); } + async createDeregisterTransceiverIx( + ix: number, + owner: web3.PublicKey + ): Promise { + const transceiver = await this.getTransceiver(ix); + if (!transceiver) { + throw new Error(`Transceiver not found`); + } + const transceiverProgramId = transceiver.programId; + + return this.program.methods + .deregisterTransceiver() + .accountsStrict({ + owner, + config: this.pdas.configAccount(), + transceiver: transceiverProgramId, + registeredTransceiver: + this.pdas.registeredTransceiver(transceiverProgramId), + }) + .instruction(); + } + async *setWormholeTransceiverPeer( peer: ChainAddress, payer: AccountAddress From b3305a5a7ecb6ef84f6f29692cefe64dbee371ec Mon Sep 17 00:00:00 2001 From: nvsriram Date: Fri, 21 Feb 2025 14:50:54 -0500 Subject: [PATCH 09/12] Have `Bitmap::len` return `u8` instead of `usize` --- .../programs/example-native-token-transfers/src/bitmap.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/solana/programs/example-native-token-transfers/src/bitmap.rs b/solana/programs/example-native-token-transfers/src/bitmap.rs index 572c3b331..b05d11f7c 100644 --- a/solana/programs/example-native-token-transfers/src/bitmap.rs +++ b/solana/programs/example-native-token-transfers/src/bitmap.rs @@ -48,8 +48,11 @@ impl Bitmap { .expect("Bitmap length must not exceed the bounds of u8") } - pub fn len(self) -> usize { - BM::<128>::from_value(self.map).len() + pub fn len(self) -> u8 { + BM::<128>::from_value(self.map) + .len() + .try_into() + .expect("Bitmap length must not exceed the bounds of u8") } pub fn is_empty(self) -> bool { From 2b77dd36b5a910bf39f57287c38fee51d3d4917a Mon Sep 17 00:00:00 2001 From: nvsriram Date: Fri, 21 Feb 2025 14:53:31 -0500 Subject: [PATCH 10/12] Make `register_transceiver` `init_if_needed` to double as reenable --- .../src/error.rs | 2 ++ .../src/instructions/admin/mod.rs | 36 ++++++++++++------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/solana/programs/example-native-token-transfers/src/error.rs b/solana/programs/example-native-token-transfers/src/error.rs index 6fc037e32..9f1b957c9 100644 --- a/solana/programs/example-native-token-transfers/src/error.rs +++ b/solana/programs/example-native-token-transfers/src/error.rs @@ -63,6 +63,8 @@ pub enum NTTError { InvalidMultisig, #[msg("ThresholdTooHigh")] ThresholdTooHigh, + #[msg("InvalidTransceiverProgram")] + InvalidTransceiverProgram, } impl From for NTTError { diff --git a/solana/programs/example-native-token-transfers/src/instructions/admin/mod.rs b/solana/programs/example-native-token-transfers/src/instructions/admin/mod.rs index 6d35a50bd..9229f7b38 100644 --- a/solana/programs/example-native-token-transfers/src/instructions/admin/mod.rs +++ b/solana/programs/example-native-token-transfers/src/instructions/admin/mod.rs @@ -77,7 +77,7 @@ pub fn set_peer(ctx: Context, args: SetPeerArgs) -> Result<()> { Ok(()) } -// * Register transceivers +// * Transceiver registration #[derive(Accounts)] pub struct RegisterTransceiver<'info> { @@ -92,13 +92,16 @@ pub struct RegisterTransceiver<'info> { #[account(mut)] pub payer: Signer<'info>, - #[account(executable)] + #[account( + executable, + constraint = transceiver.key() != Pubkey::default() @ NTTError::InvalidTransceiverProgram + )] /// CHECK: transceiver is meant to be a transceiver program. Arguably a `Program` constraint could be /// used here that wraps the Transceiver account type. pub transceiver: UncheckedAccount<'info>, #[account( - init, + init_if_needed, space = 8 + RegisteredTransceiver::INIT_SPACE, payer = payer, seeds = [RegisteredTransceiver::SEED_PREFIX, transceiver.key().as_ref()], @@ -110,17 +113,23 @@ pub struct RegisterTransceiver<'info> { } pub fn register_transceiver(ctx: Context) -> Result<()> { - let id = ctx.accounts.config.next_transceiver_id; - ctx.accounts.config.next_transceiver_id += 1; + // initialize registered transceiver with new id on init + if ctx.accounts.registered_transceiver.transceiver_address == Pubkey::default() { + let id = ctx.accounts.config.next_transceiver_id; + ctx.accounts.config.next_transceiver_id += 1; + ctx.accounts + .registered_transceiver + .set_inner(RegisteredTransceiver { + bump: ctx.bumps.registered_transceiver, + id, + transceiver_address: ctx.accounts.transceiver.key(), + }); + } + ctx.accounts - .registered_transceiver - .set_inner(RegisteredTransceiver { - bump: ctx.bumps.registered_transceiver, - id, - transceiver_address: ctx.accounts.transceiver.key(), - }); - - ctx.accounts.config.enabled_transceivers.set(id, true)?; + .config + .enabled_transceivers + .set(ctx.accounts.registered_transceiver.id, true)?; Ok(()) } @@ -242,6 +251,7 @@ pub fn set_paused(ctx: Context, paused: bool) -> Result<()> { } // * Set Threshold + #[derive(Accounts)] #[instruction(threshold: u8)] pub struct SetThreshold<'info> { From 8a3ec3f7c9b994204cd5aaabb8ecf07d04abb972 Mon Sep 17 00:00:00 2001 From: nvsriram Date: Fri, 21 Feb 2025 14:55:21 -0500 Subject: [PATCH 11/12] Add invalid transceiver and reenable transceiver test case --- .../tests/admin.rs | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/solana/programs/example-native-token-transfers/tests/admin.rs b/solana/programs/example-native-token-transfers/tests/admin.rs index 368cf15af..8db1b0eb6 100644 --- a/solana/programs/example-native-token-transfers/tests/admin.rs +++ b/solana/programs/example-native-token-transfers/tests/admin.rs @@ -1,7 +1,10 @@ #![cfg(feature = "test-sbf")] #![feature(type_changing_struct_update)] -use example_native_token_transfers::{config::Config, error::NTTError}; +use anchor_lang::{prelude::Pubkey, system_program::System, Id}; +use example_native_token_transfers::{ + config::Config, error::NTTError, registered_transceiver::RegisteredTransceiver, +}; use ntt_messages::mode::Mode; use solana_program_test::*; use solana_sdk::{instruction::InstructionError, signer::Signer, transaction::TransactionError}; @@ -30,6 +33,47 @@ async fn assert_threshold( assert_eq!(config_account.threshold, expected_threshold); } +async fn assert_transceiver_id( + ctx: &mut ProgramTestContext, + test_data: &TestData, + transceiver: &Pubkey, + expected_id: u8, +) { + let registered_transceiver_account: RegisteredTransceiver = ctx + .get_account_data_anchor(test_data.ntt.registered_transceiver(transceiver)) + .await; + assert_eq!( + registered_transceiver_account.transceiver_address, + *transceiver + ); + assert_eq!(registered_transceiver_account.id, expected_id); +} + +#[tokio::test] +async fn test_invalid_transceiver() { + let (mut ctx, test_data) = setup(Mode::Locking).await; + + // try registering system program + let err = register_transceiver( + &test_data.ntt, + RegisterTransceiver { + payer: ctx.payer.pubkey(), + owner: test_data.program_owner.pubkey(), + transceiver: System::id(), + }, + ) + .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .await + .unwrap_err(); + assert_eq!( + err.unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(NTTError::InvalidTransceiverProgram.into()) + ) + ); +} + #[tokio::test] async fn test_reregister_all_transceivers() { let (mut ctx, test_data) = setup(Mode::Locking).await; @@ -43,7 +87,7 @@ async fn test_reregister_all_transceivers() { let num_dummy_transceivers: u8 = dummy_transceivers.len().try_into().unwrap(); // register dummy transceivers - for transceiver in &dummy_transceivers { + for (idx, transceiver) in dummy_transceivers.iter().enumerate() { register_transceiver( &test_data.ntt, RegisterTransceiver { @@ -55,6 +99,7 @@ async fn test_reregister_all_transceivers() { .submit_with_signers(&[&test_data.program_owner], &mut ctx) .await .unwrap(); + assert_transceiver_id(&mut ctx, &test_data, transceiver, idx as u8 + 1).await; } // set threshold = 1 (for baked-in transceiver) + num_dummy_transceivers @@ -98,7 +143,7 @@ async fn test_reregister_all_transceivers() { assert_threshold(&mut ctx, &test_data, 1).await; // reregister dummy transceiver - for transceiver in &dummy_transceivers { + for (idx, transceiver) in dummy_transceivers.iter().enumerate() { register_transceiver( &test_data.ntt, RegisterTransceiver { @@ -110,6 +155,7 @@ async fn test_reregister_all_transceivers() { .submit_with_signers(&[&test_data.program_owner], &mut ctx) .await .unwrap(); + assert_transceiver_id(&mut ctx, &test_data, transceiver, idx as u8 + 1).await; assert_threshold(&mut ctx, &test_data, 1).await; } @@ -125,6 +171,7 @@ async fn test_reregister_all_transceivers() { .submit_with_signers(&[&test_data.program_owner], &mut ctx) .await .unwrap(); + assert_transceiver_id(&mut ctx, &test_data, &example_native_token_transfers::ID, 0).await; assert_threshold(&mut ctx, &test_data, 1).await; } From c4cfbe0f90fb1eb4af80ae0f2a94fcf7af2a9f34 Mon Sep 17 00:00:00 2001 From: nvsriram Date: Mon, 24 Feb 2025 17:20:58 -0500 Subject: [PATCH 12/12] Update IDL --- .../json/example_native_token_transfers.json | 60 +++++++++ .../ts/example_native_token_transfers.ts | 120 ++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/solana/ts/idl/3_0_0/json/example_native_token_transfers.json b/solana/ts/idl/3_0_0/json/example_native_token_transfers.json index 457cfd5f8..5c81a0e1a 100644 --- a/solana/ts/idl/3_0_0/json/example_native_token_transfers.json +++ b/solana/ts/idl/3_0_0/json/example_native_token_transfers.json @@ -1350,6 +1350,35 @@ ], "args": [] }, + { + "name": "deregisterTransceiver", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "owner", + "isMut": true, + "isSigner": true + }, + { + "name": "transceiver", + "isMut": false, + "isSigner": false, + "docs": [ + "used here that wraps the Transceiver account type." + ] + }, + { + "name": "registeredTransceiver", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, { "name": "setOutboundLimit", "accounts": [ @@ -1438,6 +1467,27 @@ "args": [], "returns": "bool" }, + { + "name": "setThreshold", + "accounts": [ + { + "name": "owner", + "isMut": false, + "isSigner": true + }, + { + "name": "config", + "isMut": true, + "isSigner": false + } + ], + "args": [ + { + "name": "threshold", + "type": "u8" + } + ] + }, { "name": "setWormholePeer", "accounts": [ @@ -2587,6 +2637,16 @@ "code": 6027, "name": "InvalidMultisig", "msg": "InvalidMultisig" + }, + { + "code": 6028, + "name": "ThresholdTooHigh", + "msg": "ThresholdTooHigh" + }, + { + "code": 6029, + "name": "InvalidTransceiverProgram", + "msg": "InvalidTransceiverProgram" } ] } diff --git a/solana/ts/idl/3_0_0/ts/example_native_token_transfers.ts b/solana/ts/idl/3_0_0/ts/example_native_token_transfers.ts index e0305fc6b..f49692f66 100644 --- a/solana/ts/idl/3_0_0/ts/example_native_token_transfers.ts +++ b/solana/ts/idl/3_0_0/ts/example_native_token_transfers.ts @@ -1350,6 +1350,35 @@ export type ExampleNativeTokenTransfers = { ], "args": [] }, + { + "name": "deregisterTransceiver", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "owner", + "isMut": true, + "isSigner": true + }, + { + "name": "transceiver", + "isMut": false, + "isSigner": false, + "docs": [ + "used here that wraps the Transceiver account type." + ] + }, + { + "name": "registeredTransceiver", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, { "name": "setOutboundLimit", "accounts": [ @@ -1438,6 +1467,27 @@ export type ExampleNativeTokenTransfers = { "args": [], "returns": "bool" }, + { + "name": "setThreshold", + "accounts": [ + { + "name": "owner", + "isMut": false, + "isSigner": true + }, + { + "name": "config", + "isMut": true, + "isSigner": false + } + ], + "args": [ + { + "name": "threshold", + "type": "u8" + } + ] + }, { "name": "setWormholePeer", "accounts": [ @@ -2587,6 +2637,16 @@ export type ExampleNativeTokenTransfers = { "code": 6027, "name": "InvalidMultisig", "msg": "InvalidMultisig" + }, + { + "code": 6028, + "name": "ThresholdTooHigh", + "msg": "ThresholdTooHigh" + }, + { + "code": 6029, + "name": "InvalidTransceiverProgram", + "msg": "InvalidTransceiverProgram" } ] } @@ -3942,6 +4002,35 @@ export const IDL: ExampleNativeTokenTransfers = { ], "args": [] }, + { + "name": "deregisterTransceiver", + "accounts": [ + { + "name": "config", + "isMut": true, + "isSigner": false + }, + { + "name": "owner", + "isMut": true, + "isSigner": true + }, + { + "name": "transceiver", + "isMut": false, + "isSigner": false, + "docs": [ + "used here that wraps the Transceiver account type." + ] + }, + { + "name": "registeredTransceiver", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, { "name": "setOutboundLimit", "accounts": [ @@ -4030,6 +4119,27 @@ export const IDL: ExampleNativeTokenTransfers = { "args": [], "returns": "bool" }, + { + "name": "setThreshold", + "accounts": [ + { + "name": "owner", + "isMut": false, + "isSigner": true + }, + { + "name": "config", + "isMut": true, + "isSigner": false + } + ], + "args": [ + { + "name": "threshold", + "type": "u8" + } + ] + }, { "name": "setWormholePeer", "accounts": [ @@ -5179,6 +5289,16 @@ export const IDL: ExampleNativeTokenTransfers = { "code": 6027, "name": "InvalidMultisig", "msg": "InvalidMultisig" + }, + { + "code": 6028, + "name": "ThresholdTooHigh", + "msg": "ThresholdTooHigh" + }, + { + "code": 6029, + "name": "InvalidTransceiverProgram", + "msg": "InvalidTransceiverProgram" } ] }