From 54e887b2972cc84c9a11479678c1ce7e661cf06b Mon Sep 17 00:00:00 2001 From: bingyuyap Date: Thu, 24 Oct 2024 09:41:08 +0800 Subject: [PATCH] pr comments - remove is_immutable and make admin optional - remove has one admin constraint as it is redundant with check_admin - fixed claim admin constraints and added test - fixed typos and other nits alike Signed-off-by: bingyuyap --- .../mock-integrator/tests/add_transceiver.rs | 2 +- .../tests/disable_transceiver.rs | 4 +- .../mock-integrator/tests/discard_admin.rs | 2 +- .../tests/enable_transceiver.rs | 2 +- .../mock-integrator/tests/register.rs | 2 +- .../mock-integrator/tests/transfer_admin.rs | 263 +++++++++++------- .../mock-integrator/tests/update_admin.rs | 10 +- svm/programs/router/README.md | 24 +- svm/programs/router/src/error.rs | 3 + .../src/instructions/add_transceiver.rs | 27 +- .../src/instructions/disable_transceiver.rs | 3 +- .../router/src/instructions/discard_admin.rs | 2 +- .../src/instructions/enable_transceiver.rs | 3 +- .../router/src/instructions/register.rs | 3 +- .../router/src/instructions/transfer_admin.rs | 24 +- .../router/src/instructions/update_admin.rs | 1 - .../src/state/integrator_chain_config.rs | 40 +-- .../router/src/state/integrator_config.rs | 9 +- svm/programs/router/src/utils/bitmap.rs | 2 +- 19 files changed, 225 insertions(+), 201 deletions(-) diff --git a/svm/programs/mock-integrator/tests/add_transceiver.rs b/svm/programs/mock-integrator/tests/add_transceiver.rs index dabe5357..d816eeb9 100644 --- a/svm/programs/mock-integrator/tests/add_transceiver.rs +++ b/svm/programs/mock-integrator/tests/add_transceiver.rs @@ -336,7 +336,7 @@ async fn test_add_transceiver_with_transfer_in_progress() { // 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.admin, Some(admin.pubkey())); assert_eq!( integrator_config.pending_admin, Some(pending_admin.pubkey()) diff --git a/svm/programs/mock-integrator/tests/disable_transceiver.rs b/svm/programs/mock-integrator/tests/disable_transceiver.rs index 67d367e5..e169fa11 100644 --- a/svm/programs/mock-integrator/tests/disable_transceiver.rs +++ b/svm/programs/mock-integrator/tests/disable_transceiver.rs @@ -444,7 +444,7 @@ async fn test_disable_transceiver_with_transfer_in_progress() { // 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.admin, Some(admin.pubkey())); assert_eq!( integrator_config.pending_admin, Some(pending_admin.pubkey()) @@ -515,5 +515,5 @@ async fn test_disable_transceiver_with_immutable_config() { // 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); + assert_eq!(integrator_config.admin, None); } diff --git a/svm/programs/mock-integrator/tests/discard_admin.rs b/svm/programs/mock-integrator/tests/discard_admin.rs index 0726be36..5f98b929 100644 --- a/svm/programs/mock-integrator/tests/discard_admin.rs +++ b/svm/programs/mock-integrator/tests/discard_admin.rs @@ -52,7 +52,7 @@ async fn test_discard_admin_success() { // 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); + assert_eq!(integrator_config.admin, None); } #[tokio::test] diff --git a/svm/programs/mock-integrator/tests/enable_transceiver.rs b/svm/programs/mock-integrator/tests/enable_transceiver.rs index 0b2bf009..60b1b7f3 100644 --- a/svm/programs/mock-integrator/tests/enable_transceiver.rs +++ b/svm/programs/mock-integrator/tests/enable_transceiver.rs @@ -508,5 +508,5 @@ async fn test_enable_transceiver_with_immutable_config() { // 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); + assert_eq!(integrator_config.admin, None); } diff --git a/svm/programs/mock-integrator/tests/register.rs b/svm/programs/mock-integrator/tests/register.rs index 43d9c09b..fba483a9 100644 --- a/svm/programs/mock-integrator/tests/register.rs +++ b/svm/programs/mock-integrator/tests/register.rs @@ -41,7 +41,7 @@ async fn test_invoke_register() { let integrator_config_data: router::state::IntegratorConfig = get_account(&mut context.banks_client, integrator_config).await; - assert_eq!(integrator_config_data.admin, admin.pubkey()); + assert_eq!(integrator_config_data.admin, Some(admin.pubkey())); assert_eq!( integrator_config_data.integrator_program_id, mock_integrator::id() diff --git a/svm/programs/mock-integrator/tests/transfer_admin.rs b/svm/programs/mock-integrator/tests/transfer_admin.rs index 4f5c7970..41ac1425 100644 --- a/svm/programs/mock-integrator/tests/transfer_admin.rs +++ b/svm/programs/mock-integrator/tests/transfer_admin.rs @@ -70,7 +70,7 @@ async fn test_transfer_admin_success() { 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 + assert_eq!(integrator_config.admin, Some(admin.pubkey())); // Admin should not change yet } #[tokio::test] @@ -120,17 +120,18 @@ async fn test_transfer_admin_with_pending_transfer() { } #[tokio::test] -async fn test_cancel_admin_transfer() { +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(); - // Initiate transfer + // First, initiate a transfer transfer_admin( &mut context, &admin, - &new_admin.pubkey(), + &pending_admin.pubkey(), &payer, integrator_config_pda, integrator_program_id, @@ -138,27 +139,50 @@ async fn test_cancel_admin_transfer() { .await .unwrap(); - // Cancel transfer by claiming as current admin - let result = claim_admin(&mut context, &admin, &payer, integrator_config_pda).await; + // 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_ok()); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().unwrap(), + TransactionError::InstructionError( + 0, + InstructionError::Custom(RouterError::AdminTransferInProgress.into()) + ) + ); - // Verify that the pending_admin has been cleared and admin remains unchanged + // 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.pending_admin, None); - assert_eq!(integrator_config.admin, admin.pubkey()); + assert_eq!(integrator_config.admin, Some(admin.pubkey())); + assert_eq!( + integrator_config.pending_admin, + Some(pending_admin.pubkey()) + ); } #[tokio::test] -async fn test_claim_admin_success() { +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(); - // First, transfer admin - transfer_admin( + // Now try to transfer admin + let result = transfer_admin( &mut context, &admin, &new_admin.pubkey(), @@ -166,28 +190,84 @@ async fn test_claim_admin_success() { integrator_config_pda, integrator_program_id, ) - .await - .unwrap(); + .await; - // Verify that the pending_admin is set + 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.pending_admin, Some(new_admin.pubkey())); + assert_eq!(integrator_config.admin, None); +} - // Now, claim admin - let result = claim_admin(&mut context, &new_admin, &payer, integrator_config_pda).await; +#[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_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 admin has been updated and pending_admin is cleared + // 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.admin, new_admin.pubkey()); assert_eq!(integrator_config.pending_admin, None); + assert_eq!(integrator_config.admin, Some(admin.pubkey())); } #[tokio::test] -async fn test_cancel_claim_admin_success() { +async fn test_claim_admin_success() { let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = setup_test_environment().await; @@ -209,29 +289,36 @@ async fn test_cancel_claim_admin_success() { 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!(integrator_config.admin.is_some()); // Now, claim admin - let result = claim_admin(&mut context, &admin, &payer, integrator_config_pda).await; + 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, admin.pubkey()); + assert_eq!(integrator_config.admin, Some(new_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; +async fn test_claim_admin_immutable_config() { + let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = + setup_test_environment().await; - let random_user = Keypair::new(); + // Set admin to None + discard_admin(&mut context, &admin, &payer, integrator_config_pda) + .await + .unwrap(); - // 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; + let new_admin = Keypair::new(); - // Assert that the operation fails with CallerNotAuthorized error + // Now, try to claim admin + let result = claim_admin(&mut context, &new_admin, &payer, integrator_config_pda).await; + + assert!(result.is_err()); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError( @@ -239,49 +326,40 @@ async fn test_claim_admin_no_pending_admin() { 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; +async fn test_claim_admin_no_pending_admin() { + let (mut context, payer, admin, _, 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; + // Try to claim admin without setting pending_admin + let result = claim_admin(&mut context, &new_admin, &payer, integrator_config_pda).await; assert!(result.is_err()); assert_eq!( result.unwrap_err().unwrap(), TransactionError::InstructionError( 0, - InstructionError::Custom(RouterError::CallerNotAuthorized.into()) + InstructionError::Custom(RouterError::NoAdminTransferInProgress.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, Some(admin.pubkey())); + assert_eq!(integrator_config.pending_admin, None); } #[tokio::test] -async fn test_claim_admin_unauthorized() { +async fn test_claim_admin_unauthorized_signer() { let (mut context, payer, admin, integrator_program_id, integrator_config_pda) = setup_test_environment().await; let new_admin = Keypair::new(); - let unauthorized = Keypair::new(); + let unauthorized_signer = Keypair::new(); // First, transfer admin transfer_admin( @@ -295,8 +373,14 @@ async fn test_claim_admin_unauthorized() { .await .unwrap(); - // Now, try to claim admin with an unauthorized key - let result = claim_admin(&mut context, &unauthorized, &payer, integrator_config_pda).await; + // Try to claim admin with an unauthorized signer + let result = claim_admin( + &mut context, + &unauthorized_signer, + &payer, + integrator_config_pda, + ) + .await; assert!(result.is_err()); assert_eq!( @@ -306,21 +390,26 @@ async fn test_claim_admin_unauthorized() { InstructionError::Custom(RouterError::CallerNotAuthorized.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, Some(admin.pubkey())); + assert_eq!(integrator_config.pending_admin, Some(new_admin.pubkey())); } #[tokio::test] -async fn test_transfer_admin_with_transfer_in_progress() { +async fn test_cancel_claim_admin_success() { 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 + // First, transfer admin transfer_admin( &mut context, &admin, - &pending_admin.pubkey(), + &new_admin.pubkey(), &payer, integrator_config_pda, integrator_program_id, @@ -328,50 +417,33 @@ async fn test_transfer_admin_with_transfer_in_progress() { .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; + // 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())); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().unwrap(), - TransactionError::InstructionError( - 0, - InstructionError::Custom(RouterError::AdminTransferInProgress.into()) - ) - ); + // Now, claim admin + let result = claim_admin(&mut context, &admin, &payer, integrator_config_pda).await; - // Verify that the admin and pending_admin haven't changed + 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, - Some(pending_admin.pubkey()) - ); + assert_eq!(integrator_config.admin, Some(admin.pubkey())); + assert_eq!(integrator_config.pending_admin, None); } #[tokio::test] -async fn test_transfer_admin_with_immutable_config() { +async fn test_claim_admin_unauthorized() { 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(); + let unauthorized = Keypair::new(); - // Now try to transfer admin - let result = transfer_admin( + // First, transfer admin + transfer_admin( &mut context, &admin, &new_admin.pubkey(), @@ -379,7 +451,11 @@ async fn test_transfer_admin_with_immutable_config() { integrator_config_pda, integrator_program_id, ) - .await; + .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!( @@ -389,9 +465,4 @@ async fn test_transfer_admin_with_immutable_config() { 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 0a9c8681..656c98ba 100644 --- a/svm/programs/mock-integrator/tests/update_admin.rs +++ b/svm/programs/mock-integrator/tests/update_admin.rs @@ -65,7 +65,7 @@ async fn test_update_admin_success() { // Verify that the admin has been updated 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.admin, Some(new_admin.pubkey())); } #[tokio::test] @@ -98,7 +98,7 @@ async fn test_update_admin_non_authority() { // Verify that the admin has not been updated 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.admin, Some(admin.pubkey())); } #[tokio::test] @@ -121,7 +121,7 @@ async fn test_update_admin_same_address() { // Verify that the admin remains the same 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.admin, Some(admin.pubkey())); } #[tokio::test] @@ -176,7 +176,7 @@ async fn test_update_admin_with_transfer_in_progress() { // 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.admin, Some(admin.pubkey())); assert_eq!( integrator_config.pending_admin, Some(pending_admin.pubkey()) @@ -223,5 +223,5 @@ async fn test_update_admin_with_immutable_config() { // 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); + assert_eq!(integrator_config.admin, None); } diff --git a/svm/programs/router/README.md b/svm/programs/router/README.md index 05f3c720..dbc5002c 100644 --- a/svm/programs/router/README.md +++ b/svm/programs/router/README.md @@ -16,9 +16,9 @@ classDiagram class IntegratorConfig { *bump: u8 *integrator_program_id: Pubkey - admin: Pubkey + admin: Option + pending_admin: Option registered_transceivers: Vec - is_immutable: boolean } class IntegratorChainConfig { @@ -125,26 +125,29 @@ Manages the configuration for a specific integrator. - **bump**: Bump seed for PDA derivation - **integrator_program_id**: The program ID associated with this integrator -- **admin**: The current admin of the IntegratorConfig account +- **admin**: The current admin of the IntegratorConfig account (None if admin is discarded) - **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]` - Unique for each integrator program -- Initialization: Requires signer's signature +- Initialization: Requires integrator_program's PDA seeded by "router_integrator" + +**Constraints**: + +- Maximum of 128 transceivers per integrator ### IntegratorChainConfig Manages transceivers enabled and config for a specific integrator on a particular chain. - **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 +- **chain_id**: Identifier for the blockchain network - **send_transceiver_bitmap**: Bitmap tracking enabled send transceivers +- **recv_transceiver_bitmap**: Bitmap tracking enabled receive transceivers **PDA Derivation**: @@ -157,7 +160,7 @@ Manages transceivers enabled and config for a specific integrator on a particula Represents a registered transceiver in the GMP Router. - **bump**: Bump seed for PDA derivation -- **id**: Unique ID of the transceiver within the integrator's context +- **index**: Unique index of the transceiver that corresponds to it's position in the registered_transceivers in IntegratorConfig account - **integrator_program_id**: The program ID of the Integrator - **address**: Public key of the transceiver's address @@ -166,11 +169,6 @@ Represents a registered transceiver in the GMP Router. - Seeds: `[SEED_PREFIX, integrator_program_id, transceiver_program_id]` - Unique for each transceiver within an integrator context -**Constraints**: - -- Maximum of 128 transceivers per integrator -- Will return an error (MaxTransceiversReached) if this limit is exceeded - ### Bitmap Utility struct for efficient storage and manipulation of boolean flags. diff --git a/svm/programs/router/src/error.rs b/svm/programs/router/src/error.rs index 893b64ed..726deb6c 100644 --- a/svm/programs/router/src/error.rs +++ b/svm/programs/router/src/error.rs @@ -20,4 +20,7 @@ pub enum RouterError { #[msg("An admin transfer is in progress")] AdminTransferInProgress, + + #[msg("No admin transfer is in progress")] + NoAdminTransferInProgress, } diff --git a/svm/programs/router/src/instructions/add_transceiver.rs b/svm/programs/router/src/instructions/add_transceiver.rs index 41bd8743..82699987 100644 --- a/svm/programs/router/src/instructions/add_transceiver.rs +++ b/svm/programs/router/src/instructions/add_transceiver.rs @@ -1,5 +1,3 @@ -use crate::error::RouterError; -use crate::error::RouterError::AdminTransferInProgress; use crate::state::{IntegratorConfig, TransceiverInfo}; use anchor_lang::prelude::*; @@ -13,12 +11,6 @@ pub struct AddTransceiverArgs { 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: AddTransceiverArgs)] pub struct AddTransceiver<'info> { @@ -26,11 +18,10 @@ pub struct AddTransceiver<'info> { pub payer: Signer<'info>, /// The admin registered on IntegratorConfig - #[account(mut)] pub admin: Signer<'info>, /// The integrator config account - /// This makes sure that the admin signing this ix is the one registed in the IntegratorConfig + /// This makes sure that the admin signing this ix is the one registered 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 @@ -38,7 +29,6 @@ pub struct AddTransceiver<'info> { mut, seeds = [IntegratorConfig::SEED_PREFIX, args.integrator_program_id.as_ref()], bump = integrator_config.bump, - has_one = admin @ RouterError::CallerNotAuthorized, )] pub integrator_config: Account<'info, IntegratorConfig>, @@ -62,6 +52,12 @@ pub struct AddTransceiver<'info> { pub system_program: Program<'info, System>, } +impl<'info> AddTransceiver<'info> { + pub fn validate(&self) -> Result<()> { + self.integrator_config.check_admin(&self.admin) + } +} + /// Register a new transceiver for an integrator. /// /// This function performs the following steps: @@ -81,12 +77,7 @@ pub struct AddTransceiver<'info> { /// Returns `Ok(())` if the transceiver is successfully registered, or an error otherwise. #[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; + let index = ctx.accounts.integrator_config.registered_transceivers.len() as u8; // Add the new transceiver to the list // The vector length check is in `add_transceiver` @@ -97,7 +88,7 @@ pub fn add_transceiver(ctx: Context, args: AddTransceiverArgs) - // Initialize TransceiverInfo ctx.accounts.transceiver_info.set_inner(TransceiverInfo { bump: ctx.bumps.transceiver_info, - index: transceiver_id, + index, integrator_program_id: args.integrator_program_id, transceiver_program_id: args.transceiver_program_id, }); diff --git a/svm/programs/router/src/instructions/disable_transceiver.rs b/svm/programs/router/src/instructions/disable_transceiver.rs index 6074f9a2..97e02dc1 100644 --- a/svm/programs/router/src/instructions/disable_transceiver.rs +++ b/svm/programs/router/src/instructions/disable_transceiver.rs @@ -15,7 +15,6 @@ pub struct DisableTransceiver<'info> { #[account( seeds = [IntegratorConfig::SEED_PREFIX, args.integrator_program_id.as_ref()], bump = integrator_config.bump, - has_one = admin @ RouterError::CallerNotAuthorized, )] pub integrator_config: Account<'info, IntegratorConfig>, @@ -45,11 +44,13 @@ pub struct DisableTransceiver<'info> { )] 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 diff --git a/svm/programs/router/src/instructions/discard_admin.rs b/svm/programs/router/src/instructions/discard_admin.rs index c5093cb3..65cfb2ab 100644 --- a/svm/programs/router/src/instructions/discard_admin.rs +++ b/svm/programs/router/src/instructions/discard_admin.rs @@ -26,6 +26,6 @@ impl<'info> DiscardAdmin<'info> { #[access_control(DiscardAdmin::validate(&ctx.accounts))] pub fn discard_admin(ctx: Context) -> Result<()> { - ctx.accounts.integrator_config.is_immutable = true; + ctx.accounts.integrator_config.admin = None; Ok(()) } diff --git a/svm/programs/router/src/instructions/enable_transceiver.rs b/svm/programs/router/src/instructions/enable_transceiver.rs index b12309d3..54e02be0 100644 --- a/svm/programs/router/src/instructions/enable_transceiver.rs +++ b/svm/programs/router/src/instructions/enable_transceiver.rs @@ -20,7 +20,6 @@ pub struct EnableTransceiver<'info> { #[account( seeds = [IntegratorConfig::SEED_PREFIX, args.integrator_program_id.as_ref()], bump = integrator_config.bump, - has_one = admin @ RouterError::CallerNotAuthorized, )] pub integrator_config: Account<'info, IntegratorConfig>, @@ -55,11 +54,13 @@ pub struct EnableTransceiver<'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 diff --git a/svm/programs/router/src/instructions/register.rs b/svm/programs/router/src/instructions/register.rs index 08640d74..932e6a72 100644 --- a/svm/programs/router/src/instructions/register.rs +++ b/svm/programs/router/src/instructions/register.rs @@ -72,11 +72,10 @@ pub fn register(ctx: Context, args: RegisterArgs) -> Result<()> { // Initialize the IntegratorConfig account with the provided information ctx.accounts.integrator_config.set_inner(IntegratorConfig { bump: ctx.bumps.integrator_config, - admin: args.admin, + admin: Some(args.admin), pending_admin: None, integrator_program_id: args.integrator_program_id, registered_transceivers: Vec::new(), - is_immutable: false, }); Ok(()) diff --git a/svm/programs/router/src/instructions/transfer_admin.rs b/svm/programs/router/src/instructions/transfer_admin.rs index 87d4b1f3..d72fb0de 100644 --- a/svm/programs/router/src/instructions/transfer_admin.rs +++ b/svm/programs/router/src/instructions/transfer_admin.rs @@ -26,7 +26,6 @@ pub struct TransferAdmin<'info> { args.integrator_program_id.key().as_ref(), ], bump = integrator_config.bump, - has_one = admin @ RouterError::CallerNotAuthorized, )] pub integrator_config: Account<'info, IntegratorConfig>, } @@ -39,14 +38,17 @@ impl<'info> TransferAdmin<'info> { #[derive(Accounts)] pub struct ClaimAdmin<'info> { - /// The signer, which can be either the pending_admin or the current admin + /// The signer, which must be the pending_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 + /// The constraint here checks that there is a pending admin transfer and the signer is the pending_admin #[account( mut, - constraint = (integrator_config.pending_admin == Some(new_admin.key()) || integrator_config.admin == new_admin.key()) @ RouterError::CallerNotAuthorized, + constraint = integrator_config.admin.is_some() @ RouterError::CallerNotAuthorized, + constraint = integrator_config.pending_admin.is_some() @ RouterError::NoAdminTransferInProgress, + constraint = integrator_config.pending_admin == Some(new_admin.key()) + || integrator_config.admin == Some(new_admin.key()) @ RouterError::CallerNotAuthorized, )] pub integrator_config: Account<'info, IntegratorConfig>, } @@ -69,20 +71,14 @@ pub struct ClaimAdmin<'info> { /// 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 +/// This function allows only the pending admin to claim the admin rights, +/// completing the two-step admin transfer process. /// /// # Arguments /// @@ -92,7 +88,9 @@ pub fn transfer_admin(ctx: Context, args: TransferAdminArgs) -> R /// /// 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(); + // The constraints in ClaimAdmin struct ensure that pending_admin is Some and matches the signer + // or the admin matches the signer + ctx.accounts.integrator_config.admin = Some(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 a7968008..608ea5c5 100644 --- a/svm/programs/router/src/instructions/update_admin.rs +++ b/svm/programs/router/src/instructions/update_admin.rs @@ -26,7 +26,6 @@ pub struct UpdateAdmin<'info> { args.integrator_program_id.key().as_ref(), ], bump = integrator_config.bump, - has_one = admin @ RouterError::CallerNotAuthorized, )] pub integrator_config: Account<'info, IntegratorConfig>, } diff --git a/svm/programs/router/src/state/integrator_chain_config.rs b/svm/programs/router/src/state/integrator_chain_config.rs index 3b37589b..f272634d 100644 --- a/svm/programs/router/src/state/integrator_chain_config.rs +++ b/svm/programs/router/src/state/integrator_chain_config.rs @@ -20,27 +20,17 @@ pub struct IntegratorChainConfig { /// 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, - /// Bitmap tracking the status of send transceivers pub send_transceiver_bitmap: Bitmap, + + /// Bitmap tracking the status of receive transceivers + pub recv_transceiver_bitmap: Bitmap, } impl IntegratorChainConfig { /// Seed prefix for deriving IntegratorChainConfig PDAs pub const SEED_PREFIX: &'static [u8] = b"integrator_chain_config"; - pub fn new(bump: u8, integrator_program_id: Pubkey, chain_id: u16) -> Self { - Self { - bump, - integrator_program_id, - chain_id, - recv_transceiver_bitmap: Bitmap::new(), - send_transceiver_bitmap: Bitmap::new(), - } - } - pub fn pda(integrator_program: &Pubkey, chain_id: u16) -> (Pubkey, u8) { Pubkey::find_program_address( &[ @@ -51,28 +41,4 @@ impl IntegratorChainConfig { &crate::ID, ) } - - 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 enable_send_transceiver(&mut self, index: u8, value: bool) -> Result<()> { - self.send_transceiver_bitmap - .set(index, value) - .map_err(|e| error!(e)) - } - - pub fn get_recv_transceiver(&self, index: u8) -> Result { - self.recv_transceiver_bitmap - .get(index) - .map_err(|e| error!(e)) - } - - pub fn get_send_transceiver(&self, index: u8) -> Result { - self.send_transceiver_bitmap - .get(index) - .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 ee830922..1bd2a07b 100644 --- a/svm/programs/router/src/state/integrator_config.rs +++ b/svm/programs/router/src/state/integrator_config.rs @@ -14,7 +14,7 @@ pub struct IntegratorConfig { pub integrator_program_id: Pubkey, /// Admin of the IntegratorConfig account - pub admin: Pubkey, + pub admin: Option, /// Pending admin of the IntegratorConfig account /// If this exists, any other admin related functions will not be authorised @@ -25,9 +25,6 @@ pub struct IntegratorConfig { /// 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 { @@ -46,7 +43,7 @@ impl IntegratorConfig { pub fn check_admin(&self, signer: &Signer) -> Result<()> { require!( - !self.is_immutable && self.admin == signer.key(), + self.admin.is_some() && self.admin == Some(signer.key()), RouterError::CallerNotAuthorized ); require!( @@ -57,7 +54,7 @@ impl IntegratorConfig { } pub fn update_admin(&mut self, new_admin: Pubkey) -> Result<()> { - self.admin = new_admin; + self.admin = Some(new_admin); Ok(()) } diff --git a/svm/programs/router/src/utils/bitmap.rs b/svm/programs/router/src/utils/bitmap.rs index ed5f0f24..7ca74e07 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 NTTError instead of RouterError +// This code is copied directly from `example-native-token-transfer` and updated to show RouterError instead of NTTError // 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::*;