use console::prelude::{Network, Result, ensure};
pub const MAX_COINBASE_REWARD: u64 = 190_258_739; const V2_MAX_BLOCK_INTERVAL: i64 = 60; const V2_MIN_BLOCK_INTERVAL: i64 = 1; const SECONDS_IN_A_YEAR: u32 = 60 * 60 * 24 * 365;
pub fn block_reward<N: Network>(
block_height: u32,
total_supply: u64,
block_time: u16,
time_since_last_block: i64,
coinbase_reward: u64,
transaction_fees: u64,
) -> u64 {
match block_height < N::CONSENSUS_V2_HEIGHT {
true => block_reward_v1(total_supply, block_time, coinbase_reward, transaction_fees),
false => block_reward_v2(total_supply, time_since_last_block, coinbase_reward, transaction_fees),
}
}
pub const fn block_reward_v1(total_supply: u64, block_time: u16, coinbase_reward: u64, transaction_fees: u64) -> u64 {
let block_height_at_year_1 = block_height_at_year(block_time, 1);
let annual_reward = total_supply / 20;
let block_reward = annual_reward / block_height_at_year_1 as u64;
block_reward + (coinbase_reward / 3) + transaction_fees
}
pub fn block_reward_v2(
total_supply: u64,
time_since_last_block: i64,
coinbase_reward: u64,
transaction_fees: u64,
) -> u64 {
let annual_reward = total_supply / 20;
let time_since_last_block = time_since_last_block.clamp(V2_MIN_BLOCK_INTERVAL, V2_MAX_BLOCK_INTERVAL);
let block_reward = annual_reward * time_since_last_block as u64 / SECONDS_IN_A_YEAR as u64;
block_reward + (coinbase_reward / 3) + transaction_fees
}
pub const fn puzzle_reward(coinbase_reward: u64) -> u64 {
coinbase_reward.saturating_mul(2).saturating_div(3)
}
pub fn coinbase_reward<N: Network>(
block_height: u32,
block_timestamp: i64,
genesis_timestamp: i64,
starting_supply: u64,
anchor_time: u16,
anchor_height: u32,
block_time: u16,
combined_proof_target: u128,
cumulative_proof_target: u64,
coinbase_target: u64,
) -> Result<u64> {
match block_height < N::CONSENSUS_V2_HEIGHT {
true => coinbase_reward_v1(
block_height,
starting_supply,
anchor_height,
block_time,
combined_proof_target,
cumulative_proof_target,
coinbase_target,
),
false => coinbase_reward_v2(
block_timestamp,
genesis_timestamp,
starting_supply,
anchor_time,
combined_proof_target,
cumulative_proof_target,
coinbase_target,
),
}
}
pub fn coinbase_reward_v1(
block_height: u32,
starting_supply: u64,
anchor_height: u32,
block_time: u16,
combined_proof_target: u128,
cumulative_proof_target: u64,
coinbase_target: u64,
) -> Result<u64> {
let remaining_coinbase_target = coinbase_target.saturating_sub(cumulative_proof_target);
let remaining_proof_target = combined_proof_target.min(remaining_coinbase_target as u128);
let anchor_block_reward = anchor_block_reward_at_height(block_height, starting_supply, anchor_height, block_time);
let reward = anchor_block_reward.saturating_mul(remaining_proof_target).saturating_div(coinbase_target as u128);
ensure!(reward <= MAX_COINBASE_REWARD as u128, "Coinbase reward ({reward}) exceeds maximum {MAX_COINBASE_REWARD}");
Ok(u64::try_from(reward).expect("Coinbase reward exceeds u64::MAX"))
}
pub fn coinbase_reward_v2(
block_timestamp: i64,
genesis_timestamp: i64,
starting_supply: u64,
anchor_time: u16,
combined_proof_target: u128,
cumulative_proof_target: u64,
coinbase_target: u64,
) -> Result<u64> {
let remaining_coinbase_target = coinbase_target.saturating_sub(cumulative_proof_target);
let remaining_proof_target = combined_proof_target.min(remaining_coinbase_target as u128);
let anchor_block_reward =
anchor_block_reward_at_timestamp(block_timestamp, genesis_timestamp, starting_supply, anchor_time);
let reward = anchor_block_reward.saturating_mul(remaining_proof_target).saturating_div(coinbase_target as u128);
ensure!(reward <= MAX_COINBASE_REWARD as u128, "Coinbase reward ({reward}) exceeds maximum {MAX_COINBASE_REWARD}");
Ok(u64::try_from(reward).expect("Coinbase reward exceeds u64::MAX"))
}
fn anchor_block_reward_at_height(block_height: u32, starting_supply: u64, anchor_height: u32, block_time: u16) -> u128 {
const fn block_reward_at_height(height: u32, starting_supply: u64, anchor_height: u32, block_time: u16) -> u128 {
let block_height_at_year_10 = block_height_at_year(block_time, 10) as u128;
let num_remaining_blocks_to_year_10 = block_height_at_year_10.saturating_sub(height as u128);
let numerator = 2 * starting_supply as u128 * anchor_height as u128 * num_remaining_blocks_to_year_10;
let denominator = block_height_at_year_10 * (block_height_at_year_10 + 1);
numerator / denominator
}
let block_height_at_year_9 = block_height_at_year(block_time, 9);
let reward_at_year_9 = block_reward_at_height(block_height_at_year_9, starting_supply, anchor_height, block_time);
let reward_at_block_height = block_reward_at_height(block_height, starting_supply, anchor_height, block_time);
reward_at_block_height.max(reward_at_year_9)
}
fn anchor_block_reward_at_timestamp(
block_timestamp: i64,
genesis_timestamp: i64,
starting_supply: u64,
anchor_time: u16,
) -> u128 {
const fn block_reward_at_timestamp(
block_timestamp: i64,
genesis_timestamp: i64,
starting_supply: u64,
anchor_time: u16,
) -> u128 {
let timestamp_at_year_10 = timestamp_at_year(genesis_timestamp, 10) as u128;
let number_of_seconds_in_10_years = (SECONDS_IN_A_YEAR as u128).saturating_mul(10);
let num_remaining_seconds_to_year_10 = timestamp_at_year_10.saturating_sub(block_timestamp as u128);
let numerator =
2 * starting_supply as u128 * anchor_time.saturating_div(10) as u128 * num_remaining_seconds_to_year_10;
let denominator = number_of_seconds_in_10_years * (number_of_seconds_in_10_years.saturating_div(10) + 1);
numerator / denominator
}
let timestamp_at_year_9 = timestamp_at_year(genesis_timestamp, 9);
let reward_at_year_9 =
block_reward_at_timestamp(timestamp_at_year_9, genesis_timestamp, starting_supply, anchor_time);
let reward_at_block_timestamp =
block_reward_at_timestamp(block_timestamp, genesis_timestamp, starting_supply, anchor_time);
reward_at_block_timestamp.max(reward_at_year_9)
}
const fn timestamp_at_year(genesis_timestamp: i64, num_years: u32) -> i64 {
let seconds_elapsed = SECONDS_IN_A_YEAR.saturating_mul(num_years);
genesis_timestamp.saturating_add(seconds_elapsed as i64)
}
const fn block_height_at_year(block_time: u16, num_years: u32) -> u32 {
let block_height_at_year_1 = SECONDS_IN_A_YEAR / block_time as u32;
block_height_at_year_1 * num_years
}
pub fn coinbase_target(
previous_target: u64,
previous_block_timestamp: i64,
block_timestamp: i64,
anchor_time: u16,
num_blocks_per_epoch: u32,
genesis_target: u64,
) -> Result<u64> {
let half_life = num_blocks_per_epoch.saturating_div(2).saturating_mul(anchor_time as u32);
let candidate_target =
retarget(previous_target, previous_block_timestamp, block_timestamp, anchor_time, half_life, true)?;
Ok(candidate_target.max(genesis_target))
}
pub fn proof_target(coinbase_target: u64, genesis_proof_target: u64, max_solutions_as_power_of_two: u8) -> u64 {
coinbase_target
.checked_shr(max_solutions_as_power_of_two as u32)
.map(|target| target.saturating_add(1))
.unwrap_or(genesis_proof_target)
}
fn retarget(
previous_target: u64,
previous_block_timestamp: i64,
block_timestamp: i64,
anchor_time: u16,
half_life: u32,
is_inverse: bool,
) -> Result<u64> {
let block_time_elapsed = block_timestamp.saturating_sub(previous_block_timestamp).max(1);
let mut drift = block_time_elapsed.saturating_sub(anchor_time as i64);
if drift == 0 {
return Ok(previous_target);
}
if is_inverse {
drift *= -1;
}
const RBITS: u32 = 16;
const RADIX: u128 = 1 << RBITS;
let (integral, fractional) = {
let exponent = (RADIX as i128).saturating_mul(drift as i128) / half_life as i128;
let integral = exponent >> RBITS;
let fractional = (exponent - (integral << RBITS)) as u128;
ensure!(fractional < RADIX, "Fractional part is not within the fixed point size");
ensure!(exponent == (integral * (RADIX as i128) + fractional as i128), "Exponent is decomposed incorrectly");
(integral, fractional)
};
let fractional_multiplier = RADIX
+ ((195_766_423_245_049_u128 * fractional
+ 971_821_376_u128 * fractional.pow(2)
+ 5_127_u128 * fractional.pow(3)
+ 2_u128.pow(RBITS * 3 - 1))
>> (RBITS * 3));
let candidate_target = (previous_target as u128).saturating_mul(fractional_multiplier);
let shifts = integral - RBITS as i128;
let mut candidate_target = if shifts < 0 {
match candidate_target.checked_shr(u32::try_from(-shifts)?) {
Some(target) => core::cmp::max(target, 1),
None => 1,
}
} else {
match candidate_target.checked_shl(u32::try_from(shifts)?) {
Some(target) => core::cmp::max(target, 1),
None => u64::MAX as u128,
}
};
candidate_target = core::cmp::min(candidate_target, u64::MAX as u128);
ensure!(candidate_target.checked_shr(64) == Some(0), "The target has overflowed");
Ok(u64::try_from(candidate_target)?)
}
pub fn to_next_targets<N: Network>(
latest_cumulative_proof_target: u128,
combined_proof_target: u128,
latest_coinbase_target: u64,
latest_cumulative_weight: u128,
last_coinbase_target: u64,
last_coinbase_timestamp: i64,
next_timestamp: i64,
) -> Result<(u64, u64, u128, u128, u64, i64)> {
let latest_coinbase_threshold = latest_coinbase_target.saturating_div(2) as u128;
let next_cumulative_proof_target = latest_cumulative_proof_target.saturating_add(combined_proof_target);
let is_coinbase_threshold_reached = next_cumulative_proof_target >= latest_coinbase_threshold;
let next_coinbase_target = coinbase_target(
last_coinbase_target,
last_coinbase_timestamp,
next_timestamp,
N::ANCHOR_TIME,
N::NUM_BLOCKS_PER_EPOCH,
N::GENESIS_COINBASE_TARGET,
)?;
let next_proof_target =
proof_target(next_coinbase_target, N::GENESIS_PROOF_TARGET, N::MAX_SOLUTIONS_AS_POWER_OF_TWO);
let next_cumulative_proof_target = match is_coinbase_threshold_reached {
true => 0,
false => next_cumulative_proof_target,
};
let next_cumulative_weight = latest_cumulative_weight.saturating_add(combined_proof_target);
let (next_last_coinbase_target, next_last_coinbase_timestamp) = match is_coinbase_threshold_reached {
true => (next_coinbase_target, next_timestamp),
false => (last_coinbase_target, last_coinbase_timestamp),
};
Ok((
next_coinbase_target,
next_proof_target,
next_cumulative_proof_target,
next_cumulative_weight,
next_last_coinbase_target,
next_last_coinbase_timestamp,
))
}
#[cfg(test)]
mod tests {
use super::*;
use console::network::{MainnetV0, TestnetV0, prelude::*};
type CurrentNetwork = MainnetV0;
const ITERATIONS: u32 = 1000;
const EXPECTED_ANCHOR_BLOCK_REWARD_AT_BLOCK_1: u128 = MAX_COINBASE_REWARD as u128;
const EXPECTED_STAKING_REWARD: u64 = 23_782_343;
const EXPECTED_COINBASE_REWARD_AT_BLOCK_1: u64 = MAX_COINBASE_REWARD;
const EXPECTED_MAX_STAKING_REWARD: u64 = 142_694_063;
#[test]
fn test_anchor_block_reward_v1() {
let reward_at_block_1 = anchor_block_reward_at_height(
1,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
);
assert_eq!(reward_at_block_1, EXPECTED_ANCHOR_BLOCK_REWARD_AT_BLOCK_1);
fn check_reward_at_year(year: u32, expected_reward: u128) {
let reward_at_year = anchor_block_reward_at_height(
block_height_at_year(CurrentNetwork::BLOCK_TIME, year),
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
);
assert_eq!(reward_at_year, expected_reward);
}
check_reward_at_year(1, 171_232_871);
check_reward_at_year(2, 152_206_996);
check_reward_at_year(3, 133_181_122);
check_reward_at_year(4, 114_155_247);
check_reward_at_year(5, 95_129_372);
check_reward_at_year(6, 76_103_498);
check_reward_at_year(7, 57_077_623);
check_reward_at_year(8, 38_051_749);
check_reward_at_year(9, 19_025_874);
check_reward_at_year(10, 19_025_874);
check_reward_at_year(11, 19_025_874);
check_reward_at_year(12, 19_025_874);
check_reward_at_year(13, 19_025_874);
check_reward_at_year(14, 19_025_874);
check_reward_at_year(15, 19_025_874);
let block_height_at_year_9 = block_height_at_year(CurrentNetwork::BLOCK_TIME, 9);
let mut previous_reward = reward_at_block_1;
let anchor_height = CurrentNetwork::ANCHOR_HEIGHT as usize;
for height in (2..block_height_at_year_9).step_by(anchor_height).skip(1) {
let reward = anchor_block_reward_at_height(
height,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
);
assert!(reward < previous_reward, "Failed on block height {height}");
previous_reward = reward;
}
for height in block_height_at_year_9..(block_height_at_year_9 + ITERATIONS) {
let reward = anchor_block_reward_at_height(
height,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
);
assert_eq!(reward, 19_025_874);
}
}
#[test]
fn test_anchor_block_reward_v2() {
let reward_at_block_1 = anchor_block_reward_at_timestamp(
CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
);
assert_eq!(reward_at_block_1, EXPECTED_ANCHOR_BLOCK_REWARD_AT_BLOCK_1);
fn check_reward_at_year(year: u32, expected_reward: u128) {
let reward_at_year = anchor_block_reward_at_timestamp(
timestamp_at_year(CurrentNetwork::GENESIS_TIMESTAMP, year),
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
);
assert_eq!(reward_at_year, expected_reward);
}
check_reward_at_year(1, 171_232_871);
check_reward_at_year(2, 152_206_996);
check_reward_at_year(3, 133_181_122);
check_reward_at_year(4, 114_155_247);
check_reward_at_year(5, 95_129_372);
check_reward_at_year(6, 76_103_498);
check_reward_at_year(7, 57_077_623);
check_reward_at_year(8, 38_051_749);
check_reward_at_year(9, 19_025_874);
check_reward_at_year(10, 19_025_874);
check_reward_at_year(11, 19_025_874);
check_reward_at_year(12, 19_025_874);
check_reward_at_year(13, 19_025_874);
check_reward_at_year(14, 19_025_874);
check_reward_at_year(15, 19_025_874);
let timestamp_at_year_9 = timestamp_at_year(CurrentNetwork::GENESIS_TIMESTAMP, 9);
let mut previous_reward = reward_at_block_1;
let anchor_time = CurrentNetwork::ANCHOR_TIME as usize;
for timestamp in (CurrentNetwork::GENESIS_TIMESTAMP..timestamp_at_year_9).step_by(anchor_time).skip(1) {
let reward = anchor_block_reward_at_timestamp(
timestamp,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
);
assert!(reward < previous_reward, "Failed on timestamp {timestamp}");
previous_reward = reward;
}
for timestamp in timestamp_at_year_9..(timestamp_at_year_9 + ITERATIONS as i64) {
let reward = anchor_block_reward_at_timestamp(
timestamp,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
);
assert_eq!(reward, 19_025_874);
}
}
#[test]
fn test_total_anchor_block_reward_v1() {
fn add_anchor_block_reward(total_reward: &mut u128, start_height: u32, end_height: u32) {
for height in start_height..end_height {
*total_reward += anchor_block_reward_at_height(
height,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
);
}
}
let mut total_reward = 0;
let mut check_sum_of_anchor_rewards = |year: u32, expected_reward: u128| {
assert!(year > 0, "Year must be greater than 0");
let end_height = block_height_at_year(CurrentNetwork::BLOCK_TIME, year);
let start_height = std::cmp::max(1, block_height_at_year(CurrentNetwork::BLOCK_TIME, year - 1));
add_anchor_block_reward(&mut total_reward, start_height, end_height);
assert_eq!(total_reward, expected_reward);
};
check_sum_of_anchor_rewards(1, 569999799602807);
check_sum_of_anchor_rewards(2, 1079999791366949);
check_sum_of_anchor_rewards(3, 1529999785033683);
check_sum_of_anchor_rewards(4, 1919999780603002);
check_sum_of_anchor_rewards(5, 2249999778074916);
check_sum_of_anchor_rewards(6, 2519999777449404);
check_sum_of_anchor_rewards(7, 2729999778726485);
check_sum_of_anchor_rewards(8, 2879999781906155);
check_sum_of_anchor_rewards(9, 2969999786988413);
check_sum_of_anchor_rewards(10, 3029999783234813);
check_sum_of_anchor_rewards(11, 3089999779481213);
check_sum_of_anchor_rewards(12, 3149999775727613);
check_sum_of_anchor_rewards(13, 3209999771974013);
check_sum_of_anchor_rewards(14, 3269999768220413);
check_sum_of_anchor_rewards(15, 3329999764466813);
}
#[test]
fn test_total_anchor_block_reward_v2() {
fn add_anchor_block_reward(total_reward: &mut u128, start_timestamp: i64, end_timestamp: i64) {
for timestamp in (start_timestamp..end_timestamp).step_by(CurrentNetwork::BLOCK_TIME as usize) {
*total_reward += anchor_block_reward_at_timestamp(
timestamp,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
);
}
}
let mut total_reward = 0;
let mut check_sum_of_anchor_rewards = |year: u32, expected_reward: u128| {
assert!(year > 0, "Year must be greater than 0");
let end_timestamp = timestamp_at_year(CurrentNetwork::GENESIS_TIMESTAMP, year);
let start_timestamp = std::cmp::max(
CurrentNetwork::GENESIS_TIMESTAMP,
timestamp_at_year(CurrentNetwork::GENESIS_TIMESTAMP, year - 1),
);
add_anchor_block_reward(&mut total_reward, start_timestamp, end_timestamp);
assert_eq!(total_reward, expected_reward);
};
check_sum_of_anchor_rewards(1, 569999989861552);
check_sum_of_anchor_rewards(2, 1079999981625694);
check_sum_of_anchor_rewards(3, 1529999975292428);
check_sum_of_anchor_rewards(4, 1919999970861747);
check_sum_of_anchor_rewards(5, 2249999968333661);
check_sum_of_anchor_rewards(6, 2519999967708149);
check_sum_of_anchor_rewards(7, 2729999968985230);
check_sum_of_anchor_rewards(8, 2879999972164900);
check_sum_of_anchor_rewards(9, 2969999977247158);
check_sum_of_anchor_rewards(10, 3029999973493558);
check_sum_of_anchor_rewards(11, 3089999969739958);
check_sum_of_anchor_rewards(12, 3149999965986358);
check_sum_of_anchor_rewards(13, 3209999962232758);
check_sum_of_anchor_rewards(14, 3269999958479158);
check_sum_of_anchor_rewards(15, 3329999954725558);
}
#[test]
fn test_block_reward() {
let mut rng = TestRng::default();
let time_since_last_block = rng.gen_range(1..=V2_MAX_BLOCK_INTERVAL);
let reward = block_reward::<TestnetV0>(
TestnetV0::CONSENSUS_V2_HEIGHT,
TestnetV0::STARTING_SUPPLY,
TestnetV0::BLOCK_TIME,
time_since_last_block,
0,
0,
);
let expected_reward = block_reward_v2(TestnetV0::STARTING_SUPPLY, time_since_last_block, 0, 0);
assert_eq!(reward, expected_reward);
for _ in 0..100 {
let consensus_v1_height = rng.gen_range(0..TestnetV0::CONSENSUS_V2_HEIGHT);
let consensus_v1_reward = block_reward::<TestnetV0>(
consensus_v1_height,
TestnetV0::STARTING_SUPPLY,
TestnetV0::BLOCK_TIME,
0,
0,
0,
);
let expected_reward = block_reward_v1(TestnetV0::STARTING_SUPPLY, TestnetV0::BLOCK_TIME, 0, 0);
assert_eq!(consensus_v1_reward, expected_reward);
let consensus_v2_height = rng.gen_range(TestnetV0::CONSENSUS_V2_HEIGHT..u32::MAX);
let time_since_last_block = rng.gen_range(1..=V2_MAX_BLOCK_INTERVAL);
let consensus_v2_reward = block_reward::<TestnetV0>(
consensus_v2_height,
TestnetV0::STARTING_SUPPLY,
TestnetV0::BLOCK_TIME,
time_since_last_block,
0,
0,
);
let expected_reward = block_reward_v2(TestnetV0::STARTING_SUPPLY, time_since_last_block, 0, 0);
assert_eq!(consensus_v2_reward, expected_reward);
}
}
#[test]
fn test_block_reward_v1() {
let reward = block_reward_v1(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME, 0, 0);
assert_eq!(reward, EXPECTED_STAKING_REWARD);
let larger_reward = block_reward_v1(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME + 1, 0, 0);
assert!(reward < larger_reward);
let smaller_reward = block_reward_v1(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME - 1, 0, 0);
assert!(reward > smaller_reward);
}
#[test]
fn test_block_reward_v2() {
let reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME as i64, 0, 0);
assert_eq!(reward, EXPECTED_STAKING_REWARD);
let larger_reward =
block_reward_v2(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME as i64 + 1, 0, 0);
assert!(reward < larger_reward);
let smaller_reward =
block_reward_v2(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME as i64 - 1, 0, 0);
assert!(reward > smaller_reward);
let max_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, V2_MAX_BLOCK_INTERVAL, 0, 0);
assert_eq!(max_reward, EXPECTED_MAX_STAKING_REWARD);
let equivalent_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, V2_MAX_BLOCK_INTERVAL + 1, 0, 0);
assert_eq!(max_reward, equivalent_reward);
let min_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, 1, 0, 0);
let equivalent_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, 0, 0, 0);
assert_eq!(min_reward, equivalent_reward);
}
#[test]
fn test_block_reward_v1_vs_v2() {
let mut rng = TestRng::default();
const TOLERANCE: f64 = 0.001; let reward_v1 = block_reward_v1(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME, 0, 0);
assert_eq!(reward_v1, EXPECTED_STAKING_REWARD);
let reward_v2 = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME as i64, 0, 0);
assert_eq!(reward_v1, reward_v2);
let shorter_time = CurrentNetwork::BLOCK_TIME / 2;
let smaller_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, shorter_time as i64, 0, 0);
let expected_reward = EXPECTED_STAKING_REWARD / 2;
assert!((smaller_reward as f64 - expected_reward as f64).abs() / expected_reward as f64 <= TOLERANCE);
let longer_time = CurrentNetwork::BLOCK_TIME * 2;
let larger_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, longer_time as i64, 0, 0);
let expected_reward = EXPECTED_STAKING_REWARD * 2;
assert!((larger_reward as f64 - expected_reward as f64).abs() / expected_reward as f64 <= TOLERANCE);
for _ in 0..10 {
let factor = rng.gen_range(1..10);
let shorter_time = CurrentNetwork::BLOCK_TIME / factor;
let time_factor: f64 = CurrentNetwork::BLOCK_TIME as f64 / shorter_time as f64;
let smaller_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, shorter_time as i64, 0, 0);
let expected_reward = (EXPECTED_STAKING_REWARD as f64 / time_factor) as u64;
assert!((smaller_reward as f64 - expected_reward as f64).abs() / expected_reward as f64 <= TOLERANCE);
let longer_time = CurrentNetwork::BLOCK_TIME * factor;
let time_factor: f64 = longer_time as f64 / CurrentNetwork::BLOCK_TIME as f64;
let larger_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, longer_time as i64, 0, 0);
let expected_reward = (EXPECTED_STAKING_REWARD as f64 * time_factor) as u64;
match longer_time as i64 > V2_MAX_BLOCK_INTERVAL {
true => assert_eq!(larger_reward, EXPECTED_MAX_STAKING_REWARD),
false => {
assert!((larger_reward as f64 - expected_reward as f64).abs() / expected_reward as f64 <= TOLERANCE)
}
}
}
}
#[test]
fn test_coinbase_reward() {
let mut rng = TestRng::default();
let block_timestamp = TestnetV0::GENESIS_TIMESTAMP
.saturating_add(TestnetV0::CONSENSUS_V2_HEIGHT.saturating_mul(TestnetV0::BLOCK_TIME as u32) as i64);
let reward = coinbase_reward::<TestnetV0>(
TestnetV0::CONSENSUS_V2_HEIGHT,
block_timestamp,
TestnetV0::GENESIS_TIMESTAMP,
TestnetV0::STARTING_SUPPLY,
TestnetV0::ANCHOR_TIME,
TestnetV0::ANCHOR_HEIGHT,
TestnetV0::BLOCK_TIME,
1,
0,
1,
)
.unwrap();
let expected_reward = coinbase_reward_v2(
block_timestamp,
TestnetV0::GENESIS_TIMESTAMP,
TestnetV0::STARTING_SUPPLY,
TestnetV0::ANCHOR_TIME,
1,
0,
1,
)
.unwrap();
assert_eq!(reward, expected_reward);
for _ in 0..100 {
let consensus_v1_height = rng.gen_range(0..TestnetV0::CONSENSUS_V2_HEIGHT);
let block_timestamp = TestnetV0::GENESIS_TIMESTAMP
.saturating_add(consensus_v1_height.saturating_mul(TestnetV0::BLOCK_TIME as u32) as i64);
let consensus_v1_reward = coinbase_reward::<TestnetV0>(
consensus_v1_height,
block_timestamp,
TestnetV0::GENESIS_TIMESTAMP,
TestnetV0::STARTING_SUPPLY,
TestnetV0::ANCHOR_TIME,
TestnetV0::ANCHOR_HEIGHT,
TestnetV0::BLOCK_TIME,
1,
0,
1,
)
.unwrap();
let expected_reward = coinbase_reward_v1(
consensus_v1_height,
TestnetV0::STARTING_SUPPLY,
TestnetV0::ANCHOR_HEIGHT,
TestnetV0::BLOCK_TIME,
1,
0,
1,
)
.unwrap();
assert_eq!(consensus_v1_reward, expected_reward);
let consensus_v2_height = rng.gen_range(TestnetV0::CONSENSUS_V2_HEIGHT..u32::MAX);
let block_timestamp = TestnetV0::GENESIS_TIMESTAMP
.saturating_add(consensus_v2_height.saturating_mul(TestnetV0::BLOCK_TIME as u32) as i64);
let consensus_v2_reward = coinbase_reward::<TestnetV0>(
consensus_v2_height,
block_timestamp,
TestnetV0::GENESIS_TIMESTAMP,
TestnetV0::STARTING_SUPPLY,
TestnetV0::ANCHOR_TIME,
TestnetV0::ANCHOR_HEIGHT,
TestnetV0::BLOCK_TIME,
1,
0,
1,
)
.unwrap();
let expected_reward = coinbase_reward_v2(
block_timestamp,
TestnetV0::GENESIS_TIMESTAMP,
TestnetV0::STARTING_SUPPLY,
TestnetV0::ANCHOR_TIME,
1,
0,
1,
)
.unwrap();
assert_eq!(consensus_v2_reward, expected_reward);
}
}
#[test]
fn test_coinbase_reward_v1() {
let coinbase_target: u64 = 10000;
let combined_proof_target: u128 = coinbase_target as u128;
let reward = coinbase_reward_v1(
1,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
combined_proof_target,
0,
coinbase_target,
)
.unwrap();
assert_eq!(reward, EXPECTED_COINBASE_REWARD_AT_BLOCK_1);
let smaller_reward = coinbase_reward_v1(
1,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
combined_proof_target / 2,
0,
coinbase_target,
)
.unwrap();
assert_eq!(smaller_reward, reward / 2);
let smaller_reward = coinbase_reward_v1(
1,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
combined_proof_target,
coinbase_target / 2,
coinbase_target,
)
.unwrap();
assert_eq!(smaller_reward, reward / 2);
let equivalent_reward = coinbase_reward_v1(
1,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
u128::MAX,
0,
coinbase_target,
)
.unwrap();
assert_eq!(reward, equivalent_reward);
let zero_reward = coinbase_reward_v1(
1,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
0,
0,
coinbase_target,
)
.unwrap();
assert_eq!(zero_reward, 0);
let zero_reward = coinbase_reward_v1(
1,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
1,
coinbase_target + 1,
coinbase_target,
)
.unwrap();
assert_eq!(zero_reward, 0);
}
#[test]
fn test_coinbase_reward_v2() {
let coinbase_target: u64 = 10000;
let combined_proof_target: u128 = coinbase_target as u128;
let reward = coinbase_reward_v2(
CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
combined_proof_target,
0,
coinbase_target,
)
.unwrap();
assert_eq!(reward, EXPECTED_COINBASE_REWARD_AT_BLOCK_1);
let smaller_reward = coinbase_reward_v2(
CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
combined_proof_target / 2,
0,
coinbase_target,
)
.unwrap();
assert_eq!(smaller_reward, reward / 2);
let smaller_reward = coinbase_reward_v2(
CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
combined_proof_target,
coinbase_target / 2,
coinbase_target,
)
.unwrap();
assert_eq!(smaller_reward, reward / 2);
let equivalent_reward = coinbase_reward_v2(
CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
u128::MAX,
0,
coinbase_target,
)
.unwrap();
assert_eq!(reward, equivalent_reward);
let zero_reward = coinbase_reward_v2(
CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
0,
0,
coinbase_target,
)
.unwrap();
assert_eq!(zero_reward, 0);
let zero_reward = coinbase_reward_v2(
CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
1,
coinbase_target + 1,
coinbase_target,
)
.unwrap();
assert_eq!(zero_reward, 0);
}
#[test]
fn test_coinbase_reward_v1_remaining_target() {
let mut rng = TestRng::default();
fn compute_coinbase_reward(
combined_proof_target: u64,
cumulative_proof_target: u64,
coinbase_target: u64,
) -> u64 {
coinbase_reward_v1(
1,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
combined_proof_target as u128,
cumulative_proof_target,
coinbase_target,
)
.unwrap()
}
let coinbase_target: u64 = rng.gen_range(1_000_000..1_000_000_000_000_000);
let cumulative_proof_target = coinbase_target / 2;
let combined_proof_target = coinbase_target / 4;
let reward = compute_coinbase_reward(combined_proof_target, cumulative_proof_target, coinbase_target);
for _ in 0..ITERATIONS {
let equivalent_reward = compute_coinbase_reward(
combined_proof_target,
rng.gen_range(0..(coinbase_target - combined_proof_target)),
coinbase_target,
);
assert_eq!(reward, equivalent_reward);
let lower_reward = compute_coinbase_reward(
combined_proof_target,
rng.gen_range((coinbase_target - combined_proof_target + 1)..coinbase_target),
coinbase_target,
);
assert!(lower_reward < reward);
let larger_reward = compute_coinbase_reward(
rng.gen_range(combined_proof_target + 1..u64::MAX),
cumulative_proof_target,
coinbase_target,
);
assert!(reward < larger_reward);
}
}
#[test]
fn test_coinbase_reward_v2_remaining_target() {
let mut rng = TestRng::default();
fn compute_coinbase_reward(
combined_proof_target: u64,
cumulative_proof_target: u64,
coinbase_target: u64,
) -> u64 {
coinbase_reward_v2(
CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
combined_proof_target as u128,
cumulative_proof_target,
coinbase_target,
)
.unwrap()
}
let coinbase_target: u64 = rng.gen_range(1_000_000..1_000_000_000_000_000);
let cumulative_proof_target = coinbase_target / 2;
let combined_proof_target = coinbase_target / 4;
let reward = compute_coinbase_reward(combined_proof_target, cumulative_proof_target, coinbase_target);
for _ in 0..ITERATIONS {
let equivalent_reward = compute_coinbase_reward(
combined_proof_target,
rng.gen_range(0..(coinbase_target - combined_proof_target)),
coinbase_target,
);
assert_eq!(reward, equivalent_reward);
let lower_reward = compute_coinbase_reward(
combined_proof_target,
rng.gen_range((coinbase_target - combined_proof_target + 1)..coinbase_target),
coinbase_target,
);
assert!(lower_reward < reward);
let larger_reward = compute_coinbase_reward(
rng.gen_range(combined_proof_target + 1..u64::MAX),
cumulative_proof_target,
coinbase_target,
);
assert!(reward < larger_reward);
}
}
#[test]
fn test_coinbase_reward_v1_up_to_year_10() {
let block_height_at_year_10 = block_height_at_year(CurrentNetwork::BLOCK_TIME, 10);
let mut block_height = 1;
let mut previous_reward = coinbase_reward_v1(
block_height,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
1,
0,
1,
)
.unwrap();
block_height += 1;
let mut total_reward = previous_reward;
let coinbase_target = CurrentNetwork::ANCHOR_HEIGHT as u64;
let mut cumulative_proof_target = 0;
let mut hit_500m = false;
let mut hit_1b = false;
while block_height < block_height_at_year_10 {
let reward = coinbase_reward_v1(
block_height,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
1,
cumulative_proof_target,
coinbase_target,
)
.unwrap();
assert!(reward <= previous_reward);
total_reward += reward;
previous_reward = reward;
block_height += 1;
cumulative_proof_target = match cumulative_proof_target + 1 {
cumulative_proof_target if cumulative_proof_target == coinbase_target => 0,
cumulative_proof_target => cumulative_proof_target,
};
if !hit_500m && total_reward > 500_000_000_000_000 {
println!("500M credits block height is {block_height}");
assert_eq!(block_height, 5_786_964, "Update me if my parameters have changed");
hit_500m = true;
} else if !hit_1b && total_reward > 1_000_000_000_000_000 {
println!("1B credits block height is {block_height}");
assert_eq!(block_height, 13_328_683, "Update me if my parameters have changed");
hit_1b = true;
}
}
assert_eq!(total_reward, 1_514_999_979_651_171, "Update me if my parameters have changed");
}
#[test]
fn test_coinbase_reward_v2_up_to_year_10() {
let block_height_at_year_10 = timestamp_at_year(CurrentNetwork::GENESIS_TIMESTAMP, 10);
let mut timestamp = CurrentNetwork::GENESIS_TIMESTAMP;
let mut previous_reward = coinbase_reward_v2(
CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
1,
0,
1,
)
.unwrap();
timestamp += CurrentNetwork::BLOCK_TIME as i64;
let mut total_reward = previous_reward;
let coinbase_target = CurrentNetwork::ANCHOR_HEIGHT as u64;
let mut cumulative_proof_target = 0;
let mut hit_500m = false;
let mut hit_1b = false;
while timestamp < block_height_at_year_10 {
let reward = coinbase_reward_v2(
timestamp,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
1,
cumulative_proof_target,
coinbase_target,
)
.unwrap();
assert!(reward <= previous_reward);
total_reward += reward;
previous_reward = reward;
timestamp += CurrentNetwork::BLOCK_TIME as i64;
cumulative_proof_target = match cumulative_proof_target + 1 {
cumulative_proof_target if cumulative_proof_target == coinbase_target => 0,
cumulative_proof_target => cumulative_proof_target,
};
if !hit_500m && total_reward > 500_000_000_000_000 {
println!("500M credits block timestamp is {timestamp}");
assert_eq!(timestamp, 1783331630, "Update me if my parameters have changed");
hit_500m = true;
} else if !hit_1b && total_reward > 1_000_000_000_000_000 {
println!("1B credits block timestamp is {timestamp}");
assert_eq!(timestamp, 1858748810, "Update me if my parameters have changed");
hit_1b = true;
}
}
assert_eq!(total_reward, 1_515_000_074_780_540, "Update me if my parameters have changed");
}
#[test]
fn test_coinbase_reward_v1_after_year_10() {
let mut rng = TestRng::default();
let block_height_at_year_10 = block_height_at_year(CurrentNetwork::BLOCK_TIME, 10);
let reward = coinbase_reward_v1(
block_height_at_year_10,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
1,
0,
1,
)
.unwrap();
assert_eq!(reward, 19_025_874);
for _ in 0..ITERATIONS {
let block_height: u32 = rng.gen_range(block_height_at_year_10..block_height_at_year_10 * 10);
let coinbase_target = rng.gen_range(1_000_000..1_000_000_000_000_000);
let cumulative_proof_target = rng.gen_range(0..coinbase_target);
let combined_proof_target = rng.gen_range(0..coinbase_target as u128);
let anchor_reward = anchor_block_reward_at_height(
block_height,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
);
assert_eq!(anchor_reward, 19_025_874);
let reward = coinbase_reward_v1(
block_height,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_HEIGHT,
CurrentNetwork::BLOCK_TIME,
combined_proof_target,
cumulative_proof_target,
coinbase_target,
)
.unwrap();
assert!(reward <= 19_025_874);
}
}
#[test]
fn test_coinbase_reward_v2_after_year_10() {
let mut rng = TestRng::default();
let timestamp_at_year_10 = timestamp_at_year(CurrentNetwork::GENESIS_TIMESTAMP, 10);
let reward = coinbase_reward_v2(
timestamp_at_year_10,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
1,
0,
1,
)
.unwrap();
assert_eq!(reward, 19_025_874);
for _ in 0..ITERATIONS {
let timestamp: i64 = rng.gen_range(timestamp_at_year_10..timestamp_at_year_10 * 10);
let coinbase_target = rng.gen_range(1_000_000..1_000_000_000_000_000);
let cumulative_proof_target = rng.gen_range(0..coinbase_target);
let combined_proof_target = rng.gen_range(0..coinbase_target as u128);
let anchor_reward = anchor_block_reward_at_timestamp(
timestamp,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
);
assert_eq!(anchor_reward, 19_025_874);
let reward = coinbase_reward_v2(
timestamp,
CurrentNetwork::GENESIS_TIMESTAMP,
CurrentNetwork::STARTING_SUPPLY,
CurrentNetwork::ANCHOR_TIME,
combined_proof_target,
cumulative_proof_target,
coinbase_target,
)
.unwrap();
assert!(reward <= 19_025_874);
}
}
#[test]
fn test_targets() {
let mut rng = TestRng::default();
let minimum_coinbase_target: u64 = 2u64.pow(10) - 1;
fn test_new_targets(rng: &mut TestRng, minimum_coinbase_target: u64) {
let previous_coinbase_target: u64 = rng.gen_range(minimum_coinbase_target..u64::MAX);
let previous_prover_target = proof_target(
previous_coinbase_target,
CurrentNetwork::GENESIS_PROOF_TARGET,
CurrentNetwork::MAX_SOLUTIONS_AS_POWER_OF_TWO,
);
let previous_timestamp = rng.gen();
let next_timestamp = previous_timestamp + CurrentNetwork::ANCHOR_TIME as i64;
let new_coinbase_target = coinbase_target(
previous_coinbase_target,
previous_timestamp,
next_timestamp,
CurrentNetwork::ANCHOR_TIME,
CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
CurrentNetwork::GENESIS_COINBASE_TARGET,
)
.unwrap();
let new_prover_target = proof_target(
new_coinbase_target,
CurrentNetwork::GENESIS_PROOF_TARGET,
CurrentNetwork::MAX_SOLUTIONS_AS_POWER_OF_TWO,
);
assert_eq!(new_coinbase_target, previous_coinbase_target);
assert_eq!(new_prover_target, previous_prover_target);
let new_timestamp = previous_timestamp + 2 * CurrentNetwork::ANCHOR_TIME as i64;
let new_coinbase_target = coinbase_target(
previous_coinbase_target,
previous_timestamp,
new_timestamp,
CurrentNetwork::ANCHOR_TIME,
CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
CurrentNetwork::GENESIS_COINBASE_TARGET,
)
.unwrap();
let new_prover_target = proof_target(
new_coinbase_target,
CurrentNetwork::GENESIS_PROOF_TARGET,
CurrentNetwork::MAX_SOLUTIONS_AS_POWER_OF_TWO,
);
assert!(new_coinbase_target < previous_coinbase_target);
assert!(new_prover_target < previous_prover_target);
let next_timestamp = previous_timestamp + (CurrentNetwork::ANCHOR_TIME / 2) as i64;
let new_coinbase_target = coinbase_target(
previous_coinbase_target,
previous_timestamp,
next_timestamp,
CurrentNetwork::ANCHOR_TIME,
CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
CurrentNetwork::GENESIS_COINBASE_TARGET,
)
.unwrap();
let new_prover_target = proof_target(
new_coinbase_target,
CurrentNetwork::GENESIS_PROOF_TARGET,
CurrentNetwork::MAX_SOLUTIONS_AS_POWER_OF_TWO,
);
assert!(new_coinbase_target > previous_coinbase_target);
assert!(new_prover_target > previous_prover_target);
}
for _ in 0..ITERATIONS {
test_new_targets(&mut rng, minimum_coinbase_target);
}
}
#[test]
fn test_target_halving() {
let mut rng = TestRng::default();
let minimum_coinbase_target: u64 = 2u64.pow(10) - 1;
for _ in 0..ITERATIONS {
let previous_coinbase_target: u64 = rng.gen_range(minimum_coinbase_target..u64::MAX);
let previous_timestamp = rng.gen();
let half_life = CurrentNetwork::NUM_BLOCKS_PER_EPOCH
.saturating_div(2)
.saturating_mul(CurrentNetwork::ANCHOR_TIME as u32) as i64;
let next_timestamp = previous_timestamp + half_life;
let next_coinbase_target = coinbase_target(
previous_coinbase_target,
previous_timestamp,
next_timestamp,
CurrentNetwork::ANCHOR_TIME,
CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
CurrentNetwork::GENESIS_COINBASE_TARGET,
)
.unwrap();
assert!(next_coinbase_target > previous_coinbase_target / 2);
let next_timestamp = previous_timestamp + half_life + CurrentNetwork::ANCHOR_TIME as i64;
let next_coinbase_target = coinbase_target(
previous_coinbase_target,
previous_timestamp,
next_timestamp,
CurrentNetwork::ANCHOR_TIME,
CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
CurrentNetwork::GENESIS_COINBASE_TARGET,
)
.unwrap();
assert_eq!(next_coinbase_target, previous_coinbase_target / 2);
let next_timestamp = previous_timestamp + half_life + 2 * CurrentNetwork::ANCHOR_TIME as i64;
let next_coinbase_target = coinbase_target(
previous_coinbase_target,
previous_timestamp,
next_timestamp,
CurrentNetwork::ANCHOR_TIME,
CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
CurrentNetwork::GENESIS_COINBASE_TARGET,
)
.unwrap();
assert!(next_coinbase_target < previous_coinbase_target / 2);
}
}
#[test]
fn test_target_doubling() {
let mut rng = TestRng::default();
const ANCHOR_TIME_DELTA: i64 = 15;
const EXPECTED_NUM_BLOCKS_TO_DOUBLE: u32 = 451;
let minimum_coinbase_target: u64 = 2u64.pow(10) - 1;
let initial_coinbase_target: u64 = rng.gen_range(minimum_coinbase_target..u64::MAX / 2);
let initial_timestamp: i64 = rng.gen();
let mut previous_coinbase_target: u64 = initial_coinbase_target;
let mut previous_timestamp = initial_timestamp;
let mut num_blocks = 0;
while previous_coinbase_target < initial_coinbase_target * 2 {
let next_timestamp = previous_timestamp + ANCHOR_TIME_DELTA;
let next_coinbase_target = coinbase_target(
previous_coinbase_target,
previous_timestamp,
next_timestamp,
CurrentNetwork::ANCHOR_TIME,
CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
CurrentNetwork::GENESIS_COINBASE_TARGET,
)
.unwrap();
assert!(next_coinbase_target > previous_coinbase_target);
previous_coinbase_target = next_coinbase_target;
previous_timestamp = next_timestamp;
num_blocks += 1;
}
let seconds = previous_timestamp - initial_timestamp;
println!(
"For drifts of {ANCHOR_TIME_DELTA} seconds and epochs of {} blocks, doubling the coinbase target took {num_blocks} blocks. ({seconds} seconds)",
CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
);
assert_eq!(EXPECTED_NUM_BLOCKS_TO_DOUBLE, num_blocks);
}
#[test]
fn test_to_next_targets_meets_threshold() {
let mut rng = TestRng::default();
let minimum_coinbase_target: u64 = 2u64.pow(10) - 1;
for _ in 0..ITERATIONS {
let latest_coinbase_target = rng.gen_range(minimum_coinbase_target..u64::MAX / 2);
let threshold = latest_coinbase_target as u128 / 2;
let last_coinbase_target = rng.gen_range(minimum_coinbase_target..latest_coinbase_target);
let last_coinbase_timestamp = rng.gen_range(0..i64::MAX / 2);
let next_timestamp = last_coinbase_timestamp + 100;
let latest_cumulative_weight = rng.gen_range(0..u128::MAX / 2);
let latest_cumulative_proof_target = rng.gen_range(0..threshold);
let combined_proof_target =
rng.gen_range(threshold.saturating_sub(latest_cumulative_proof_target)..u128::MAX);
assert!(latest_cumulative_proof_target.saturating_add(combined_proof_target) >= threshold);
let (
_,
_,
next_cumulative_proof_target,
next_cumulative_weight,
next_last_coinbase_target,
next_last_coinbase_timestamp,
) = to_next_targets::<CurrentNetwork>(
latest_cumulative_proof_target,
combined_proof_target,
latest_coinbase_target,
latest_cumulative_weight,
last_coinbase_target,
last_coinbase_timestamp,
next_timestamp,
)
.unwrap();
assert_eq!(next_cumulative_proof_target, 0);
assert_ne!(next_last_coinbase_target, last_coinbase_target);
assert_eq!(next_last_coinbase_timestamp, next_timestamp);
assert_eq!(next_cumulative_weight, latest_cumulative_weight.saturating_add(combined_proof_target));
}
}
#[test]
fn test_to_next_targets_does_not_meet_threshold() {
let mut rng = TestRng::default();
let minimum_coinbase_target: u64 = 2u64.pow(10) - 1;
for _ in 0..ITERATIONS {
let latest_coinbase_target = rng.gen_range(minimum_coinbase_target..u64::MAX / 2);
let threshold = latest_coinbase_target as u128 / 2;
let last_coinbase_target = rng.gen_range(minimum_coinbase_target..latest_coinbase_target);
let last_coinbase_timestamp = rng.gen_range(0..i64::MAX / 2);
let next_timestamp = last_coinbase_timestamp + 100;
let latest_cumulative_weight = rng.gen_range(0..u128::MAX / 2);
let latest_cumulative_proof_target = rng.gen_range(0..threshold);
let combined_proof_target = rng.gen_range(0..threshold.saturating_sub(latest_cumulative_proof_target));
assert!(latest_cumulative_proof_target.saturating_add(combined_proof_target) < threshold);
let (
_,
_,
next_cumulative_proof_target,
next_cumulative_weight,
next_last_coinbase_target,
next_last_coinbase_timestamp,
) = to_next_targets::<CurrentNetwork>(
latest_cumulative_proof_target,
combined_proof_target,
latest_coinbase_target,
latest_cumulative_weight,
last_coinbase_target,
last_coinbase_timestamp,
next_timestamp,
)
.unwrap();
assert_eq!(next_cumulative_proof_target, latest_cumulative_proof_target + combined_proof_target);
assert_eq!(next_last_coinbase_target, last_coinbase_target);
assert_eq!(next_last_coinbase_timestamp, last_coinbase_timestamp);
assert_eq!(next_cumulative_weight, latest_cumulative_weight.saturating_add(combined_proof_target));
}
}
}