From 0cfda96ecd73898a4ab346f3fc046bde6bd3292f Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Sat, 8 Mar 2025 18:51:48 +0100 Subject: [PATCH 1/2] feat: Add close validator vault --- src/discriminator.rs | 3 + .../close_validator_fees_vault.rs | 25 +++++++ src/instruction_builder/mod.rs | 2 + src/lib.rs | 3 + src/processor/close_validator_fees_vault.rs | 63 +++++++++++++++++ src/processor/mod.rs | 2 + tests/integration/tests/test-delegation.ts | 2 +- tests/test_close_validator_fees_vault.rs | 67 +++++++++++++++++++ 8 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 src/instruction_builder/close_validator_fees_vault.rs create mode 100644 src/processor/close_validator_fees_vault.rs create mode 100644 tests/test_close_validator_fees_vault.rs diff --git a/src/discriminator.rs b/src/discriminator.rs index b244ce6..e896fa2 100644 --- a/src/discriminator.rs +++ b/src/discriminator.rs @@ -31,6 +31,8 @@ pub enum DlpDiscriminator { ProtocolClaimFees = 12, /// See [crate::processor::process_commit_state_from_buffer] for docs. CommitStateFromBuffer = 13, + /// See [crate::processor::process_close_validator_fees_vault] for docs. + CloseValidatorFeesVault = 14, } impl DlpDiscriminator { @@ -57,6 +59,7 @@ impl TryFrom<[u8; 8]> for DlpDiscriminator { 0xb => Ok(DlpDiscriminator::CloseEphemeralBalance), 0xc => Ok(DlpDiscriminator::ProtocolClaimFees), 0xd => Ok(DlpDiscriminator::CommitStateFromBuffer), + 0xe => Ok(DlpDiscriminator::CloseValidatorFeesVault), _ => Err(ProgramError::InvalidInstructionData), } } diff --git a/src/instruction_builder/close_validator_fees_vault.rs b/src/instruction_builder/close_validator_fees_vault.rs new file mode 100644 index 0000000..70b6de9 --- /dev/null +++ b/src/instruction_builder/close_validator_fees_vault.rs @@ -0,0 +1,25 @@ +use solana_program::instruction::Instruction; +use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; + +use crate::discriminator::DlpDiscriminator; +use crate::pda::validator_fees_vault_pda_from_validator; + +/// Close a validator fees vault PDA. +/// See [crate::processor::process_close_validator_fees_vault] for docs. +pub fn close_validator_fees_vault( + payer: Pubkey, + admin: Pubkey, + validator_identity: Pubkey, +) -> Instruction { + let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator_identity); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(payer, true), + AccountMeta::new(admin, true), + AccountMeta::new(validator_identity, false), + AccountMeta::new(validator_fees_vault_pda, false), + ], + data: DlpDiscriminator::CloseValidatorFeesVault.to_vec(), + } +} diff --git a/src/instruction_builder/mod.rs b/src/instruction_builder/mod.rs index 10d182c..e0cae79 100644 --- a/src/instruction_builder/mod.rs +++ b/src/instruction_builder/mod.rs @@ -1,6 +1,7 @@ mod close_ephemeral_balance; mod commit_state; +mod close_validator_fees_vault; mod commit_state_from_buffer; mod delegate; mod delegate_ephemeral_balance; @@ -14,6 +15,7 @@ mod validator_claim_fees; mod whitelist_validator_for_program; pub use close_ephemeral_balance::*; +pub use close_validator_fees_vault::*; pub use commit_state::*; pub use commit_state_from_buffer::*; pub use delegate::*; diff --git a/src/lib.rs b/src/lib.rs index eccd61d..4b1eca8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,6 +90,9 @@ pub fn process_instruction( discriminator::DlpDiscriminator::ProtocolClaimFees => { processor::process_protocol_claim_fees(program_id, accounts, data)? } + discriminator::DlpDiscriminator::CloseValidatorFeesVault => { + processor::process_close_validator_fees_vault(program_id, accounts, data)? + } } Ok(()) } diff --git a/src/processor/close_validator_fees_vault.rs b/src/processor/close_validator_fees_vault.rs new file mode 100644 index 0000000..39fc6ad --- /dev/null +++ b/src/processor/close_validator_fees_vault.rs @@ -0,0 +1,63 @@ +use solana_program::msg; +use solana_program::program_error::ProgramError; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; + +use crate::consts::ADMIN_PUBKEY; +use crate::error::DlpError::Unauthorized; +use crate::processor::utils::loaders::{load_initialized_pda, load_signer}; +use crate::processor::utils::pda::close_pda; +use crate::validator_fees_vault_seeds_from_validator; + +/// Process the close of the validator fees vault +/// +/// Accounts: +/// +/// 0; `[signer]` payer +/// 1; `[signer]` admin that controls the vault +/// 2; `[]` validator_identity +/// 3; `[]` validator_fees_vault_pda +/// +/// Requirements: +/// +/// - validator admin need to be signer since the existence of the validator fees vault +/// is used as proof later that the validator is whitelisted +/// - validator fees vault is closed +/// +/// 1. Close the validator fees vault PDA +pub fn process_close_validator_fees_vault( + _program_id: &Pubkey, + accounts: &[AccountInfo], + _data: &[u8], +) -> ProgramResult { + // Load Accounts + let [payer, admin, validator_identity, validator_fees_vault] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + // Check if the payer and admin are signers + load_signer(payer, "payer")?; + load_signer(admin, "admin")?; + + // Check if the admin is the correct one + if !admin.key.eq(&ADMIN_PUBKEY) { + msg!( + "Expected admin pubkey: {} but got {}", + ADMIN_PUBKEY, + admin.key + ); + return Err(Unauthorized.into()); + } + + load_initialized_pda( + validator_fees_vault, + validator_fees_vault_seeds_from_validator!(validator_identity.key), + &crate::id(), + true, + "validator fees vault", + )?; + + // Close the fees vault PDA + close_pda(validator_fees_vault, validator_identity)?; + + Ok(()) +} diff --git a/src/processor/mod.rs b/src/processor/mod.rs index 47696e4..e9b25db 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -1,4 +1,5 @@ mod close_ephemeral_balance; +mod close_validator_fees_vault; mod commit_state; mod commit_state_from_buffer; mod delegate; @@ -14,6 +15,7 @@ mod validator_claim_fees; mod whitelist_validator_for_program; pub use close_ephemeral_balance::*; +pub use close_validator_fees_vault::*; pub use commit_state::*; pub use commit_state_from_buffer::*; pub use delegate::*; diff --git a/tests/integration/tests/test-delegation.ts b/tests/integration/tests/test-delegation.ts index 5f54d48..ce6103c 100644 --- a/tests/integration/tests/test-delegation.ts +++ b/tests/integration/tests/test-delegation.ts @@ -48,7 +48,7 @@ describe("TestDelegation", () => { console.log("Claim validator fee vault tx:", txId); }); - it("Claim protocol fees", async () => { + it.only("Claim protocol fees", async () => { const ix = createClaimProtocolFeesVaultInstruction(admin); const txId = await processInstruction(ix); console.log("Claim protocol fee vault tx:", txId); diff --git a/tests/test_close_validator_fees_vault.rs b/tests/test_close_validator_fees_vault.rs new file mode 100644 index 0000000..719fadc --- /dev/null +++ b/tests/test_close_validator_fees_vault.rs @@ -0,0 +1,67 @@ +use crate::fixtures::TEST_AUTHORITY; +use dlp::pda::validator_fees_vault_pda_from_validator; +use solana_program::{hash::Hash, native_token::LAMPORTS_PER_SOL, system_program}; +use solana_program_test::{processor, BanksClient, ProgramTest}; +use solana_sdk::{ + account::Account, + signature::{Keypair, Signer}, + transaction::Transaction, +}; + +mod fixtures; + +#[tokio::test] +async fn test_close_validator_fees_vault() { + // Setup + let (banks, admin, validator, blockhash) = setup_program_test_env().await; + + let validator_fees_vault_pda = validator_fees_vault_pda_from_validator(&validator.pubkey()); + + // Submit the close vault tx + let ix = dlp::instruction_builder::close_validator_fees_vault( + admin.pubkey(), + admin.pubkey(), + validator.pubkey(), + ); + let tx = Transaction::new_signed_with_payer(&[ix], Some(&admin.pubkey()), &[&admin], blockhash); + let res = banks.process_transaction(tx).await; + assert!(res.is_ok()); + + // Assert the validator fees vault now has been closed + let validator_fees_vault_account = banks.get_account(validator_fees_vault_pda).await.unwrap(); + assert!(validator_fees_vault_account.is_none()); +} + +async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { + let mut program_test = ProgramTest::new("dlp", dlp::ID, processor!(dlp::process_instruction)); + program_test.prefer_bpf(true); + + let admin_keypair = Keypair::from_bytes(&TEST_AUTHORITY).unwrap(); + let validator = Keypair::new(); + + program_test.add_account( + admin_keypair.pubkey(), + Account { + lamports: LAMPORTS_PER_SOL, + data: vec![], + owner: system_program::id(), + executable: false, + rent_epoch: 0, + }, + ); + + // Setup the validator fees vault + program_test.add_account( + validator_fees_vault_pda_from_validator(&validator.pubkey()), + Account { + lamports: LAMPORTS_PER_SOL, + data: vec![], + owner: dlp::id(), + executable: false, + rent_epoch: 0, + }, + ); + + let (banks, _, blockhash) = program_test.start().await; + (banks, admin_keypair, validator, blockhash) +} From 54bc90bf9ceac0116858b9aec7c4cd3b5a607543 Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Sat, 8 Mar 2025 19:00:04 +0100 Subject: [PATCH 2/2] chore: Refactor --- tests/integration/tests/test-delegation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/tests/test-delegation.ts b/tests/integration/tests/test-delegation.ts index ce6103c..5f54d48 100644 --- a/tests/integration/tests/test-delegation.ts +++ b/tests/integration/tests/test-delegation.ts @@ -48,7 +48,7 @@ describe("TestDelegation", () => { console.log("Claim validator fee vault tx:", txId); }); - it.only("Claim protocol fees", async () => { + it("Claim protocol fees", async () => { const ix = createClaimProtocolFeesVaultInstruction(admin); const txId = await processInstruction(ix); console.log("Claim protocol fee vault tx:", txId);