use std::str::FromStr;
use fvm_shared::bigint::{BigInt, Integer};
use fvm_shared::clock::ChainEpoch;
use fvm_shared::econ::TokenAmount;
use fvm_shared::math::PRECISION;
use fvm_shared::sector::StoragePower;
use lazy_static::lazy_static;
use super::expneg::expneg;
lazy_static! {
pub static ref BASELINE_EXPONENT: StoragePower =
StoragePower::from_str("340282591298641078465964189926313473653").unwrap();
pub static ref BASELINE_INITIAL_VALUE: StoragePower = StoragePower::from(2_888_888_880_000_000_000u128);
pub static ref INIT_BASELINE_POWER: StoragePower =
((BASELINE_INITIAL_VALUE.clone() << (2*PRECISION)) / &*BASELINE_EXPONENT) >> PRECISION;
pub(super) static ref SIMPLE_TOTAL: TokenAmount = TokenAmount::from_whole(330_000_000);
pub(super) static ref BASELINE_TOTAL: TokenAmount = TokenAmount::from_whole(770_000_000);
static ref EXP_LAM_SUB_ONE: BigInt = BigInt::from(37396273494747879394193016954629u128);
static ref LAMBDA: BigInt = BigInt::from(37396271439864487274534522888786u128);
}
pub(crate) fn baseline_power_from_prev(prev_power: &StoragePower) -> StoragePower {
(prev_power * &*BASELINE_EXPONENT) >> PRECISION
}
pub(crate) fn compute_r_theta(
effective_network_time: ChainEpoch,
baseline_power_at_effective_network_time: &BigInt,
cumsum_realized: &BigInt,
cumsum_baseline: &BigInt,
) -> BigInt {
if effective_network_time != 0 {
let reward_theta = BigInt::from(effective_network_time) << PRECISION;
let diff = ((cumsum_baseline - cumsum_realized) << PRECISION)
.div_floor(baseline_power_at_effective_network_time);
reward_theta - diff
} else {
Default::default()
}
}
pub(crate) fn compute_reward(
epoch: ChainEpoch,
prev_theta: BigInt,
curr_theta: BigInt,
simple_total: &TokenAmount,
baseline_total: &TokenAmount,
) -> TokenAmount {
let mut simple_reward = simple_total.atto() * &*EXP_LAM_SUB_ONE;
let epoch_lam = &*LAMBDA * epoch;
simple_reward *= expneg(&epoch_lam);
simple_reward >>= PRECISION;
let baseline_reward = compute_baseline_supply(curr_theta, baseline_total.atto())
- compute_baseline_supply(prev_theta, baseline_total.atto());
TokenAmount::from_atto((simple_reward + baseline_reward) >> PRECISION)
}
fn compute_baseline_supply(theta: BigInt, baseline_total: &BigInt) -> BigInt {
let theta_lam = (theta * &*LAMBDA) >> PRECISION;
let etl = expneg(&theta_lam);
let one = BigInt::from(1) << PRECISION;
let one_sub = one - etl;
one_sub * baseline_total
}
#[cfg(test)]
mod tests {
const SECONDS_IN_HOUR: i64 = 60 * 60;
const EPOCH_DURATION_IN_SECONDS: i64 = 30;
const EPOCHS_IN_HOUR: i64 = SECONDS_IN_HOUR / EPOCH_DURATION_IN_SECONDS;
const EPOCHS_IN_DAY: i64 = 24 * EPOCHS_IN_HOUR;
const EPOCHS_IN_YEAR: i64 = 365 * EPOCHS_IN_DAY;
use super::*;
use num::BigRational;
use num::ToPrimitive;
use std::fs;
use std::ops::Shl;
fn q128_to_f64(x: BigInt) -> f64 {
let denom = BigInt::from(1u64).shl(u128::BITS);
BigRational::new(x, denom).to_f64().expect("BigInt cannot be expressed as a 64bit float")
}
#[test]
fn test_compute_r_theta() {
fn baseline_power_at(epoch: ChainEpoch) -> BigInt {
(BigInt::from(epoch) + BigInt::from(1i64)) * BigInt::from(2048)
}
assert_eq!(
q128_to_f64(compute_r_theta(
1,
&baseline_power_at(1),
&BigInt::from(2048 + 2 * 2048 / 2),
&BigInt::from(2048 + 2 * 2048),
)),
0.5
);
assert_eq!(
q128_to_f64(compute_r_theta(
1,
&baseline_power_at(1),
&BigInt::from(2048 + 2 * 2048 / 4),
&BigInt::from(2048 + 2 * 2048),
)),
0.25
);
let cumsum15 = (0..16).map(baseline_power_at).sum::<BigInt>();
assert_eq!(
q128_to_f64(compute_r_theta(
16,
&baseline_power_at(16),
&(&cumsum15 + baseline_power_at(16) / BigInt::from(4)),
&(&cumsum15 + baseline_power_at(16)),
)),
15.25
);
}
#[test]
fn test_baseline_reward() {
let step = BigInt::from(5000_i64).shl(u128::BITS) - BigInt::from(77_777_777_777_i64); let delta = BigInt::from(1_i64).shl(u128::BITS) - BigInt::from(33_333_333_333_i64);
let mut prev_theta = BigInt::from(0i64);
let mut theta = delta;
let mut b = String::from("t0, t1, y\n");
let simple = compute_reward(
0,
BigInt::from(0i64),
BigInt::from(0i64),
&SIMPLE_TOTAL,
&BASELINE_TOTAL,
);
for _ in 0..512 {
let mut reward = compute_reward(
0,
prev_theta.clone(),
theta.clone(),
&SIMPLE_TOTAL,
&BASELINE_TOTAL,
);
reward -= &simple;
let prev_theta_str = &prev_theta.to_string();
let theta_str = &theta.to_string();
let reward_str = &reward.atto().to_string();
b.push_str(prev_theta_str);
b.push(',');
b.push_str(theta_str);
b.push(',');
b.push_str(reward_str);
b.push('\n');
prev_theta += &step;
theta += &step;
}
let filename = "testdata/TestBaselineReward.golden";
let golden_contents =
fs::read_to_string(filename).expect("Something went wrong reading the file");
assert_eq!(golden_contents, b);
}
#[test]
fn test_simple_reward() {
let mut b = String::from("x, y\n");
for i in 0..512 {
let x: i64 = i * 5000;
let reward = compute_reward(
x,
BigInt::from(0i64),
BigInt::from(0i64),
&SIMPLE_TOTAL,
&BASELINE_TOTAL,
);
let x_str = &x.to_string();
let reward_str = &reward.atto().to_string();
b.push_str(x_str);
b.push(',');
b.push_str(reward_str);
b.push('\n');
}
let filename = "testdata/TestSimpleReward.golden";
let golden_contents =
fs::read_to_string(filename).expect("Something went wrong reading the file");
assert_eq!(golden_contents, b);
}
#[test]
fn test_baseline_reward_growth() {
fn baseline_in_years(start: StoragePower, x: ChainEpoch) -> StoragePower {
let mut baseline = start;
for _ in 0..(x * EPOCHS_IN_YEAR) {
baseline = baseline_power_from_prev(&baseline);
}
baseline
}
struct GrowthTestCase {
start_val: StoragePower,
err_bound: f64,
}
let cases: [GrowthTestCase; 7] = [
GrowthTestCase { start_val: StoragePower::from(1i64), err_bound: 1.0 },
GrowthTestCase { start_val: StoragePower::from(1i64 << 30), err_bound: 1e-3 },
GrowthTestCase { start_val: StoragePower::from(1i64 << 40), err_bound: 1e-6 },
GrowthTestCase { start_val: StoragePower::from(1i64 << 50), err_bound: 1e-8 },
GrowthTestCase { start_val: BASELINE_INITIAL_VALUE.clone(), err_bound: 1e-8 },
GrowthTestCase { start_val: StoragePower::from(1u128 << 70), err_bound: 1e-8 },
GrowthTestCase {
start_val: StoragePower::from(513_633_559_722_596_517_u128),
err_bound: 1e-8,
},
];
for case in cases {
let years = 1u32;
let end = baseline_in_years(case.start_val.clone(), 1);
let multiplier = BigInt::pow(&BigInt::from(2u32), years);
let expected = case.start_val * multiplier;
let diff = &expected - end;
let perr = BigRational::new(diff, expected)
.to_f64()
.expect("BigInt cannot be expressed as a 64bit float");
assert!(perr < case.err_bound);
}
}
}