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 3b929bbcb..375c64271 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 @@ -142,6 +142,7 @@ pub fn register_transceiver(ctx: Context) -> Result<()> { Ok(()) } +// TODO: Rename to DisableTransceiver #[derive(Accounts)] pub struct DeregisterTransceiver<'info> { #[account( @@ -166,11 +167,14 @@ pub fn deregister_transceiver(ctx: Context) -> Result<()> .enabled_transceivers .set(ctx.accounts.registered_transceiver.id, false)?; - // decrement threshold if too high let num_enabled_transceivers = ctx.accounts.config.enabled_transceivers.len(); + // at least one transceiver should be enabled + if num_enabled_transceivers == 0 { + return Err(NTTError::ZeroThreshold.into()); + } + // decrement threshold if too high if num_enabled_transceivers < ctx.accounts.config.threshold { - // threshold should be at least 1 - ctx.accounts.config.threshold = num_enabled_transceivers.max(1); + ctx.accounts.config.threshold = num_enabled_transceivers; } Ok(()) } diff --git a/solana/programs/example-native-token-transfers/tests/admin.rs b/solana/programs/example-native-token-transfers/tests/admin.rs index c3fd0d2b6..84357b97d 100644 --- a/solana/programs/example-native-token-transfers/tests/admin.rs +++ b/solana/programs/example-native-token-transfers/tests/admin.rs @@ -118,23 +118,11 @@ async fn test_reregister_all_transceivers() { .submit_with_signers(&[&test_data.program_owner], &mut ctx) .await .unwrap(); + // assert threshold decreases assert_threshold(&mut ctx, num_dummy_transceivers - idx as u8).await; } - // deregister baked-in transceiver - deregister_transceiver( - &good_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, 1).await; - - // reregister dummy transceiver + // reregister dummy transceivers for (idx, transceiver) in dummy_transceivers.iter().enumerate() { register_transceiver( &good_ntt, @@ -147,6 +135,7 @@ async fn test_reregister_all_transceivers() { .submit_with_signers(&[&test_data.program_owner], &mut ctx) .await .unwrap(); + // assert transceiver_id and threshold are retained assert_transceiver_id(&mut ctx, transceiver, idx as u8 + 1).await; assert_threshold(&mut ctx, 1).await; } @@ -163,10 +152,80 @@ async fn test_reregister_all_transceivers() { .submit_with_signers(&[&test_data.program_owner], &mut ctx) .await .unwrap(); + // assert transceiver_id and threshold are retained assert_transceiver_id(&mut ctx, &example_native_token_transfers::ID, 0).await; assert_threshold(&mut ctx, 1).await; } +#[tokio::test] +async fn test_deregister_last_enabled_transceiver() { + let (mut ctx, test_data) = setup(Mode::Locking).await; + + // attempt to deregister only enabled transceiver (baked-in transceiver) + let err = deregister_transceiver( + &good_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_err(); + assert_eq!( + err.unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(NTTError::ZeroThreshold.into()) + ) + ); + + // register arbitrary executable program as dummy transceiver + let dummy_transceiver = wormhole_anchor_sdk::wormhole::program::Wormhole::id(); + register_transceiver( + &good_ntt, + RegisterTransceiver { + payer: ctx.payer.pubkey(), + owner: test_data.program_owner.pubkey(), + transceiver: dummy_transceiver, + }, + ) + .submit_with_signers(&[&test_data.program_owner], &mut ctx) + .await + .unwrap(); + + // deregister baked-in transceiver + deregister_transceiver( + &good_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(); + + // attempt to deregister last enabled transceiver (dummy transceiver) + let err = deregister_transceiver( + &good_ntt, + DeregisterTransceiver { + owner: test_data.program_owner.pubkey(), + transceiver: dummy_transceiver, + }, + ) + .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_zero_threshold() { let (mut ctx, test_data) = setup(Mode::Locking).await;