snarkvm_ledger_block/helpers/
target.rs

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