Skip to content

feat: Add close validator vault #71

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/discriminator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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),
}
}
Expand Down
25 changes: 25 additions & 0 deletions src/instruction_builder/close_validator_fees_vault.rs
Original file line number Diff line number Diff line change
@@ -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(),
}
}
2 changes: 2 additions & 0 deletions src/instruction_builder/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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::*;
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
63 changes: 63 additions & 0 deletions src/processor/close_validator_fees_vault.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
2 changes: 2 additions & 0 deletions src/processor/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod close_ephemeral_balance;
mod close_validator_fees_vault;
mod commit_state;
mod commit_state_from_buffer;
mod delegate;
Expand All @@ -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::*;
Expand Down
67 changes: 67 additions & 0 deletions tests/test_close_validator_fees_vault.rs
Original file line number Diff line number Diff line change
@@ -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)
}