use {
self::points::{
CalculatedStakePoints, CalculationEnvironment, DelegatedVoteState,
InflationPointCalculationEvent, SkippedReason, calculate_stake_points_and_credits,
},
solana_instruction::error::InstructionError,
solana_stake_interface::{
error::StakeError,
state::{Stake, StakeStateV2},
},
};
pub mod points;
#[derive(Debug, PartialEq, Eq)]
struct CalculatedStakeRewards {
staker_rewards: u64,
voter_rewards: u64,
new_credits_observed: u64,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn redeem_rewards<'a>(
stake_state: &StakeStateV2,
voter_commission_bps: u16,
vote_state: DelegatedVoteState,
calculation_environment: CalculationEnvironment<'a>,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
stake_account_lamports_for_trace: u64,
) -> Result<(u64, u64, Stake), InstructionError> {
if let StakeStateV2::Stake(_meta, stake, _stake_flags) = stake_state {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
let CalculationEnvironment {
rewarded_epoch,
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
..
} = calculation_environment;
inflation_point_calc_tracer(
&InflationPointCalculationEvent::EffectiveStakeAtRewardedEpoch(stake.stake(
rewarded_epoch,
stake_history,
new_rate_activation_epoch,
)),
);
inflation_point_calc_tracer(&InflationPointCalculationEvent::PriorTotalLamports(
stake_account_lamports_for_trace,
));
if commission_rate_in_basis_points {
inflation_point_calc_tracer(&InflationPointCalculationEvent::CommissionBps(
voter_commission_bps,
));
} else {
inflation_point_calc_tracer(&InflationPointCalculationEvent::Commission(
(voter_commission_bps / 100) as u8,
));
}
}
let mut stake = *stake;
if let Some((stakers_reward, voters_reward)) = redeem_stake_rewards(
&mut stake,
voter_commission_bps,
vote_state,
calculation_environment,
inflation_point_calc_tracer,
) {
Ok((stakers_reward, voters_reward, stake))
} else {
Err(StakeError::NoCreditsToRedeem.into())
}
} else {
Err(InstructionError::InvalidAccountData)
}
}
fn redeem_stake_rewards<'a>(
stake: &mut Stake,
voter_commission_bps: u16,
vote_state: DelegatedVoteState,
calculation_environment: CalculationEnvironment<'a>,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
) -> Option<(u64, u64)> {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
stake.credits_observed,
None,
));
}
calculate_stake_rewards(
stake,
voter_commission_bps,
vote_state,
calculation_environment,
inflation_point_calc_tracer.as_ref(),
)
.map(|calculated_stake_rewards| {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer {
inflation_point_calc_tracer(&InflationPointCalculationEvent::CreditsObserved(
stake.credits_observed,
Some(calculated_stake_rewards.new_credits_observed),
));
}
stake.credits_observed = calculated_stake_rewards.new_credits_observed;
stake.delegation.stake += calculated_stake_rewards.staker_rewards;
(
calculated_stake_rewards.staker_rewards,
calculated_stake_rewards.voter_rewards,
)
})
}
fn calculate_stake_rewards<'a>(
stake: &Stake,
voter_commission_bps: u16,
vote_state: DelegatedVoteState,
calculation_environment: CalculationEnvironment<'a>,
inflation_point_calc_tracer: Option<impl Fn(&InflationPointCalculationEvent)>,
) -> Option<CalculatedStakeRewards> {
let CalculationEnvironment {
stake_history,
new_rate_activation_epoch,
point_value,
rewarded_epoch,
..
} = calculation_environment;
let CalculatedStakePoints {
points,
new_credits_observed,
mut force_credits_update_with_skipped_reward,
} = calculate_stake_points_and_credits(
stake,
vote_state,
stake_history,
inflation_point_calc_tracer.as_ref(),
new_rate_activation_epoch,
);
if point_value.rewards == 0 {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::DisabledInflation.into());
}
force_credits_update_with_skipped_reward = true;
} else if stake.delegation.activation_epoch == rewarded_epoch {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::JustActivated.into());
}
force_credits_update_with_skipped_reward = true;
}
if force_credits_update_with_skipped_reward {
return Some(CalculatedStakeRewards {
staker_rewards: 0,
voter_rewards: 0,
new_credits_observed,
});
}
if points == 0 {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::ZeroPoints.into());
}
return None;
}
if point_value.points == 0 {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::ZeroPointValue.into());
}
return None;
}
let rewards = points
.checked_mul(u128::from(point_value.rewards))
.expect("Rewards intermediate calculation should fit within u128")
.checked_div(point_value.points)
.unwrap();
let rewards = u64::try_from(rewards).expect("Rewards should fit within u64");
if rewards == 0 {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::ZeroReward.into());
}
return None;
}
let (voter_rewards, staker_rewards, is_split) = commission_split(voter_commission_bps, rewards);
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&InflationPointCalculationEvent::SplitRewards(
rewards,
voter_rewards,
staker_rewards,
point_value.clone(),
));
}
if (voter_rewards == 0 || staker_rewards == 0) && is_split {
if let Some(inflation_point_calc_tracer) = inflation_point_calc_tracer.as_ref() {
inflation_point_calc_tracer(&SkippedReason::TooEarlyUnfairSplit.into());
}
return None;
}
Some(CalculatedStakeRewards {
staker_rewards,
voter_rewards,
new_credits_observed,
})
}
fn commission_split(commission_bps: u16, on: u64) -> (u64, u64, bool) {
const MAX_BPS: u16 = 10_000;
const MAX_BPS_U128: u128 = MAX_BPS as u128;
match commission_bps.min(MAX_BPS) {
0 => (0, on, false),
MAX_BPS => (on, 0, false),
split => {
let on = u128::from(on);
let mine = on
.checked_mul(u128::from(split))
.expect("multiplication of a u64 and u16 should not overflow")
/ MAX_BPS_U128;
let theirs = on
.checked_mul(u128::from(
MAX_BPS
.checked_sub(split)
.expect("commission cannot be greater than MAX_BPS"),
))
.expect("multiplication of a u64 and u16 should not overflow")
/ MAX_BPS_U128;
(mine as u64, theirs as u64, true)
}
}
}
#[cfg(test)]
mod tests {
use {
self::points::{PointValue, null_tracer},
super::*,
proptest::prelude::*,
solana_clock::Epoch,
solana_native_token::LAMPORTS_PER_SOL,
solana_pubkey::Pubkey,
solana_stake_interface::{stake_history::StakeHistory, state::Delegation},
solana_vote_program::vote_state::{VoteStateV4, handler::VoteStateHandler},
test_case::test_case,
};
fn new_stake(
stake: u64,
voter_pubkey: &Pubkey,
vote_state: &VoteStateV4,
activation_epoch: Epoch,
) -> Stake {
Stake {
delegation: Delegation::new(voter_pubkey, stake, activation_epoch),
credits_observed: vote_state.credits(),
}
}
#[test]
fn test_stake_state_redeem_rewards() {
let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::default());
let stake_lamports = 1;
let mut stake = new_stake(
stake_lamports,
&Pubkey::default(),
vote_state.as_ref_v4(),
u64::MAX,
);
let stake_history = &StakeHistory::default();
let new_rate_activation_epoch = None;
let commission_rate_in_basis_points = true;
assert_eq!(
None,
redeem_stake_rewards(
&mut stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 0,
point_value: &PointValue {
rewards: 1_000_000_000,
points: 1
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
vote_state.increment_credits(0, 1);
vote_state.increment_credits(0, 1);
assert_eq!(
Some((stake_lamports * 2, 0)),
redeem_stake_rewards(
&mut stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 0,
point_value: &PointValue {
rewards: 1,
points: 1
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
assert_eq!(
stake.delegation.stake,
stake_lamports + (stake_lamports * 2)
);
assert_eq!(stake.credits_observed, 2);
}
#[test]
fn test_stake_state_calculate_rewards() {
let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::default());
let mut stake = new_stake(1, &Pubkey::default(), vote_state.as_ref_v4(), u64::MAX);
let stake_history = &StakeHistory::default();
let new_rate_activation_epoch = None;
let commission_rate_in_basis_points = true;
assert_eq!(
None,
calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 0,
point_value: &PointValue {
rewards: 1_000_000_000,
points: 1
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
vote_state.increment_credits(0, 1);
vote_state.increment_credits(0, 1);
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake * 2,
voter_rewards: 0,
new_credits_observed: 2,
}),
calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 0,
point_value: &PointValue {
rewards: 2,
points: 2 },
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
stake.credits_observed = 1;
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake,
voter_rewards: 0,
new_credits_observed: 2,
}),
calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 0,
point_value: &PointValue {
rewards: 1,
points: 1
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
vote_state.increment_credits(1, 1);
stake.credits_observed = 2;
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake,
voter_rewards: 0,
new_credits_observed: 3,
}),
calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 1,
point_value: &PointValue {
rewards: 2,
points: 2
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
vote_state.increment_credits(2, 1);
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake * 2,
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 2,
point_value: &PointValue {
rewards: 2,
points: 2
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
stake.credits_observed = 0;
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake * 2 + stake.delegation.stake + stake.delegation.stake, voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 2,
point_value: &PointValue {
rewards: 4,
points: 4
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
vote_state.set_inflation_rewards_commission_bps(100);
assert_eq!(
None, calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 2,
point_value: &PointValue {
rewards: 4,
points: 4
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
vote_state.set_inflation_rewards_commission_bps(9900);
assert_eq!(
None, calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 2,
point_value: &PointValue {
rewards: 4,
points: 4
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: 0,
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 2,
point_value: &PointValue {
rewards: 0,
points: 4
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
stake.credits_observed = 4;
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: 0,
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 2,
point_value: &PointValue {
rewards: 0,
points: 4
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
assert_eq!(
CalculatedStakePoints {
points: 0,
new_credits_observed: 4,
force_credits_update_with_skipped_reward: false,
},
calculate_stake_points_and_credits(
&stake,
DelegatedVoteState::from(vote_state.as_ref_v4()),
&StakeHistory::default(),
null_tracer(),
None
)
);
stake.credits_observed = 1000;
assert_eq!(
CalculatedStakePoints {
points: 0,
new_credits_observed: 4,
force_credits_update_with_skipped_reward: true,
},
calculate_stake_points_and_credits(
&stake,
DelegatedVoteState::from(vote_state.as_ref_v4()),
&StakeHistory::default(),
null_tracer(),
None
)
);
stake.credits_observed = 4;
assert_eq!(
CalculatedStakePoints {
points: 0,
new_credits_observed: 4,
force_credits_update_with_skipped_reward: false,
},
calculate_stake_points_and_credits(
&stake,
DelegatedVoteState::from(vote_state.as_ref_v4()),
&StakeHistory::default(),
null_tracer(),
None
)
);
vote_state.set_inflation_rewards_commission_bps(0);
stake.credits_observed = 3;
stake.delegation.activation_epoch = 1;
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: stake.delegation.stake, voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 2,
point_value: &PointValue {
rewards: 1,
points: 1
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
stake.delegation.activation_epoch = 2;
stake.credits_observed = 3;
assert_eq!(
Some(CalculatedStakeRewards {
staker_rewards: 0,
voter_rewards: 0,
new_credits_observed: 4,
}),
calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 2,
point_value: &PointValue {
rewards: 1,
points: 1
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
}
#[test_case(u64::MAX, 1_000, u64::MAX => panics "Rewards intermediate calculation should fit within u128")]
#[test_case(1, u64::MAX, u64::MAX => panics "Rewards should fit within u64")]
fn calculate_rewards_tests(stake: u64, rewards: u64, credits: u64) {
let mut vote_state = VoteStateHandler::new_v4(VoteStateV4::default());
let stake = new_stake(stake, &Pubkey::default(), vote_state.as_ref_v4(), u64::MAX);
vote_state.increment_credits(0, credits);
let stake_history = &StakeHistory::default();
let new_rate_activation_epoch = None;
let commission_rate_in_basis_points = true;
calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 0,
point_value: &PointValue { rewards, points: 1 },
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
);
}
#[test]
fn test_stake_state_calculate_points_with_typical_values() {
let vote_state = VoteStateHandler::new_v4(VoteStateV4::default());
let stake = new_stake(
10_000_000 * LAMPORTS_PER_SOL,
&Pubkey::default(),
vote_state.as_ref_v4(),
u64::MAX,
);
let stake_history = &StakeHistory::default();
let new_rate_activation_epoch = None;
let commission_rate_in_basis_points = true;
assert_eq!(
None,
calculate_stake_rewards(
&stake,
vote_state.as_ref_v4().inflation_rewards_commission_bps,
DelegatedVoteState::from(vote_state.as_ref_v4()),
CalculationEnvironment {
rewarded_epoch: 0,
point_value: &PointValue {
rewards: 1_000_000_000,
points: 1
},
stake_history,
new_rate_activation_epoch,
commission_rate_in_basis_points,
},
null_tracer(),
)
);
}
#[test]
fn test_commission_split_bps() {
assert_eq!(commission_split(0, 1), (0, 1, false));
assert_eq!(commission_split(0, 10), (0, 10, false));
assert_eq!(commission_split(0, 100), (0, 100, false));
assert_eq!(commission_split(0, 1_000), (0, 1_000, false));
assert_eq!(commission_split(0, u64::MAX), (0, u64::MAX, false));
assert_eq!(commission_split(10_000, 1), (1, 0, false));
assert_eq!(commission_split(10_000, 10), (10, 0, false));
assert_eq!(commission_split(10_000, 100), (100, 0, false));
assert_eq!(commission_split(10_000, 1_000), (1_000, 0, false));
assert_eq!(commission_split(10_000, u64::MAX), (u64::MAX, 0, false));
assert_eq!(commission_split(u16::MAX, 1), (1, 0, false));
assert_eq!(commission_split(u16::MAX, 10), (10, 0, false));
assert_eq!(commission_split(u16::MAX, 100), (100, 0, false));
assert_eq!(commission_split(u16::MAX, 1_000), (1_000, 0, false));
assert_eq!(commission_split(u16::MAX, u64::MAX), (u64::MAX, 0, false));
assert_eq!(commission_split(9_900, 1), (0, 0, true)); assert_eq!(commission_split(9_900, 10), (9, 0, true)); assert_eq!(commission_split(9_900, 100), (99, 1, true));
assert_eq!(commission_split(9_900, 1_000), (990, 10, true));
assert_eq!(
commission_split(9_900, u64::MAX),
(
(u64::MAX as u128 * 9_900 / 10_000) as u64,
(u64::MAX as u128 * 100 / 10_000) as u64,
true
)
);
assert_eq!(commission_split(9_999, 1), (0, 0, true)); assert_eq!(commission_split(9_999, 10), (9, 0, true)); assert_eq!(commission_split(9_999, 100), (99, 0, true)); assert_eq!(commission_split(9_999, 1_000), (999, 0, true)); assert_eq!(
commission_split(9_999, u64::MAX),
(
(u64::MAX as u128 * 9_999 / 10_000) as u64,
(u64::MAX as u128 / 10_000) as u64,
true
)
);
assert_eq!(commission_split(100, 1), (0, 0, true)); assert_eq!(commission_split(100, 10), (0, 9, true)); assert_eq!(commission_split(100, 100), (1, 99, true));
assert_eq!(commission_split(100, 1_000), (10, 990, true));
assert_eq!(
commission_split(100, u64::MAX),
(
(u64::MAX as u128 * 100 / 10_000) as u64,
(u64::MAX as u128 * 9_900 / 10_000) as u64,
true
)
);
assert_eq!(commission_split(5_000, 1), (0, 0, true)); assert_eq!(commission_split(5_000, 10), (5, 5, true));
assert_eq!(commission_split(5_000, 100), (50, 50, true));
assert_eq!(commission_split(5_000, 1_000), (500, 500, true));
assert_eq!(
commission_split(5_000, u64::MAX),
(
(u64::MAX as u128 * 5_000 / 10_000) as u64,
(u64::MAX as u128 * 5_000 / 10_000) as u64,
true
)
);
assert_eq!(commission_split(1_234, 1), (0, 0, true)); assert_eq!(commission_split(1_234, 10), (1, 8, true)); assert_eq!(commission_split(1_234, 1_000), (123, 876, true)); assert_eq!(commission_split(1_234, 10_000), (1_234, 8_766, true));
assert_eq!(
commission_split(1_234, u64::MAX),
(
(u64::MAX as u128 * 1_234 / 10_000) as u64,
(u64::MAX as u128 * 8_766 / 10_000) as u64,
true
)
);
assert_eq!(commission_split(3_333, 1), (0, 0, true)); assert_eq!(commission_split(3_333, 10), (3, 6, true)); assert_eq!(commission_split(3_333, 1_000), (333, 666, true)); assert_eq!(commission_split(3_333, 10_000), (3_333, 6_667, true));
assert_eq!(
commission_split(3_333, u64::MAX),
(
(u64::MAX as u128 * 3_333 / 10_000) as u64,
(u64::MAX as u128 * 6_667 / 10_000) as u64,
true
)
); }
proptest! {
#[test]
fn test_commission_split_properties(
commission_bps in 0..=u16::MAX,
rewards in 0..=u64::MAX,
) {
let (voter, staker, was_split) = commission_split(commission_bps, rewards);
prop_assert!(voter + staker <= rewards);
prop_assert!(rewards - voter - staker <= 1);
let effective_bps = commission_bps.min(10_000);
if effective_bps == 0 || effective_bps == 10_000 {
prop_assert!(!was_split);
} else {
prop_assert!(was_split);
}
if effective_bps == 0 {
prop_assert_eq!(voter, 0);
prop_assert_eq!(staker, rewards);
}
if effective_bps == 10_000 {
prop_assert_eq!(voter, rewards);
prop_assert_eq!(staker, 0);
}
if commission_bps > 10_000 {
let (clamped_voter, clamped_staker, clamped_ws) =
commission_split(10_000, rewards);
prop_assert_eq!(voter, clamped_voter);
prop_assert_eq!(staker, clamped_staker);
prop_assert_eq!(was_split, clamped_ws);
}
if commission_bps > 0 {
let lower_bps = commission_bps - 1;
let (lower_voter, _, _) = commission_split(lower_bps, rewards);
prop_assert!(voter >= lower_voter);
}
let expected_voter =
(u128::from(rewards) * u128::from(effective_bps) / 10_000) as u64;
let expected_staker =
(u128::from(rewards) * u128::from(10_000 - effective_bps) / 10_000) as u64;
prop_assert_eq!(voter, expected_voter);
prop_assert_eq!(staker, expected_staker);
}
}
}