|
/// CHECK: Avoiding struct deserialization here to avoid default Owner trait check. |
|
/// `owner = config.priority_fee_distribution_program.key()` here is sufficient. |
|
#[account( |
|
seeds = [ |
|
PriorityFeeDistributionAccount::SEED, |
|
vote_account.key().as_ref(), |
|
epoch.to_le_bytes().as_ref(), |
|
], |
|
bump, |
|
seeds::program = config.priority_fee_distribution_program.key(), |
|
)] |
|
pub distribution_account: UncheckedAccount<'info>, |
|
|
|
#[account(mut)] |
|
pub signer: Signer<'info>, |
|
} |
|
|
|
pub fn handle_copy_priority_fee_distribution_account( |
|
ctx: Context<CopyPriorityFeeDistribution>, |
|
epoch: u64, |
|
) -> Result<()> { |
|
// cant set data in validator history for future epochs |
|
if epoch > Clock::get()?.epoch { |
|
return Err(ValidatorHistoryError::EpochOutOfRange.into()); |
|
} |
|
|
|
// The PDFA's priority_fee data is not valid until epoch n+1 |
|
if epoch == Clock::get()?.epoch { |
|
return Err(ValidatorHistoryError::PriorityFeeDistributionAccountNotFinalized.into()); |
|
} |
|
|
|
let epoch = cast_epoch(epoch)?; |
|
let mut validator_history_account = ctx.accounts.validator_history_account.load_mut()?; |
|
|
|
let validator_history_entry_for_epoch = validator_history_account |
|
.history |
|
.arr_mut() |
|
.iter_mut() |
|
.find(|entry| entry.epoch == epoch); |
|
|
|
// This ensures there is no possibility to overwrite a validator history entry after the PFDA |
|
// rent has been reclaimed and introduce an erroneous unstake. |
|
if let Some(entry) = validator_history_entry_for_epoch { |
|
if entry.priority_fee_merkle_root_upload_authority != MerkleRootUploadAuthority::Unset { |
|
return Err(ValidatorHistoryError::PriorityFeeDistributionAccountAlreadyCopied.into()); |
|
} |
|
} |
|
|
|
let mut pdfa_data: &[u8] = &ctx.accounts.distribution_account.try_borrow_data()?; |
|
|
|
let distribution_account = PriorityFeeDistributionAccount::try_deserialize(&mut pdfa_data) |
|
.unwrap_or(PriorityFeeDistributionAccount { |
|
validator_vote_account: Pubkey::default(), |
|
merkle_root_upload_authority: DNE_AUTHORITY, |
|
validator_commission_bps: 0, |
|
total_lamports_transferred: 0, |
|
merkle_root: None, |
|
epoch_created_at: 0, |
|
expires_at: 0, |
|
bump: 0, |
|
}); |
|
// If the distribution account is not found, we set the default values of 0 for the commission and priority fees earned |
|
let commission_bps = distribution_account.validator_commission_bps; |
|
let priority_fees_transferred = distribution_account.total_lamports_transferred; |
|
// If the distribution account is not found, we set '11111111111111111111111111111111' as the merkle root upload authority |
|
// passing this to MerkleRootUploadAuthority::from_pubkey resolve to a DNE authority |
|
let merkle_root_upload_authority = distribution_account.merkle_root_upload_authority; |
|
|
|
validator_history_account.set_priority_fees_transferred_and_commission( |
|
epoch, |
|
commission_bps, |
|
priority_fees_transferred, |
|
MerkleRootUploadAuthority::from_pubkey(&merkle_root_upload_authority), |
|
)?; |
describe the bug
copy_priority_fee_distributionchecks the expected pda seeds fordistribution_account, but does not check that the account is owned by the configured priority fee distribution program.code:
stakenet/programs/validator-history/src/instructions/copy_priority_fee_distribution.rs
Lines 34 to 107 in 0a70837
if a caller passes the correct pda address before the real keeper copy, deserialization can fail and the instruction records the account as
dne. that poisoned value can persist in validator history.impact
steward can later treat the validator as having 100% realized priority fee commission and/or a bad priority fee upload authority, reducing score or contributing to unstake decisions once priority fee scoring is active.
note: on the current jitosol steward config this appears effectively disabled today because
priority_fee_scoring_start_epochis in the future and the configured max commission plus error margin is above the 100% value recorded fordne. the current risk is persistent validator-history poisoning and future delegation impact if priority fee scoring is enabled while poisoned epochs remain in lookback.related scoring paths:
stakenet/programs/steward/src/score.rs
Lines 701 to 720 in 0a70837
expected behavior
distribution_accountshould be required to be owned byconfig.priority_fee_distribution_program.suggested fix