use anchor_lang::prelude::*;
use num_traits::ToPrimitive;
use spl_math::uint::U192;
use vipers::prelude::*;
use crate::Rewarder;
impl Rewarder {
fn compute_quarry_annual_rewards_rate_unsafe(&self, quarry_rewards_share: u64) -> Option<u128> {
let quarry_annual_rewards_rate = U192::from(self.annual_rewards_rate)
.checked_mul(quarry_rewards_share.into())?
.checked_div(self.total_rewards_shares.into())?;
quarry_annual_rewards_rate.try_into().ok()
}
pub fn compute_quarry_annual_rewards_rate(&self, quarry_rewards_share: u64) -> Result<u64> {
invariant!(
quarry_rewards_share <= self.total_rewards_shares,
InvalidRewardsShare
);
if self.total_rewards_shares == 0 || self.annual_rewards_rate == 0 || quarry_rewards_share == 0
{
return Ok(0);
}
let raw_rate =
unwrap_int!(self.compute_quarry_annual_rewards_rate_unsafe(quarry_rewards_share));
Ok(unwrap_int!(raw_rate.to_u64()))
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use proptest::prelude::*;
use rand::thread_rng;
use std::vec::Vec;
use crate::MAX_ANNUAL_REWARDS_RATE;
const DEFAULT_ANNUAL_REWARDS_RATE: u64 = 100_000_000_000_000_000;
fn add_quarry(l: &mut Rewarder, quarry_share: u64) {
l.total_rewards_shares += quarry_share;
}
#[test]
fn test_compute_quarry_annual_rewards_rate() {
let mut rewarder = Rewarder {
annual_rewards_rate: DEFAULT_ANNUAL_REWARDS_RATE,
..Default::default()
};
let invalid: Result<u64> = err!(InvalidRewardsShare);
assert_eq!(
rewarder
.compute_quarry_annual_rewards_rate(DEFAULT_ANNUAL_REWARDS_RATE)
.into_cmp_error(),
invalid.into_cmp_error()
);
rewarder.total_rewards_shares = 1_000_000_000_000;
let tokens_per_share = DEFAULT_ANNUAL_REWARDS_RATE / rewarder.total_rewards_shares;
assert_eq!(rewarder.compute_quarry_annual_rewards_rate(0).unwrap(), 0);
assert_eq!(
rewarder.compute_quarry_annual_rewards_rate(1).unwrap(),
tokens_per_share
);
assert_eq!(
rewarder.compute_quarry_annual_rewards_rate(10).unwrap(),
10 * tokens_per_share
);
assert_eq!(
rewarder.compute_quarry_annual_rewards_rate(100).unwrap(),
100 * tokens_per_share
);
assert_eq!(
rewarder.compute_quarry_annual_rewards_rate(1_000).unwrap(),
1_000 * tokens_per_share
);
assert_eq!(
rewarder.compute_quarry_annual_rewards_rate(10_000).unwrap(),
10_000 * tokens_per_share
);
assert_eq!(
rewarder
.compute_quarry_annual_rewards_rate(100_000)
.unwrap(),
100_000 * tokens_per_share
);
}
#[test]
fn test_compute_quarry_rewards_rate_with_multiple_quarries_fixed() {
let rewarder = &mut Rewarder::default();
rewarder.annual_rewards_rate = DEFAULT_ANNUAL_REWARDS_RATE;
rewarder.num_quarries = 1_000;
let mut rng = thread_rng();
let mut quarry_rewards_shares: Vec<u64> = Vec::new();
for _ in 0..rewarder.num_quarries {
let quarry_rewards_share: u32 = rng.gen_range(1..rewarder.annual_rewards_rate as u32);
add_quarry(rewarder, quarry_rewards_share as u64);
quarry_rewards_shares.push(quarry_rewards_share.into());
}
let mut total_rewards_per_day: u64 = 0;
for i in 0..rewarder.num_quarries {
total_rewards_per_day += rewarder
.compute_quarry_annual_rewards_rate(quarry_rewards_shares[i as usize])
.unwrap();
}
let diff = rewarder.annual_rewards_rate - total_rewards_per_day;
const MAX_EPSILON: u64 = 30;
let num_quarries = rewarder.num_quarries as u64;
let epsilon: u64 = if diff > num_quarries / 2 {
diff - num_quarries / 2
} else {
num_quarries / 2 - diff
};
assert!(
epsilon <= MAX_EPSILON,
"diff: {}, num_quarries / 2: {}, epsilon: {}",
diff,
num_quarries / 2,
epsilon
);
}
proptest! {
#[test]
fn test_compute_rewards_rate_when_total_rewards_shares_is_zero(
num_quarries in 0..u16::MAX,
annual_rewards_rate in 0..u64::MAX,
quarry_rewards_share in 0..u64::MAX,
) {
let rewarder = Rewarder {
bump: 254,
num_quarries,
annual_rewards_rate,
..Default::default()
};
assert_eq!(rewarder.compute_quarry_annual_rewards_rate(quarry_rewards_share).into_cmp_error(), error!(crate::ErrorCode::InvalidRewardsShare).into_cmp_error());
assert_eq!(rewarder.compute_quarry_annual_rewards_rate(0).unwrap(), 0);
}
}
proptest! {
#[test]
fn test_compute_quarry_rewards_rate_with_multiple_quarries(
annual_rewards_rate in 0..=MAX_ANNUAL_REWARDS_RATE,
num_quarries in 0..=u16::MAX,
total_rewards_shares in 0..=u64::MAX
) {
let rewarder = &mut Rewarder::default();
rewarder.annual_rewards_rate = annual_rewards_rate;
let mut rng = thread_rng();
let mut quarry_rewards_shares: Vec<u64> = Vec::new();
let mut total_rewards_shares_remaining = total_rewards_shares;
for _ in 0..(num_quarries - 1) {
let quarry_rewards_share: u64 = rng.gen_range(0..(total_rewards_shares_remaining / (num_quarries as u64)));
add_quarry(rewarder, quarry_rewards_share);
quarry_rewards_shares.push(quarry_rewards_share);
total_rewards_shares_remaining -= quarry_rewards_share;
}
add_quarry(rewarder, total_rewards_shares_remaining);
quarry_rewards_shares.push(total_rewards_shares_remaining);
let mut total_rewards_per_year: u64 = 0;
for i in 0..num_quarries {
total_rewards_per_year += rewarder.compute_quarry_annual_rewards_rate(quarry_rewards_shares[i as usize]).unwrap();
}
let diff = rewarder.annual_rewards_rate - total_rewards_per_year;
let max_diff: u64 = num_quarries.into();
assert!(diff <= max_diff, "diff: {}, num quarries: {}", diff, num_quarries);
}
}
}