snarkvm_ledger_block/helpers/
target.rs

1// Copyright 2024 Aleo Network Foundation
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use console::prelude::{ConsensusVersion, Network, Result, ensure};
17
18/// A safety bound (sanity-check) for the coinbase reward.
19pub const MAX_COINBASE_REWARD: u64 = 190_258_739; // Coinbase reward at block 1.
20
21/// A the maximum block interval in seconds. This is used to upper bound the block interval in the V2 block reward calculation to
22/// prevent the block reward from becoming too large in the event of a long block interval.
23const V2_MAX_BLOCK_INTERVAL: i64 = 60; // 1 minute.
24/// A the minimum block interval in seconds. This is used to lower bound the block interval in the V2 block reward calculation to
25/// prevent the block reward from becoming too small in the event of an extremely short block interval.
26const V2_MIN_BLOCK_INTERVAL: i64 = 1; // 1 second.
27
28/// The number of seconds in a year with 365 days. Leap years are ignored for simplicity.
29const SECONDS_IN_A_YEAR: u32 = 60 * 60 * 24 * 365;
30
31/// Calculate the block reward based on the network’s consensus version, determined by the given block height.
32pub fn block_reward<N: Network>(
33    block_height: u32,
34    total_supply: u64,
35    block_time: u16,
36    time_since_last_block: i64,
37    coinbase_reward: u64,
38    transaction_fees: u64,
39) -> Result<u64> {
40    // Determine which block reward version to use.
41    let consensus_version = N::CONSENSUS_VERSION(block_height)?;
42    if consensus_version == ConsensusVersion::V1 {
43        Ok(block_reward_v1(total_supply, block_time, coinbase_reward, transaction_fees))
44    } else {
45        Ok(block_reward_v2(total_supply, time_since_last_block, coinbase_reward, transaction_fees))
46    }
47}
48
49/// Calculate the V1 block reward, given the total supply, block time, coinbase reward, and transaction fees.
50///     R_staking = floor((0.05 * S) / H_Y1) + CR / 3 + TX_F.
51///     S = Total supply.
52///     H_Y1 = Expected block height at year 1.
53///     CR = Coinbase reward.
54///     TX_F = Transaction fees.
55pub const fn block_reward_v1(total_supply: u64, block_time: u16, coinbase_reward: u64, transaction_fees: u64) -> u64 {
56    // Compute the expected block height at year 1.
57    let block_height_at_year_1 = block_height_at_year(block_time, 1);
58    // Compute the annual reward: (0.05 * S).
59    let annual_reward = total_supply / 20;
60    // Compute the block reward: (0.05 * S) / H_Y1.
61    let block_reward = annual_reward / block_height_at_year_1 as u64;
62    // Return the sum of the block reward, coinbase reward, and transaction fees.
63    block_reward + (coinbase_reward / 3) + transaction_fees
64}
65
66/// Calculate the V2 block reward, given the total supply, block interval, coinbase reward, and transaction fees.
67///     R_staking = floor((0.05 * S) * clamp(I, MIN_BI, MAX_BI) / S_Y) + CR / 3 + TX_F.
68///     S = Total supply.
69///     I = Seconds elapsed since last block.
70///     S_Y = Seconds in a year (31536000).
71///     CR = Coinbase reward.
72///     TX_F = Transaction fees.
73///     MIN_BI = Minimum block interval.
74///     MAX_BI = Maximum block interval.
75pub fn block_reward_v2(
76    total_supply: u64,
77    time_since_last_block: i64,
78    coinbase_reward: u64,
79    transaction_fees: u64,
80) -> u64 {
81    // Compute the annual reward: (0.05 * S).
82    let annual_reward = total_supply / 20;
83    // Compute the seconds since last block with a maximum of `V2_MAX_BLOCK_INTERVAL` seconds and minimum of `V2_MIN_BLOCK_INTERVAL` seconds;
84    let time_since_last_block = time_since_last_block.clamp(V2_MIN_BLOCK_INTERVAL, V2_MAX_BLOCK_INTERVAL);
85    // Compute the block reward: (0.05 * S) * min(max(I, MIN_BLOCK_INTERVAL), MAX_BLOCK_INTERVAL) / S_Y.
86    let block_reward = annual_reward * time_since_last_block as u64 / SECONDS_IN_A_YEAR as u64;
87    // Return the sum of the block reward, coinbase reward, and transaction fees.
88    block_reward + (coinbase_reward / 3) + transaction_fees
89}
90
91/// Calculate the puzzle reward, given the coinbase reward.
92/// The puzzle reward is 2/3 of the total coinbase reward and paid out to the provers. The other 1/3 of
93/// the coinbase reward is included in the block reward and paid out to stakers.
94pub const fn puzzle_reward(coinbase_reward: u64) -> u64 {
95    // Return the coinbase reward multiplied by 2 and divided by 3.
96    coinbase_reward.saturating_mul(2).saturating_div(3)
97}
98
99/// Calculate the coinbase reward based on the network’s consensus version, determined by the given block height.
100pub fn coinbase_reward<N: Network>(
101    block_height: u32,
102    block_timestamp: i64,
103    genesis_timestamp: i64,
104    starting_supply: u64,
105    anchor_time: u16,
106    anchor_height: u32,
107    block_time: u16,
108    combined_proof_target: u128,
109    cumulative_proof_target: u64,
110    coinbase_target: u64,
111) -> Result<u64> {
112    // Determine which coinbase reward version to use.
113    let consensus_version = N::CONSENSUS_VERSION(block_height)?;
114    if consensus_version == ConsensusVersion::V1 {
115        coinbase_reward_v1(
116            block_height,
117            starting_supply,
118            anchor_height,
119            block_time,
120            combined_proof_target,
121            cumulative_proof_target,
122            coinbase_target,
123        )
124    } else {
125        coinbase_reward_v2(
126            block_timestamp,
127            genesis_timestamp,
128            starting_supply,
129            anchor_time,
130            combined_proof_target,
131            cumulative_proof_target,
132            coinbase_target,
133        )
134    }
135}
136
137/// Calculates the V1 coinbase reward for a given block.
138///     R_coinbase = R_anchor(H) * min(P, C_R) / C
139///     R_anchor = Anchor reward at block height.
140///     H = Current block height.
141///     P = Combined proof target.
142///     C_R = Remaining coinbase target.
143///     C = Coinbase target.
144pub fn coinbase_reward_v1(
145    block_height: u32,
146    starting_supply: u64,
147    anchor_height: u32,
148    block_time: u16,
149    combined_proof_target: u128,
150    cumulative_proof_target: u64,
151    coinbase_target: u64,
152) -> Result<u64> {
153    // Compute the remaining coinbase target.
154    let remaining_coinbase_target = coinbase_target.saturating_sub(cumulative_proof_target);
155    // Compute the remaining proof target.
156    let remaining_proof_target = combined_proof_target.min(remaining_coinbase_target as u128);
157
158    // Compute the anchor block reward.
159    let anchor_block_reward = anchor_block_reward_at_height(block_height, starting_supply, anchor_height, block_time);
160
161    // Calculate the coinbase reward.
162    let reward = anchor_block_reward.saturating_mul(remaining_proof_target).saturating_div(coinbase_target as u128);
163
164    // Ensure the coinbase reward is less than the maximum coinbase reward.
165    ensure!(reward <= MAX_COINBASE_REWARD as u128, "Coinbase reward ({reward}) exceeds maximum {MAX_COINBASE_REWARD}");
166
167    // Return the coinbase reward.
168    // Note: This '.expect' is guaranteed to be safe, as we ensure the reward is within a safe bound.
169    Ok(u64::try_from(reward).expect("Coinbase reward exceeds u64::MAX"))
170}
171
172/// Calculates the V2 coinbase reward for a given block.
173///     R_coinbase = R_anchor(H) * min(P, C_R) / C
174///     R_anchor = Anchor reward at block height.
175///     H = Current block height.
176///     P = Combined proof target.
177///     C_R = Remaining coinbase target.
178///     C = Coinbase target.
179pub fn coinbase_reward_v2(
180    block_timestamp: i64,
181    genesis_timestamp: i64,
182    starting_supply: u64,
183    anchor_time: u16,
184    combined_proof_target: u128,
185    cumulative_proof_target: u64,
186    coinbase_target: u64,
187) -> Result<u64> {
188    // Compute the remaining coinbase target.
189    let remaining_coinbase_target = coinbase_target.saturating_sub(cumulative_proof_target);
190    // Compute the remaining proof target.
191    let remaining_proof_target = combined_proof_target.min(remaining_coinbase_target as u128);
192
193    // Compute the anchor block reward.
194    let anchor_block_reward =
195        anchor_block_reward_at_timestamp(block_timestamp, genesis_timestamp, starting_supply, anchor_time);
196
197    // Calculate the coinbase reward.
198    let reward = anchor_block_reward.saturating_mul(remaining_proof_target).saturating_div(coinbase_target as u128);
199
200    // Ensure the coinbase reward is less than the maximum coinbase reward.
201    ensure!(reward <= MAX_COINBASE_REWARD as u128, "Coinbase reward ({reward}) exceeds maximum {MAX_COINBASE_REWARD}");
202
203    // Return the coinbase reward.
204    // Note: This '.expect' is guaranteed to be safe, as we ensure the reward is within a safe bound.
205    Ok(u64::try_from(reward).expect("Coinbase reward exceeds u64::MAX"))
206}
207
208/// Calculates the anchor block reward for the given block height.
209/// The anchor block reward is upper bound of the coinbase reward for the given block before
210/// calculating the final pro-rata coinbase reward based on the targets.
211///     R_anchor = max(floor((2 * S * H_A * H_R) / (H_Y10 * (H_Y10 + 1))), R_Y9).
212///     S = Starting supply.
213///     H_A = Anchor block height.
214///     H_R = Remaining number of blocks until year 10.
215///     H_Y10 = Expected block height at year 10.
216///     R_Y9 = Reward at year 9.
217fn anchor_block_reward_at_height(block_height: u32, starting_supply: u64, anchor_height: u32, block_time: u16) -> u128 {
218    // A helper function to calculate the reward at a given block height, without the year 9 baseline.
219    const fn block_reward_at_height(height: u32, starting_supply: u64, anchor_height: u32, block_time: u16) -> u128 {
220        // Calculate the block height at year 10.
221        let block_height_at_year_10 = block_height_at_year(block_time, 10) as u128;
222        // Compute the remaining blocks until year 10.
223        let num_remaining_blocks_to_year_10 = block_height_at_year_10.saturating_sub(height as u128);
224        // Compute the numerator.
225        let numerator = 2 * starting_supply as u128 * anchor_height as u128 * num_remaining_blocks_to_year_10;
226        // Compute the denominator.
227        let denominator = block_height_at_year_10 * (block_height_at_year_10 + 1);
228        // Compute the quotient.
229        numerator / denominator
230    }
231
232    // Calculate the block height at year 9.
233    let block_height_at_year_9 = block_height_at_year(block_time, 9);
234    // Compute the unadjusted reward at year 9.
235    let reward_at_year_9 = block_reward_at_height(block_height_at_year_9, starting_supply, anchor_height, block_time);
236    // Compute the unadjusted reward at the given block height.
237    let reward_at_block_height = block_reward_at_height(block_height, starting_supply, anchor_height, block_time);
238    // Compute the anchor block reward.
239    reward_at_block_height.max(reward_at_year_9)
240}
241
242/// Calculates the anchor block reward for the given block timestamp.
243/// The anchor block reward is upper bound of the coinbase reward for the given block before
244/// calculating the final pro-rata coinbase reward based on the targets.
245/// This function uses timestamp rather than block height to determine the reward in order to combat
246/// the volatility of block times and better align with human timescales.
247///     R_anchor = max(floor((2 * S * T_A * T_R) / (T_Y10 * (T_Y10 + 1))), R_Y9).
248///     S = Starting supply.
249///     T_A = Anchor block time.
250///     T_R = Remaining number of seconds until year 10.
251///     T_Y10 = Number of seconds elapsed in 10 years.
252///     R_Y9 = Reward at year 9.
253fn anchor_block_reward_at_timestamp(
254    block_timestamp: i64,
255    genesis_timestamp: i64,
256    starting_supply: u64,
257    anchor_time: u16,
258) -> u128 {
259    // A helper function to calculate the reward at a given block timestamp, without the year 9 baseline.
260    const fn block_reward_at_timestamp(
261        block_timestamp: i64,
262        genesis_timestamp: i64,
263        starting_supply: u64,
264        anchor_time: u16,
265    ) -> u128 {
266        // Calculate the timestamp at year 10.
267        let timestamp_at_year_10 = timestamp_at_year(genesis_timestamp, 10) as u128;
268        // Calculate the number of seconds elapsed in 10 years.
269        let number_of_seconds_in_10_years = (SECONDS_IN_A_YEAR as u128).saturating_mul(10);
270        // Compute the remaining seconds until year 10.
271        let num_remaining_seconds_to_year_10 = timestamp_at_year_10.saturating_sub(block_timestamp as u128);
272
273        // Compute the numerator.
274        // Note that we perform a `saturating_div(10)` on the `anchor_time` in the numerator and the `number_of_seconds_in_10_years` denominator.
275        // This is done to to match the truncation of `anchor_block_reward_at_height` in an attempt to
276        // keep the reward more consistent between the two functions.
277        let numerator =
278            2 * starting_supply as u128 * anchor_time.saturating_div(10) as u128 * num_remaining_seconds_to_year_10;
279        // Compute the denominator.
280        let denominator = number_of_seconds_in_10_years * (number_of_seconds_in_10_years.saturating_div(10) + 1);
281        // Compute the quotient.
282        numerator / denominator
283    }
284
285    // Calculate the timestamp at year 9.
286    let timestamp_at_year_9 = timestamp_at_year(genesis_timestamp, 9);
287    // Compute the unadjusted reward at year 9.
288    let reward_at_year_9 =
289        block_reward_at_timestamp(timestamp_at_year_9, genesis_timestamp, starting_supply, anchor_time);
290    // Compute the unadjusted reward at the given block timestamp.
291    let reward_at_block_timestamp =
292        block_reward_at_timestamp(block_timestamp, genesis_timestamp, starting_supply, anchor_time);
293    // Compute the anchor block reward.
294    reward_at_block_timestamp.max(reward_at_year_9)
295}
296
297/// Returns the timestamp for a given year, relative to the genesis timestamp.
298/// We assume a year is 365 days and ignore leap years for simplicity.
299const fn timestamp_at_year(genesis_timestamp: i64, num_years: u32) -> i64 {
300    // Calculate the number of seconds elapsed in `num_years`.
301    let seconds_elapsed = SECONDS_IN_A_YEAR.saturating_mul(num_years);
302    // Return the timestamp for the given year.
303    genesis_timestamp.saturating_add(seconds_elapsed as i64)
304}
305
306/// Returns the block height after a given number of years for a specific block time.
307const fn block_height_at_year(block_time: u16, num_years: u32) -> u32 {
308    // Calculate the one-year block height.
309    let block_height_at_year_1 = SECONDS_IN_A_YEAR / block_time as u32;
310    // Return the block height for the given number of years.
311    block_height_at_year_1 * num_years
312}
313
314/// Calculate the coinbase target for the given block timestamps and target.
315pub fn coinbase_target(
316    previous_target: u64,
317    previous_block_timestamp: i64,
318    block_timestamp: i64,
319    anchor_time: u16,
320    num_blocks_per_epoch: u32,
321    genesis_target: u64,
322) -> Result<u64> {
323    // Compute the half life.
324    let half_life = num_blocks_per_epoch.saturating_div(2).saturating_mul(anchor_time as u32);
325    // Compute the new coinbase target.
326    let candidate_target =
327        retarget(previous_target, previous_block_timestamp, block_timestamp, anchor_time, half_life, true)?;
328    // Return the new coinbase target, floored at the genesis target.
329    Ok(candidate_target.max(genesis_target))
330}
331
332/// Calculate the minimum proof target for the given coinbase target.
333pub fn proof_target(coinbase_target: u64, genesis_proof_target: u64, max_solutions_as_power_of_two: u8) -> u64 {
334    coinbase_target
335        .checked_shr(max_solutions_as_power_of_two as u32)
336        .map(|target| target.saturating_add(1))
337        .unwrap_or(genesis_proof_target)
338}
339
340/// Retarget algorithm using fixed point arithmetic from https://www.reference.cash/protocol/forks/2020-11-15-asert.
341///     T_{i+1} = T_i * 2^(INV * (D - A) / TAU).
342///     T_i = Current target.
343///     D = Drift, defined as the number of blocks elapsed.
344///     A = Anchor timestamp, defined as expected number of seconds elapsed.
345///     TAU = Rate of doubling (or half-life) in seconds.
346///     INV = {-1, 1} depending on whether the target is increasing or decreasing.
347fn retarget(
348    previous_target: u64,
349    previous_block_timestamp: i64,
350    block_timestamp: i64,
351    anchor_time: u16,
352    half_life: u32,
353    is_inverse: bool,
354) -> Result<u64> {
355    // Determine the block time elapsed (in seconds) since the previous block.
356    // Note: This operation includes a safety check for a repeat block timestamp.
357    let block_time_elapsed = block_timestamp.saturating_sub(previous_block_timestamp).max(1);
358    // Compute the drift.
359    let mut drift = block_time_elapsed.saturating_sub(anchor_time as i64);
360
361    // If the drift is zero, return the previous target.
362    if drift == 0 {
363        return Ok(previous_target);
364    }
365
366    // Negate the drift if the inverse flag is set.
367    if is_inverse {
368        drift *= -1;
369    }
370
371    // Constants used for fixed point arithmetic.
372    const RBITS: u32 = 16;
373    const RADIX: u128 = 1 << RBITS;
374
375    // Compute the exponent factor, and decompose it into integral & fractional parts for fixed point arithmetic.
376    let (integral, fractional) = {
377        // Calculate the exponent factor.
378        let exponent = (RADIX as i128).saturating_mul(drift as i128) / half_life as i128;
379
380        // Decompose into the integral and fractional parts.
381        let integral = exponent >> RBITS;
382        let fractional = (exponent - (integral << RBITS)) as u128;
383        ensure!(fractional < RADIX, "Fractional part is not within the fixed point size");
384        ensure!(exponent == (integral * (RADIX as i128) + fractional as i128), "Exponent is decomposed incorrectly");
385
386        (integral, fractional)
387    };
388
389    // Approximate the fractional multiplier as 2^RBITS * 2^fractional, where:
390    // 2^x ~= (1 + 0.695502049*x + 0.2262698*x**2 + 0.0782318*x**3)
391    let fractional_multiplier = RADIX
392        + ((195_766_423_245_049_u128 * fractional
393            + 971_821_376_u128 * fractional.pow(2)
394            + 5_127_u128 * fractional.pow(3)
395            + 2_u128.pow(RBITS * 3 - 1))
396            >> (RBITS * 3));
397
398    // Cast the previous coinbase target from a u64 to a u128.
399    // The difficulty target must allow for leading zeros to account for overflows;
400    // an additional 64-bits for the leading zeros suffices.
401    let candidate_target = (previous_target as u128).saturating_mul(fractional_multiplier);
402
403    // Calculate the new difficulty.
404    // Shift the target to multiply by 2^(integer) / RADIX.
405    let shifts = integral - RBITS as i128;
406    let mut candidate_target = if shifts < 0 {
407        match candidate_target.checked_shr(u32::try_from(-shifts)?) {
408            Some(target) => core::cmp::max(target, 1),
409            None => 1,
410        }
411    } else {
412        match candidate_target.checked_shl(u32::try_from(shifts)?) {
413            Some(target) => core::cmp::max(target, 1),
414            None => u64::MAX as u128,
415        }
416    };
417
418    // Cap the target at `u64::MAX` if it has overflowed.
419    candidate_target = core::cmp::min(candidate_target, u64::MAX as u128);
420
421    // Ensure that the leading 64 bits are zeros.
422    ensure!(candidate_target.checked_shr(64) == Some(0), "The target has overflowed");
423    // Cast the new target down from a u128 to a u64.
424    Ok(u64::try_from(candidate_target)?)
425}
426
427/// This function calculates the next targets for the given attributes:
428///     `latest_cumulative_proof_target`: The latest cumulative proof target.
429///     `combined_proof_target`: The combined proof target of solutions in the block.
430///     `latest_coinbase_target`: The latest coinbase target.
431///     `last_coinbase_target`: The coinbase target for the last coinbase.
432///     `last_coinbase_timestamp`: The timestamp for the last coinbase.
433///     `next_timestamp`: The timestamp for the next block.
434///
435/// Returns the following as a tuple:
436///     `next_coinbase_target` - The next coinbase target.
437///     `next_proof_target` - The next proof target.
438///     `next_cumulative_proof_target` - The next cumulative proof target.
439///     `next_cumulative_weight` - The next cumulative weight.
440///     `next_last_coinbase_target` - The next last coinbase target.
441///     `next_last_coinbase_timestamp` - The next last coinbase timestamp.
442pub fn to_next_targets<N: Network>(
443    latest_cumulative_proof_target: u128,
444    combined_proof_target: u128,
445    latest_coinbase_target: u64,
446    latest_cumulative_weight: u128,
447    last_coinbase_target: u64,
448    last_coinbase_timestamp: i64,
449    next_timestamp: i64,
450) -> Result<(u64, u64, u128, u128, u64, i64)> {
451    // Compute the coinbase target threshold.
452    let latest_coinbase_threshold = latest_coinbase_target.saturating_div(2) as u128;
453    // Compute the next cumulative proof target.
454    let next_cumulative_proof_target = latest_cumulative_proof_target.saturating_add(combined_proof_target);
455    // Determine if the coinbase target threshold is reached.
456    let is_coinbase_threshold_reached = next_cumulative_proof_target >= latest_coinbase_threshold;
457    // Construct the next coinbase target.
458    let next_coinbase_target = coinbase_target(
459        last_coinbase_target,
460        last_coinbase_timestamp,
461        next_timestamp,
462        N::ANCHOR_TIME,
463        N::NUM_BLOCKS_PER_EPOCH,
464        N::GENESIS_COINBASE_TARGET,
465    )?;
466    // Construct the next proof target.
467    let next_proof_target =
468        proof_target(next_coinbase_target, N::GENESIS_PROOF_TARGET, N::MAX_SOLUTIONS_AS_POWER_OF_TWO);
469
470    // Update the next cumulative proof target, if necessary.
471    let next_cumulative_proof_target = match is_coinbase_threshold_reached {
472        true => 0,
473        false => next_cumulative_proof_target,
474    };
475
476    // Compute the next cumulative weight.
477    let next_cumulative_weight = latest_cumulative_weight.saturating_add(combined_proof_target);
478
479    // Construct the next last coinbase target and next last coinbase timestamp.
480    let (next_last_coinbase_target, next_last_coinbase_timestamp) = match is_coinbase_threshold_reached {
481        true => (next_coinbase_target, next_timestamp),
482        false => (last_coinbase_target, last_coinbase_timestamp),
483    };
484
485    Ok((
486        next_coinbase_target,
487        next_proof_target,
488        next_cumulative_proof_target,
489        next_cumulative_weight,
490        next_last_coinbase_target,
491        next_last_coinbase_timestamp,
492    ))
493}
494
495#[cfg(test)]
496mod tests {
497    use super::*;
498    use console::network::{MainnetV0, TestnetV0, prelude::*};
499
500    type CurrentNetwork = MainnetV0;
501
502    const ITERATIONS: u32 = 1000;
503
504    const EXPECTED_ANCHOR_BLOCK_REWARD_AT_BLOCK_1: u128 = MAX_COINBASE_REWARD as u128;
505    const EXPECTED_STAKING_REWARD: u64 = 23_782_343;
506    const EXPECTED_COINBASE_REWARD_AT_BLOCK_1: u64 = MAX_COINBASE_REWARD;
507    const EXPECTED_MAX_STAKING_REWARD: u64 = 142_694_063;
508
509    #[test]
510    fn test_anchor_block_reward_v1() {
511        // Check the anchor block reward at block 1.
512        let reward_at_block_1 = anchor_block_reward_at_height(
513            1,
514            CurrentNetwork::STARTING_SUPPLY,
515            CurrentNetwork::ANCHOR_HEIGHT,
516            CurrentNetwork::BLOCK_TIME,
517        );
518        assert_eq!(reward_at_block_1, EXPECTED_ANCHOR_BLOCK_REWARD_AT_BLOCK_1);
519
520        // A helper function to check the the reward at the first expected block of a given year.
521        fn check_reward_at_year(year: u32, expected_reward: u128) {
522            let reward_at_year = anchor_block_reward_at_height(
523                block_height_at_year(CurrentNetwork::BLOCK_TIME, year),
524                CurrentNetwork::STARTING_SUPPLY,
525                CurrentNetwork::ANCHOR_HEIGHT,
526                CurrentNetwork::BLOCK_TIME,
527            );
528            assert_eq!(reward_at_year, expected_reward);
529        }
530
531        // Check the anchor block reward at the start of years 1 through 15.
532        check_reward_at_year(1, 171_232_871);
533        check_reward_at_year(2, 152_206_996);
534        check_reward_at_year(3, 133_181_122);
535        check_reward_at_year(4, 114_155_247);
536        check_reward_at_year(5, 95_129_372);
537        check_reward_at_year(6, 76_103_498);
538        check_reward_at_year(7, 57_077_623);
539        check_reward_at_year(8, 38_051_749);
540        check_reward_at_year(9, 19_025_874);
541        check_reward_at_year(10, 19_025_874);
542        check_reward_at_year(11, 19_025_874);
543        check_reward_at_year(12, 19_025_874);
544        check_reward_at_year(13, 19_025_874);
545        check_reward_at_year(14, 19_025_874);
546        check_reward_at_year(15, 19_025_874);
547
548        // Calculate the block height at year 9.
549        let block_height_at_year_9 = block_height_at_year(CurrentNetwork::BLOCK_TIME, 9);
550
551        // Ensure that the reward is decreasing for blocks before year 9.
552        let mut previous_reward = reward_at_block_1;
553        let anchor_height = CurrentNetwork::ANCHOR_HEIGHT as usize;
554        for height in (2..block_height_at_year_9).step_by(anchor_height).skip(1) {
555            let reward = anchor_block_reward_at_height(
556                height,
557                CurrentNetwork::STARTING_SUPPLY,
558                CurrentNetwork::ANCHOR_HEIGHT,
559                CurrentNetwork::BLOCK_TIME,
560            );
561            assert!(reward < previous_reward, "Failed on block height {height}");
562            previous_reward = reward;
563        }
564
565        // Ensure that the reward is 19_025_874 for blocks after year 9.
566        for height in block_height_at_year_9..(block_height_at_year_9 + ITERATIONS) {
567            let reward = anchor_block_reward_at_height(
568                height,
569                CurrentNetwork::STARTING_SUPPLY,
570                CurrentNetwork::ANCHOR_HEIGHT,
571                CurrentNetwork::BLOCK_TIME,
572            );
573            assert_eq!(reward, 19_025_874);
574        }
575    }
576
577    #[test]
578    fn test_anchor_block_reward_v2() {
579        // Check the anchor block reward at block 1.
580        let reward_at_block_1 = anchor_block_reward_at_timestamp(
581            CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
582            CurrentNetwork::GENESIS_TIMESTAMP,
583            CurrentNetwork::STARTING_SUPPLY,
584            CurrentNetwork::ANCHOR_TIME,
585        );
586        assert_eq!(reward_at_block_1, EXPECTED_ANCHOR_BLOCK_REWARD_AT_BLOCK_1);
587
588        // A helper function to check the the reward at the first expected block of a given year.
589        fn check_reward_at_year(year: u32, expected_reward: u128) {
590            let reward_at_year = anchor_block_reward_at_timestamp(
591                timestamp_at_year(CurrentNetwork::GENESIS_TIMESTAMP, year),
592                CurrentNetwork::GENESIS_TIMESTAMP,
593                CurrentNetwork::STARTING_SUPPLY,
594                CurrentNetwork::ANCHOR_TIME,
595            );
596            assert_eq!(reward_at_year, expected_reward);
597        }
598
599        // Check the anchor block reward at the start of years 1 through 15.
600        check_reward_at_year(1, 171_232_871);
601        check_reward_at_year(2, 152_206_996);
602        check_reward_at_year(3, 133_181_122);
603        check_reward_at_year(4, 114_155_247);
604        check_reward_at_year(5, 95_129_372);
605        check_reward_at_year(6, 76_103_498);
606        check_reward_at_year(7, 57_077_623);
607        check_reward_at_year(8, 38_051_749);
608        check_reward_at_year(9, 19_025_874);
609        check_reward_at_year(10, 19_025_874);
610        check_reward_at_year(11, 19_025_874);
611        check_reward_at_year(12, 19_025_874);
612        check_reward_at_year(13, 19_025_874);
613        check_reward_at_year(14, 19_025_874);
614        check_reward_at_year(15, 19_025_874);
615
616        // Calculate the timestamp at year 9.
617        let timestamp_at_year_9 = timestamp_at_year(CurrentNetwork::GENESIS_TIMESTAMP, 9);
618
619        // Ensure that the reward is decreasing for blocks before year 9.
620        let mut previous_reward = reward_at_block_1;
621        let anchor_time = CurrentNetwork::ANCHOR_TIME as usize;
622        for timestamp in (CurrentNetwork::GENESIS_TIMESTAMP..timestamp_at_year_9).step_by(anchor_time).skip(1) {
623            let reward = anchor_block_reward_at_timestamp(
624                timestamp,
625                CurrentNetwork::GENESIS_TIMESTAMP,
626                CurrentNetwork::STARTING_SUPPLY,
627                CurrentNetwork::ANCHOR_TIME,
628            );
629            assert!(reward < previous_reward, "Failed on timestamp {timestamp}");
630            previous_reward = reward;
631        }
632
633        // Ensure that the reward is 19_025_874 for blocks after year 9.
634        for timestamp in timestamp_at_year_9..(timestamp_at_year_9 + ITERATIONS as i64) {
635            let reward = anchor_block_reward_at_timestamp(
636                timestamp,
637                CurrentNetwork::GENESIS_TIMESTAMP,
638                CurrentNetwork::STARTING_SUPPLY,
639                CurrentNetwork::ANCHOR_TIME,
640            );
641            assert_eq!(reward, 19_025_874);
642        }
643    }
644
645    #[test]
646    fn test_total_anchor_block_reward_v1() {
647        // A helper function used to add the anchor block reward for a given range of block heights.
648        fn add_anchor_block_reward(total_reward: &mut u128, start_height: u32, end_height: u32) {
649            for height in start_height..end_height {
650                *total_reward += anchor_block_reward_at_height(
651                    height,
652                    CurrentNetwork::STARTING_SUPPLY,
653                    CurrentNetwork::ANCHOR_HEIGHT,
654                    CurrentNetwork::BLOCK_TIME,
655                );
656            }
657        }
658
659        // Initialize the total reward.
660        let mut total_reward = 0;
661
662        // A helper function to check the sum of all possible anchor rewards over a given year.
663        let mut check_sum_of_anchor_rewards = |year: u32, expected_reward: u128| {
664            assert!(year > 0, "Year must be greater than 0");
665            let end_height = block_height_at_year(CurrentNetwork::BLOCK_TIME, year);
666            let start_height = std::cmp::max(1, block_height_at_year(CurrentNetwork::BLOCK_TIME, year - 1));
667            add_anchor_block_reward(&mut total_reward, start_height, end_height);
668            assert_eq!(total_reward, expected_reward);
669        };
670
671        // Check the sum of all anchor block rewards at block at year 1.
672        check_sum_of_anchor_rewards(1, 569999799602807);
673        // Check the sum of all anchor block rewards at block at year 2.
674        check_sum_of_anchor_rewards(2, 1079999791366949);
675        // Check the sum of all anchor block rewards at block at year 3.
676        check_sum_of_anchor_rewards(3, 1529999785033683);
677        // Check the sum of all anchor block rewards at block at year 4.
678        check_sum_of_anchor_rewards(4, 1919999780603002);
679        // Check the sum of all anchor block rewards at block at year 5.
680        check_sum_of_anchor_rewards(5, 2249999778074916);
681        // Check the sum of all anchor block rewards at block at year 6.
682        check_sum_of_anchor_rewards(6, 2519999777449404);
683        // Check the sum of all anchor block rewards at block at year 7.
684        check_sum_of_anchor_rewards(7, 2729999778726485);
685        // Check the sum of all anchor block rewards at block at year 8.
686        check_sum_of_anchor_rewards(8, 2879999781906155);
687        // Check the sum of all anchor block rewards at block at year 9.
688        check_sum_of_anchor_rewards(9, 2969999786988413);
689        // Check the sum of all anchor block rewards at block at year 10.
690        check_sum_of_anchor_rewards(10, 3029999783234813);
691        // Check the sum of all anchor block rewards at block at year 11.
692        check_sum_of_anchor_rewards(11, 3089999779481213);
693        // Check the sum of all anchor block rewards at block at year 12.
694        check_sum_of_anchor_rewards(12, 3149999775727613);
695        // Check the sum of all anchor block rewards at block at year 13.
696        check_sum_of_anchor_rewards(13, 3209999771974013);
697        // Check the sum of all anchor block rewards at block at year 14.
698        check_sum_of_anchor_rewards(14, 3269999768220413);
699        // Check the sum of all anchor block rewards at block at year 15.
700        check_sum_of_anchor_rewards(15, 3329999764466813);
701    }
702
703    #[test]
704    fn test_total_anchor_block_reward_v2() {
705        // A helper function used to add the anchor block reward for a given range of block timestamps.
706        fn add_anchor_block_reward(total_reward: &mut u128, start_timestamp: i64, end_timestamp: i64) {
707            for timestamp in (start_timestamp..end_timestamp).step_by(CurrentNetwork::BLOCK_TIME as usize) {
708                *total_reward += anchor_block_reward_at_timestamp(
709                    timestamp,
710                    CurrentNetwork::GENESIS_TIMESTAMP,
711                    CurrentNetwork::STARTING_SUPPLY,
712                    CurrentNetwork::ANCHOR_TIME,
713                );
714            }
715        }
716
717        // Initialize the total reward.
718        let mut total_reward = 0;
719
720        // A helper function to check the sum of all possible anchor rewards over a given year.
721        let mut check_sum_of_anchor_rewards = |year: u32, expected_reward: u128| {
722            assert!(year > 0, "Year must be greater than 0");
723            let end_timestamp = timestamp_at_year(CurrentNetwork::GENESIS_TIMESTAMP, year);
724            let start_timestamp = std::cmp::max(
725                CurrentNetwork::GENESIS_TIMESTAMP,
726                timestamp_at_year(CurrentNetwork::GENESIS_TIMESTAMP, year - 1),
727            );
728            add_anchor_block_reward(&mut total_reward, start_timestamp, end_timestamp);
729            // println!("year {year}, total_reward: {total_reward} expected_reward: {expected_reward}")
730            assert_eq!(total_reward, expected_reward);
731        };
732
733        // Check the sum of all anchor block rewards at block at year 1.
734        check_sum_of_anchor_rewards(1, 569999989861552);
735        // Check the sum of all anchor block rewards at block at year 2.
736        check_sum_of_anchor_rewards(2, 1079999981625694);
737        // Check the sum of all anchor block rewards at block at year 3.
738        check_sum_of_anchor_rewards(3, 1529999975292428);
739        // Check the sum of all anchor block rewards at block at year 4.
740        check_sum_of_anchor_rewards(4, 1919999970861747);
741        // Check the sum of all anchor block rewards at block at year 5.
742        check_sum_of_anchor_rewards(5, 2249999968333661);
743        // Check the sum of all anchor block rewards at block at year 6.
744        check_sum_of_anchor_rewards(6, 2519999967708149);
745        // Check the sum of all anchor block rewards at block at year 7.
746        check_sum_of_anchor_rewards(7, 2729999968985230);
747        // Check the sum of all anchor block rewards at block at year 8.
748        check_sum_of_anchor_rewards(8, 2879999972164900);
749        // Check the sum of all anchor block rewards at block at year 9.
750        check_sum_of_anchor_rewards(9, 2969999977247158);
751        // Check the sum of all anchor block rewards at block at year 10.
752        check_sum_of_anchor_rewards(10, 3029999973493558);
753        // Check the sum of all anchor block rewards at block at year 11.
754        check_sum_of_anchor_rewards(11, 3089999969739958);
755        // Check the sum of all anchor block rewards at block at year 12.
756        check_sum_of_anchor_rewards(12, 3149999965986358);
757        // Check the sum of all anchor block rewards at block at year 13.
758        check_sum_of_anchor_rewards(13, 3209999962232758);
759        // Check the sum of all anchor block rewards at block at year 14.
760        check_sum_of_anchor_rewards(14, 3269999958479158);
761        // Check the sum of all anchor block rewards at block at year 15.
762        check_sum_of_anchor_rewards(15, 3329999954725558);
763    }
764
765    #[test]
766    fn test_block_reward() {
767        let mut rng = TestRng::default();
768
769        // Ensure that a block height of `TestnetV0::CONSENSUS_HEIGHT(ConsensusVersion::V2).unwrap()` uses block reward V2.
770        let time_since_last_block = rng.gen_range(1..=V2_MAX_BLOCK_INTERVAL);
771        let reward = block_reward::<TestnetV0>(
772            TestnetV0::CONSENSUS_HEIGHT(ConsensusVersion::V2).unwrap(),
773            TestnetV0::STARTING_SUPPLY,
774            TestnetV0::BLOCK_TIME,
775            time_since_last_block,
776            0,
777            0,
778        )
779        .unwrap();
780        let expected_reward = block_reward_v2(TestnetV0::STARTING_SUPPLY, time_since_last_block, 0, 0);
781        assert_eq!(reward, expected_reward);
782
783        for _ in 0..100 {
784            // Check that the block reward is correct for the first consensus version.
785            let consensus_v1_height = rng.gen_range(0..TestnetV0::CONSENSUS_HEIGHT(ConsensusVersion::V2).unwrap());
786            let consensus_v1_reward = block_reward::<TestnetV0>(
787                consensus_v1_height,
788                TestnetV0::STARTING_SUPPLY,
789                TestnetV0::BLOCK_TIME,
790                0,
791                0,
792                0,
793            )
794            .unwrap();
795            let expected_reward = block_reward_v1(TestnetV0::STARTING_SUPPLY, TestnetV0::BLOCK_TIME, 0, 0);
796            assert_eq!(consensus_v1_reward, expected_reward);
797
798            // Check that the block reward is correct for the second consensus version.
799            let consensus_v2_height =
800                rng.gen_range(TestnetV0::CONSENSUS_HEIGHT(ConsensusVersion::V2).unwrap()..u32::MAX);
801            let time_since_last_block = rng.gen_range(1..=V2_MAX_BLOCK_INTERVAL);
802            let consensus_v2_reward = block_reward::<TestnetV0>(
803                consensus_v2_height,
804                TestnetV0::STARTING_SUPPLY,
805                TestnetV0::BLOCK_TIME,
806                time_since_last_block,
807                0,
808                0,
809            )
810            .unwrap();
811            let expected_reward = block_reward_v2(TestnetV0::STARTING_SUPPLY, time_since_last_block, 0, 0);
812            assert_eq!(consensus_v2_reward, expected_reward);
813        }
814    }
815
816    #[test]
817    fn test_block_reward_v1() {
818        let reward = block_reward_v1(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME, 0, 0);
819        assert_eq!(reward, EXPECTED_STAKING_REWARD);
820
821        // Increasing the anchor time will increase the reward.
822        let larger_reward = block_reward_v1(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME + 1, 0, 0);
823        assert!(reward < larger_reward);
824
825        // Decreasing the anchor time will decrease the reward.
826        let smaller_reward = block_reward_v1(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME - 1, 0, 0);
827        assert!(reward > smaller_reward);
828    }
829
830    #[test]
831    fn test_block_reward_v2() {
832        let reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME as i64, 0, 0);
833        assert_eq!(reward, EXPECTED_STAKING_REWARD);
834
835        // Increasing the anchor time will increase the reward.
836        let larger_reward =
837            block_reward_v2(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME as i64 + 1, 0, 0);
838        assert!(reward < larger_reward);
839
840        // Decreasing the anchor time will decrease the reward.
841        let smaller_reward =
842            block_reward_v2(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME as i64 - 1, 0, 0);
843        assert!(reward > smaller_reward);
844
845        // Increasing the block interval past `V2_MAX_BLOCK_INTERVAL` does not increase the reward.
846        let max_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, V2_MAX_BLOCK_INTERVAL, 0, 0);
847        assert_eq!(max_reward, EXPECTED_MAX_STAKING_REWARD);
848        let equivalent_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, V2_MAX_BLOCK_INTERVAL + 1, 0, 0);
849        assert_eq!(max_reward, equivalent_reward);
850
851        // Test that there is a minimum block reward when the time since last block is 1 second.
852        let min_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, 1, 0, 0);
853        let equivalent_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, 0, 0, 0);
854        assert_eq!(min_reward, equivalent_reward);
855    }
856
857    #[test]
858    fn test_block_reward_v1_vs_v2() {
859        let mut rng = TestRng::default();
860
861        // Declare a tolerance for reward divergence between v1 and v2 due to truncation.
862        const TOLERANCE: f64 = 0.001; // 0.1% tolerance
863
864        // Expect that the v2 block reward is equivalent to the v1 block reward if the `CurrentNetwork::BLOCK_TIME` is fixed.
865        let reward_v1 = block_reward_v1(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME, 0, 0);
866        assert_eq!(reward_v1, EXPECTED_STAKING_REWARD);
867        let reward_v2 = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, CurrentNetwork::BLOCK_TIME as i64, 0, 0);
868        assert_eq!(reward_v1, reward_v2);
869
870        // Decreasing the time since last block based on `CurrentNetwork::BLOCK_TIME` will proportionally reduce the v2 rewards.
871        let shorter_time = CurrentNetwork::BLOCK_TIME / 2;
872        let smaller_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, shorter_time as i64, 0, 0);
873        let expected_reward = EXPECTED_STAKING_REWARD / 2;
874        assert!((smaller_reward as f64 - expected_reward as f64).abs() / expected_reward as f64 <= TOLERANCE);
875
876        // Increasing the time since last block based on `CurrentNetwork::BLOCK_TIME` will proportionally increase the v2 rewards (up to a certain cap).
877        let longer_time = CurrentNetwork::BLOCK_TIME * 2;
878        let larger_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, longer_time as i64, 0, 0);
879        let expected_reward = EXPECTED_STAKING_REWARD * 2;
880        assert!((larger_reward as f64 - expected_reward as f64).abs() / expected_reward as f64 <= TOLERANCE);
881
882        for _ in 0..10 {
883            // Randomly sample the time factor.
884            let factor = rng.gen_range(1..10);
885
886            // Ensure that scaling the time elapsed down scales the reward down proportionally.
887            let shorter_time = CurrentNetwork::BLOCK_TIME / factor;
888            let time_factor: f64 = CurrentNetwork::BLOCK_TIME as f64 / shorter_time as f64;
889            let smaller_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, shorter_time as i64, 0, 0);
890            let expected_reward = (EXPECTED_STAKING_REWARD as f64 / time_factor) as u64;
891            assert!((smaller_reward as f64 - expected_reward as f64).abs() / expected_reward as f64 <= TOLERANCE);
892
893            // Ensure that scaling the time elapsed up scales the reward up proportionally (up to a certain cap).
894            let longer_time = CurrentNetwork::BLOCK_TIME * factor;
895            let time_factor: f64 = longer_time as f64 / CurrentNetwork::BLOCK_TIME as f64;
896            let larger_reward = block_reward_v2(CurrentNetwork::STARTING_SUPPLY, longer_time as i64, 0, 0);
897            let expected_reward = (EXPECTED_STAKING_REWARD as f64 * time_factor) as u64;
898            match longer_time as i64 > V2_MAX_BLOCK_INTERVAL {
899                true => assert_eq!(larger_reward, EXPECTED_MAX_STAKING_REWARD),
900                false => {
901                    assert!((larger_reward as f64 - expected_reward as f64).abs() / expected_reward as f64 <= TOLERANCE)
902                }
903            }
904        }
905    }
906
907    #[test]
908    fn test_coinbase_reward() {
909        let mut rng = TestRng::default();
910
911        // Ensure that a block height of `TestnetV0::CONSENSUS_HEIGHT(ConsensusVersion::V2).unwrap()` uses coinbase reward V2.
912        let block_timestamp = TestnetV0::GENESIS_TIMESTAMP.saturating_add(
913            TestnetV0::CONSENSUS_HEIGHT(ConsensusVersion::V2).unwrap().saturating_mul(TestnetV0::BLOCK_TIME as u32)
914                as i64,
915        );
916        let reward = coinbase_reward::<TestnetV0>(
917            TestnetV0::CONSENSUS_HEIGHT(ConsensusVersion::V2).unwrap(),
918            block_timestamp,
919            TestnetV0::GENESIS_TIMESTAMP,
920            TestnetV0::STARTING_SUPPLY,
921            TestnetV0::ANCHOR_TIME,
922            TestnetV0::ANCHOR_HEIGHT,
923            TestnetV0::BLOCK_TIME,
924            1,
925            0,
926            1,
927        )
928        .unwrap();
929        let expected_reward = coinbase_reward_v2(
930            block_timestamp,
931            TestnetV0::GENESIS_TIMESTAMP,
932            TestnetV0::STARTING_SUPPLY,
933            TestnetV0::ANCHOR_TIME,
934            1,
935            0,
936            1,
937        )
938        .unwrap();
939        assert_eq!(reward, expected_reward);
940
941        for _ in 0..100 {
942            // Check that the block reward is correct for the first consensus version.
943            let consensus_v1_height = rng.gen_range(0..TestnetV0::CONSENSUS_HEIGHT(ConsensusVersion::V2).unwrap());
944            let block_timestamp = TestnetV0::GENESIS_TIMESTAMP
945                .saturating_add(consensus_v1_height.saturating_mul(TestnetV0::BLOCK_TIME as u32) as i64);
946            let consensus_v1_reward = coinbase_reward::<TestnetV0>(
947                consensus_v1_height,
948                block_timestamp,
949                TestnetV0::GENESIS_TIMESTAMP,
950                TestnetV0::STARTING_SUPPLY,
951                TestnetV0::ANCHOR_TIME,
952                TestnetV0::ANCHOR_HEIGHT,
953                TestnetV0::BLOCK_TIME,
954                1,
955                0,
956                1,
957            )
958            .unwrap();
959            let expected_reward = coinbase_reward_v1(
960                consensus_v1_height,
961                TestnetV0::STARTING_SUPPLY,
962                TestnetV0::ANCHOR_HEIGHT,
963                TestnetV0::BLOCK_TIME,
964                1,
965                0,
966                1,
967            )
968            .unwrap();
969            assert_eq!(consensus_v1_reward, expected_reward);
970
971            // Check that the block reward is correct for the second consensus version.
972            let consensus_v2_height =
973                rng.gen_range(TestnetV0::CONSENSUS_HEIGHT(ConsensusVersion::V2).unwrap()..u32::MAX);
974            let block_timestamp = TestnetV0::GENESIS_TIMESTAMP
975                .saturating_add(consensus_v2_height.saturating_mul(TestnetV0::BLOCK_TIME as u32) as i64);
976            let consensus_v2_reward = coinbase_reward::<TestnetV0>(
977                consensus_v2_height,
978                block_timestamp,
979                TestnetV0::GENESIS_TIMESTAMP,
980                TestnetV0::STARTING_SUPPLY,
981                TestnetV0::ANCHOR_TIME,
982                TestnetV0::ANCHOR_HEIGHT,
983                TestnetV0::BLOCK_TIME,
984                1,
985                0,
986                1,
987            )
988            .unwrap();
989            let expected_reward = coinbase_reward_v2(
990                block_timestamp,
991                TestnetV0::GENESIS_TIMESTAMP,
992                TestnetV0::STARTING_SUPPLY,
993                TestnetV0::ANCHOR_TIME,
994                1,
995                0,
996                1,
997            )
998            .unwrap();
999            assert_eq!(consensus_v2_reward, expected_reward);
1000        }
1001    }
1002
1003    #[test]
1004    fn test_coinbase_reward_v1() {
1005        let coinbase_target: u64 = 10000;
1006        let combined_proof_target: u128 = coinbase_target as u128;
1007
1008        let reward = coinbase_reward_v1(
1009            1,
1010            CurrentNetwork::STARTING_SUPPLY,
1011            CurrentNetwork::ANCHOR_HEIGHT,
1012            CurrentNetwork::BLOCK_TIME,
1013            combined_proof_target,
1014            0,
1015            coinbase_target,
1016        )
1017        .unwrap();
1018        assert_eq!(reward, EXPECTED_COINBASE_REWARD_AT_BLOCK_1);
1019
1020        // Halving the combined proof target halves the reward.
1021        let smaller_reward = coinbase_reward_v1(
1022            1,
1023            CurrentNetwork::STARTING_SUPPLY,
1024            CurrentNetwork::ANCHOR_HEIGHT,
1025            CurrentNetwork::BLOCK_TIME,
1026            combined_proof_target / 2,
1027            0,
1028            coinbase_target,
1029        )
1030        .unwrap();
1031        assert_eq!(smaller_reward, reward / 2);
1032
1033        // Halving the remaining coinbase target halves the reward.
1034        let smaller_reward = coinbase_reward_v1(
1035            1,
1036            CurrentNetwork::STARTING_SUPPLY,
1037            CurrentNetwork::ANCHOR_HEIGHT,
1038            CurrentNetwork::BLOCK_TIME,
1039            combined_proof_target,
1040            coinbase_target / 2,
1041            coinbase_target,
1042        )
1043        .unwrap();
1044        assert_eq!(smaller_reward, reward / 2);
1045
1046        // Dramatically increasing the combined proof target greater than the remaining coinbase target will not increase the reward.
1047        let equivalent_reward = coinbase_reward_v1(
1048            1,
1049            CurrentNetwork::STARTING_SUPPLY,
1050            CurrentNetwork::ANCHOR_HEIGHT,
1051            CurrentNetwork::BLOCK_TIME,
1052            u128::MAX,
1053            0,
1054            coinbase_target,
1055        )
1056        .unwrap();
1057        assert_eq!(reward, equivalent_reward);
1058
1059        // Decreasing the combined proof target to 0 will result in a reward of 0.
1060        let zero_reward = coinbase_reward_v1(
1061            1,
1062            CurrentNetwork::STARTING_SUPPLY,
1063            CurrentNetwork::ANCHOR_HEIGHT,
1064            CurrentNetwork::BLOCK_TIME,
1065            0,
1066            0,
1067            coinbase_target,
1068        )
1069        .unwrap();
1070        assert_eq!(zero_reward, 0);
1071
1072        // Increasing the cumulative proof target beyond the coinbase target will result in a reward of 0.
1073        let zero_reward = coinbase_reward_v1(
1074            1,
1075            CurrentNetwork::STARTING_SUPPLY,
1076            CurrentNetwork::ANCHOR_HEIGHT,
1077            CurrentNetwork::BLOCK_TIME,
1078            1,
1079            coinbase_target + 1,
1080            coinbase_target,
1081        )
1082        .unwrap();
1083        assert_eq!(zero_reward, 0);
1084    }
1085
1086    #[test]
1087    fn test_coinbase_reward_v2() {
1088        let coinbase_target: u64 = 10000;
1089        let combined_proof_target: u128 = coinbase_target as u128;
1090
1091        let reward = coinbase_reward_v2(
1092            CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
1093            CurrentNetwork::GENESIS_TIMESTAMP,
1094            CurrentNetwork::STARTING_SUPPLY,
1095            CurrentNetwork::ANCHOR_TIME,
1096            combined_proof_target,
1097            0,
1098            coinbase_target,
1099        )
1100        .unwrap();
1101        assert_eq!(reward, EXPECTED_COINBASE_REWARD_AT_BLOCK_1);
1102
1103        // Halving the combined proof target halves the reward.
1104        let smaller_reward = coinbase_reward_v2(
1105            CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
1106            CurrentNetwork::GENESIS_TIMESTAMP,
1107            CurrentNetwork::STARTING_SUPPLY,
1108            CurrentNetwork::ANCHOR_TIME,
1109            combined_proof_target / 2,
1110            0,
1111            coinbase_target,
1112        )
1113        .unwrap();
1114        assert_eq!(smaller_reward, reward / 2);
1115
1116        // Halving the remaining coinbase target halves the reward.
1117        let smaller_reward = coinbase_reward_v2(
1118            CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
1119            CurrentNetwork::GENESIS_TIMESTAMP,
1120            CurrentNetwork::STARTING_SUPPLY,
1121            CurrentNetwork::ANCHOR_TIME,
1122            combined_proof_target,
1123            coinbase_target / 2,
1124            coinbase_target,
1125        )
1126        .unwrap();
1127        assert_eq!(smaller_reward, reward / 2);
1128
1129        // Dramatically increasing the combined proof target greater than the remaining coinbase target will not increase the reward.
1130        let equivalent_reward = coinbase_reward_v2(
1131            CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
1132            CurrentNetwork::GENESIS_TIMESTAMP,
1133            CurrentNetwork::STARTING_SUPPLY,
1134            CurrentNetwork::ANCHOR_TIME,
1135            u128::MAX,
1136            0,
1137            coinbase_target,
1138        )
1139        .unwrap();
1140        assert_eq!(reward, equivalent_reward);
1141
1142        // Decreasing the combined proof target to 0 will result in a reward of 0.
1143        let zero_reward = coinbase_reward_v2(
1144            CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
1145            CurrentNetwork::GENESIS_TIMESTAMP,
1146            CurrentNetwork::STARTING_SUPPLY,
1147            CurrentNetwork::ANCHOR_TIME,
1148            0,
1149            0,
1150            coinbase_target,
1151        )
1152        .unwrap();
1153        assert_eq!(zero_reward, 0);
1154
1155        // Increasing the cumulative proof target beyond the coinbase target will result in a reward of 0.
1156        let zero_reward = coinbase_reward_v2(
1157            CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
1158            CurrentNetwork::GENESIS_TIMESTAMP,
1159            CurrentNetwork::STARTING_SUPPLY,
1160            CurrentNetwork::ANCHOR_TIME,
1161            1,
1162            coinbase_target + 1,
1163            coinbase_target,
1164        )
1165        .unwrap();
1166        assert_eq!(zero_reward, 0);
1167    }
1168
1169    #[test]
1170    fn test_coinbase_reward_v1_remaining_target() {
1171        let mut rng = TestRng::default();
1172
1173        fn compute_coinbase_reward(
1174            combined_proof_target: u64,
1175            cumulative_proof_target: u64,
1176            coinbase_target: u64,
1177        ) -> u64 {
1178            coinbase_reward_v1(
1179                1,
1180                CurrentNetwork::STARTING_SUPPLY,
1181                CurrentNetwork::ANCHOR_HEIGHT,
1182                CurrentNetwork::BLOCK_TIME,
1183                combined_proof_target as u128,
1184                cumulative_proof_target,
1185                coinbase_target,
1186            )
1187            .unwrap()
1188        }
1189
1190        // Sample the starting conditions.
1191        let coinbase_target: u64 = rng.gen_range(1_000_000..1_000_000_000_000_000);
1192        let cumulative_proof_target = coinbase_target / 2;
1193        let combined_proof_target = coinbase_target / 4;
1194        let reward = compute_coinbase_reward(combined_proof_target, cumulative_proof_target, coinbase_target);
1195
1196        for _ in 0..ITERATIONS {
1197            // Check that as long as the sum of the combined proof target and cumulative proof target is less than the coinbase target,
1198            // the reward remains the same.
1199            // Intuition: Staying below the coinbase target preserves the reward for the combined proof target.
1200            let equivalent_reward = compute_coinbase_reward(
1201                combined_proof_target,
1202                rng.gen_range(0..(coinbase_target - combined_proof_target)),
1203                coinbase_target,
1204            );
1205            assert_eq!(reward, equivalent_reward);
1206
1207            // Check that increasing the cumulative proof target to devalue the combined proof target will decrease the reward.
1208            // Intuition: Overflowing the coinbase target crowds out the combined proof target, leading to less reward for the combined proof target.
1209            let lower_reward = compute_coinbase_reward(
1210                combined_proof_target,
1211                rng.gen_range((coinbase_target - combined_proof_target + 1)..coinbase_target),
1212                coinbase_target,
1213            );
1214            assert!(lower_reward < reward);
1215
1216            // Check that increasing the combined proof target increases the reward.
1217            // Intuition: If a prover contributes more proof target, they should be rewarded more.
1218            let larger_reward = compute_coinbase_reward(
1219                rng.gen_range(combined_proof_target + 1..u64::MAX),
1220                cumulative_proof_target,
1221                coinbase_target,
1222            );
1223            assert!(reward < larger_reward);
1224        }
1225    }
1226
1227    #[test]
1228    fn test_coinbase_reward_v2_remaining_target() {
1229        let mut rng = TestRng::default();
1230
1231        fn compute_coinbase_reward(
1232            combined_proof_target: u64,
1233            cumulative_proof_target: u64,
1234            coinbase_target: u64,
1235        ) -> u64 {
1236            coinbase_reward_v2(
1237                CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
1238                CurrentNetwork::GENESIS_TIMESTAMP,
1239                CurrentNetwork::STARTING_SUPPLY,
1240                CurrentNetwork::ANCHOR_TIME,
1241                combined_proof_target as u128,
1242                cumulative_proof_target,
1243                coinbase_target,
1244            )
1245            .unwrap()
1246        }
1247
1248        // Sample the starting conditions.
1249        let coinbase_target: u64 = rng.gen_range(1_000_000..1_000_000_000_000_000);
1250        let cumulative_proof_target = coinbase_target / 2;
1251        let combined_proof_target = coinbase_target / 4;
1252        let reward = compute_coinbase_reward(combined_proof_target, cumulative_proof_target, coinbase_target);
1253
1254        for _ in 0..ITERATIONS {
1255            // Check that as long as the sum of the combined proof target and cumulative proof target is less than the coinbase target,
1256            // the reward remains the same.
1257            // Intuition: Staying below the coinbase target preserves the reward for the combined proof target.
1258            let equivalent_reward = compute_coinbase_reward(
1259                combined_proof_target,
1260                rng.gen_range(0..(coinbase_target - combined_proof_target)),
1261                coinbase_target,
1262            );
1263            assert_eq!(reward, equivalent_reward);
1264
1265            // Check that increasing the cumulative proof target to devalue the combined proof target will decrease the reward.
1266            // Intuition: Overflowing the coinbase target crowds out the combined proof target, leading to less reward for the combined proof target.
1267            let lower_reward = compute_coinbase_reward(
1268                combined_proof_target,
1269                rng.gen_range((coinbase_target - combined_proof_target + 1)..coinbase_target),
1270                coinbase_target,
1271            );
1272            assert!(lower_reward < reward);
1273
1274            // Check that increasing the combined proof target increases the reward.
1275            // Intuition: If a prover contributes more proof target, they should be rewarded more.
1276            let larger_reward = compute_coinbase_reward(
1277                rng.gen_range(combined_proof_target + 1..u64::MAX),
1278                cumulative_proof_target,
1279                coinbase_target,
1280            );
1281            assert!(reward < larger_reward);
1282        }
1283    }
1284
1285    #[test]
1286    fn test_coinbase_reward_v1_up_to_year_10() {
1287        let block_height_at_year_10 = block_height_at_year(CurrentNetwork::BLOCK_TIME, 10);
1288
1289        let mut block_height = 1;
1290
1291        let mut previous_reward = coinbase_reward_v1(
1292            block_height,
1293            CurrentNetwork::STARTING_SUPPLY,
1294            CurrentNetwork::ANCHOR_HEIGHT,
1295            CurrentNetwork::BLOCK_TIME,
1296            1,
1297            0,
1298            1,
1299        )
1300        .unwrap();
1301
1302        block_height += 1;
1303
1304        let mut total_reward = previous_reward;
1305
1306        let coinbase_target = CurrentNetwork::ANCHOR_HEIGHT as u64;
1307        let mut cumulative_proof_target = 0;
1308
1309        let mut hit_500m = false;
1310        let mut hit_1b = false;
1311
1312        while block_height < block_height_at_year_10 {
1313            let reward = coinbase_reward_v1(
1314                block_height,
1315                CurrentNetwork::STARTING_SUPPLY,
1316                CurrentNetwork::ANCHOR_HEIGHT,
1317                CurrentNetwork::BLOCK_TIME,
1318                1,
1319                cumulative_proof_target,
1320                coinbase_target,
1321            )
1322            .unwrap();
1323            assert!(reward <= previous_reward);
1324
1325            total_reward += reward;
1326            previous_reward = reward;
1327            block_height += 1;
1328
1329            // Update the cumulative proof target.
1330            cumulative_proof_target = match cumulative_proof_target + 1 {
1331                cumulative_proof_target if cumulative_proof_target == coinbase_target => 0,
1332                cumulative_proof_target => cumulative_proof_target,
1333            };
1334
1335            if !hit_500m && total_reward > 500_000_000_000_000 {
1336                println!("500M credits block height is {block_height}");
1337                assert_eq!(block_height, 5_786_964, "Update me if my parameters have changed");
1338                hit_500m = true;
1339            } else if !hit_1b && total_reward > 1_000_000_000_000_000 {
1340                println!("1B credits block height is {block_height}");
1341                assert_eq!(block_height, 13_328_683, "Update me if my parameters have changed");
1342                hit_1b = true;
1343            }
1344        }
1345
1346        assert_eq!(total_reward, 1_514_999_979_651_171, "Update me if my parameters have changed");
1347    }
1348
1349    #[test]
1350    fn test_coinbase_reward_v2_up_to_year_10() {
1351        let block_height_at_year_10 = timestamp_at_year(CurrentNetwork::GENESIS_TIMESTAMP, 10);
1352
1353        let mut timestamp = CurrentNetwork::GENESIS_TIMESTAMP;
1354
1355        let mut previous_reward = coinbase_reward_v2(
1356            CurrentNetwork::GENESIS_TIMESTAMP + CurrentNetwork::BLOCK_TIME as i64,
1357            CurrentNetwork::GENESIS_TIMESTAMP,
1358            CurrentNetwork::STARTING_SUPPLY,
1359            CurrentNetwork::ANCHOR_TIME,
1360            1,
1361            0,
1362            1,
1363        )
1364        .unwrap();
1365
1366        timestamp += CurrentNetwork::BLOCK_TIME as i64;
1367
1368        let mut total_reward = previous_reward;
1369
1370        let coinbase_target = CurrentNetwork::ANCHOR_HEIGHT as u64;
1371        let mut cumulative_proof_target = 0;
1372
1373        let mut hit_500m = false;
1374        let mut hit_1b = false;
1375
1376        while timestamp < block_height_at_year_10 {
1377            let reward = coinbase_reward_v2(
1378                timestamp,
1379                CurrentNetwork::GENESIS_TIMESTAMP,
1380                CurrentNetwork::STARTING_SUPPLY,
1381                CurrentNetwork::ANCHOR_TIME,
1382                1,
1383                cumulative_proof_target,
1384                coinbase_target,
1385            )
1386            .unwrap();
1387            assert!(reward <= previous_reward);
1388
1389            total_reward += reward;
1390            previous_reward = reward;
1391            timestamp += CurrentNetwork::BLOCK_TIME as i64;
1392
1393            // Update the cumulative proof target.
1394            cumulative_proof_target = match cumulative_proof_target + 1 {
1395                cumulative_proof_target if cumulative_proof_target == coinbase_target => 0,
1396                cumulative_proof_target => cumulative_proof_target,
1397            };
1398
1399            if !hit_500m && total_reward > 500_000_000_000_000 {
1400                println!("500M credits block timestamp is {timestamp}");
1401                assert_eq!(timestamp, 1783331630, "Update me if my parameters have changed");
1402                hit_500m = true;
1403            } else if !hit_1b && total_reward > 1_000_000_000_000_000 {
1404                println!("1B credits block timestamp is {timestamp}");
1405                assert_eq!(timestamp, 1858748810, "Update me if my parameters have changed");
1406                hit_1b = true;
1407            }
1408        }
1409
1410        assert_eq!(total_reward, 1_515_000_074_780_540, "Update me if my parameters have changed");
1411    }
1412
1413    #[test]
1414    fn test_coinbase_reward_v1_after_year_10() {
1415        let mut rng = TestRng::default();
1416
1417        let block_height_at_year_10 = block_height_at_year(CurrentNetwork::BLOCK_TIME, 10);
1418
1419        // Check that the block at year 10 has a reward of 19.
1420        let reward = coinbase_reward_v1(
1421            block_height_at_year_10,
1422            CurrentNetwork::STARTING_SUPPLY,
1423            CurrentNetwork::ANCHOR_HEIGHT,
1424            CurrentNetwork::BLOCK_TIME,
1425            1,
1426            0,
1427            1,
1428        )
1429        .unwrap();
1430        assert_eq!(reward, 19_025_874);
1431
1432        // Check that the subsequent blocks have an anchor reward of 19 and reward less than or equal to 19.
1433        for _ in 0..ITERATIONS {
1434            let block_height: u32 = rng.gen_range(block_height_at_year_10..block_height_at_year_10 * 10);
1435            let coinbase_target = rng.gen_range(1_000_000..1_000_000_000_000_000);
1436            let cumulative_proof_target = rng.gen_range(0..coinbase_target);
1437            let combined_proof_target = rng.gen_range(0..coinbase_target as u128);
1438
1439            let anchor_reward = anchor_block_reward_at_height(
1440                block_height,
1441                CurrentNetwork::STARTING_SUPPLY,
1442                CurrentNetwork::ANCHOR_HEIGHT,
1443                CurrentNetwork::BLOCK_TIME,
1444            );
1445            assert_eq!(anchor_reward, 19_025_874);
1446
1447            let reward = coinbase_reward_v1(
1448                block_height,
1449                CurrentNetwork::STARTING_SUPPLY,
1450                CurrentNetwork::ANCHOR_HEIGHT,
1451                CurrentNetwork::BLOCK_TIME,
1452                combined_proof_target,
1453                cumulative_proof_target,
1454                coinbase_target,
1455            )
1456            .unwrap();
1457            assert!(reward <= 19_025_874);
1458        }
1459    }
1460
1461    #[test]
1462    fn test_coinbase_reward_v2_after_year_10() {
1463        let mut rng = TestRng::default();
1464
1465        let timestamp_at_year_10 = timestamp_at_year(CurrentNetwork::GENESIS_TIMESTAMP, 10);
1466
1467        // Check that the block at year 10 has a reward of 19.
1468        let reward = coinbase_reward_v2(
1469            timestamp_at_year_10,
1470            CurrentNetwork::GENESIS_TIMESTAMP,
1471            CurrentNetwork::STARTING_SUPPLY,
1472            CurrentNetwork::ANCHOR_TIME,
1473            1,
1474            0,
1475            1,
1476        )
1477        .unwrap();
1478        assert_eq!(reward, 19_025_874);
1479
1480        // Check that the subsequent blocks have an anchor reward of 19 and reward less than or equal to 19.
1481        for _ in 0..ITERATIONS {
1482            let timestamp: i64 = rng.gen_range(timestamp_at_year_10..timestamp_at_year_10 * 10);
1483            let coinbase_target = rng.gen_range(1_000_000..1_000_000_000_000_000);
1484            let cumulative_proof_target = rng.gen_range(0..coinbase_target);
1485            let combined_proof_target = rng.gen_range(0..coinbase_target as u128);
1486
1487            let anchor_reward = anchor_block_reward_at_timestamp(
1488                timestamp,
1489                CurrentNetwork::GENESIS_TIMESTAMP,
1490                CurrentNetwork::STARTING_SUPPLY,
1491                CurrentNetwork::ANCHOR_TIME,
1492            );
1493            assert_eq!(anchor_reward, 19_025_874);
1494
1495            let reward = coinbase_reward_v2(
1496                timestamp,
1497                CurrentNetwork::GENESIS_TIMESTAMP,
1498                CurrentNetwork::STARTING_SUPPLY,
1499                CurrentNetwork::ANCHOR_TIME,
1500                combined_proof_target,
1501                cumulative_proof_target,
1502                coinbase_target,
1503            )
1504            .unwrap();
1505            assert!(reward <= 19_025_874);
1506        }
1507    }
1508
1509    #[test]
1510    fn test_targets() {
1511        let mut rng = TestRng::default();
1512
1513        let minimum_coinbase_target: u64 = 2u64.pow(10) - 1;
1514
1515        fn test_new_targets(rng: &mut TestRng, minimum_coinbase_target: u64) {
1516            let previous_coinbase_target: u64 = rng.gen_range(minimum_coinbase_target..u64::MAX);
1517            let previous_prover_target = proof_target(
1518                previous_coinbase_target,
1519                CurrentNetwork::GENESIS_PROOF_TARGET,
1520                CurrentNetwork::MAX_SOLUTIONS_AS_POWER_OF_TWO,
1521            );
1522
1523            let previous_timestamp = rng.gen();
1524
1525            // Targets stay the same when the drift is as expected.
1526            let next_timestamp = previous_timestamp + CurrentNetwork::ANCHOR_TIME as i64;
1527            let new_coinbase_target = coinbase_target(
1528                previous_coinbase_target,
1529                previous_timestamp,
1530                next_timestamp,
1531                CurrentNetwork::ANCHOR_TIME,
1532                CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
1533                CurrentNetwork::GENESIS_COINBASE_TARGET,
1534            )
1535            .unwrap();
1536            let new_prover_target = proof_target(
1537                new_coinbase_target,
1538                CurrentNetwork::GENESIS_PROOF_TARGET,
1539                CurrentNetwork::MAX_SOLUTIONS_AS_POWER_OF_TWO,
1540            );
1541            assert_eq!(new_coinbase_target, previous_coinbase_target);
1542            assert_eq!(new_prover_target, previous_prover_target);
1543
1544            // Targets decrease (easier) when the drift is greater than expected.
1545            let new_timestamp = previous_timestamp + 2 * CurrentNetwork::ANCHOR_TIME as i64;
1546            let new_coinbase_target = coinbase_target(
1547                previous_coinbase_target,
1548                previous_timestamp,
1549                new_timestamp,
1550                CurrentNetwork::ANCHOR_TIME,
1551                CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
1552                CurrentNetwork::GENESIS_COINBASE_TARGET,
1553            )
1554            .unwrap();
1555            let new_prover_target = proof_target(
1556                new_coinbase_target,
1557                CurrentNetwork::GENESIS_PROOF_TARGET,
1558                CurrentNetwork::MAX_SOLUTIONS_AS_POWER_OF_TWO,
1559            );
1560            assert!(new_coinbase_target < previous_coinbase_target);
1561            assert!(new_prover_target < previous_prover_target);
1562
1563            // Targets increase (harder) when the drift is less than expected.
1564            let next_timestamp = previous_timestamp + (CurrentNetwork::ANCHOR_TIME / 2) as i64;
1565            let new_coinbase_target = coinbase_target(
1566                previous_coinbase_target,
1567                previous_timestamp,
1568                next_timestamp,
1569                CurrentNetwork::ANCHOR_TIME,
1570                CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
1571                CurrentNetwork::GENESIS_COINBASE_TARGET,
1572            )
1573            .unwrap();
1574            let new_prover_target = proof_target(
1575                new_coinbase_target,
1576                CurrentNetwork::GENESIS_PROOF_TARGET,
1577                CurrentNetwork::MAX_SOLUTIONS_AS_POWER_OF_TWO,
1578            );
1579
1580            assert!(new_coinbase_target > previous_coinbase_target);
1581            assert!(new_prover_target > previous_prover_target);
1582        }
1583
1584        for _ in 0..ITERATIONS {
1585            test_new_targets(&mut rng, minimum_coinbase_target);
1586        }
1587    }
1588
1589    #[test]
1590    fn test_target_halving() {
1591        let mut rng = TestRng::default();
1592
1593        let minimum_coinbase_target: u64 = 2u64.pow(10) - 1;
1594
1595        for _ in 0..ITERATIONS {
1596            let previous_coinbase_target: u64 = rng.gen_range(minimum_coinbase_target..u64::MAX);
1597            let previous_timestamp = rng.gen();
1598
1599            let half_life = CurrentNetwork::NUM_BLOCKS_PER_EPOCH
1600                .saturating_div(2)
1601                .saturating_mul(CurrentNetwork::ANCHOR_TIME as u32) as i64;
1602
1603            // New coinbase target is greater than half if the drift equals the half life.
1604            let next_timestamp = previous_timestamp + half_life;
1605            let next_coinbase_target = coinbase_target(
1606                previous_coinbase_target,
1607                previous_timestamp,
1608                next_timestamp,
1609                CurrentNetwork::ANCHOR_TIME,
1610                CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
1611                CurrentNetwork::GENESIS_COINBASE_TARGET,
1612            )
1613            .unwrap();
1614
1615            assert!(next_coinbase_target > previous_coinbase_target / 2);
1616
1617            // New coinbase target is halved if the drift is 1 anchor height past the half life.
1618            let next_timestamp = previous_timestamp + half_life + CurrentNetwork::ANCHOR_TIME as i64;
1619            let next_coinbase_target = coinbase_target(
1620                previous_coinbase_target,
1621                previous_timestamp,
1622                next_timestamp,
1623                CurrentNetwork::ANCHOR_TIME,
1624                CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
1625                CurrentNetwork::GENESIS_COINBASE_TARGET,
1626            )
1627            .unwrap();
1628
1629            assert_eq!(next_coinbase_target, previous_coinbase_target / 2);
1630
1631            // New coinbase target is less than half if the drift is more than 1 anchor height past the half life.
1632            let next_timestamp = previous_timestamp + half_life + 2 * CurrentNetwork::ANCHOR_TIME as i64;
1633            let next_coinbase_target = coinbase_target(
1634                previous_coinbase_target,
1635                previous_timestamp,
1636                next_timestamp,
1637                CurrentNetwork::ANCHOR_TIME,
1638                CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
1639                CurrentNetwork::GENESIS_COINBASE_TARGET,
1640            )
1641            .unwrap();
1642
1643            assert!(next_coinbase_target < previous_coinbase_target / 2);
1644        }
1645    }
1646
1647    #[test]
1648    fn test_target_doubling() {
1649        let mut rng = TestRng::default();
1650
1651        // The custom block height drift that is faster than the anchor time.
1652        const ANCHOR_TIME_DELTA: i64 = 15;
1653        // The expected number of blocks before the coinbase target is doubled.
1654        const EXPECTED_NUM_BLOCKS_TO_DOUBLE: u32 = 451;
1655
1656        let minimum_coinbase_target: u64 = 2u64.pow(10) - 1;
1657
1658        let initial_coinbase_target: u64 = rng.gen_range(minimum_coinbase_target..u64::MAX / 2);
1659        let initial_timestamp: i64 = rng.gen();
1660
1661        let mut previous_coinbase_target: u64 = initial_coinbase_target;
1662        let mut previous_timestamp = initial_timestamp;
1663        let mut num_blocks = 0;
1664
1665        while previous_coinbase_target < initial_coinbase_target * 2 {
1666            // Targets increase (harder) when the timestamp is less than expected.
1667            let next_timestamp = previous_timestamp + ANCHOR_TIME_DELTA;
1668            let next_coinbase_target = coinbase_target(
1669                previous_coinbase_target,
1670                previous_timestamp,
1671                next_timestamp,
1672                CurrentNetwork::ANCHOR_TIME,
1673                CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
1674                CurrentNetwork::GENESIS_COINBASE_TARGET,
1675            )
1676            .unwrap();
1677
1678            assert!(next_coinbase_target > previous_coinbase_target);
1679
1680            previous_coinbase_target = next_coinbase_target;
1681            previous_timestamp = next_timestamp;
1682            num_blocks += 1;
1683        }
1684
1685        let seconds = previous_timestamp - initial_timestamp;
1686        println!(
1687            "For drifts of {ANCHOR_TIME_DELTA} seconds and epochs of {} blocks, doubling the coinbase target took {num_blocks} blocks. ({seconds} seconds)",
1688            CurrentNetwork::NUM_BLOCKS_PER_EPOCH,
1689        );
1690
1691        assert_eq!(EXPECTED_NUM_BLOCKS_TO_DOUBLE, num_blocks);
1692    }
1693
1694    #[test]
1695    fn test_to_next_targets_meets_threshold() {
1696        let mut rng = TestRng::default();
1697
1698        let minimum_coinbase_target: u64 = 2u64.pow(10) - 1;
1699
1700        for _ in 0..ITERATIONS {
1701            // Sample the initial values.
1702            let latest_coinbase_target = rng.gen_range(minimum_coinbase_target..u64::MAX / 2);
1703            let threshold = latest_coinbase_target as u128 / 2;
1704            let last_coinbase_target = rng.gen_range(minimum_coinbase_target..latest_coinbase_target);
1705            let last_coinbase_timestamp = rng.gen_range(0..i64::MAX / 2);
1706            let next_timestamp = last_coinbase_timestamp + 100;
1707            let latest_cumulative_weight = rng.gen_range(0..u128::MAX / 2);
1708
1709            // Sample a cumulative proof target and combined proof target pair that meets the threshold.
1710            let latest_cumulative_proof_target = rng.gen_range(0..threshold);
1711            let combined_proof_target =
1712                rng.gen_range(threshold.saturating_sub(latest_cumulative_proof_target)..u128::MAX);
1713
1714            assert!(latest_cumulative_proof_target.saturating_add(combined_proof_target) >= threshold);
1715
1716            // Calculate the next targets.
1717            let (
1718                _,
1719                _,
1720                next_cumulative_proof_target,
1721                next_cumulative_weight,
1722                next_last_coinbase_target,
1723                next_last_coinbase_timestamp,
1724            ) = to_next_targets::<CurrentNetwork>(
1725                latest_cumulative_proof_target,
1726                combined_proof_target,
1727                latest_coinbase_target,
1728                latest_cumulative_weight,
1729                last_coinbase_target,
1730                last_coinbase_timestamp,
1731                next_timestamp,
1732            )
1733            .unwrap();
1734
1735            // Check that meeting the target threshold does the following:
1736            // 1. Resets the next_cumulative_proof_target.
1737            // 2. Updates the last_coinbase_target.
1738            // 3. Updates the last_coinbase_timestamp.
1739            assert_eq!(next_cumulative_proof_target, 0);
1740            assert_ne!(next_last_coinbase_target, last_coinbase_target);
1741            assert_eq!(next_last_coinbase_timestamp, next_timestamp);
1742
1743            // Check that the cumulative_weight is updated correctly.
1744            assert_eq!(next_cumulative_weight, latest_cumulative_weight.saturating_add(combined_proof_target));
1745        }
1746    }
1747
1748    #[test]
1749    fn test_to_next_targets_does_not_meet_threshold() {
1750        let mut rng = TestRng::default();
1751
1752        let minimum_coinbase_target: u64 = 2u64.pow(10) - 1;
1753
1754        for _ in 0..ITERATIONS {
1755            // Sample the initial values.
1756            let latest_coinbase_target = rng.gen_range(minimum_coinbase_target..u64::MAX / 2);
1757            let threshold = latest_coinbase_target as u128 / 2;
1758            let last_coinbase_target = rng.gen_range(minimum_coinbase_target..latest_coinbase_target);
1759            let last_coinbase_timestamp = rng.gen_range(0..i64::MAX / 2);
1760            let next_timestamp = last_coinbase_timestamp + 100;
1761            let latest_cumulative_weight = rng.gen_range(0..u128::MAX / 2);
1762
1763            // Sample a cumulative proof target and combined proof target pair that meets the threshold.
1764            let latest_cumulative_proof_target = rng.gen_range(0..threshold);
1765            let combined_proof_target = rng.gen_range(0..threshold.saturating_sub(latest_cumulative_proof_target));
1766
1767            assert!(latest_cumulative_proof_target.saturating_add(combined_proof_target) < threshold);
1768
1769            // Calculate the next targets.
1770            let (
1771                _,
1772                _,
1773                next_cumulative_proof_target,
1774                next_cumulative_weight,
1775                next_last_coinbase_target,
1776                next_last_coinbase_timestamp,
1777            ) = to_next_targets::<CurrentNetwork>(
1778                latest_cumulative_proof_target,
1779                combined_proof_target,
1780                latest_coinbase_target,
1781                latest_cumulative_weight,
1782                last_coinbase_target,
1783                last_coinbase_timestamp,
1784                next_timestamp,
1785            )
1786            .unwrap();
1787
1788            // Check that missing the target threshold does the following:
1789            // 1. Does not reset the next_cumulative_proof_target.
1790            // 2. Does not update the last_coinbase_target.
1791            // 3. Does not update the last_coinbase_timestamp.
1792            assert_eq!(next_cumulative_proof_target, latest_cumulative_proof_target + combined_proof_target);
1793            assert_eq!(next_last_coinbase_target, last_coinbase_target);
1794            assert_eq!(next_last_coinbase_timestamp, last_coinbase_timestamp);
1795
1796            // Check that the cumulative_weight is updated correctly.
1797            assert_eq!(next_cumulative_weight, latest_cumulative_weight.saturating_add(combined_proof_target));
1798        }
1799    }
1800}