Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a9bffed
feat: configurable fees receiver
Dodecahedr0x Sep 15, 2025
fa71919
feat: remote claim admin
Dodecahedr0x Sep 15, 2025
1ff8e98
docs: fix
Dodecahedr0x Sep 15, 2025
ab07130
Merge branch 'main' into feat/fee-receiver
Dodecahedr0x Oct 28, 2025
e7cf69c
fix: move fees receiver to fees vault PDA
Dodecahedr0x Oct 28, 2025
932f418
Merge branch 'main' into feat/fee-receiver
Dodecahedr0x Oct 28, 2025
ce0a0c5
test: assert fees vault
Dodecahedr0x Oct 28, 2025
90e02a1
test: wrong receiver
Dodecahedr0x Oct 28, 2025
794d846
fix: remove program config changes
Dodecahedr0x Oct 28, 2025
04d31e2
feat: prevent fees vault as receiver
Dodecahedr0x Oct 29, 2025
d06db16
feat: get rent sysvar
Dodecahedr0x Oct 29, 2025
58bad68
docs: refer to fees vault
Dodecahedr0x Oct 29, 2025
60552e9
feat: use default fees vault on migration
Dodecahedr0x Oct 29, 2025
364abb2
feat: load system program
Dodecahedr0x Oct 29, 2025
584d0bc
feat: remove unused program
Dodecahedr0x Oct 29, 2025
bd8dd99
test: helper for common code
Dodecahedr0x Oct 29, 2025
74c5e7f
docs: removed irrelevant change
Dodecahedr0x Oct 29, 2025
e41c86e
test: check state
Dodecahedr0x Oct 29, 2025
e42869f
feat: remove unused program account
Dodecahedr0x Oct 29, 2025
09760e9
docs: fix comments
Dodecahedr0x Oct 29, 2025
0102b0b
test: match specific error
Dodecahedr0x Oct 29, 2025
7b1fb92
feat: short circuit no-op transfer
Dodecahedr0x Oct 29, 2025
42ce607
test: assert balance after
Dodecahedr0x Oct 29, 2025
fc11856
test: unauthorized
Dodecahedr0x Oct 29, 2025
efce731
feat: compute rent using actual account
Dodecahedr0x Oct 29, 2025
1117c3e
docs: fix comment
Dodecahedr0x Oct 29, 2025
fa55c6e
test: assert balances unchanged
Dodecahedr0x Oct 29, 2025
16ecbac
test: assert state unchanged
Dodecahedr0x Oct 29, 2025
d974afb
feat: cache balances
Dodecahedr0x Oct 29, 2025
16b560b
test: edge cases
Dodecahedr0x Oct 29, 2025
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
2 changes: 2 additions & 0 deletions src/args/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod call_handler;
mod commit_state;
mod delegate;
mod delegate_ephemeral_balance;
mod set_fees_receiver;
mod top_up_ephemeral_balance;
mod validator_claim_fees;
mod whitelist_validator_for_program;
Expand All @@ -10,6 +11,7 @@ pub use call_handler::*;
pub use commit_state::*;
pub use delegate::*;
pub use delegate_ephemeral_balance::*;
pub use set_fees_receiver::*;
pub use top_up_ephemeral_balance::*;
pub use validator_claim_fees::*;
pub use whitelist_validator_for_program::*;
7 changes: 7 additions & 0 deletions src/args/set_fees_receiver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::pubkey::Pubkey;

#[derive(BorshSerialize, BorshDeserialize)]
pub struct SetFeesReceiverArgs {
pub fees_receiver: Pubkey,
}
2 changes: 2 additions & 0 deletions src/discriminator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub enum DlpDiscriminator {
CloseValidatorFeesVault = 14,
/// See [crate::processor::process_call_handler] for docs.
CallHandler = 15,
/// See [crate::processor::process_set_fees_receiver] for docs.
SetFeesReceiver = 16,
}

impl DlpDiscriminator {
Expand Down
12 changes: 9 additions & 3 deletions src/instruction_builder/init_protocol_fees_vault.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use solana_program::instruction::Instruction;
use solana_program::system_program;
use solana_program::{instruction::AccountMeta, pubkey::Pubkey};
use solana_program::{
bpf_loader_upgradeable,
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
system_program,
};

use crate::discriminator::DlpDiscriminator;
use crate::pda::fees_vault_pda;
Expand All @@ -9,11 +12,14 @@ use crate::pda::fees_vault_pda;
/// See [crate::processor::process_init_protocol_fees_vault] for docs.
pub fn init_protocol_fees_vault(payer: Pubkey) -> Instruction {
let fees_vault_pda = fees_vault_pda();
let delegation_program_data =
Pubkey::find_program_address(&[crate::ID.as_ref()], &bpf_loader_upgradeable::id()).0;
Instruction {
program_id: crate::id(),
accounts: vec![
AccountMeta::new(payer, true),
AccountMeta::new(fees_vault_pda, false),
AccountMeta::new_readonly(delegation_program_data, false),
AccountMeta::new_readonly(system_program::id(), false),
],
data: DlpDiscriminator::InitProtocolFeesVault.to_vec(),
Expand Down
2 changes: 2 additions & 0 deletions src/instruction_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod finalize;
mod init_protocol_fees_vault;
mod init_validator_fees_vault;
mod protocol_claim_fees;
mod set_fees_receiver;
mod top_up_ephemeral_balance;
mod undelegate;
mod validator_claim_fees;
Expand All @@ -25,6 +26,7 @@ pub use finalize::*;
pub use init_protocol_fees_vault::*;
pub use init_validator_fees_vault::*;
pub use protocol_claim_fees::*;
pub use set_fees_receiver::*;
pub use top_up_ephemeral_balance::*;
pub use undelegate::*;
pub use validator_claim_fees::*;
Expand Down
10 changes: 4 additions & 6 deletions src/instruction_builder/protocol_claim_fees.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
use solana_program::instruction::Instruction;
use solana_program::{bpf_loader_upgradeable, instruction::AccountMeta, pubkey::Pubkey};
use solana_program::{instruction::AccountMeta, pubkey::Pubkey};

use crate::discriminator::DlpDiscriminator;
use crate::pda::fees_vault_pda;

/// Claim the accrued fees from the protocol fees vault.
/// See [crate::processor::process_protocol_claim_fees] for docs.
pub fn protocol_claim_fees(admin: Pubkey) -> Instruction {
pub fn protocol_claim_fees(fees_receiver: Pubkey, program: Pubkey) -> Instruction {
let fees_vault_pda = fees_vault_pda();
let delegation_program_data =
Pubkey::find_program_address(&[crate::ID.as_ref()], &bpf_loader_upgradeable::id()).0;
Instruction {
program_id: crate::id(),
accounts: vec![
AccountMeta::new(admin, true),
AccountMeta::new(fees_vault_pda, false),
AccountMeta::new_readonly(delegation_program_data, false),
AccountMeta::new(fees_receiver, false),
AccountMeta::new_readonly(program, false),
],
data: DlpDiscriminator::ProtocolClaimFees.to_vec(),
}
Expand Down
31 changes: 31 additions & 0 deletions src/instruction_builder/set_fees_receiver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use borsh::to_vec;
use solana_program::instruction::Instruction;
use solana_program::{
bpf_loader_upgradeable, instruction::AccountMeta, pubkey::Pubkey, system_program,
};

use crate::args::SetFeesReceiverArgs;
use crate::discriminator::DlpDiscriminator;
use crate::pda::fees_vault_pda;

/// Set the fees receiver.
/// See [crate::processor::process_set_fees_receiver] for docs.
pub fn set_fees_receiver(admin: Pubkey, fees_receiver: Pubkey) -> Instruction {
let fees_vault_pda = fees_vault_pda();
let delegation_program_data =
Pubkey::find_program_address(&[crate::ID.as_ref()], &bpf_loader_upgradeable::id()).0;
Instruction {
program_id: crate::id(),
accounts: vec![
AccountMeta::new(admin, true),
AccountMeta::new(fees_vault_pda, false),
AccountMeta::new_readonly(delegation_program_data, false),
AccountMeta::new_readonly(system_program::id(), false),
],
data: [
DlpDiscriminator::SetFeesReceiver.to_vec(),
to_vec(&SetFeesReceiverArgs { fees_receiver }).unwrap(),
]
.concat(),
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ pub fn slow_process_instruction(
DlpDiscriminator::CallHandler => {
processor::process_call_handler(program_id, accounts, data)?
}
DlpDiscriminator::SetFeesReceiver => {
processor::process_set_fees_receiver(program_id, accounts, data)?
}
_ => {
log!("PANIC: Instruction must be processed by fast_process_instruction");
return Err(ProgramError::InvalidInstructionData);
Expand Down
5 changes: 3 additions & 2 deletions src/processor/close_validator_fees_vault.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ use crate::validator_fees_vault_seeds_from_validator;
///
/// 0; `[signer]` payer
/// 1; `[signer]` admin that controls the vault
/// 2; `[]` validator_identity
/// 3; `[]` validator_fees_vault_pda
/// 2; `[]` delegation program
/// 3; `[]` validator_identity
/// 4; `[]` validator_fees_vault_pda
///
/// Requirements:
///
Expand Down
37 changes: 29 additions & 8 deletions src/processor/init_protocol_fees_vault.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
use solana_program::program_error::ProgramError;
use solana_program::{
account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey, system_program,
account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError,
pubkey::Pubkey, system_program,
};

use crate::fees_vault_seeds;
use crate::processor::utils::loaders::{load_program, load_signer, load_uninitialized_pda};
use crate::processor::utils::pda::create_pda;
use crate::{
error::DlpError::Unauthorized,
fees_vault_seeds,
processor::utils::{
loaders::{
load_program, load_program_upgrade_authority, load_signer, load_uninitialized_pda,
},
pda::create_pda,
},
state::FeesVault,
};

/// Initialize the global fees vault
///
/// Accounts:
/// 0: `[signer]` the account paying for the transaction
/// 1: `[writable]` the fees vault PDA we are initializing
/// 2: `[]` the system program
/// 2: `[]` the delegation program data
/// 3: `[]` the system program
///
/// Requirements:
///
Expand All @@ -29,7 +38,7 @@ pub fn process_init_protocol_fees_vault(
_data: &[u8],
) -> ProgramResult {
// Load Accounts
let [payer, protocol_fees_vault, system_program] = accounts else {
let [payer, protocol_fees_vault, delegation_program_data, system_program] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};

Expand All @@ -44,16 +53,28 @@ pub fn process_init_protocol_fees_vault(
"fees vault",
)?;

// Check if the admin is the correct one
let admin_pubkey =
load_program_upgrade_authority(&crate::ID, delegation_program_data)?.ok_or(Unauthorized)?;

let fees_vault = FeesVault {
fees_receiver: admin_pubkey,
};

// Create the fees vault account
create_pda(
protocol_fees_vault,
&crate::id(),
8,
fees_vault.size_with_discriminator(),
fees_vault_seeds!(),
bump_fees_vault,
system_program,
payer,
)?;

// Write the fees vault data
let mut fees_vault_data = protocol_fees_vault.try_borrow_mut_data()?;
fees_vault.to_bytes_with_discriminator(&mut fees_vault_data.as_mut())?;

Ok(())
}
2 changes: 2 additions & 0 deletions src/processor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod delegate_ephemeral_balance;
mod init_protocol_fees_vault;
mod init_validator_fees_vault;
mod protocol_claim_fees;
mod set_fees_receiver;
mod top_up_ephemeral_balance;
mod utils;
mod validator_claim_fees;
Expand All @@ -19,6 +20,7 @@ pub use delegate_ephemeral_balance::*;
pub use init_protocol_fees_vault::*;
pub use init_validator_fees_vault::*;
pub use protocol_claim_fees::*;
pub use set_fees_receiver::*;
pub use top_up_ephemeral_balance::*;
pub use validator_claim_fees::*;
pub use whitelist_validator_for_program::*;
49 changes: 22 additions & 27 deletions src/processor/protocol_claim_fees.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
use crate::error::DlpError::Unauthorized;
use crate::processor::utils::loaders::{
load_initialized_protocol_fees_vault, load_program_upgrade_authority, load_signer,
};
use solana_program::msg;
use crate::processor::utils::loaders::{load_account, load_initialized_protocol_fees_vault};
use crate::state::FeesVault;
use solana_program::program_error::ProgramError;
use solana_program::rent::Rent;
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
Expand All @@ -11,15 +8,16 @@ use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubke
///
/// Accounts:
///
/// 1. `[signer]` admin account that can claim the fees
/// 2. `[writable]` protocol fees vault PDA
/// 1. `[writable]` protocol fees vault PDA
/// 2. `[writable]` fees receiver PDA
/// 3. `[]` delegation program
///
/// Requirements:
///
/// - protocol fees vault is initialized
/// - protocol fees vault has enough lamports to claim fees and still be
/// rent exempt
/// - admin is the protocol fees vault admin
/// - fees receiver is the correct one
///
/// 1. Transfer lamports from protocol fees_vault PDA to the admin authority
pub fn process_protocol_claim_fees(
Expand All @@ -28,40 +26,37 @@ pub fn process_protocol_claim_fees(
_data: &[u8],
) -> ProgramResult {
// Load Accounts
let [admin, fees_vault, delegation_program_data] = accounts else {
let [fees_vault_account, fees_receiver, _program] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};

// Check if the admin is signer
load_signer(admin, "admin")?;
load_initialized_protocol_fees_vault(fees_vault, true)?;
load_initialized_protocol_fees_vault(fees_vault_account, true)?;

// Check if the admin is the correct one
let admin_pubkey =
load_program_upgrade_authority(&crate::ID, delegation_program_data)?.ok_or(Unauthorized)?;
if !admin.key.eq(&admin_pubkey) {
msg!(
"Expected admin pubkey: {} but got {}",
admin_pubkey,
admin.key
);
return Err(Unauthorized.into());
}
let fees_vault_data = fees_vault_account.try_borrow_data()?;
let fees_vault = FeesVault::try_from_bytes_with_discriminator(&fees_vault_data)?;

load_account(
fees_receiver,
fees_vault.fees_receiver,
true,
"fees receiver",
)?;

// Calculate the amount to transfer
let min_rent = Rent::default().minimum_balance(8);
if fees_vault.lamports() < min_rent {
let min_rent = Rent::default().minimum_balance(fees_vault.size_with_discriminator());
if fees_vault_account.lamports() < min_rent {
return Err(ProgramError::InsufficientFunds);
}
let amount = fees_vault.lamports() - min_rent;
let amount = fees_vault_account.lamports() - min_rent;

// Transfer fees to the admin pubkey
**fees_vault.try_borrow_mut_lamports()? = fees_vault
**fees_vault_account.try_borrow_mut_lamports()? = fees_vault_account
.lamports()
.checked_sub(amount)
.ok_or(ProgramError::InsufficientFunds)?;

**admin.try_borrow_mut_lamports()? = admin
**fees_receiver.try_borrow_mut_lamports()? = fees_receiver
.lamports()
.checked_add(amount)
.ok_or(ProgramError::ArithmeticOverflow)?;
Expand Down
Loading