From 64e3659a0a89cb33f56bffbf07acdbde0a863f50 Mon Sep 17 00:00:00 2001 From: bingyuyap Date: Sat, 19 Oct 2024 07:31:06 +0800 Subject: [PATCH] solana: address PR comments Signed-off-by: bingyuyap --- svm/Anchor.toml | 2 +- svm/README.md | 2 +- svm/programs/mock-integrator/README.md | 65 +++ svm/programs/mock-integrator/src/lib.rs | 22 +- ...ster_transceiver.rs => add_transceiver.rs} | 201 ++++++--- ...transceivers.rs => disable_transceiver.rs} | 189 ++++++++- .../mock-integrator/tests/discard_admin.rs | 129 ++++++ ..._transceivers.rs => enable_transceiver.rs} | 236 +++++++++-- ...ster_transceiver.rs => add_transceiver.rs} | 20 +- ...transceivers.rs => disable_transceiver.rs} | 22 +- .../tests/instructions/discard_admin.rs | 30 ++ ..._transceivers.rs => enable_transceiver.rs} | 40 +- .../mock-integrator/tests/instructions/mod.rs | 8 +- .../tests/instructions/register.rs | 11 +- .../tests/instructions/transfer_admin.rs | 57 +++ .../tests/instructions/update_admin.rs | 11 +- .../mock-integrator/tests/transfer_admin.rs | 397 ++++++++++++++++++ .../mock-integrator/tests/update_admin.rs | 126 +++++- svm/programs/router/README.md | 85 ++-- svm/programs/router/src/error.rs | 7 +- ...ster_transceiver.rs => add_transceiver.rs} | 60 +-- .../router/src/instructions/common/mod.rs | 3 + .../common/transceiver_info_args.rs | 14 + ...transceivers.rs => disable_transceiver.rs} | 48 +-- .../router/src/instructions/discard_admin.rs | 31 ++ ..._transceivers.rs => enable_transceiver.rs} | 61 ++- svm/programs/router/src/instructions/mod.rs | 18 +- .../router/src/instructions/register.rs | 24 +- .../router/src/instructions/transfer_admin.rs | 98 +++++ .../router/src/instructions/update_admin.rs | 45 +- svm/programs/router/src/lib.rs | 81 ++-- .../src/state/integrator_chain_config.rs | 21 +- .../router/src/state/integrator_config.rs | 30 +- .../src/state/registered_transceiver.rs | 15 +- svm/programs/router/src/utils/bitmap.rs | 18 +- 35 files changed, 1803 insertions(+), 424 deletions(-) create mode 100644 svm/programs/mock-integrator/README.md rename svm/programs/mock-integrator/tests/{register_transceiver.rs => add_transceiver.rs} (52%) rename svm/programs/mock-integrator/tests/{disable_transceivers.rs => disable_transceiver.rs} (64%) create mode 100644 svm/programs/mock-integrator/tests/discard_admin.rs rename svm/programs/mock-integrator/tests/{set_transceivers.rs => enable_transceiver.rs} (58%) rename svm/programs/mock-integrator/tests/instructions/{register_transceiver.rs => add_transceiver.rs} (66%) rename svm/programs/mock-integrator/tests/instructions/{disable_transceivers.rs => disable_transceiver.rs} (84%) create mode 100644 svm/programs/mock-integrator/tests/instructions/discard_admin.rs rename svm/programs/mock-integrator/tests/instructions/{set_transceivers.rs => enable_transceiver.rs} (69%) create mode 100644 svm/programs/mock-integrator/tests/instructions/transfer_admin.rs create mode 100644 svm/programs/mock-integrator/tests/transfer_admin.rs rename svm/programs/router/src/instructions/{register_transceiver.rs => add_transceiver.rs} (59%) create mode 100644 svm/programs/router/src/instructions/common/mod.rs create mode 100644 svm/programs/router/src/instructions/common/transceiver_info_args.rs rename svm/programs/router/src/instructions/{disable_transceivers.rs => disable_transceiver.rs} (77%) create mode 100644 svm/programs/router/src/instructions/discard_admin.rs rename svm/programs/router/src/instructions/{set_transceivers.rs => enable_transceiver.rs} (73%) create mode 100644 svm/programs/router/src/instructions/transfer_admin.rs diff --git a/svm/Anchor.toml b/svm/Anchor.toml index 297bfee6..cde412fc 100644 --- a/svm/Anchor.toml +++ b/svm/Anchor.toml @@ -1,6 +1,6 @@ [toolchain] anchor_version = "0.30.1" -solana_version = "1.18.23" +solana_version = "1.18.17" [features] resolution = true diff --git a/svm/README.md b/svm/README.md index f0ff490b..bcc30aa5 100644 --- a/svm/README.md +++ b/svm/README.md @@ -37,7 +37,7 @@ anchor deploy --provider.cluster mainnet --provider.wallet ~/.config/solana/your ### Upgrading ``` -anchor upgrade --provider.cluster --provider.wallet ~/.config/solana/your-key.json --program-id target/deploy/routers.so +anchor upgrade --provider.cluster --provider.wallet ~/.config/solana/your-key.json --program-id target/deploy/router.so ``` If you get an error like this diff --git a/svm/programs/mock-integrator/README.md b/svm/programs/mock-integrator/README.md new file mode 100644 index 00000000..d297ddbb --- /dev/null +++ b/svm/programs/mock-integrator/README.md @@ -0,0 +1,65 @@ +# Mock Integrator Program + +This program serves as a mock integrator to demonstrate how to call the register function in the router program. It's designed to simulate the process of registering an integrator, which requires a Cross-Program Invocation (CPI) call with a Program Derived Address (PDA) signer. + +## Overview + +The mock integrator program is crucial for testing the entire router program functionality. It serves as a gateway to test all other features of the router program because: + +1. It simulates the registration process, which is a prerequisite for all other router program operations. +2. By successfully calling the `register` function, it establishes the necessary account such as `IntegratorConfig` and permissions required for subsequent router program interactions. +3. It allows us to test the router program's handling of Cross-Program Invocations (CPIs) from an integrator. + +This program contains a single instruction: + +1. `invoke_register`: This instruction demonstrates how to properly set up the accounts and sign the transaction using a PDA, which is required for the registration process. + +By first calling `invoke_register`, we can then proceed to test all other aspects of the router program, ensuring its complete functionality in a controlled testing environment. + +## Requirements + +Before testing, ensure you have the following installed: + +- [Rust 1.75.0](https://www.rust-lang.org/tools/install) +- [Solana 1.18.17](https://solana.com/docs/intro/installation) +- [Anchor 0.30.1](https://www.anchor-lang.com/docs/installation) +- [Yarn](https://yarnpkg.com/getting-started/install) + +## How to Test + +To test this program, follow these steps: + +1. Ensure you have Rust, Anchor, and the Solana tool suite installed on your system. + +2. Navigate to the root directory of the SVM project: + + ``` + cd svm + ``` + +3. Build the Anchor project: + + ``` + anchor build + ``` + +4. Navigate to the mock-integrator program's directory: + + ``` + cd programs/mock-integrator + ``` + +5. Run the tests using Cargo: + ``` + cargo test-sbf + ``` + +This process will first build all the programs in the workspace, including the router program that the mock-integrator depends on, and then run the tests for the mock-integrator program in a Solana BPF environment. + +## Code Structure + +The main components of the program are: + +- `invoke_register`: The instruction that demonstrates the CPI call to the router program's register function. +- `InvokeRegisterArgs`: The struct that defines the arguments for the invoke_register instruction. +- `InvokeRegister`: The struct that defines the accounts required for the invoke_register instruction. diff --git a/svm/programs/mock-integrator/src/lib.rs b/svm/programs/mock-integrator/src/lib.rs index 51593413..a2139e4c 100644 --- a/svm/programs/mock-integrator/src/lib.rs +++ b/svm/programs/mock-integrator/src/lib.rs @@ -16,27 +16,32 @@ pub mod mock_integrator { /// Invokes the register function in the router program via a CPI call. /// This function demonstrates how to properly set up the accounts and sign the transaction /// using a PDA, which is required for the registration process. - pub fn invoke_register(ctx: Context, args: RegisterArgs) -> Result<()> { - let bump_seed = &[args.integrator_program_pda_bump][..]; + pub fn invoke_register(ctx: Context, args: InvokeRegisterArgs) -> Result<()> { + let bump_seed = &[ctx.bumps.integrator_program_pda][..]; let signer_seeds: &[&[&[u8]]] = &[&[b"router_integrator", bump_seed]]; router::cpi::register( ctx.accounts.invoke_register().with_signer(signer_seeds), - args, + RegisterArgs { + integrator_program_pda_bump: ctx.bumps.integrator_program_pda, + integrator_program_id: crate::ID, + admin: args.admin, + }, )?; Ok(()) } } +#[derive(AnchorSerialize, AnchorDeserialize)] +pub struct InvokeRegisterArgs { + pub admin: Pubkey, +} #[derive(Accounts)] -#[instruction(args: RegisterArgs)] +#[instruction(args: InvokeRegisterArgs)] pub struct InvokeRegister<'info> { #[account(mut)] pub payer: Signer<'info>, - /// CHECK: This account is not checked for safety because it is assumed to be a trusted admin account. - pub admin: UncheckedAccount<'info>, - #[account(mut)] /// CHECK: This account is to be checked and initialized by the router program pub integrator_config: UncheckedAccount<'info>, @@ -44,7 +49,7 @@ pub struct InvokeRegister<'info> { /// The integrator program's PDA #[account( seeds = [b"router_integrator"], - bump = args.integrator_program_pda_bump, + bump, )] pub integrator_program_pda: SystemAccount<'info>, @@ -59,7 +64,6 @@ impl<'info> InvokeRegister<'info> { let cpi_program = self.router_program.to_account_info(); let cpi_accounts = Register { payer: self.payer.to_account_info(), - admin: self.admin.to_account_info(), integrator_config: self.integrator_config.to_account_info(), integrator_program_pda: self.integrator_program_pda.to_account_info(), system_program: self.system_program.to_account_info(), diff --git a/svm/programs/mock-integrator/tests/register_transceiver.rs b/svm/programs/mock-integrator/tests/add_transceiver.rs similarity index 52% rename from svm/programs/mock-integrator/tests/register_transceiver.rs rename to svm/programs/mock-integrator/tests/add_transceiver.rs index 842fdfb8..dabe5357 100644 --- a/svm/programs/mock-integrator/tests/register_transceiver.rs +++ b/svm/programs/mock-integrator/tests/add_transceiver.rs @@ -3,8 +3,10 @@ mod common; mod instructions; +use crate::instructions::add_transceiver::add_transceiver; +use crate::instructions::discard_admin::discard_admin; use crate::instructions::register::register; -use crate::instructions::register_transceiver::register_transceiver; +use crate::instructions::transfer_admin::transfer_admin; use anchor_lang::prelude::*; use common::setup::{get_account, setup}; use router::error::RouterError; @@ -46,38 +48,38 @@ async fn register_test_transceiver( admin: &Keypair, payer: &Keypair, integrator_config_pda: Pubkey, - integrator_program: Pubkey, + integrator_program_id: Pubkey, ) -> (Pubkey, Pubkey) { - let transceiver_address = Keypair::new().pubkey(); + let transceiver_program_id = Keypair::new().pubkey(); let (registered_transceiver_pda, _) = - TransceiverInfo::pda(&integrator_program, &transceiver_address); + TransceiverInfo::pda(&integrator_program_id, &transceiver_program_id); - register_transceiver( + add_transceiver( context, admin, payer, integrator_config_pda, registered_transceiver_pda, - integrator_program, - transceiver_address, + integrator_program_id, + transceiver_program_id, ) .await .unwrap(); - (transceiver_address, registered_transceiver_pda) + (transceiver_program_id, registered_transceiver_pda) } #[tokio::test] -async fn test_register_transceiver_success() { - let (mut context, payer, admin, integrator_program, integrator_config_pda) = +async fn test_add_transceiver_success() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = setup_test_environment().await; - let (transceiver_address, registered_transceiver_pda) = register_test_transceiver( + let (transceiver_program_id, registered_transceiver_pda) = register_test_transceiver( &mut context, &admin, &payer, integrator_config_pda, - integrator_program, + integrator_program_id, ) .await; @@ -85,14 +87,14 @@ async fn test_register_transceiver_success() { let registered_transceiver: TransceiverInfo = get_account(&mut context.banks_client, registered_transceiver_pda).await; - assert_eq!(registered_transceiver.id, 0); + assert_eq!(registered_transceiver.index, 0); assert_eq!( registered_transceiver.integrator_program_id, - integrator_program + integrator_program_id ); assert_eq!( - registered_transceiver.transceiver_address, - transceiver_address + registered_transceiver.transceiver_program_id, + transceiver_program_id ); // Verify that the integrator config's transceivers list has been updated @@ -101,40 +103,40 @@ async fn test_register_transceiver_success() { assert_eq!(integrator_config.registered_transceivers.len(), 1); assert_eq!( integrator_config.registered_transceivers[0], - transceiver_address + transceiver_program_id ); } #[tokio::test] async fn test_register_multiple_transceivers() { - let (mut context, payer, admin, integrator_program, integrator_config_pda) = + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = setup_test_environment().await; // Register two transceivers - let mut transceiver_addresses = Vec::new(); + let mut transceiver_program_ides = Vec::new(); for id in 0..2 { - let (transceiver_address, registered_transceiver_pda) = register_test_transceiver( + let (transceiver_program_id, registered_transceiver_pda) = register_test_transceiver( &mut context, &admin, &payer, integrator_config_pda, - integrator_program, + integrator_program_id, ) .await; - transceiver_addresses.push(transceiver_address); + transceiver_program_ides.push(transceiver_program_id); // Fetch and verify the registered transceiver let registered_transceiver: TransceiverInfo = get_account(&mut context.banks_client, registered_transceiver_pda).await; - assert_eq!(registered_transceiver.id, id as u8); + assert_eq!(registered_transceiver.index, id as u8); assert_eq!( registered_transceiver.integrator_program_id, - integrator_program + integrator_program_id ); assert_eq!( - registered_transceiver.transceiver_address, - transceiver_address + registered_transceiver.transceiver_program_id, + transceiver_program_id ); } @@ -144,13 +146,13 @@ async fn test_register_multiple_transceivers() { assert_eq!(integrator_config.registered_transceivers.len(), 2); assert_eq!( integrator_config.registered_transceivers, - transceiver_addresses + transceiver_program_ides ); } #[tokio::test] async fn test_register_max_transceivers() { - let (mut context, payer, admin, integrator_program, integrator_config_pda) = + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = setup_test_environment().await; // Register the maximum number of transceivers @@ -160,24 +162,24 @@ async fn test_register_max_transceivers() { &admin, &payer, integrator_config_pda, - integrator_program, + integrator_program_id, ) .await; } // Attempt to register one more transceiver (should fail) - let extra_transceiver_address = Keypair::new().pubkey(); + let extra_transceiver_program_id = Keypair::new().pubkey(); let (extra_registered_transceiver_pda, _) = - TransceiverInfo::pda(&integrator_program, &extra_transceiver_address); + TransceiverInfo::pda(&integrator_program_id, &extra_transceiver_program_id); - let result = register_transceiver( + let result = add_transceiver( &mut context, &admin, &payer, integrator_config_pda, extra_registered_transceiver_pda, - integrator_program, - extra_transceiver_address, + integrator_program_id, + extra_transceiver_program_id, ) .await; @@ -201,29 +203,29 @@ async fn test_register_max_transceivers() { } #[tokio::test] -async fn test_register_transceiver_reinitialization() { - let (mut context, payer, admin, integrator_program, integrator_config_pda) = +async fn test_add_transceiver_reinitialization() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = setup_test_environment().await; // Register a transceiver - let (transceiver_address, registered_transceiver_pda) = register_test_transceiver( + let (transceiver_program_id, registered_transceiver_pda) = register_test_transceiver( &mut context, &admin, &payer, integrator_config_pda, - integrator_program, + integrator_program_id, ) .await; // Attempt to register the same transceiver again - let result = register_transceiver( + let result = add_transceiver( &mut context, &admin, &payer, integrator_config_pda, registered_transceiver_pda, - integrator_program, - transceiver_address, + integrator_program_id, + transceiver_program_id, ) .await; @@ -243,41 +245,138 @@ async fn test_register_transceiver_reinitialization() { assert_eq!(integrator_config.registered_transceivers.len(), 1); assert_eq!( integrator_config.registered_transceivers[0], - transceiver_address + transceiver_program_id ); } #[tokio::test] -async fn test_register_transceiver_non_authority() { - let (mut context, payer, _, integrator_program, integrator_config_pda) = +async fn test_add_transceiver_non_authority() { + let (mut context, payer, _, integrator_program_id, integrator_config_pda) = setup_test_environment().await; // Create a non-authority signer let non_authority = Keypair::new(); // Attempt to register a transceiver with non-authority signer - let transceiver_address = Keypair::new().pubkey(); + let transceiver_program_id = Keypair::new().pubkey(); let (registered_transceiver_pda, _) = - TransceiverInfo::pda(&integrator_program, &transceiver_address); + TransceiverInfo::pda(&integrator_program_id, &transceiver_program_id); - let result = register_transceiver( + let result = add_transceiver( &mut context, &non_authority, // Use non-authority signer &payer, integrator_config_pda, registered_transceiver_pda, - integrator_program, - transceiver_address, + integrator_program_id, + transceiver_program_id, ) .await; - // Verify that the transaction failed with the InvalidIntegratorAuthority error + // Verify that the transaction failed with the CallerNotAuthorized error assert!(result.is_err()); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError( 0, - InstructionError::Custom(RouterError::InvalidIntegratorAuthority.into()) + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) + ) + ); + + // Verify that the integrator config's transceivers list has not been updated + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.registered_transceivers.len(), 0); +} + +#[tokio::test] +async fn test_add_transceiver_with_transfer_in_progress() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + let pending_admin = Keypair::new(); + + // First, initiate a transfer + transfer_admin( + &mut context, + &admin, + &pending_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await + .unwrap(); + + // Now try to add a transceiver + let transceiver_program_id = Keypair::new().pubkey(); + let (registered_transceiver_pda, _) = + TransceiverInfo::pda(&integrator_program_id, &transceiver_program_id); + + let result = add_transceiver( + &mut context, + &admin, + &payer, + integrator_config_pda, + registered_transceiver_pda, + integrator_program_id, + transceiver_program_id, + ) + .await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::AdminTransferInProgress.into()) + ) + ); + + // Verify that the integrator config hasn't changed + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.admin, admin.pubkey()); + assert_eq!( + integrator_config.pending_admin, + Some(pending_admin.pubkey()) + ); + assert_eq!(integrator_config.registered_transceivers.len(), 0); +} + +#[tokio::test] +async fn test_add_transceiver_with_immutable_config() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + // First, discard the admin to make the config immutable + discard_admin(&mut context, &admin, &payer, integrator_config_pda) + .await + .unwrap(); + + // Now try to add a transceiver + let transceiver_program_id = Keypair::new().pubkey(); + let (registered_transceiver_pda, _) = + TransceiverInfo::pda(&integrator_program_id, &transceiver_program_id); + + let result = add_transceiver( + &mut context, + &admin, + &payer, + integrator_config_pda, + registered_transceiver_pda, + integrator_program_id, + transceiver_program_id, + ) + .await; + + // The transaction should fail due to immutable config + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) ) ); diff --git a/svm/programs/mock-integrator/tests/disable_transceivers.rs b/svm/programs/mock-integrator/tests/disable_transceiver.rs similarity index 64% rename from svm/programs/mock-integrator/tests/disable_transceivers.rs rename to svm/programs/mock-integrator/tests/disable_transceiver.rs index 3378e21f..67d367e5 100644 --- a/svm/programs/mock-integrator/tests/disable_transceivers.rs +++ b/svm/programs/mock-integrator/tests/disable_transceiver.rs @@ -3,13 +3,14 @@ mod common; mod instructions; -use crate::instructions::disable_transceivers::{ +use crate::instructions::add_transceiver::add_transceiver; +use crate::instructions::disable_transceiver::{ disable_recv_transceiver, disable_send_transceiver, }; +use crate::instructions::discard_admin::discard_admin; +use crate::instructions::enable_transceiver::{enable_recv_transceiver, enable_send_transceiver}; use crate::instructions::register::register; -use crate::instructions::register_transceiver::register_transceiver; -use crate::instructions::set_transceivers::{set_recv_transceiver, set_send_transceiver}; - +use crate::instructions::transfer_admin::transfer_admin; use anchor_lang::prelude::*; use common::setup::{get_account, setup}; use router::error::RouterError; @@ -28,10 +29,10 @@ async fn initialize_test_environment( ) -> (Keypair, Pubkey, Pubkey, Pubkey, Pubkey, Pubkey, u16) { let payer = context.payer.insecure_clone(); let admin = Keypair::new(); - let integrator_program = mock_integrator::id(); + let integrator_program_id = mock_integrator::id(); let chain_id: u16 = 1; - let (integrator_config_pda, _) = IntegratorConfig::pda(&integrator_program); + let (integrator_config_pda, _) = IntegratorConfig::pda(&integrator_program_id); register( context, @@ -45,32 +46,32 @@ async fn initialize_test_environment( // Prepare integrator_chain_config_pda let (integrator_chain_config_pda, _) = - IntegratorChainConfig::pda(&integrator_program, chain_id); + IntegratorChainConfig::pda(&integrator_program_id, chain_id); // Register a transceiver - let transceiver_address = Keypair::new().pubkey(); + let transceiver_program_id = Keypair::new().pubkey(); let (registered_transceiver_pda, _) = - TransceiverInfo::pda(&integrator_program, &transceiver_address); + TransceiverInfo::pda(&integrator_program_id, &transceiver_program_id); - register_transceiver( + add_transceiver( context, &admin, &payer, integrator_config_pda, registered_transceiver_pda, - integrator_program, - transceiver_address, + integrator_program_id, + transceiver_program_id, ) .await .unwrap(); ( admin, - integrator_program, + integrator_program_id, integrator_config_pda, integrator_chain_config_pda, registered_transceiver_pda, - transceiver_address, + transceiver_program_id, chain_id, ) } @@ -110,7 +111,7 @@ async fn test_disable_recv_transceiver_success() { let payer = context.payer.insecure_clone(); // Set the receive transceiver first - set_recv_transceiver( + enable_recv_transceiver( &mut context, &authority, &payer, @@ -160,7 +161,7 @@ async fn test_disable_send_transceiver_success() { let payer = context.payer.insecure_clone(); // Set the send transceiver first - set_send_transceiver( + enable_send_transceiver( &mut context, &authority, &payer, @@ -198,7 +199,7 @@ async fn test_disable_send_transceiver_success() { } #[tokio::test] -async fn test_disable_transceivers_invalid_authority() { +async fn test_disable_transceiver_invalid_authority() { let mut context = setup().await; let ( authority, @@ -213,7 +214,7 @@ async fn test_disable_transceivers_invalid_authority() { let payer = context.payer.insecure_clone(); // Set the receive transceiver first - set_recv_transceiver( + enable_recv_transceiver( &mut context, &authority, &payer, @@ -250,13 +251,13 @@ async fn test_disable_transceivers_invalid_authority() { err.unwrap(), TransactionError::InstructionError( 0, - InstructionError::Custom(RouterError::InvalidIntegratorAuthority.into()) + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) ) ); } #[tokio::test] -async fn test_disable_transceivers_invalid_transceiver_id() { +async fn test_disable_transceiver_invalid_transceiver_id() { let mut context = setup().await; let ( authority, @@ -312,7 +313,7 @@ async fn test_disable_already_disabled_transceiver() { // Set the receive transceiver first to make sure that the integrator_chain_config_pda is // initialized - set_recv_transceiver( + enable_recv_transceiver( &mut context, &authority, &payer, @@ -370,3 +371,149 @@ async fn test_disable_already_disabled_transceiver() { ) ); } + +#[tokio::test] +async fn test_disable_transceiver_with_transfer_in_progress() { + let mut context = setup().await; + let ( + admin, + integrator_program_id, + integrator_config_pda, + integrator_chain_config_pda, + registered_transceiver_pda, + transceiver, + chain_id, + ) = initialize_test_environment(&mut context).await; + + let payer = context.payer.insecure_clone(); + let pending_admin = Keypair::new(); + + // Enable the receive transceiver first + enable_recv_transceiver( + &mut context, + &admin, + &payer, + integrator_config_pda, + integrator_chain_config_pda, + registered_transceiver_pda, + chain_id, + transceiver, + integrator_program_id, + ) + .await + .unwrap(); + + // Initiate an admin transfer + transfer_admin( + &mut context, + &admin, + &pending_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await + .unwrap(); + + // Now try to disable the transceiver + let result = disable_recv_transceiver( + &mut context, + &admin, + &payer, + integrator_config_pda, + integrator_chain_config_pda, + registered_transceiver_pda, + chain_id, + transceiver, + integrator_program_id, + ) + .await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::AdminTransferInProgress.into()) + ) + ); + + // Verify that the transceiver state hasn't changed + verify_transceiver_state(&mut context, integrator_chain_config_pda, 1, 0).await; + + // Verify that the integrator config hasn't changed + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.admin, admin.pubkey()); + assert_eq!( + integrator_config.pending_admin, + Some(pending_admin.pubkey()) + ); +} + +#[tokio::test] +async fn test_disable_transceiver_with_immutable_config() { + let mut context = setup().await; + let ( + admin, + integrator_program_id, + integrator_config_pda, + integrator_chain_config_pda, + registered_transceiver_pda, + transceiver, + chain_id, + ) = initialize_test_environment(&mut context).await; + + let payer = context.payer.insecure_clone(); + + // Enable the receive transceiver first + enable_recv_transceiver( + &mut context, + &admin, + &payer, + integrator_config_pda, + integrator_chain_config_pda, + registered_transceiver_pda, + chain_id, + transceiver, + integrator_program_id, + ) + .await + .unwrap(); + + // Discard the admin to make the config immutable + discard_admin(&mut context, &admin, &payer, integrator_config_pda) + .await + .unwrap(); + + // Now try to disable the transceiver + let result = disable_recv_transceiver( + &mut context, + &admin, + &payer, + integrator_config_pda, + integrator_chain_config_pda, + registered_transceiver_pda, + chain_id, + transceiver, + integrator_program_id, + ) + .await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) + ) + ); + + // Verify that the transceiver state hasn't changed + verify_transceiver_state(&mut context, integrator_chain_config_pda, 1, 0).await; + + // Verify that the integrator config is immutable + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.is_immutable, true); +} diff --git a/svm/programs/mock-integrator/tests/discard_admin.rs b/svm/programs/mock-integrator/tests/discard_admin.rs new file mode 100644 index 00000000..0726be36 --- /dev/null +++ b/svm/programs/mock-integrator/tests/discard_admin.rs @@ -0,0 +1,129 @@ +#![cfg(feature = "test-sbf")] + +mod common; +mod instructions; + +use crate::instructions::discard_admin::discard_admin; +use crate::instructions::register::register; +use anchor_lang::prelude::*; +use common::setup::{get_account, setup}; +use router::{error::RouterError, state::IntegratorConfig}; +use solana_program_test::*; +use solana_sdk::{ + instruction::InstructionError, signature::Keypair, signer::Signer, + transaction::TransactionError, +}; + +async fn setup_test_environment() -> (ProgramTestContext, Keypair, Keypair, Pubkey, Pubkey) { + let mut context = setup().await; + let payer = context.payer.insecure_clone(); + let admin = Keypair::new(); + let integrator_program_id = mock_integrator::id(); + + let (integrator_config_pda, _) = IntegratorConfig::pda(&integrator_program_id); + + register( + &mut context, + &payer, + &admin, + integrator_config_pda, + integrator_program_id, + ) + .await + .unwrap(); + + ( + context, + payer, + admin, + integrator_program_id, + integrator_config_pda, + ) +} + +#[tokio::test] +async fn test_discard_admin_success() { + let (mut context, payer, admin, _, integrator_config_pda) = setup_test_environment().await; + + let result = discard_admin(&mut context, &admin, &payer, integrator_config_pda).await; + + assert!(result.is_ok()); + + // Verify that the admin has been discarded + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.is_immutable, true); +} + +#[tokio::test] +async fn test_discard_admin_non_authority() { + let (mut context, payer, _, _, integrator_config_pda) = setup_test_environment().await; + + let non_authority = Keypair::new(); + + let result = discard_admin(&mut context, &non_authority, &payer, integrator_config_pda).await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) + ) + ); +} + +#[tokio::test] +async fn test_discard_admin_already_discarded() { + let (mut context, payer, admin, _, integrator_config_pda) = setup_test_environment().await; + + // First, discard the admin + discard_admin(&mut context, &admin, &payer, integrator_config_pda) + .await + .unwrap(); + + // Try to discard the admin again + let result = discard_admin(&mut context, &admin, &payer, integrator_config_pda).await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) + ) + ); +} + +#[tokio::test] +async fn test_discard_admin_with_pending_transfer() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + let new_admin = Keypair::new(); + + // First, initiate a transfer + crate::instructions::transfer_admin::transfer_admin( + &mut context, + &admin, + &new_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await + .unwrap(); + + // Now try to discard the admin + let result = discard_admin(&mut context, &admin, &payer, integrator_config_pda).await; + + assert!(result.is_err()); + + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::AdminTransferInProgress.into()) + ) + ); +} diff --git a/svm/programs/mock-integrator/tests/set_transceivers.rs b/svm/programs/mock-integrator/tests/enable_transceiver.rs similarity index 58% rename from svm/programs/mock-integrator/tests/set_transceivers.rs rename to svm/programs/mock-integrator/tests/enable_transceiver.rs index 3578ffa5..0b2bf009 100644 --- a/svm/programs/mock-integrator/tests/set_transceivers.rs +++ b/svm/programs/mock-integrator/tests/enable_transceiver.rs @@ -3,10 +3,12 @@ mod common; mod instructions; +use crate::instructions::add_transceiver::add_transceiver; +use crate::instructions::discard_admin::discard_admin; +use crate::instructions::enable_transceiver::{enable_recv_transceiver, enable_send_transceiver}; use crate::instructions::register::register; -use crate::instructions::register_transceiver::register_transceiver; -use crate::instructions::set_transceivers::{set_recv_transceiver, set_send_transceiver}; +use crate::instructions::transfer_admin::transfer_admin; use anchor_lang::prelude::*; use common::setup::{get_account, setup}; use router::error::RouterError; @@ -25,7 +27,7 @@ async fn initialize_test_environment( ) -> (Keypair, Pubkey, Pubkey, Pubkey, Pubkey, Pubkey, u16) { let payer = context.payer.insecure_clone(); let admin = Keypair::new(); - let integrator_program = mock_integrator::id(); + let integrator_program_id = mock_integrator::id(); let chain_id: u16 = 1; let (integrator_config_pda, _) = IntegratorConfig::pda(&mock_integrator::id()); @@ -42,32 +44,32 @@ async fn initialize_test_environment( // Prepare integrator_chain_config_pda let (integrator_chain_config_pda, _) = - IntegratorChainConfig::pda(&integrator_program, chain_id); + IntegratorChainConfig::pda(&integrator_program_id, chain_id); // Register a transceiver - let transceiver_address = Keypair::new().pubkey(); + let transceiver_program_id = Keypair::new().pubkey(); let (registered_transceiver_pda, _) = - TransceiverInfo::pda(&integrator_program, &transceiver_address); + TransceiverInfo::pda(&integrator_program_id, &transceiver_program_id); - register_transceiver( + add_transceiver( context, &admin, &payer, integrator_config_pda, registered_transceiver_pda, - integrator_program, - transceiver_address, + integrator_program_id, + transceiver_program_id, ) .await .unwrap(); ( admin, - integrator_program, + integrator_program_id, integrator_config_pda, integrator_chain_config_pda, registered_transceiver_pda, - transceiver_address, + transceiver_program_id, chain_id, ) } @@ -92,10 +94,10 @@ async fn verify_transceiver_state( } #[tokio::test] -async fn test_set_in_transceivers_success() { +async fn test_enable_in_transceivers_success() { let mut context = setup().await; let ( - authority, + admin, integrator_program_id, integrator_config_pda, integrator_chain_config_pda, @@ -106,9 +108,9 @@ async fn test_set_in_transceivers_success() { let payer = context.payer.insecure_clone(); - let result = set_recv_transceiver( + let result = enable_recv_transceiver( &mut context, - &authority, + &admin, &payer, integrator_config_pda, integrator_chain_config_pda, @@ -124,10 +126,10 @@ async fn test_set_in_transceivers_success() { } #[tokio::test] -async fn test_set_in_transceivers_multiple_sets_success() { +async fn test_enable_in_transceivers_multiple_sets_success() { let mut context = setup().await; let ( - authority, + admin, integrator_program_id, integrator_config_pda, integrator_chain_config_pda, @@ -139,9 +141,9 @@ async fn test_set_in_transceivers_multiple_sets_success() { let payer = context.payer.insecure_clone(); // Set the first transceiver - let result = set_recv_transceiver( + let result = enable_recv_transceiver( &mut context, - &authority, + &admin, &payer, integrator_config_pda, integrator_chain_config_pda, @@ -158,9 +160,9 @@ async fn test_set_in_transceivers_multiple_sets_success() { let (registered_transceiver2_pda, _) = TransceiverInfo::pda(&integrator_program_id, &transceiver2_address); - register_transceiver( + add_transceiver( &mut context, - &authority, + &admin, &payer, integrator_config_pda, registered_transceiver2_pda, @@ -170,9 +172,9 @@ async fn test_set_in_transceivers_multiple_sets_success() { .await .unwrap(); - let result = set_recv_transceiver( + let result = enable_recv_transceiver( &mut context, - &authority, + &admin, &payer, integrator_config_pda, integrator_chain_config_pda, @@ -189,10 +191,10 @@ async fn test_set_in_transceivers_multiple_sets_success() { } #[tokio::test] -async fn test_set_out_transceivers_success() { +async fn test_enable_out_transceivers_success() { let mut context = setup().await; let ( - authority, + admin, integrator_program_id, integrator_config_pda, integrator_chain_config_pda, @@ -203,9 +205,9 @@ async fn test_set_out_transceivers_success() { let payer = context.payer.insecure_clone(); - let result = set_send_transceiver( + let result = enable_send_transceiver( &mut context, - &authority, + &admin, &payer, integrator_config_pda, integrator_chain_config_pda, @@ -222,10 +224,10 @@ async fn test_set_out_transceivers_success() { } #[tokio::test] -async fn test_set_transceivers_invalid_authority() { +async fn test_enable_transceiver_invalid_admin() { let mut context = setup().await; let ( - _authority, + _admin, integrator_program_id, integrator_config_pda, integrator_chain_config_pda, @@ -234,13 +236,13 @@ async fn test_set_transceivers_invalid_authority() { chain_id, ) = initialize_test_environment(&mut context).await; - // Create a new keypair to act as an invalid authority - let invalid_authority = Keypair::new(); + // Create a new keypair to act as an invalid admin + let invalid_admin = Keypair::new(); let payer = context.payer.insecure_clone(); - let result = set_recv_transceiver( + let result = enable_recv_transceiver( &mut context, - &invalid_authority, + &invalid_admin, &payer, integrator_config_pda, integrator_chain_config_pda, @@ -251,23 +253,23 @@ async fn test_set_transceivers_invalid_authority() { ) .await; - // The transaction should fail due to invalid authority + // The transaction should fail due to invalid admin let err = result.unwrap_err(); assert_eq!( err.unwrap(), TransactionError::InstructionError( 0, - InstructionError::Custom(RouterError::InvalidIntegratorAuthority.into()) + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) ) ); } #[tokio::test] -async fn test_set_transceivers_invalid_transceiver_id() { +async fn test_enable_transceiver_invalid_transceiver_id() { let mut context = setup().await; let ( - authority, + admin, integrator_program_id, integrator_config_pda, integrator_chain_config_pda, @@ -280,9 +282,9 @@ async fn test_set_transceivers_invalid_transceiver_id() { let invalid_transceiver = Keypair::new().pubkey(); let payer = context.payer.insecure_clone(); - let result = set_recv_transceiver( + let result = enable_recv_transceiver( &mut context, - &authority, + &admin, &payer, integrator_config_pda, integrator_chain_config_pda, @@ -307,7 +309,7 @@ async fn test_set_transceivers_invalid_transceiver_id() { async fn test_enable_already_enabled_transceiver() { let mut context = setup().await; let ( - authority, + admin, integrator_program_id, integrator_config_pda, integrator_chain_config_pda, @@ -319,9 +321,9 @@ async fn test_enable_already_enabled_transceiver() { let payer = context.payer.insecure_clone(); // First attempt: should succeed - let result = set_recv_transceiver( + let result = enable_recv_transceiver( &mut context, - &authority, + &admin, &payer, integrator_config_pda, integrator_chain_config_pda, @@ -336,9 +338,9 @@ async fn test_enable_already_enabled_transceiver() { verify_transceiver_state(&mut context, integrator_chain_config_pda, 1, 0).await; // Second attempt: should fail with TransceiverAlreadyEnabled - let result = set_recv_transceiver( + let result = enable_recv_transceiver( &mut context, - &authority, + &admin, &payer, integrator_config_pda, integrator_chain_config_pda, @@ -362,3 +364,149 @@ async fn test_enable_already_enabled_transceiver() { // Verify that the state hasn't changed verify_transceiver_state(&mut context, integrator_chain_config_pda, 1, 0).await; } + +#[tokio::test] +async fn test_enable_transceiver_with_transfer_in_progress() { + let mut context = setup().await; + let ( + admin, + integrator_program_id, + integrator_config_pda, + integrator_chain_config_pda, + registered_transceiver_pda, + transceiver, + chain_id, + ) = initialize_test_environment(&mut context).await; + + let payer = context.payer.insecure_clone(); + let pending_admin = Keypair::new(); + + // Initiate an admin transfer + transfer_admin( + &mut context, + &admin, + &pending_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await + .unwrap(); + + // Now try to enable the receive transceiver + let result = enable_recv_transceiver( + &mut context, + &admin, + &payer, + integrator_config_pda, + integrator_chain_config_pda, + registered_transceiver_pda, + chain_id, + transceiver, + integrator_program_id, + ) + .await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::AdminTransferInProgress.into()) + ) + ); + + // Verify that the IntegratorChainConfig account doesn't exist + let chain_config_account = context + .banks_client + .get_account(integrator_chain_config_pda) + .await + .expect("Failed to get account"); + assert!( + chain_config_account.is_none(), + "IntegratorChainConfig account should not exist" + ); + + // Try to enable the send transceiver + let result = enable_send_transceiver( + &mut context, + &admin, + &payer, + integrator_config_pda, + integrator_chain_config_pda, + registered_transceiver_pda, + chain_id, + transceiver, + integrator_program_id, + ) + .await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::AdminTransferInProgress.into()) + ) + ); + + // Verify that the IntegratorChainConfig account still doesn't exist + let chain_config_account = context + .banks_client + .get_account(integrator_chain_config_pda) + .await + .expect("Failed to get account"); + assert!( + chain_config_account.is_none(), + "IntegratorChainConfig account should not exist" + ); +} + +#[tokio::test] +async fn test_enable_transceiver_with_immutable_config() { + let mut context = setup().await; + let ( + admin, + integrator_program_id, + integrator_config_pda, + integrator_chain_config_pda, + registered_transceiver_pda, + transceiver, + chain_id, + ) = initialize_test_environment(&mut context).await; + + let payer = context.payer.insecure_clone(); + + // Discard the admin to make the config immutable + discard_admin(&mut context, &admin, &payer, integrator_config_pda) + .await + .unwrap(); + + // Now try to enable the receive transceiver + let result = enable_recv_transceiver( + &mut context, + &admin, + &payer, + integrator_config_pda, + integrator_chain_config_pda, + registered_transceiver_pda, + chain_id, + transceiver, + integrator_program_id, + ) + .await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) + ) + ); + + // Verify that the integrator config is immutable + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.is_immutable, true); +} diff --git a/svm/programs/mock-integrator/tests/instructions/register_transceiver.rs b/svm/programs/mock-integrator/tests/instructions/add_transceiver.rs similarity index 66% rename from svm/programs/mock-integrator/tests/instructions/register_transceiver.rs rename to svm/programs/mock-integrator/tests/instructions/add_transceiver.rs index c3115211..d79afd37 100644 --- a/svm/programs/mock-integrator/tests/instructions/register_transceiver.rs +++ b/svm/programs/mock-integrator/tests/instructions/add_transceiver.rs @@ -1,6 +1,6 @@ use anchor_lang::{InstructionData, ToAccountMetas}; -use router::accounts::RegisterTransceiver; -use router::instructions::RegisterTransceiverArgs; +use router::accounts::AddTransceiver; +use router::instructions::AddTransceiverArgs; use solana_program_test::*; use solana_sdk::{ instruction::Instruction, @@ -10,16 +10,16 @@ use solana_sdk::{ use crate::common::execute_transaction::execute_transaction; -pub async fn register_transceiver( +pub async fn add_transceiver( context: &mut ProgramTestContext, admin: &Keypair, payer: &Keypair, integrator_config: Pubkey, transceiver_info: Pubkey, - integrator_program: Pubkey, - transceiver_address: Pubkey, + integrator_program_id: Pubkey, + transceiver_program_id: Pubkey, ) -> Result<(), BanksClientError> { - let accounts = RegisterTransceiver { + let accounts = AddTransceiver { payer: payer.pubkey(), admin: admin.pubkey(), integrator_config, @@ -27,15 +27,15 @@ pub async fn register_transceiver( system_program: solana_sdk::system_program::id(), }; - let args = RegisterTransceiverArgs { - integrator_program, - transceiver_address, + let args = AddTransceiverArgs { + integrator_program_id, + transceiver_program_id, }; let ix = Instruction { program_id: router::id(), accounts: accounts.to_account_metas(None), - data: router::instruction::RegisterTransceiver { args }.data(), + data: router::instruction::AddTransceiver { args }.data(), }; execute_transaction(context, ix, &[admin, payer], payer).await diff --git a/svm/programs/mock-integrator/tests/instructions/disable_transceivers.rs b/svm/programs/mock-integrator/tests/instructions/disable_transceiver.rs similarity index 84% rename from svm/programs/mock-integrator/tests/instructions/disable_transceivers.rs rename to svm/programs/mock-integrator/tests/instructions/disable_transceiver.rs index 5aed283a..59fa8c97 100644 --- a/svm/programs/mock-integrator/tests/instructions/disable_transceivers.rs +++ b/svm/programs/mock-integrator/tests/instructions/disable_transceiver.rs @@ -1,6 +1,6 @@ use anchor_lang::{InstructionData, ToAccountMetas}; use router::accounts::DisableTransceiver; -use router::instructions::DisableTransceiverArgs; +use router::instructions::TransceiverInfoArgs; use solana_program_test::*; use solana_sdk::{ instruction::Instruction, @@ -42,13 +42,13 @@ pub async fn disable_recv_transceiver( integrator_chain_config: Pubkey, registered_transceiver: Pubkey, chain_id: u16, - transceiver: Pubkey, - integrator_program: Pubkey, + transceiver_program_id: Pubkey, + integrator_program_id: Pubkey, ) -> Result<(), BanksClientError> { - let args = DisableTransceiverArgs { + let args = TransceiverInfoArgs { chain_id, - transceiver, - integrator_program, + transceiver_program_id, + integrator_program_id, }; let instruction_data = router::instruction::DisableRecvTransceiver { args }.data(); execute_disable_transceiver( @@ -71,13 +71,13 @@ pub async fn disable_send_transceiver( integrator_chain_config: Pubkey, registered_transceiver: Pubkey, chain_id: u16, - transceiver: Pubkey, - integrator_program: Pubkey, + transceiver_program_id: Pubkey, + integrator_program_id: Pubkey, ) -> Result<(), BanksClientError> { - let args = DisableTransceiverArgs { + let args = TransceiverInfoArgs { chain_id, - transceiver, - integrator_program, + transceiver_program_id, + integrator_program_id, }; let instruction_data = router::instruction::DisableSendTransceiver { args }.data(); execute_disable_transceiver( diff --git a/svm/programs/mock-integrator/tests/instructions/discard_admin.rs b/svm/programs/mock-integrator/tests/instructions/discard_admin.rs new file mode 100644 index 00000000..91ca6589 --- /dev/null +++ b/svm/programs/mock-integrator/tests/instructions/discard_admin.rs @@ -0,0 +1,30 @@ +use anchor_lang::{InstructionData, ToAccountMetas}; +use router::accounts::DiscardAdmin; +use solana_program_test::*; +use solana_sdk::{ + instruction::Instruction, + pubkey::Pubkey, + signer::{keypair::Keypair, Signer}, +}; + +use crate::common::execute_transaction::execute_transaction; + +pub async fn discard_admin( + context: &mut ProgramTestContext, + admin: &Keypair, + payer: &Keypair, + integrator_config: Pubkey, +) -> Result<(), BanksClientError> { + let accounts = DiscardAdmin { + admin: admin.pubkey(), + integrator_config, + }; + + let ix = Instruction { + program_id: router::id(), + accounts: accounts.to_account_metas(None), + data: router::instruction::DiscardAdmin {}.data(), + }; + + execute_transaction(context, ix, &[admin, payer], payer).await +} diff --git a/svm/programs/mock-integrator/tests/instructions/set_transceivers.rs b/svm/programs/mock-integrator/tests/instructions/enable_transceiver.rs similarity index 69% rename from svm/programs/mock-integrator/tests/instructions/set_transceivers.rs rename to svm/programs/mock-integrator/tests/instructions/enable_transceiver.rs index 9331466d..98036b77 100644 --- a/svm/programs/mock-integrator/tests/instructions/set_transceivers.rs +++ b/svm/programs/mock-integrator/tests/instructions/enable_transceiver.rs @@ -1,6 +1,6 @@ use anchor_lang::{InstructionData, ToAccountMetas}; -use router::accounts::SetTransceiver; -use router::instructions::SetTransceiverArgs; +use router::accounts::EnableTransceiver; +use router::instructions::TransceiverInfoArgs; use solana_program_test::*; use solana_sdk::{ instruction::Instruction, @@ -10,7 +10,7 @@ use solana_sdk::{ use crate::common::execute_transaction::execute_transaction; -pub async fn execute_set_transceiver( +pub async fn execute_enable_transceiver( context: &mut ProgramTestContext, admin: &Keypair, payer: &Keypair, @@ -19,7 +19,7 @@ pub async fn execute_set_transceiver( registered_transceiver: Pubkey, instruction_data: Vec, ) -> Result<(), BanksClientError> { - let accounts = SetTransceiver { + let accounts = EnableTransceiver { payer: payer.pubkey(), admin: admin.pubkey(), integrator_config, @@ -36,7 +36,7 @@ pub async fn execute_set_transceiver( execute_transaction(context, ix, &[admin, payer], payer).await } -pub async fn set_recv_transceiver( +pub async fn enable_recv_transceiver( context: &mut ProgramTestContext, admin: &Keypair, payer: &Keypair, @@ -44,16 +44,16 @@ pub async fn set_recv_transceiver( integrator_chain_config: Pubkey, registered_transceiver: Pubkey, chain_id: u16, - transceiver: Pubkey, - integrator_program: Pubkey, + transceiver_program_id: Pubkey, + integrator_program_id: Pubkey, ) -> Result<(), BanksClientError> { - let args = SetTransceiverArgs { + let args = TransceiverInfoArgs { chain_id, - transceiver, - integrator_program, + transceiver_program_id, + integrator_program_id, }; - let instruction_data = router::instruction::SetRecvTransceiver { args }.data(); - execute_set_transceiver( + let instruction_data = router::instruction::EnableRecvTransceiver { args }.data(); + execute_enable_transceiver( context, admin, payer, @@ -65,7 +65,7 @@ pub async fn set_recv_transceiver( .await } -pub async fn set_send_transceiver( +pub async fn enable_send_transceiver( context: &mut ProgramTestContext, admin: &Keypair, payer: &Keypair, @@ -73,16 +73,16 @@ pub async fn set_send_transceiver( integrator_chain_config: Pubkey, registered_transceiver: Pubkey, chain_id: u16, - transceiver: Pubkey, - integrator_program: Pubkey, + transceiver_program_id: Pubkey, + integrator_program_id: Pubkey, ) -> Result<(), BanksClientError> { - let args = SetTransceiverArgs { + let args = TransceiverInfoArgs { chain_id, - transceiver, - integrator_program, + transceiver_program_id, + integrator_program_id, }; - let instruction_data = router::instruction::SetSendTransceiver { args }.data(); - execute_set_transceiver( + let instruction_data = router::instruction::EnableSendTransceiver { args }.data(); + execute_enable_transceiver( context, admin, payer, diff --git a/svm/programs/mock-integrator/tests/instructions/mod.rs b/svm/programs/mock-integrator/tests/instructions/mod.rs index 04db7379..a846df48 100644 --- a/svm/programs/mock-integrator/tests/instructions/mod.rs +++ b/svm/programs/mock-integrator/tests/instructions/mod.rs @@ -1,5 +1,7 @@ -pub mod disable_transceivers; +pub mod add_transceiver; +pub mod disable_transceiver; +pub mod discard_admin; +pub mod enable_transceiver; pub mod register; -pub mod register_transceiver; -pub mod set_transceivers; +pub mod transfer_admin; pub mod update_admin; diff --git a/svm/programs/mock-integrator/tests/instructions/register.rs b/svm/programs/mock-integrator/tests/instructions/register.rs index 5b2f24ea..b46cb1c9 100644 --- a/svm/programs/mock-integrator/tests/instructions/register.rs +++ b/svm/programs/mock-integrator/tests/instructions/register.rs @@ -1,6 +1,5 @@ use anchor_lang::{InstructionData, ToAccountMetas}; -use mock_integrator::accounts::InvokeRegister; -use router::instructions::RegisterArgs; +use mock_integrator::{accounts::InvokeRegister, InvokeRegisterArgs}; use solana_program_test::*; use solana_sdk::{ instruction::Instruction, @@ -17,21 +16,19 @@ pub async fn register( integrator_config: Pubkey, integrator_program_id: Pubkey, ) -> Result<(), BanksClientError> { - let (integrator_program_pda, integrator_program_pda_bump) = + let (integrator_program_pda, _) = Pubkey::find_program_address(&[b"router_integrator"], &integrator_program_id); let accounts = InvokeRegister { payer: payer.pubkey(), - admin: admin.pubkey(), integrator_config, integrator_program_pda, system_program: solana_sdk::system_program::id(), router_program: router::id(), }; - let args = RegisterArgs { - integrator_program_id, - integrator_program_pda_bump, + let args = InvokeRegisterArgs { + admin: admin.pubkey(), }; let ix = Instruction { diff --git a/svm/programs/mock-integrator/tests/instructions/transfer_admin.rs b/svm/programs/mock-integrator/tests/instructions/transfer_admin.rs new file mode 100644 index 00000000..b78dea08 --- /dev/null +++ b/svm/programs/mock-integrator/tests/instructions/transfer_admin.rs @@ -0,0 +1,57 @@ +use anchor_lang::{InstructionData, ToAccountMetas}; +use router::accounts::{ClaimAdmin, TransferAdmin}; +use solana_program_test::*; +use solana_sdk::{ + instruction::Instruction, + pubkey::Pubkey, + signer::{keypair::Keypair, Signer}, +}; + +use crate::common::execute_transaction::execute_transaction; + +pub async fn transfer_admin( + context: &mut ProgramTestContext, + admin: &Keypair, + new_admin: &Pubkey, + payer: &Keypair, + integrator_config: Pubkey, + integrator_program_id: Pubkey, +) -> Result<(), BanksClientError> { + let accounts = TransferAdmin { + admin: admin.pubkey(), + integrator_config, + }; + + let args = router::instructions::TransferAdminArgs { + integrator_program_id, + new_admin: *new_admin, + }; + + let ix = Instruction { + program_id: router::id(), + accounts: accounts.to_account_metas(None), + data: router::instruction::TransferAdmin { args }.data(), + }; + + execute_transaction(context, ix, &[admin, payer], payer).await +} + +pub async fn claim_admin( + context: &mut ProgramTestContext, + new_admin: &Keypair, + payer: &Keypair, + integrator_config: Pubkey, +) -> Result<(), BanksClientError> { + let accounts = ClaimAdmin { + new_admin: new_admin.pubkey(), + integrator_config, + }; + + let ix = Instruction { + program_id: router::id(), + accounts: accounts.to_account_metas(None), + data: router::instruction::ClaimAdmin {}.data(), + }; + + execute_transaction(context, ix, &[new_admin, payer], payer).await +} diff --git a/svm/programs/mock-integrator/tests/instructions/update_admin.rs b/svm/programs/mock-integrator/tests/instructions/update_admin.rs index 7bf53c2a..fc564271 100644 --- a/svm/programs/mock-integrator/tests/instructions/update_admin.rs +++ b/svm/programs/mock-integrator/tests/instructions/update_admin.rs @@ -15,19 +15,22 @@ pub async fn update_admin( new_admin: &Pubkey, payer: &Keypair, integrator_config: Pubkey, - integrator_program: Pubkey, + integrator_program_id: Pubkey, ) -> Result<(), BanksClientError> { let accounts = UpdateAdmin { admin: admin.pubkey(), - new_admin: *new_admin, integrator_config, - integrator_program, + }; + + let args = router::instructions::UpdateAdminArgs { + integrator_program_id, + new_admin: *new_admin, }; let ix = Instruction { program_id: router::id(), accounts: accounts.to_account_metas(None), - data: router::instruction::UpdateAdmin {}.data(), + data: router::instruction::UpdateAdmin { args }.data(), }; execute_transaction(context, ix, &[admin, payer], payer).await diff --git a/svm/programs/mock-integrator/tests/transfer_admin.rs b/svm/programs/mock-integrator/tests/transfer_admin.rs new file mode 100644 index 00000000..4f5c7970 --- /dev/null +++ b/svm/programs/mock-integrator/tests/transfer_admin.rs @@ -0,0 +1,397 @@ +#![cfg(feature = "test-sbf")] + +mod common; +mod instructions; + +use crate::instructions::discard_admin::discard_admin; +use crate::instructions::register::register; +use crate::instructions::transfer_admin::{claim_admin, transfer_admin}; +use anchor_lang::prelude::*; +use common::setup::{get_account, setup}; +use router::{error::RouterError, state::IntegratorConfig}; +use solana_program_test::*; +use solana_sdk::{ + instruction::InstructionError, signature::Keypair, signer::Signer, + transaction::TransactionError, +}; + +async fn setup_test_environment() -> (ProgramTestContext, Keypair, Keypair, Pubkey, Pubkey) { + let mut context = setup().await; + let payer = context.payer.insecure_clone(); + let admin = Keypair::new(); + let integrator_program_id = mock_integrator::id(); + + let (integrator_config_pda, _) = IntegratorConfig::pda(&integrator_program_id); + + register( + &mut context, + &payer, + &admin, + integrator_config_pda, + integrator_program_id, + ) + .await + .unwrap(); + + ( + context, + payer, + admin, + integrator_program_id, + integrator_config_pda, + ) +} + +#[tokio::test] +async fn test_transfer_admin_success() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + let new_admin = Keypair::new(); + + // Verify that there's no pending transfer initially + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.pending_admin, None); + + let result = transfer_admin( + &mut context, + &admin, + &new_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await; + + assert!(result.is_ok()); + + // Verify that the pending_admin has been set + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.pending_admin, Some(new_admin.pubkey())); + assert_eq!(integrator_config.admin, admin.pubkey()); // Admin should not change yet +} + +#[tokio::test] +async fn test_transfer_admin_with_pending_transfer() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + let new_admin1 = Keypair::new(); + let new_admin2 = Keypair::new(); + + // First transfer + transfer_admin( + &mut context, + &admin, + &new_admin1.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await + .unwrap(); + + // Attempt second transfer + let result = transfer_admin( + &mut context, + &admin, + &new_admin2.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::AdminTransferInProgress.into()) + ) + ); + + // Verify that the pending_admin is still the first new admin + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.pending_admin, Some(new_admin1.pubkey())); +} + +#[tokio::test] +async fn test_cancel_admin_transfer() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + let new_admin = Keypair::new(); + + // Initiate transfer + transfer_admin( + &mut context, + &admin, + &new_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await + .unwrap(); + + // Cancel transfer by claiming as current admin + let result = claim_admin(&mut context, &admin, &payer, integrator_config_pda).await; + + assert!(result.is_ok()); + + // Verify that the pending_admin has been cleared and admin remains unchanged + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.pending_admin, None); + assert_eq!(integrator_config.admin, admin.pubkey()); +} + +#[tokio::test] +async fn test_claim_admin_success() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + let new_admin = Keypair::new(); + + // First, transfer admin + transfer_admin( + &mut context, + &admin, + &new_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await + .unwrap(); + + // Verify that the pending_admin is set + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.pending_admin, Some(new_admin.pubkey())); + + // Now, claim admin + let result = claim_admin(&mut context, &new_admin, &payer, integrator_config_pda).await; + + assert!(result.is_ok()); + + // Verify that the admin has been updated and pending_admin is cleared + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.admin, new_admin.pubkey()); + assert_eq!(integrator_config.pending_admin, None); +} + +#[tokio::test] +async fn test_cancel_claim_admin_success() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + let new_admin = Keypair::new(); + + // First, transfer admin + transfer_admin( + &mut context, + &admin, + &new_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await + .unwrap(); + + // Verify that the pending_admin is set + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.pending_admin, Some(new_admin.pubkey())); + + // Now, claim admin + let result = claim_admin(&mut context, &admin, &payer, integrator_config_pda).await; + + assert!(result.is_ok()); + + // Verify that the admin has been updated and pending_admin is cleared + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.admin, admin.pubkey()); + assert_eq!(integrator_config.pending_admin, None); +} + +#[tokio::test] +async fn test_claim_admin_no_pending_admin() { + let (mut context, payer, admin, _, integrator_config_pda) = setup_test_environment().await; + + let random_user = Keypair::new(); + + // Attempt to claim admin with a random user (neither admin nor pending_admin) + let result = claim_admin(&mut context, &random_user, &payer, integrator_config_pda).await; + + // Assert that the operation fails with CallerNotAuthorized error + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) + ) + ); + + // Verify that the admin remains unchanged + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.admin, admin.pubkey()); + assert_eq!(integrator_config.pending_admin, None); +} + +#[tokio::test] +async fn test_transfer_admin_non_authority() { + let (mut context, payer, _, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + let non_authority = Keypair::new(); + let new_admin = Keypair::new(); + + let result = transfer_admin( + &mut context, + &non_authority, + &new_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) + ) + ); +} + +#[tokio::test] +async fn test_claim_admin_unauthorized() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + let new_admin = Keypair::new(); + let unauthorized = Keypair::new(); + + // First, transfer admin + transfer_admin( + &mut context, + &admin, + &new_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await + .unwrap(); + + // Now, try to claim admin with an unauthorized key + let result = claim_admin(&mut context, &unauthorized, &payer, integrator_config_pda).await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) + ) + ); +} + +#[tokio::test] +async fn test_transfer_admin_with_transfer_in_progress() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + let pending_admin = Keypair::new(); + let new_admin = Keypair::new(); + + // First, initiate a transfer + transfer_admin( + &mut context, + &admin, + &pending_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await + .unwrap(); + + // Now try to initiate another transfer + let result = transfer_admin( + &mut context, + &admin, + &new_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::AdminTransferInProgress.into()) + ) + ); + + // Verify that the admin and pending_admin haven't changed + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.admin, admin.pubkey()); + assert_eq!( + integrator_config.pending_admin, + Some(pending_admin.pubkey()) + ); +} + +#[tokio::test] +async fn test_transfer_admin_with_immutable_config() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + // Discard the admin to make the config immutable + discard_admin(&mut context, &admin, &payer, integrator_config_pda) + .await + .unwrap(); + + let new_admin = Keypair::new(); + + // Now try to transfer admin + let result = transfer_admin( + &mut context, + &admin, + &new_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) + ) + ); + + // Verify that the integrator config is immutable + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.is_immutable, true); +} diff --git a/svm/programs/mock-integrator/tests/update_admin.rs b/svm/programs/mock-integrator/tests/update_admin.rs index a75a109c..0a9c8681 100644 --- a/svm/programs/mock-integrator/tests/update_admin.rs +++ b/svm/programs/mock-integrator/tests/update_admin.rs @@ -3,7 +3,9 @@ mod common; mod instructions; +use crate::instructions::discard_admin::discard_admin; use crate::instructions::register::register; +use crate::instructions::transfer_admin::transfer_admin; use crate::instructions::update_admin::update_admin; use anchor_lang::prelude::*; use common::setup::{get_account, setup}; @@ -18,16 +20,16 @@ async fn setup_test_environment() -> (ProgramTestContext, Keypair, Keypair, Pubk let mut context = setup().await; let payer = context.payer.insecure_clone(); let admin = Keypair::new(); - let integrator_program = mock_integrator::id(); + let integrator_program_id = mock_integrator::id(); - let (integrator_config_pda, _) = IntegratorConfig::pda(&integrator_program); + let (integrator_config_pda, _) = IntegratorConfig::pda(&integrator_program_id); register( &mut context, &payer, &admin, integrator_config_pda, - integrator_program, + integrator_program_id, ) .await .unwrap(); @@ -36,14 +38,14 @@ async fn setup_test_environment() -> (ProgramTestContext, Keypair, Keypair, Pubk context, payer, admin, - integrator_program, + integrator_program_id, integrator_config_pda, ) } #[tokio::test] async fn test_update_admin_success() { - let (mut context, payer, admin, integrator_program, integrator_config_pda) = + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = setup_test_environment().await; let new_admin = Keypair::new(); @@ -54,7 +56,7 @@ async fn test_update_admin_success() { &new_admin.pubkey(), &payer, integrator_config_pda, - integrator_program, + integrator_program_id, ) .await; @@ -68,7 +70,7 @@ async fn test_update_admin_success() { #[tokio::test] async fn test_update_admin_non_authority() { - let (mut context, payer, admin, integrator_program, integrator_config_pda) = + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = setup_test_environment().await; let non_authority = Keypair::new(); @@ -80,7 +82,7 @@ async fn test_update_admin_non_authority() { &new_admin.pubkey(), &payer, integrator_config_pda, - integrator_program, + integrator_program_id, ) .await; @@ -89,7 +91,7 @@ async fn test_update_admin_non_authority() { result.unwrap_err().unwrap(), TransactionError::InstructionError( 0, - InstructionError::Custom(RouterError::InvalidIntegratorAuthority.into()) + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) ) ); @@ -101,7 +103,7 @@ async fn test_update_admin_non_authority() { #[tokio::test] async fn test_update_admin_same_address() { - let (mut context, payer, admin, integrator_program, integrator_config_pda) = + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = setup_test_environment().await; let result = update_admin( @@ -110,7 +112,7 @@ async fn test_update_admin_same_address() { &admin.pubkey(), &payer, integrator_config_pda, - integrator_program, + integrator_program_id, ) .await; @@ -121,3 +123,105 @@ async fn test_update_admin_same_address() { get_account(&mut context.banks_client, integrator_config_pda).await; assert_eq!(integrator_config.admin, admin.pubkey()); } + +#[tokio::test] +async fn test_update_admin_with_transfer_in_progress() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + let pending_admin = Keypair::new(); + let new_admin = Keypair::new(); + + // Initiate a transfer + transfer_admin( + &mut context, + &admin, + &pending_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await + .unwrap(); + + // Verify that the pending_admin is set + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!( + integrator_config.pending_admin, + Some(pending_admin.pubkey()) + ); + + // Try to update admin + let result = update_admin( + &mut context, + &admin, + &new_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await; + + // Check that the update fails due to transfer in progress + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::AdminTransferInProgress.into()) + ) + ); + + // Verify that the admin and pending_admin remain unchanged + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.admin, admin.pubkey()); + assert_eq!( + integrator_config.pending_admin, + Some(pending_admin.pubkey()) + ); +} + +#[tokio::test] +async fn test_update_admin_with_immutable_config() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; + + // Discard the admin to make the config immutable + crate::instructions::discard_admin::discard_admin( + &mut context, + &admin, + &payer, + integrator_config_pda, + ) + .await + .unwrap(); + + let new_admin = Keypair::new(); + + // Now try to update admin + let result = update_admin( + &mut context, + &admin, + &new_admin.pubkey(), + &payer, + integrator_config_pda, + integrator_program_id, + ) + .await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::CallerNotAuthorized.into()) + ) + ); + + // Verify that the integrator config is immutable + let integrator_config: IntegratorConfig = + get_account(&mut context.banks_client, integrator_config_pda).await; + assert_eq!(integrator_config.is_immutable, true); +} diff --git a/svm/programs/router/README.md b/svm/programs/router/README.md index 95e1256b..05f3c720 100644 --- a/svm/programs/router/README.md +++ b/svm/programs/router/README.md @@ -18,6 +18,7 @@ classDiagram *integrator_program_id: Pubkey admin: Pubkey registered_transceivers: Vec + is_immutable: boolean } class IntegratorChainConfig { @@ -31,7 +32,7 @@ classDiagram class TransceiverInfo { *bump: u8 *integrator_program_id: Pubkey - *transceiver_address: Pubkey + *transceiver_program_id: Pubkey id: u8 } @@ -118,21 +119,22 @@ This diagram illustrates the overall structure of the GMP Router program: ## Key Components -### IntegratorChainConfig +### IntegratorConfig -Manages transceivers enabled and config for a specific integrator on a particular chain. +Manages the configuration for a specific integrator. - **bump**: Bump seed for PDA derivation -- **chain_id**: Identifier for the blockchain network -- **integrator_program_id**: The program ID of the Integrator -- **recv_transceiver_bitmap**: Bitmap tracking enabled receive transceivers -- **send_transceiver_bitmap**: Bitmap tracking enabled send transceivers +- **integrator_program_id**: The program ID associated with this integrator +- **admin**: The current admin of the IntegratorConfig account +- **pending_admin**: The pending admin of the IntegratorConfig account (if a transfer is in progress) +- **registered_transceivers**: Vector of registered transceiver addresses +- **is_immutable**: A boolean to mark that the program is immutable **PDA Derivation**: -- Seeds: `[SEED_PREFIX, integrator_program_id, chain_id]` -- Unique for each integrator program and chain combination -- Initialization: Requires admin's signature and existing IntegratorConfig account +- Seeds: `[SEED_PREFIX, integrator_program_id]` +- Unique for each integrator program +- Initialization: Requires signer's signature ### IntegratorChainConfig @@ -161,7 +163,7 @@ Represents a registered transceiver in the GMP Router. **PDA Derivation**: -- Seeds: `[SEED_PREFIX, integrator_program_id, transceiver_address]` +- Seeds: `[SEED_PREFIX, integrator_program_id, transceiver_program_id]` - Unique for each transceiver within an integrator context **Constraints**: @@ -178,12 +180,15 @@ Utility struct for efficient storage and manipulation of boolean flags. ## Instructions 1. `register`: Registers an integrator and initializes their configuration -2. `register_transceiver`: Registers a new transceiver for an integrator -3. `set_recv_transceiver`: Sets a transceiver as a receive transceiver for a specific chain -4. `set_send_transceiver`: Sets a transceiver as a send transceiver for a specific chain +2. `add_transceiver`: Registers a new transceiver for an integrator +3. `enable_recv_transceiver`: Sets a transceiver as a receive transceiver for a specific chain +4. `enable_send_transceiver`: Sets a transceiver as a send transceiver for a specific chain 5. `disable_recv_transceiver`: Disables a receive transceiver for a specific chain 6. `disable_send_transceiver`: Disables a send transceiver for a specific chain -7. `update_admin`: Transfers admin of the IntegratorConfig to a new admin +7. `update_admin`: A one-step transfer of admin rights for the IntegratorConfig to a new admin +8. `transfer_admin`: Initiates the transfer of admin rights for the IntegratorConfig to a new admin +9. `claim_admin`: Completes the transfer of admin rights, allowing the new admin to claim authority +10. `discard_admin`: Sets IntegratorConfig as immutable to emulate discarding admin on EVM. Action is irreversible ## Error Handling @@ -194,43 +199,19 @@ The program uses a custom `RouterError` enum to handle various error cases, incl - `MaxTransceiversReached`: Maximum number of transceivers reached - `TransceiverAlreadyEnabled`: Transceiver was already enabled - `TransceiverAlreadyDisabled`: Transceiver was already disabled +- `AdminTransferInProgress`: An Admin transfer is in progress ## Testing -### Register - -- [x] Successful initialization of IntegratorConfig -- [x] Reinitialization (fails with AccountAlreadyInUse error) - -### RegisterTransceiver - -- [x] Successful registration -- [x] Registration of multiple transceivers -- [x] Registration of more than 128 transceivers (fails with MaxTransceiversReached) -- [x] Registration of duplicate transceiver (fails with AccountAlreadyInUse error) -- [x] Registration with non-authority signer (fails with InvalidIntegratorAuthority error) - -### SetTransceivers - -- [x] Successful setting of incoming transceivers -- [x] Successful setting of outgoing transceivers -- [x] Setting transceivers with invalid authority (fails with InvalidIntegratorAuthority error) -- [x] Setting transceivers with invalid transceiver ID (fails with AccountNotInitialized error) -- [x] Multiple updates of transceiver settings -- [x] Attempt to enable already enabled transceiver (fails with TransceiverAlreadyEnabledError) - -### DisableTransceivers - -- [x] Successful disabling of incoming transceivers -- [x] Successful disabling of outgoing transceivers -- [x] Disabling transceivers with invalid authority (fails with InvalidIntegratorAuthority error) -- [x] Disabling transceivers with invalid transceiver ID (fails with AccountNotInitialized error) -- [x] Attempt to disable already disabled transceiver (fails with TransceiverAlreadyDisabled error) - -### UpdateAdmin - -> **Note:** The `update_admin` logic needs to be redone. Ignore this for now - -- [x] Successful admin transfer -- [x] Transfer with invalid current admin -- [x] Transfer to the same admin +| Instruction | Requirements | Implemented Tests | +| -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| register(initialAdmin) | - Check caller not already registered
- Initialize registration and set initial admin | [x] Successful initialization of IntegratorConfig
[x] Reinitialization (fails with AccountAlreadyInUse error) | +| updateAdmin(integratorAddr, newAdmin) | - Check caller is current admin
- Check no pending transfer
- Check IntegratorConfig is not immutable
- Immediately set new admin | [x] Successful admin update
[x] Update with non-authority signer (fails with CallerNotAuthorized)
[x] Update to the same admin address (succeeds)
[x] Update when admin transfer in progress (fails with AdminTransferInProgress)
[x] Update when IntegratorConfig is immutable (fails with CallerNotAuthorized) | +| transferAdmin(integratorAddr, newAdmin) | - Check caller is current admin
- Check no pending transfer
- Check IntegratorConfig is not immutable
- Set pending admin to the new admin | [x] Successful initiation of admin transfer
[x] Transfer when transfer already in progress (fails with AdminTransferInProgress)
[x] Transfer by non-authority signer (fails with CallerNotAuthorized)
[x] Transfer when IntegratorConfig is immutable (fails with CallerNotAuthorized) | +| claimAdmin(integratorAddr) | - Check caller is current or pending admin
- Check admin transfer is pending
- Complete/cancel transfer | [x] Successful claiming of admin rights by new admin
[x] Successful claiming of admin rights by current admin (cancels transfer)
[x] Claim when there is no pending admin (fails with CallerNotAuthorized)
[x] Claim by unauthorized user (fails with CallerNotAuthorized) | +| discardAdmin(integratorAddr) | - Check caller is current admin
- Check no pending transfer
- Check IntegratorConfig is not immutable
- Clear current admin (make config immutable) | [x] Successful discarding of admin
[x] Discard when already discarded (fails with CallerNotAuthorized)
[x] Discard when transfer in progress (fails with AdminTransferInProgress) | +| addTransceiver(integratorAddr, transceiverAddr) | - Check caller is current admin
- Check no pending transfer
- Check IntegratorConfig is not immutable
- Check transceiver not already in array
- Check array won't surpass 128 entries
- Append transceiver to array | [x] Successful addition of a transceiver
[x] Addition of multiple transceivers
[x] Addition with non-authority signer (fails with CallerNotAuthorized)
[x] Addition when admin transfer in progress (fails with AdminTransferInProgress)
[x] Addition when IntegratorConfig is immutable (fails with CallerNotAuthorized)
[x] Register max transceivers (fails when exceeding)
[x] Reinitialization of existing transceiver (fails) | +| enableSendTransceiver(integratorAddr, chain, transceiverAddr) | - Check caller is current admin
- Check no pending transfer
- Check IntegratorConfig is not immutable
- Check transceiver in array
- Check transceiver currently disabled for sending
- Enable transceiver for sending | [x] Successful enabling of send transceiver
[x] Enabling with invalid admin (fails with CallerNotAuthorized)
[x] Enabling with invalid transceiver ID (fails with AccountNotInitialized)
[x] Enabling when admin transfer in progress (fails with AdminTransferInProgress)
[x] Enabling when IntegratorConfig is immutable (fails with CallerNotAuthorized)
[x] Enabling already enabled transceiver (fails with TransceiverAlreadyEnabled) | +| disableSendTransceiver(integratorAddr, chain, transceiverAddr) | - Check caller is current admin
- Check no pending transfer
- Check IntegratorConfig is not immutable
- Check transceiver in array
- Check transceiver currently enabled for sending
- Disable transceiver for sending | [x] Successful disabling of send transceiver
[x] Disabling with invalid admin (fails with CallerNotAuthorized)
[x] Disabling when admin transfer in progress (fails with AdminTransferInProgress)
[x] Disabling when IntegratorConfig is immutable (fails with CallerNotAuthorized)
[x] Disabling already disabled transceiver (fails with TransceiverAlreadyDisabled) | +| enableRecvTransceiver(integratorAddr, chain, transceiverAddr) | - Check caller is current admin
- Check no pending transfer
- Check IntegratorConfig is not immutable
- Check transceiver in array
- Check transceiver currently disabled for receiving
- Enable transceiver for receiving | [x] Successful enabling of receive transceiver
[x] Enabling with invalid admin (fails with CallerNotAuthorized)
[x] Enabling with invalid transceiver ID (fails with AccountNotInitialized)
[x] Enabling when admin transfer in progress (fails with AdminTransferInProgress)
[x] Enabling when IntegratorConfig is immutable (fails with CallerNotAuthorized)
[x] Enabling already enabled transceiver (fails with TransceiverAlreadyEnabled) | +| disableRecvTransceiver(integratorAddr, chain, transceiverAddr) | - Check caller is current admin
- Check no pending transfer
- Check IntegratorConfig is not immutable
- Check transceiver in array
- Check transceiver currently enabled for receiving
- Disable transceiver for receiving | [x] Successful disabling of receive transceiver
[x] Disabling with invalid admin (fails with CallerNotAuthorized)
[x] Disabling when admin transfer in progress (fails with AdminTransferInProgress)
[x] Disabling when IntegratorConfig is immutable (fails with CallerNotAuthorized)
[x] Disabling already disabled transceiver (fails with TransceiverAlreadyDisabled) | diff --git a/svm/programs/router/src/error.rs b/svm/programs/router/src/error.rs index b6626e75..893b64ed 100644 --- a/svm/programs/router/src/error.rs +++ b/svm/programs/router/src/error.rs @@ -3,8 +3,8 @@ use anchor_lang::prelude::*; #[error_code] #[derive(PartialEq)] pub enum RouterError { - #[msg("Invalid integrator authority")] - InvalidIntegratorAuthority, + #[msg("Caller is not authorized")] + CallerNotAuthorized, #[msg("Bitmap index is out of bounds")] BitmapIndexOutOfBounds, @@ -17,4 +17,7 @@ pub enum RouterError { #[msg("Transceiver was already disabled")] TransceiverAlreadyDisabled, + + #[msg("An admin transfer is in progress")] + AdminTransferInProgress, } diff --git a/svm/programs/router/src/instructions/register_transceiver.rs b/svm/programs/router/src/instructions/add_transceiver.rs similarity index 59% rename from svm/programs/router/src/instructions/register_transceiver.rs rename to svm/programs/router/src/instructions/add_transceiver.rs index 56e7531d..41bd8743 100644 --- a/svm/programs/router/src/instructions/register_transceiver.rs +++ b/svm/programs/router/src/instructions/add_transceiver.rs @@ -1,24 +1,31 @@ use crate::error::RouterError; +use crate::error::RouterError::AdminTransferInProgress; use crate::state::{IntegratorConfig, TransceiverInfo}; use anchor_lang::prelude::*; -/// Arguments for the register_transceiver instruction +/// Arguments for the add_transceiver instruction #[derive(AnchorSerialize, AnchorDeserialize)] -pub struct RegisterTransceiverArgs { +pub struct AddTransceiverArgs { /// The Pubkey of the integrator program - pub integrator_program: Pubkey, + pub integrator_program_id: Pubkey, /// The Pubkey of the transceiver to be registered - pub transceiver_address: Pubkey, + pub transceiver_program_id: Pubkey, +} + +impl<'info> AddTransceiver<'info> { + pub fn validate(&self) -> Result<()> { + self.integrator_config.check_admin(&self.admin) + } } #[derive(Accounts)] -#[instruction(args: RegisterTransceiverArgs)] -pub struct RegisterTransceiver<'info> { +#[instruction(args: AddTransceiverArgs)] +pub struct AddTransceiver<'info> { #[account(mut)] pub payer: Signer<'info>, - /// The admin registered on IntegratroConfig + /// The admin registered on IntegratorConfig #[account(mut)] pub admin: Signer<'info>, @@ -26,23 +33,26 @@ pub struct RegisterTransceiver<'info> { /// This makes sure that the admin signing this ix is the one registed in the IntegratorConfig /// The new registered transceiver will be pushed to the `registered_transceivers` field in /// this account + /// `has_one` constraint checks if admin signer is the current admin of the config #[account( mut, - seeds = [IntegratorConfig::SEED_PREFIX, args.integrator_program.as_ref()], + seeds = [IntegratorConfig::SEED_PREFIX, args.integrator_program_id.as_ref()], bump = integrator_config.bump, - has_one = admin @ RouterError::InvalidIntegratorAuthority, + has_one = admin @ RouterError::CallerNotAuthorized, )] pub integrator_config: Account<'info, IntegratorConfig>, /// The account to store information about the registered transceiver + /// The `init` constraint checks that the transceiver has not been added. If it is, + /// `AccountAlreadyInUse` error will be thrown #[account( init, payer = payer, space = 8 + TransceiverInfo::INIT_SPACE, seeds = [ TransceiverInfo::SEED_PREFIX, - args.integrator_program.as_ref(), - args.transceiver_address.as_ref(), + args.integrator_program_id.as_ref(), + args.transceiver_program_id.as_ref(), ], bump )] @@ -64,34 +74,32 @@ pub struct RegisterTransceiver<'info> { /// * `ctx` - The context for the instruction, containing the accounts. /// * `args` - The arguments for registering a transceiver, including: /// * `integrator_program`: The Pubkey of the integrator program. -/// * `transceiver_address`: The Pubkey of the transceiver to be registered. +/// * `transceiver_program_id`: The Pubkey of the transceiver to be registered. /// /// # Returns /// /// Returns `Ok(())` if the transceiver is successfully registered, or an error otherwise. -pub fn register_transceiver( - ctx: Context, - args: RegisterTransceiverArgs, -) -> Result<()> { - let transceiver_id = ctx.accounts.integrator_config.registered_transceivers.len() as u8; - - // Check if we've reached the maximum number of transceivers - if transceiver_id >= IntegratorConfig::MAX_TRANSCEIVERS as u8 { - return Err(RouterError::MaxTransceiversReached.into()); +#[access_control(AddTransceiver::validate(&ctx.accounts))] +pub fn add_transceiver(ctx: Context, args: AddTransceiverArgs) -> Result<()> { + // Check if there's a pending_config + if ctx.accounts.integrator_config.pending_admin.is_some() { + return Err(AdminTransferInProgress.into()); } + let transceiver_id = ctx.accounts.integrator_config.registered_transceivers.len() as u8; + // Add the new transceiver to the list + // The vector length check is in `add_transceiver` ctx.accounts .integrator_config - .registered_transceivers - .push(args.transceiver_address); + .add_transceiver(args.transceiver_program_id)?; // Initialize TransceiverInfo ctx.accounts.transceiver_info.set_inner(TransceiverInfo { bump: ctx.bumps.transceiver_info, - id: transceiver_id, - integrator_program_id: args.integrator_program, - transceiver_address: args.transceiver_address, + index: transceiver_id, + integrator_program_id: args.integrator_program_id, + transceiver_program_id: args.transceiver_program_id, }); Ok(()) diff --git a/svm/programs/router/src/instructions/common/mod.rs b/svm/programs/router/src/instructions/common/mod.rs new file mode 100644 index 00000000..09f6a92d --- /dev/null +++ b/svm/programs/router/src/instructions/common/mod.rs @@ -0,0 +1,3 @@ +pub mod transceiver_info_args; + +pub use transceiver_info_args::*; diff --git a/svm/programs/router/src/instructions/common/transceiver_info_args.rs b/svm/programs/router/src/instructions/common/transceiver_info_args.rs new file mode 100644 index 00000000..74822513 --- /dev/null +++ b/svm/programs/router/src/instructions/common/transceiver_info_args.rs @@ -0,0 +1,14 @@ +use anchor_lang::prelude::*; + +/// Common arguments for transceiver info operations +#[derive(AnchorSerialize, AnchorDeserialize)] +pub struct TransceiverInfoArgs { + /// The ID of the chain + pub chain_id: u16, + + /// The Pubkey of the transceiver + pub transceiver_program_id: Pubkey, + + /// The Pubkey of the integrator program + pub integrator_program_id: Pubkey, +} diff --git a/svm/programs/router/src/instructions/disable_transceivers.rs b/svm/programs/router/src/instructions/disable_transceiver.rs similarity index 77% rename from svm/programs/router/src/instructions/disable_transceivers.rs rename to svm/programs/router/src/instructions/disable_transceiver.rs index 1a88e4e1..6074f9a2 100644 --- a/svm/programs/router/src/instructions/disable_transceivers.rs +++ b/svm/programs/router/src/instructions/disable_transceiver.rs @@ -1,22 +1,10 @@ use crate::error::RouterError; +use crate::instructions::common::TransceiverInfoArgs; use crate::state::{IntegratorChainConfig, IntegratorConfig, TransceiverInfo}; use anchor_lang::prelude::*; -/// Arguments for disabling a transceiver -#[derive(AnchorSerialize, AnchorDeserialize)] -pub struct DisableTransceiverArgs { - /// The ID of the chain - pub chain_id: u16, - - /// The Pubkey of the transceiver to be disabled - pub transceiver: Pubkey, - - /// The Pubkey of the integrator program - pub integrator_program: Pubkey, -} - #[derive(Accounts)] -#[instruction(args: DisableTransceiverArgs)] +#[instruction(args: TransceiverInfoArgs)] pub struct DisableTransceiver<'info> { /// The admin account that has the authority to disable transceivers pub admin: Signer<'info>, @@ -25,9 +13,9 @@ pub struct DisableTransceiver<'info> { /// The account constraints here make sure that the one signing this transaction is the admin /// of the config #[account( - seeds = [IntegratorConfig::SEED_PREFIX, args.integrator_program.as_ref()], + seeds = [IntegratorConfig::SEED_PREFIX, args.integrator_program_id.as_ref()], bump = integrator_config.bump, - has_one = admin @ RouterError::InvalidIntegratorAuthority, + has_one = admin @ RouterError::CallerNotAuthorized, )] pub integrator_config: Account<'info, IntegratorConfig>, @@ -37,8 +25,8 @@ pub struct DisableTransceiver<'info> { mut, seeds = [ IntegratorChainConfig::SEED_PREFIX, - args.integrator_program.as_ref(), - args.chain_id.to_le_bytes().as_ref(), + args.integrator_program_id.as_ref(), + args.chain_id.to_be_bytes().as_ref(), ], bump, )] @@ -50,14 +38,18 @@ pub struct DisableTransceiver<'info> { #[account( seeds = [ TransceiverInfo::SEED_PREFIX, - args.integrator_program.as_ref(), - args.transceiver.as_ref(), + args.integrator_program_id.as_ref(), + args.transceiver_program_id.as_ref(), ], bump = registered_transceiver.bump, )] pub registered_transceiver: Account<'info, TransceiverInfo>, } - +impl<'info> DisableTransceiver<'info> { + pub fn validate(&self) -> Result<()> { + self.integrator_config.check_admin(&self.admin) + } +} /// Disables a receive transceiver /// /// # Arguments @@ -68,9 +60,10 @@ pub struct DisableTransceiver<'info> { /// # Returns /// /// * `Result<()>` - Ok if the transceiver was successfully disabled, otherwise an error +#[access_control(DisableTransceiver::validate(&ctx.accounts))] pub fn disable_recv_transceiver( ctx: Context, - _args: DisableTransceiverArgs, + _args: TransceiverInfoArgs, ) -> Result<()> { let registered_transceiver = &ctx.accounts.registered_transceiver; let integrator_chain_config = &mut ctx.accounts.integrator_chain_config; @@ -78,7 +71,7 @@ pub fn disable_recv_transceiver( // Check if the transceiver is already disabled if !integrator_chain_config .recv_transceiver_bitmap - .get(registered_transceiver.id)? + .get(registered_transceiver.index)? { return Err(RouterError::TransceiverAlreadyDisabled.into()); } @@ -86,7 +79,7 @@ pub fn disable_recv_transceiver( // Disable the transceiver in the bitmap integrator_chain_config .recv_transceiver_bitmap - .set(registered_transceiver.id, false)?; + .set(registered_transceiver.index, false)?; Ok(()) } @@ -101,9 +94,10 @@ pub fn disable_recv_transceiver( /// # Returns /// /// * `Result<()>` - Ok if the transceiver was successfully disabled, otherwise an error +#[access_control(DisableTransceiver::validate(&ctx.accounts))] pub fn disable_send_transceiver( ctx: Context, - _args: DisableTransceiverArgs, + _args: TransceiverInfoArgs, ) -> Result<()> { let registered_transceiver = &ctx.accounts.registered_transceiver; let integrator_chain_config = &mut ctx.accounts.integrator_chain_config; @@ -111,7 +105,7 @@ pub fn disable_send_transceiver( // Check if the transceiver is already disabled if !integrator_chain_config .send_transceiver_bitmap - .get(registered_transceiver.id)? + .get(registered_transceiver.index)? { return Err(RouterError::TransceiverAlreadyDisabled.into()); } @@ -119,7 +113,7 @@ pub fn disable_send_transceiver( // Disable the transceiver in the bitmap integrator_chain_config .send_transceiver_bitmap - .set(registered_transceiver.id, false)?; + .set(registered_transceiver.index, false)?; Ok(()) } diff --git a/svm/programs/router/src/instructions/discard_admin.rs b/svm/programs/router/src/instructions/discard_admin.rs new file mode 100644 index 00000000..c5093cb3 --- /dev/null +++ b/svm/programs/router/src/instructions/discard_admin.rs @@ -0,0 +1,31 @@ +use crate::state::IntegratorConfig; +use anchor_lang::prelude::*; + +#[derive(Accounts)] +pub struct DiscardAdmin<'info> { + /// The current admin of the IntegratorConfig account + pub admin: Signer<'info>, + + /// The IntegratorConfig account being modified + #[account( + mut, + seeds = [ + IntegratorConfig::SEED_PREFIX, + integrator_config.integrator_program_id.key().as_ref(), + ], + bump = integrator_config.bump, + )] + pub integrator_config: Account<'info, IntegratorConfig>, +} + +impl<'info> DiscardAdmin<'info> { + pub fn validate(&self) -> Result<()> { + self.integrator_config.check_admin(&self.admin) + } +} + +#[access_control(DiscardAdmin::validate(&ctx.accounts))] +pub fn discard_admin(ctx: Context) -> Result<()> { + ctx.accounts.integrator_config.is_immutable = true; + Ok(()) +} diff --git a/svm/programs/router/src/instructions/set_transceivers.rs b/svm/programs/router/src/instructions/enable_transceiver.rs similarity index 73% rename from svm/programs/router/src/instructions/set_transceivers.rs rename to svm/programs/router/src/instructions/enable_transceiver.rs index 70607efd..b12309d3 100644 --- a/svm/programs/router/src/instructions/set_transceivers.rs +++ b/svm/programs/router/src/instructions/enable_transceiver.rs @@ -1,22 +1,11 @@ use crate::error::RouterError; +use crate::instructions::common::TransceiverInfoArgs; use crate::state::{IntegratorChainConfig, IntegratorConfig, TransceiverInfo}; use anchor_lang::prelude::*; -#[derive(AnchorSerialize, AnchorDeserialize)] -pub struct SetTransceiverArgs { - /// The chain ID for the integrator chain configuration - pub chain_id: u16, - - /// The Pubkey of the transceiver to be set - pub transceiver: Pubkey, - - /// The Pubkey of the integrator program - pub integrator_program: Pubkey, -} - #[derive(Accounts)] -#[instruction(args: SetTransceiverArgs)] -pub struct SetTransceiver<'info> { +#[instruction(args: TransceiverInfoArgs)] +pub struct EnableTransceiver<'info> { /// The account that pays for the transaction #[account(mut)] pub payer: Signer<'info>, @@ -27,10 +16,11 @@ pub struct SetTransceiver<'info> { /// The integrator config account /// The account constraints here make sure that the one signing this transaction is the admin /// of the config + /// The `has_one` constraint checks if admin signer is the current admin of the config #[account( - seeds = [IntegratorConfig::SEED_PREFIX, args.integrator_program.as_ref()], + seeds = [IntegratorConfig::SEED_PREFIX, args.integrator_program_id.as_ref()], bump = integrator_config.bump, - has_one = admin @ RouterError::InvalidIntegratorAuthority, + has_one = admin @ RouterError::CallerNotAuthorized, )] pub integrator_config: Account<'info, IntegratorConfig>, @@ -42,8 +32,8 @@ pub struct SetTransceiver<'info> { space = 8 + IntegratorChainConfig::INIT_SPACE, seeds = [ IntegratorChainConfig::SEED_PREFIX, - args.integrator_program.as_ref(), - args.chain_id.to_le_bytes().as_ref(), + args.integrator_program_id.as_ref(), + args.chain_id.to_be_bytes().as_ref(), ], bump, )] @@ -55,8 +45,8 @@ pub struct SetTransceiver<'info> { #[account( seeds = [ TransceiverInfo::SEED_PREFIX, - args.integrator_program.as_ref(), - args.transceiver.as_ref(), + args.integrator_program_id.as_ref(), + args.transceiver_program_id.as_ref(), ], bump = registered_transceiver.bump, )] @@ -65,7 +55,11 @@ pub struct SetTransceiver<'info> { /// The System Program pub system_program: Program<'info, System>, } - +impl<'info> EnableTransceiver<'info> { + pub fn validate(&self) -> Result<()> { + self.integrator_config.check_admin(&self.admin) + } +} /// Sets a receive transceiver for the integrator chain configuration /// /// # Arguments @@ -79,25 +73,24 @@ pub struct SetTransceiver<'info> { /// # Returns /// /// * `Result<()>` - The result of the operation -pub fn set_recv_transceiver(ctx: Context, _args: SetTransceiverArgs) -> Result<()> { - msg!( - "Set Recv Transceiver PDA: {:?}", - ctx.accounts.integrator_chain_config.key() - ); - +#[access_control(EnableTransceiver::validate(&ctx.accounts))] +pub fn enable_recv_transceiver( + ctx: Context, + _args: TransceiverInfoArgs, +) -> Result<()> { let registered_transceiver = &ctx.accounts.registered_transceiver; let integrator_chain_config = &mut ctx.accounts.integrator_chain_config; if integrator_chain_config .recv_transceiver_bitmap - .get(registered_transceiver.id)? + .get(registered_transceiver.index)? { return Err(RouterError::TransceiverAlreadyEnabled.into()); } integrator_chain_config .recv_transceiver_bitmap - .set(registered_transceiver.id, true)?; + .set(registered_transceiver.index, true)?; Ok(()) } @@ -115,20 +108,24 @@ pub fn set_recv_transceiver(ctx: Context, _args: SetTransceiverA /// # Returns /// /// * `Result<()>` - The result of the operation -pub fn set_send_transceiver(ctx: Context, _args: SetTransceiverArgs) -> Result<()> { +#[access_control(EnableTransceiver::validate(&ctx.accounts))] +pub fn enable_send_transceiver( + ctx: Context, + _args: TransceiverInfoArgs, +) -> Result<()> { let registered_transceiver = &ctx.accounts.registered_transceiver; let integrator_chain_config = &mut ctx.accounts.integrator_chain_config; if integrator_chain_config .send_transceiver_bitmap - .get(registered_transceiver.id)? + .get(registered_transceiver.index)? { return Err(RouterError::TransceiverAlreadyEnabled.into()); } integrator_chain_config .send_transceiver_bitmap - .set(registered_transceiver.id, true)?; + .set(registered_transceiver.index, true)?; Ok(()) } diff --git a/svm/programs/router/src/instructions/mod.rs b/svm/programs/router/src/instructions/mod.rs index d191a0fd..d943fbfe 100644 --- a/svm/programs/router/src/instructions/mod.rs +++ b/svm/programs/router/src/instructions/mod.rs @@ -1,11 +1,17 @@ -pub mod disable_transceivers; +pub mod add_transceiver; +pub mod common; +pub mod disable_transceiver; +pub mod discard_admin; +pub mod enable_transceiver; pub mod register; -pub mod register_transceiver; -pub mod set_transceivers; +pub mod transfer_admin; pub mod update_admin; -pub use disable_transceivers::*; +pub use add_transceiver::*; +pub use common::*; +pub use disable_transceiver::*; +pub use discard_admin::*; +pub use enable_transceiver::*; pub use register::*; -pub use register_transceiver::*; -pub use set_transceivers::*; +pub use transfer_admin::*; pub use update_admin::*; diff --git a/svm/programs/router/src/instructions/register.rs b/svm/programs/router/src/instructions/register.rs index 3c968aa6..08640d74 100644 --- a/svm/programs/router/src/instructions/register.rs +++ b/svm/programs/router/src/instructions/register.rs @@ -8,6 +8,9 @@ pub struct RegisterArgs { // Bump to make sure the same PDA is derived pub integrator_program_pda_bump: u8, + + // Admin of the IntegratorConfig account + pub admin: Pubkey, } #[derive(Accounts)] @@ -16,11 +19,8 @@ pub struct Register<'info> { #[account(mut)] pub payer: Signer<'info>, - /// The admin of the IntegratorConfig account - /// CHECK: The integrator program is responsible for passing the correct admin - pub admin: UncheckedAccount<'info>, - /// The IntegratorConfig account being initialized + /// `init` constraint checks that caller is not already registered #[account( init, payer = payer, @@ -35,6 +35,12 @@ pub struct Register<'info> { /// The integrator program's PDA /// This makes sure that the Signer is a Integrator Program PDA Signer + /// TODO: Ideally there is a `AccountUncheckedOwner` that does not explicitly enforce owner + /// check on AccountUncheckedOwner and use the `owner = another_program.ID` but it is not + /// possible for now. So we have to pass in the bump manually in the args to use it here + /// This is easier for monitoring anyways since you don't have to lookup the this account to + /// get the integrator program id and bump + /// Link to discussion: https://github.com/coral-xyz/anchor/issues/3285#issuecomment-2381329832 #[account( seeds = [b"router_integrator"], bump = args.integrator_program_pda_bump, @@ -63,19 +69,15 @@ pub struct Register<'info> { /// /// Returns `Ok(())` if the registration is successful, or an error if it fails pub fn register(ctx: Context, args: RegisterArgs) -> Result<()> { - msg!( - "Initializing IntegratorConfig for program: {}", - args.integrator_program_id - ); - // Initialize the IntegratorConfig account with the provided information ctx.accounts.integrator_config.set_inner(IntegratorConfig { bump: ctx.bumps.integrator_config, - admin: ctx.accounts.admin.key(), + admin: args.admin, + pending_admin: None, integrator_program_id: args.integrator_program_id, registered_transceivers: Vec::new(), + is_immutable: false, }); - msg!("IntegratorConfig initialized successfully"); Ok(()) } diff --git a/svm/programs/router/src/instructions/transfer_admin.rs b/svm/programs/router/src/instructions/transfer_admin.rs new file mode 100644 index 00000000..87d4b1f3 --- /dev/null +++ b/svm/programs/router/src/instructions/transfer_admin.rs @@ -0,0 +1,98 @@ +use crate::error::RouterError; +use crate::state::IntegratorConfig; +use anchor_lang::prelude::*; + +#[derive(AnchorSerialize, AnchorDeserialize)] +pub struct TransferAdminArgs { + /// The new_admin to be assigned + pub new_admin: Pubkey, + + /// The integrator_program for the integrator_config + pub integrator_program_id: Pubkey, +} + +#[derive(Accounts)] +#[instruction(args: TransferAdminArgs)] +pub struct TransferAdmin<'info> { + /// The current admin of the IntegratorConfig account + pub admin: Signer<'info>, + + /// The IntegratorConfig account being transferred + /// `has_one` constraint checks that the signer is the current admin + #[account( + mut, + seeds = [ + IntegratorConfig::SEED_PREFIX, + args.integrator_program_id.key().as_ref(), + ], + bump = integrator_config.bump, + has_one = admin @ RouterError::CallerNotAuthorized, + )] + pub integrator_config: Account<'info, IntegratorConfig>, +} + +impl<'info> TransferAdmin<'info> { + pub fn validate(&self) -> Result<()> { + self.integrator_config.check_admin(&self.admin) + } +} + +#[derive(Accounts)] +pub struct ClaimAdmin<'info> { + /// The signer, which can be either the pending_admin or the current admin + pub new_admin: Signer<'info>, + + /// The IntegratorConfig account being claimed + /// The constraint here checks that the signer is either the pending_admin or the current_admin + #[account( + mut, + constraint = (integrator_config.pending_admin == Some(new_admin.key()) || integrator_config.admin == new_admin.key()) @ RouterError::CallerNotAuthorized, + )] + pub integrator_config: Account<'info, IntegratorConfig>, +} + +/// Initiates the transfer of admin rights for an IntegratorConfig account. +/// +/// This function sets a pending admin for the IntegratorConfig account. The pending admin +/// must later claim the admin rights using the `claim_admin` function. +/// +/// If there is already a transfer in progress (in other words `pending_admin` is not null) +/// `AdminTransferInProgress` will be returned +/// +/// # Arguments +/// +/// * `ctx` - The context of the request, containing the accounts involved in the admin update. +/// * `args` - The TransferAdminArg struct containing the new admin's public key. +/// +/// # Returns +/// +/// Returns `Ok(())` if setting the pending admin is successful, otherwise returns an error. +#[access_control(TransferAdmin::validate(&ctx.accounts))] +pub fn transfer_admin(ctx: Context, args: TransferAdminArgs) -> Result<()> { + // Checks that there is no pending transfer + if ctx.accounts.integrator_config.pending_admin.is_some() { + return Err(RouterError::AdminTransferInProgress.into()); + } + + ctx.accounts.integrator_config.pending_admin = Some(args.new_admin); + Ok(()) +} + +/// Claims the admin rights for an IntegratorConfig account. +/// +/// This function allows either the current admin or the pending admin to claim the admin rights, +/// completing the two-step admin transfer process or cancelling the transfer by setting the admin +/// back as the current admin +/// +/// # Arguments +/// +/// * `ctx` - The context of the request, containing the accounts involved in claiming admin rights. +/// +/// # Returns +/// +/// Returns `Ok(())` if claiming admin rights is successful, otherwise returns an error. +pub fn claim_admin(ctx: Context) -> Result<()> { + ctx.accounts.integrator_config.admin = ctx.accounts.new_admin.key(); + ctx.accounts.integrator_config.pending_admin = None; + Ok(()) +} diff --git a/svm/programs/router/src/instructions/update_admin.rs b/svm/programs/router/src/instructions/update_admin.rs index 92085f48..a7968008 100644 --- a/svm/programs/router/src/instructions/update_admin.rs +++ b/svm/programs/router/src/instructions/update_admin.rs @@ -2,56 +2,65 @@ use crate::error::RouterError; use crate::state::IntegratorConfig; use anchor_lang::prelude::*; +#[derive(AnchorSerialize, AnchorDeserialize)] +pub struct UpdateAdminArgs { + /// The new_admin to be assigned + pub new_admin: Pubkey, + + /// The integrator_program for the integrator_config + pub integrator_program_id: Pubkey, +} + #[derive(Accounts)] +#[instruction(args: UpdateAdminArgs)] pub struct UpdateAdmin<'info> { /// The current admin of the IntegratorConfig account pub admin: Signer<'info>, - /// The new admin of the IntegratorConfig account - /// CHECK: The integrator program is responsible for passing the correct admin - pub new_admin: UncheckedAccount<'info>, - /// The IntegratorConfig account being transferred + /// `has_one` constraint checks that the signer is the current admin #[account( mut, seeds = [ IntegratorConfig::SEED_PREFIX, - integrator_program.key().as_ref(), + args.integrator_program_id.key().as_ref(), ], bump = integrator_config.bump, - has_one = admin @ RouterError::InvalidIntegratorAuthority, + has_one = admin @ RouterError::CallerNotAuthorized, )] pub integrator_config: Account<'info, IntegratorConfig>, +} - /// The integrator program - /// CHECK: This account is not read or written in this instruction - pub integrator_program: UncheckedAccount<'info>, +impl<'info> UpdateAdmin<'info> { + pub fn validate(&self) -> Result<()> { + self.integrator_config.check_admin(&self.admin) + } } /// Updates the admin of an IntegratorConfig account. /// -/// This function transfers the adminship of an IntegratorConfig account from the current admin +/// This function transfers the administration of an IntegratorConfig account from the current admin /// to a new admin. It checks that the current admin is the signer of the transaction and updates /// the admin field in the IntegratorConfig account. /// /// # Arguments /// /// * `ctx` - The context of the request, containing the accounts involved in the admin update. +/// * `args` - The UpdateAdminArg struct containing the new admin's public key. /// /// # Returns /// /// Returns `Ok(())` if the admin update is successful, otherwise returns an error. -pub fn update_admin(ctx: Context) -> Result<()> { - msg!( - "Transferring IntegratorConfig admin from {} to {}", - ctx.accounts.admin.key(), - ctx.accounts.new_admin.key() - ); +#[access_control(UpdateAdmin::validate(&ctx.accounts))] +pub fn update_admin(ctx: Context, args: UpdateAdminArgs) -> Result<()> { + // Check if there's a pending admin transfer + if ctx.accounts.integrator_config.pending_admin.is_some() { + return Err(RouterError::AdminTransferInProgress.into()); + } ctx.accounts .integrator_config - .update_admin(&ctx.accounts.admin, ctx.accounts.new_admin.key())?; + .update_admin(args.new_admin)?; - msg!("IntegratorConfig adminship transferred successfully"); Ok(()) } diff --git a/svm/programs/router/src/lib.rs b/svm/programs/router/src/lib.rs index aa0151bb..875082bb 100644 --- a/svm/programs/router/src/lib.rs +++ b/svm/programs/router/src/lib.rs @@ -31,14 +31,11 @@ pub mod router { /// # Arguments /// /// * `ctx` - The context of the instruction - /// * `args` - The `RegisterTransceiverArgs` struct containing: + /// * `args` - The `EnableTransceiverArgs` struct containing: /// * `integrator_program` - The program id of the integrator_program - /// * `transceiver_address` - The address of the transceiver to register - pub fn register_transceiver( - ctx: Context, - args: RegisterTransceiverArgs, - ) -> Result<()> { - instructions::register_transceiver::register_transceiver(ctx, args) + /// * `transceiver_program_id` - The address of the transceiver to register + pub fn add_transceiver(ctx: Context, args: AddTransceiverArgs) -> Result<()> { + instructions::add_transceiver::add_transceiver(ctx, args) } /// Sets a transceiver as a receive transceiver for a specific chain @@ -46,15 +43,15 @@ pub mod router { /// # Arguments /// /// * `ctx` - The context of the instruction - /// * `args` - The `SetTransceiverArgs` struct containing: + /// * `args` - The `EnableTransceiverArgs` struct containing: /// * `chain_id` - The ID of the chain for which the transceiver is being set /// * `transceiver` - The Pubkey of the transceiver to be set /// * `integrator_program` - The Pubkey of the integrator program - pub fn set_recv_transceiver( - ctx: Context, - args: SetTransceiverArgs, + pub fn enable_recv_transceiver( + ctx: Context, + args: TransceiverInfoArgs, ) -> Result<()> { - instructions::set_transceivers::set_recv_transceiver(ctx, args) + instructions::enable_transceiver::enable_recv_transceiver(ctx, args) } /// Sets a transceiver as a send transceiver for a specific chain @@ -62,15 +59,15 @@ pub mod router { /// # Arguments /// /// * `ctx` - The context of the instruction - /// * `args` - The `SetTransceiverArgs` struct containing: + /// * `args` - The `EnableTransceiverArgs` struct containing: /// * `chain_id` - The ID of the chain for which the transceiver is being set /// * `transceiver` - The Pubkey of the transceiver to be set /// * `integrator_program` - The Pubkey of the integrator program - pub fn set_send_transceiver( - ctx: Context, - args: SetTransceiverArgs, + pub fn enable_send_transceiver( + ctx: Context, + args: TransceiverInfoArgs, ) -> Result<()> { - instructions::set_transceivers::set_send_transceiver(ctx, args) + instructions::enable_transceiver::enable_send_transceiver(ctx, args) } /// Disables a receive transceiver for a specific chain @@ -84,9 +81,9 @@ pub mod router { /// * `integrator_program` - The Pubkey of the integrator program pub fn disable_recv_transceiver( ctx: Context, - args: DisableTransceiverArgs, + args: TransceiverInfoArgs, ) -> Result<()> { - instructions::disable_transceivers::disable_recv_transceiver(ctx, args) + instructions::disable_transceiver::disable_recv_transceiver(ctx, args) } /// Disables a send transceiver for a specific chain @@ -100,20 +97,52 @@ pub mod router { /// * `integrator_program` - The Pubkey of the integrator program pub fn disable_send_transceiver( ctx: Context, - args: DisableTransceiverArgs, + args: TransceiverInfoArgs, ) -> Result<()> { - instructions::disable_transceivers::disable_send_transceiver(ctx, args) + instructions::disable_transceiver::disable_send_transceiver(ctx, args) } - /// Transfers adminship of the IntegratorConfig to a new admin + /// Updates the admin of an IntegratorConfig account /// /// # Arguments /// /// * `ctx` - The context of the instruction, containing: - /// * `authority` - The current admin (signer) - /// * `new_admin` - The account of the new admin + /// * `admin` - The current admin (signer) /// * `integrator_config` - The IntegratorConfig account to update - pub fn update_admin(ctx: Context) -> Result<()> { - instructions::update_admin::update_admin(ctx) + /// * `args` - The `UpdateAdminArgs` struct containing: + /// * `new_admin` - The public key of the new admin + /// * `integrator_program_id` - The program ID of the integrator + pub fn update_admin(ctx: Context, args: UpdateAdminArgs) -> Result<()> { + instructions::update_admin::update_admin(ctx, args) + } + + /// Initiates the transfer of admin rights for an IntegratorConfig account + /// + /// # Arguments + /// + /// * `ctx` - The context of the instruction + /// * `args` - The `TransferAdminArgs` struct containing: + /// * `new_admin` - The public key of the new admin + /// * `integrator_program_id` - The program ID of the integrator + pub fn transfer_admin(ctx: Context, args: TransferAdminArgs) -> Result<()> { + instructions::transfer_admin::transfer_admin(ctx, args) + } + + /// Claims the admin rights for an IntegratorConfig account + /// + /// # Arguments + /// + /// * `ctx` - The context of the instruction + pub fn claim_admin(ctx: Context) -> Result<()> { + instructions::transfer_admin::claim_admin(ctx) + } + + /// Discards the admin role for an IntegratorConfig account, making it immutable + /// + /// # Arguments + /// + /// * `ctx` - The context of the instruction + pub fn discard_admin(ctx: Context) -> Result<()> { + instructions::discard_admin::discard_admin(ctx) } } diff --git a/svm/programs/router/src/state/integrator_chain_config.rs b/svm/programs/router/src/state/integrator_chain_config.rs index 3477ff76..3b37589b 100644 --- a/svm/programs/router/src/state/integrator_chain_config.rs +++ b/svm/programs/router/src/state/integrator_chain_config.rs @@ -12,12 +12,14 @@ pub struct IntegratorChainConfig { /// Bump seed for PDA derivation pub bump: u8, - /// Identifier for the blockchain - pub chain_id: u16, - /// The program ID of the integrator + /// This is used as a seed for PDA derivation pub integrator_program_id: Pubkey, + /// Identifier for the blockchain + /// This is used as a seed for PDA derivation + pub chain_id: u16, + /// Bitmap tracking the status of receive transceivers pub recv_transceiver_bitmap: Bitmap, @@ -29,14 +31,11 @@ impl IntegratorChainConfig { /// Seed prefix for deriving IntegratorChainConfig PDAs pub const SEED_PREFIX: &'static [u8] = b"integrator_chain_config"; - /// Maximum number of transceivers allowed per direction (recv/send) - pub const MAX_TRANSCEIVERS: u8 = 128; - - pub fn new(bump: u8, chain_id: u16, integrator_program_id: Pubkey) -> Self { + pub fn new(bump: u8, integrator_program_id: Pubkey, chain_id: u16) -> Self { Self { bump, - chain_id, integrator_program_id, + chain_id, recv_transceiver_bitmap: Bitmap::new(), send_transceiver_bitmap: Bitmap::new(), } @@ -47,19 +46,19 @@ impl IntegratorChainConfig { &[ Self::SEED_PREFIX, integrator_program.as_ref(), - chain_id.to_le_bytes().as_ref(), + chain_id.to_be_bytes().as_ref(), ], &crate::ID, ) } - pub fn set_recv_transceiver(&mut self, index: u8, value: bool) -> Result<()> { + pub fn enable_recv_transceiver(&mut self, index: u8, value: bool) -> Result<()> { self.recv_transceiver_bitmap .set(index, value) .map_err(|e| error!(e)) } - pub fn set_send_transceiver(&mut self, index: u8, value: bool) -> Result<()> { + pub fn enable_send_transceiver(&mut self, index: u8, value: bool) -> Result<()> { self.send_transceiver_bitmap .set(index, value) .map_err(|e| error!(e)) diff --git a/svm/programs/router/src/state/integrator_config.rs b/svm/programs/router/src/state/integrator_config.rs index e6c401fa..ee830922 100644 --- a/svm/programs/router/src/state/integrator_config.rs +++ b/svm/programs/router/src/state/integrator_config.rs @@ -9,15 +9,25 @@ pub struct IntegratorConfig { /// Bump seed for PDA derivation pub bump: u8, + /// Program ID associated with this integrator + /// This is used as a seed for PDA derivation + pub integrator_program_id: Pubkey, + /// Admin of the IntegratorConfig account pub admin: Pubkey, - /// Program ID associated with this integrator - pub integrator_program_id: Pubkey, + /// Pending admin of the IntegratorConfig account + /// If this exists, any other admin related functions will not be authorised + /// This must be null (in other words claim_admin will need to be called) before other ixs are + /// enabled + pub pending_admin: Option, /// Vector of registered transceiver addresses #[max_len(128)] pub registered_transceivers: Vec, + + /// A boolean to mark if config is immutable in other words admin is discarded + pub is_immutable: bool, } impl IntegratorConfig { @@ -34,15 +44,25 @@ impl IntegratorConfig { ) } - pub fn update_admin(&mut self, current_admin: &Signer, new_admin: Pubkey) -> Result<()> { + pub fn check_admin(&self, signer: &Signer) -> Result<()> { require!( - self.admin == current_admin.key(), - RouterError::InvalidIntegratorAuthority + !self.is_immutable && self.admin == signer.key(), + RouterError::CallerNotAuthorized ); + require!( + self.pending_admin.is_none(), + RouterError::AdminTransferInProgress + ); + Ok(()) + } + + pub fn update_admin(&mut self, new_admin: Pubkey) -> Result<()> { self.admin = new_admin; Ok(()) } + /// The `init` constraint in the add_transceiver instruction checks that the transceiver has not been added. If it is, + /// `AccountAlreadyInUse` error will be thrown pub fn add_transceiver(&mut self, transceiver: Pubkey) -> Result<()> { require!( self.registered_transceivers.len() < Self::MAX_TRANSCEIVERS, diff --git a/svm/programs/router/src/state/registered_transceiver.rs b/svm/programs/router/src/state/registered_transceiver.rs index b58057e0..a823a557 100644 --- a/svm/programs/router/src/state/registered_transceiver.rs +++ b/svm/programs/router/src/state/registered_transceiver.rs @@ -10,26 +10,29 @@ pub struct TransceiverInfo { /// Bump seed for PDA derivation pub bump: u8, - /// Unique identifier for the transceiver within its integrator context - pub id: u8, - /// The program ID of the integrator + /// This is used as a seed for PDA derivation pub integrator_program_id: Pubkey, /// Public key of the transceiver's address - pub transceiver_address: Pubkey, + /// This is used as a seed for PDA derivation + pub transceiver_program_id: Pubkey, + + /// Index of the transceiver with respect to the registered_transceiver vector in + /// IntegratorConfig + pub index: u8, } impl TransceiverInfo { /// Seed prefix for deriving TransceiverInfo PDAs pub const SEED_PREFIX: &'static [u8] = b"transceiver_info"; - pub fn pda(integrator_program_id: &Pubkey, transceiver_address: &Pubkey) -> (Pubkey, u8) { + pub fn pda(integrator_program_id: &Pubkey, transceiver_program_id: &Pubkey) -> (Pubkey, u8) { Pubkey::find_program_address( &[ Self::SEED_PREFIX, integrator_program_id.as_ref(), - transceiver_address.as_ref(), + transceiver_program_id.as_ref(), ], &crate::ID, ) diff --git a/svm/programs/router/src/utils/bitmap.rs b/svm/programs/router/src/utils/bitmap.rs index f2512aaa..ed5f0f24 100644 --- a/svm/programs/router/src/utils/bitmap.rs +++ b/svm/programs/router/src/utils/bitmap.rs @@ -1,4 +1,4 @@ -// This code is copied directly from `example-native-token-transfer` and updated to show RouterError instead of RouterError +// This code is copied directly from `example-native-token-transfer` and updated to show NTTError instead of RouterError // Link: https://github.com/wormhole-foundation/example-native-token-transfers/blob/6cc8beee57e8a06dec96fffa02dd4ace7b22168d/solana/programs/example-native-token-transfers/src/bitmap.rs use crate::error::RouterError; use anchor_lang::prelude::*; @@ -44,7 +44,7 @@ impl Bitmap { Ok(BM::<128>::from_value(self.map).get(usize::from(index))) } - pub fn count_enabled_votes(&self, enabled: Bitmap) -> u8 { + pub fn count_enabled_bits(&self, enabled: Bitmap) -> u8 { let bm = BM::<128>::from_value(self.map) & BM::<128>::from_value(enabled.map); bm.len() .try_into() @@ -68,30 +68,30 @@ mod tests { fn test_bitmap() { let mut enabled = Bitmap::from_value(u128::MAX); let mut bm = Bitmap::new(); - assert_eq!(bm.count_enabled_votes(enabled), 0); + assert_eq!(bm.count_enabled_bits(enabled), 0); bm.set(0, true).unwrap(); - assert_eq!(bm.count_enabled_votes(enabled), 1); + assert_eq!(bm.count_enabled_bits(enabled), 1); assert!(bm.get(0).unwrap()); assert!(!bm.get(1).unwrap()); bm.set(1, true).unwrap(); - assert_eq!(bm.count_enabled_votes(enabled), 2); + assert_eq!(bm.count_enabled_bits(enabled), 2); assert!(bm.get(0).unwrap()); assert!(bm.get(1).unwrap()); bm.set(0, false).unwrap(); - assert_eq!(bm.count_enabled_votes(enabled), 1); + assert_eq!(bm.count_enabled_bits(enabled), 1); assert!(!bm.get(0).unwrap()); assert!(bm.get(1).unwrap()); bm.set(18, true).unwrap(); - assert_eq!(bm.count_enabled_votes(enabled), 2); + assert_eq!(bm.count_enabled_bits(enabled), 2); enabled.set(18, false).unwrap(); - assert_eq!(bm.count_enabled_votes(enabled), 1); + assert_eq!(bm.count_enabled_bits(enabled), 1); } #[test] fn test_bitmap_len() { let max_bitmap = Bitmap::from_value(u128::MAX); - assert_eq!(128, max_bitmap.count_enabled_votes(max_bitmap)); + assert_eq!(128, max_bitmap.count_enabled_bits(max_bitmap)); } #[test]