Skip to content
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
249 changes: 125 additions & 124 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,18 @@ unit_test_config = []
[dependencies]
borsh = "0.10.3"
paste = "^1.0"
solana-program = "^1.18.25"
solana-program = "1.18.26"
bytemuck = "^1.14.3"
num_enum = "^0.7.2"
thiserror = "^1.0.57"
solana-security-txt = { version = "1.1.1", optional = true }
solana-curve25519 = "2.0.13"
bincode = "1.3.3"

[dev-dependencies]
base64 = "0.22.0"
rand = "0.8.5"
solana-program-test = "^1.18.25"
solana-sdk = "^1.18.25"
solana-program-test = "1.18.26"
solana-sdk = "1.18.26"
tokio = { version = "1.40.0", features = ["full"] }
delegation-program = { path = ".", features = ["unit_test_config"] }
3 changes: 1 addition & 2 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
[toolchain]
channel = "1.75.0"
channel = "1.79.0"
components = [ "rustfmt", "rust-analyzer", "clippy"]
targets = [ "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-apple-darwin"]
profile = "minimal"
2 changes: 2 additions & 0 deletions src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ pub const COMMIT_RECORD: &[u8] = b"commit-state-record";

/// The account to store lamports deposited for paying fees.
pub const FEES_VAULT: &[u8] = b"fees-vault";

pub const VALIDATOR_FEES_VAULT: &[u8] = b"v-fees-vault";
pub const PROGRAM_CONFIG: &[u8] = b"p-conf";

/// The discriminator for the external undelegate instruction.
pub const EXTERNAL_UNDELEGATE_DISCRIMINATOR: [u8; 8] = [196, 28, 41, 206, 48, 37, 51, 167];
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ pub enum DlpError {
InvalidValidatorBalanceAfterCPI = 8,
#[error("Invalid reimbursement address for delegation rent")]
InvalidReimbursementAddressForDelegationRent = 9,
#[error("Authority is invalid for the delegated account program owner")]
InvalidWhitelistProgramConfig = 10,
}

impl From<DlpError> for ProgramError {
Expand Down
43 changes: 42 additions & 1 deletion src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
use num_enum::TryFromPrimitive;
use solana_program::program_error::ProgramError;
use solana_program::{
bpf_loader_upgradeable,
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
system_program,
Expand All @@ -11,7 +12,8 @@ use crate::consts::{BUFFER, FEES_VAULT};
use crate::pda::{
buffer_pda_from_pubkey, committed_state_pda_from_pubkey,
committed_state_record_pda_from_pubkey, delegation_metadata_pda_from_pubkey,
delegation_record_pda_from_pubkey, validator_fees_vault_pda_from_pubkey,
delegation_record_pda_from_pubkey, program_config_pda_from_pubkey,
validator_fees_vault_pda_from_pubkey,
};

#[derive(Debug, BorshSerialize, BorshDeserialize)]
Expand All @@ -34,6 +36,11 @@ pub struct ClaimFeesArgs {
pub amount: Option<u64>,
}

#[derive(Debug, BorshSerialize, BorshDeserialize)]
pub struct WhitelistValidatorForProgramArgs {
pub insert: bool,
}

#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
#[rustfmt::skip]
Expand All @@ -46,6 +53,7 @@ pub enum DlpInstruction {
InitFeesVault = 5,
InitValidatorFeesVault = 6,
ValidatorClaimFees = 7,
WhitelistValidatorForProgram = 8,
}

impl DlpInstruction {
Expand All @@ -67,6 +75,7 @@ impl TryFrom<[u8; 8]> for DlpInstruction {
[0x5, 0, 0, 0, 0, 0, 0, 0] => Ok(DlpInstruction::InitFeesVault),
[0x6, 0, 0, 0, 0, 0, 0, 0] => Ok(DlpInstruction::InitValidatorFeesVault),
[0x7, 0, 0, 0, 0, 0, 0, 0] => Ok(DlpInstruction::ValidatorClaimFees),
[0x8, 0, 0, 0, 0, 0, 0, 0] => Ok(DlpInstruction::WhitelistValidatorForProgram),
_ => Err(ProgramError::InvalidInstructionData),
}
}
Expand Down Expand Up @@ -136,6 +145,7 @@ pub fn delegate_on_curve(
pub fn commit_state(
validator: Pubkey,
delegated_account: Pubkey,
delegated_account_owner: Pubkey,
commit_args: CommitAccountArgs,
) -> Instruction {
let commit_args = commit_args.try_to_vec().unwrap();
Expand All @@ -144,6 +154,7 @@ pub fn commit_state(
let commit_state_record_pda = committed_state_record_pda_from_pubkey(&delegated_account);
let validator_fees_vault_pda = validator_fees_vault_pda_from_pubkey(&validator);
let delegation_metadata_pda = delegation_metadata_pda_from_pubkey(&delegated_account);
let whitelist_program_config = program_config_pda_from_pubkey(&delegated_account_owner);
Instruction {
program_id: crate::id(),
accounts: vec![
Expand All @@ -154,6 +165,7 @@ pub fn commit_state(
AccountMeta::new(delegation_record_pda, false),
AccountMeta::new(delegation_metadata_pda, false),
AccountMeta::new(validator_fees_vault_pda, false),
AccountMeta::new_readonly(whitelist_program_config, false),
AccountMeta::new_readonly(system_program::id(), false),
],
data: [DlpInstruction::CommitState.to_vec(), commit_args].concat(),
Expand Down Expand Up @@ -294,3 +306,32 @@ pub fn validator_claim_fees(validator: Pubkey, amount: Option<u64>) -> Instructi
.concat(),
}
}

/// Whitelist validator for program
pub fn whitelist_validator_for_program(
authority: Pubkey,
validator_identity: Pubkey,
program: Pubkey,
insert: bool,
) -> Instruction {
let args = WhitelistValidatorForProgramArgs { insert };
let program_data =
Pubkey::find_program_address(&[program.as_ref()], &bpf_loader_upgradeable::id()).0;
let program_config = program_config_pda_from_pubkey(&program);
Instruction {
program_id: crate::id(),
accounts: vec![
AccountMeta::new(authority, true),
AccountMeta::new(validator_identity, false),
AccountMeta::new_readonly(program, false),
AccountMeta::new_readonly(program_data, false),
AccountMeta::new(program_config, false),
AccountMeta::new_readonly(system_program::id(), false),
],
data: [
DlpInstruction::WhitelistValidatorForProgram.to_vec(),
args.try_to_vec().unwrap(),
]
.concat(),
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ pub fn process_instruction(
DlpInstruction::ValidatorClaimFees => {
process_validator_claim_fees(program_id, accounts, data)?
}
DlpInstruction::WhitelistValidatorForProgram => {
process_whitelist_validator_for_program(program_id, accounts, data)?
}
}
Ok(())
}
5 changes: 4 additions & 1 deletion src/pda.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use paste::paste;

use crate::consts::{
BUFFER, COMMIT_RECORD, COMMIT_STATE, DELEGATION_METADATA, DELEGATION_RECORD,
BUFFER, COMMIT_RECORD, COMMIT_STATE, DELEGATION_METADATA, DELEGATION_RECORD, PROGRAM_CONFIG,
VALIDATOR_FEES_VAULT,
};

Expand Down Expand Up @@ -86,6 +86,9 @@ pda! { validator_fees_vault }
seeds! { buffer, BUFFER }
pda! { buffer }

seeds! { program_config, PROGRAM_CONFIG }
pda! { program_config }

#[cfg(test)]
mod tests {
use std::str::FromStr;
Expand Down
27 changes: 24 additions & 3 deletions src/processor/commit_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ use std::mem::size_of;
use crate::consts::{COMMIT_RECORD, COMMIT_STATE};
use crate::error::DlpError;
use crate::instruction::CommitAccountArgs;
use crate::state::{CommitRecord, DelegationMetadata, DelegationRecord};
use crate::state::{CommitRecord, DelegationMetadata, DelegationRecord, WhitelistForProgram};
use crate::utils::loaders::{
load_initialized_delegation_metadata, load_initialized_delegation_record, load_owned_pda,
load_signer, load_uninitialized_pda, load_validator_fees_vault,
load_program_config, load_signer, load_uninitialized_pda, load_validator_fees_vault,
};
use crate::utils::utils_account::{AccountDeserialize, Discriminator};
use crate::utils::utils_pda::create_pda;
Expand All @@ -18,6 +18,7 @@ use solana_program::system_instruction::transfer;
use solana_program::{
account_info::AccountInfo,
entrypoint::ProgramResult,
msg,
pubkey::Pubkey,
{self},
};
Expand All @@ -38,7 +39,7 @@ pub fn process_commit_state(
let args = CommitAccountArgs::try_from_slice(data)?;
let data: &[u8] = args.data.as_ref();

let [validator, delegated_account, commit_state_account, commit_state_record, delegation_record, delegation_metadata, validator_fees_vault, system_program] =
let [validator, delegated_account, commit_state_account, commit_state_record, delegation_record, delegation_metadata, validator_fees_vault, program_config, system_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
Expand All @@ -55,6 +56,17 @@ pub fn process_commit_state(
let mut delegation_data = delegation_record.try_borrow_mut_data()?;
let delegation = DelegationRecord::try_from_bytes_mut(&mut delegation_data)?;

// Load the program configuration and validate it, if any
let is_config = load_program_config(program_config, delegation.owner)?;
msg!("Config: {:?}", is_config);
if is_config {
msg!("Checking config");
let program_config_data = program_config.try_borrow_data()?;
let config = WhitelistForProgram::try_from_slice(&program_config_data)?;
msg!("Config: {:?}", config);
validate_config(config, validator.key)?;
}

let mut delegation_metadata_data = delegation_metadata.try_borrow_mut_data()?;
let mut delegation_metadata = DelegationMetadata::try_from_slice(&delegation_metadata_data)?;

Expand Down Expand Up @@ -136,3 +148,12 @@ pub fn process_commit_state(

Ok(())
}

/// If there exists a validators whitelist for the delegated account program owner, check that the validator is whitelisted for it
fn validate_config(config: WhitelistForProgram, validator: &Pubkey) -> Result<(), ProgramError> {
if !config.approved_validators.is_empty() && !config.approved_validators.contains(validator) {
return Err(DlpError::InvalidWhitelistProgramConfig.into());
}
msg!("Valid config");
Ok(())
}
2 changes: 2 additions & 0 deletions src/processor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod init_fees_vault;
mod init_validator_fees_vault;
mod undelegate;
mod validator_claim_fees;
mod whitelist_validator_for_program;

pub use allow_undelegate::*;
pub use commit_state::*;
Expand All @@ -15,3 +16,4 @@ pub use init_fees_vault::*;
pub use init_validator_fees_vault::*;
pub use undelegate::*;
pub use validator_claim_fees::*;
pub use whitelist_validator_for_program::*;
120 changes: 120 additions & 0 deletions src/processor/whitelist_validator_for_program.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use crate::consts::{ADMIN_PUBKEY, PROGRAM_CONFIG};
use crate::error::DlpError::Unauthorized;
use crate::instruction::WhitelistValidatorForProgramArgs;
use crate::state::WhitelistForProgram;
use crate::utils::loaders::{load_pda, load_signer};
use crate::utils::utils_pda::{create_pda, resize_pda};
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::bpf_loader_upgradeable::UpgradeableLoaderState;
use solana_program::program_error::ProgramError;
use solana_program::{
account_info::AccountInfo,
bpf_loader_upgradeable,
entrypoint::ProgramResult,
pubkey::Pubkey,
{self},
};

/// Whitelist a validator for a program
///
pub fn process_whitelist_validator_for_program(
_program_id: &Pubkey,
accounts: &[AccountInfo],
data: &[u8],
) -> ProgramResult {
let args = WhitelistValidatorForProgramArgs::try_from_slice(data)?;

// Load Accounts
let [authority, validator_identity, program, program_data, program_config_account, system_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};

load_signer(authority)?;
validate_authority(authority, program, program_data)?;

let bump = load_pda(
program_config_account,
&[PROGRAM_CONFIG, program.key.as_ref()],
&crate::id(),
true,
)?;

// Get the program whitelist. If the account doesn't exist, create it
let mut program_whitelist = if program_config_account.owner.eq(system_program.key) {
create_pda(
program_config_account,
&crate::id(),
8 + WhitelistForProgram::default().serialized_len(),
&[PROGRAM_CONFIG, program.key.as_ref(), &[bump]],
system_program,
authority,
)?;
WhitelistForProgram::default()
} else {
let data = program_config_account.try_borrow_data()?;
WhitelistForProgram::try_from_slice(&data)?
};
if args.insert {
program_whitelist
.approved_validators
.insert(*validator_identity.key);
} else {
program_whitelist
.approved_validators
.remove(validator_identity.key);
}
resize_pda(
authority,
program_config_account,
system_program,
program_whitelist.serialized_len(),
)?;
let mut data = program_config_account.try_borrow_mut_data()?;
program_whitelist.serialize(&mut &mut data.as_mut())?;

Ok(())
}

/// Authority is valid if either the authority is the ADMIN_PUBKEY or the program upgrade authority
fn validate_authority(
authority: &AccountInfo,
program: &AccountInfo,
program_data: &AccountInfo,
) -> Result<(), ProgramError> {
if authority.key.eq(&ADMIN_PUBKEY)
|| authority
.key
.eq(&get_program_upgrade_authority(program, program_data)?.ok_or(Unauthorized)?)
{
Ok(())
} else {
Err(Unauthorized.into())
}
}

/// Get the program upgrade authority for a given program
fn get_program_upgrade_authority(
program: &AccountInfo,
program_data: &AccountInfo,
) -> Result<Option<Pubkey>, ProgramError> {
let program_data_address =
Pubkey::find_program_address(&[program.key.as_ref()], &bpf_loader_upgradeable::id()).0;

if !program_data_address.eq(program_data.key) {
return Err(ProgramError::InvalidAccountData);
}

let program_account_data = program_data.try_borrow_data()?;
if let UpgradeableLoaderState::ProgramData {
upgrade_authority_address,
..
} =
bincode::deserialize(&program_account_data).map_err(|_| ProgramError::InvalidAccountData)?
{
Ok(upgrade_authority_address)
} else {
Err(ProgramError::InvalidAccountData)
}
}
2 changes: 2 additions & 0 deletions src/state/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod commit_record;
mod delegated_metadata;
mod delegation_record;
mod whitelist_for_program;

pub use commit_record::*;
pub use delegated_metadata::*;
pub use delegation_record::*;
pub use whitelist_for_program::*;
Loading