@@ -29,6 +29,7 @@ const V2_MIN_BLOCK_INTERVAL: i64 = 1; // 1 second.
2929const SECONDS_IN_A_YEAR : u32 = 60 * 60 * 24 * 365 ;
3030
3131/// Calculate the block reward based on the network’s consensus version, determined by the given block height.
32+ /// If the block height is at or beyond the max supply limit height, the block reward is zero.
3233pub fn block_reward < N : Network > (
3334 block_height : u32 ,
3435 total_supply : u64 ,
@@ -37,6 +38,11 @@ pub fn block_reward<N: Network>(
3738 coinbase_reward : u64 ,
3839 transaction_fees : u64 ,
3940) -> Result < u64 > {
41+ // If the height is at or beyond the max supply limit height, set rewards to zero.
42+ if block_height >= N :: MAX_SUPPLY_LIMIT_HEIGHT {
43+ return Ok ( 0 ) ;
44+ }
45+
4046 // Determine which block reward version to use.
4147 let consensus_version = N :: CONSENSUS_VERSION ( block_height) ?;
4248 match consensus_version == ConsensusVersion :: V1 {
@@ -96,6 +102,7 @@ pub const fn puzzle_reward(coinbase_reward: u64) -> u64 {
96102}
97103
98104/// Calculate the coinbase reward based on the network’s consensus version, determined by the given block height.
105+ /// If the block height is at or beyond the max supply limit height, the coinbase reward is zero.
99106pub fn coinbase_reward < N : Network > (
100107 block_height : u32 ,
101108 block_timestamp : i64 ,
@@ -108,6 +115,11 @@ pub fn coinbase_reward<N: Network>(
108115 cumulative_proof_target : u64 ,
109116 coinbase_target : u64 ,
110117) -> Result < u64 > {
118+ // If the height is at or beyond the max supply limit height, set rewards to zero.
119+ if block_height >= N :: MAX_SUPPLY_LIMIT_HEIGHT {
120+ return Ok ( 0 ) ;
121+ }
122+
111123 // Determine which coinbase reward version to use.
112124 let consensus_version = N :: CONSENSUS_VERSION ( block_height) ?;
113125 if consensus_version == ConsensusVersion :: V1 {
@@ -795,8 +807,9 @@ mod tests {
795807 assert_eq ! ( consensus_v1_reward, expected_reward) ;
796808
797809 // 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 ) ;
810+ let consensus_v2_height = rng. gen_range (
811+ TestnetV0 :: CONSENSUS_HEIGHT ( ConsensusVersion :: V2 ) . unwrap ( ) ..TestnetV0 :: MAX_SUPPLY_LIMIT_HEIGHT ,
812+ ) ;
800813 let time_since_last_block = rng. gen_range ( 1 ..=V2_MAX_BLOCK_INTERVAL ) ;
801814 let consensus_v2_reward = block_reward :: < TestnetV0 > (
802815 consensus_v2_height,
@@ -809,6 +822,19 @@ mod tests {
809822 . unwrap ( ) ;
810823 let expected_reward = block_reward_v2 ( TestnetV0 :: STARTING_SUPPLY , time_since_last_block, 0 , 0 ) ;
811824 assert_eq ! ( consensus_v2_reward, expected_reward) ;
825+
826+ // Check that the block reward is 0 after the max supply limit height.
827+ let after_max_supply_limit_height = rng. gen_range ( TestnetV0 :: MAX_SUPPLY_LIMIT_HEIGHT ..u32:: MAX ) ;
828+ let block_reward = block_reward :: < TestnetV0 > (
829+ after_max_supply_limit_height,
830+ TestnetV0 :: STARTING_SUPPLY ,
831+ TestnetV0 :: BLOCK_TIME ,
832+ time_since_last_block,
833+ 0 ,
834+ 0 ,
835+ )
836+ . unwrap ( ) ;
837+ assert_eq ! ( block_reward, 0 ) ;
812838 }
813839 }
814840
@@ -968,8 +994,9 @@ mod tests {
968994 assert_eq ! ( consensus_v1_reward, expected_reward) ;
969995
970996 // 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 ) ;
997+ let consensus_v2_height = rng. gen_range (
998+ TestnetV0 :: CONSENSUS_HEIGHT ( ConsensusVersion :: V2 ) . unwrap ( ) ..TestnetV0 :: MAX_SUPPLY_LIMIT_HEIGHT ,
999+ ) ;
9731000 let block_timestamp = TestnetV0 :: GENESIS_TIMESTAMP
9741001 . saturating_add ( consensus_v2_height. saturating_mul ( TestnetV0 :: BLOCK_TIME as u32 ) as i64 ) ;
9751002 let consensus_v2_reward = coinbase_reward :: < TestnetV0 > (
@@ -996,6 +1023,23 @@ mod tests {
9961023 )
9971024 . unwrap ( ) ;
9981025 assert_eq ! ( consensus_v2_reward, expected_reward) ;
1026+
1027+ // Check that the coinbase reward is 0 after the max supply limit height.
1028+ let after_max_supply_limit_height = rng. gen_range ( TestnetV0 :: MAX_SUPPLY_LIMIT_HEIGHT ..u32:: MAX ) ;
1029+ let coinbase_reward = coinbase_reward :: < TestnetV0 > (
1030+ after_max_supply_limit_height,
1031+ block_timestamp,
1032+ TestnetV0 :: GENESIS_TIMESTAMP ,
1033+ TestnetV0 :: STARTING_SUPPLY ,
1034+ TestnetV0 :: ANCHOR_TIME ,
1035+ TestnetV0 :: ANCHOR_HEIGHT ,
1036+ TestnetV0 :: BLOCK_TIME ,
1037+ 1 ,
1038+ 0 ,
1039+ 1 ,
1040+ )
1041+ . unwrap ( ) ;
1042+ assert_eq ! ( coinbase_reward, 0 ) ;
9991043 }
10001044 }
10011045
@@ -1505,6 +1549,69 @@ mod tests {
15051549 }
15061550 }
15071551
1552+ fn check_total_supply_cap < N : Network > ( ) {
1553+ const AVG_BLOCK_TIME : i64 = 3 ;
1554+
1555+ let blocks_per_year = block_height_at_year ( AVG_BLOCK_TIME as u16 , 1 ) ;
1556+
1557+ // The tracking state for the simluation
1558+ let mut total_supply = N :: STARTING_SUPPLY ;
1559+ let mut total_block_rewards = 0u64 ;
1560+ let mut total_coinbase_rewards = 0u64 ;
1561+ let mut block_height = 1u32 ;
1562+ let mut latest_timetamp = 0 ;
1563+
1564+ // Iterate until we reach 5 billion credits
1565+ while total_supply < N :: MAX_SUPPLY {
1566+ // Calculate the block reward.
1567+ let block_reward =
1568+ block_reward :: < N > ( block_height, N :: STARTING_SUPPLY , N :: BLOCK_TIME , AVG_BLOCK_TIME , 0 , 0 ) . unwrap ( ) ;
1569+
1570+ // Calculate the coinbase reward.
1571+ let timestamp = N :: GENESIS_TIMESTAMP + ( block_height as i64 * AVG_BLOCK_TIME ) ;
1572+ let coinbase_reward = coinbase_reward :: < N > (
1573+ block_height,
1574+ timestamp,
1575+ N :: GENESIS_TIMESTAMP ,
1576+ N :: STARTING_SUPPLY ,
1577+ N :: ANCHOR_TIME ,
1578+ N :: ANCHOR_HEIGHT ,
1579+ N :: BLOCK_TIME ,
1580+ 1 ,
1581+ 0 ,
1582+ 1 ,
1583+ )
1584+ . unwrap ( ) ;
1585+
1586+ // Calculate the average expected coinbase reward per block based on the retargeting interval.
1587+ // This is the upper bound, because we consider hitting 50% of the coinbase target eligible for retargeting.
1588+ let avg_coinbase_reward_per_block = coinbase_reward * AVG_BLOCK_TIME as u64 / N :: ANCHOR_TIME as u64 ;
1589+
1590+ // Update the trackers.
1591+ block_height += 1 ;
1592+ total_block_rewards += block_reward;
1593+ total_coinbase_rewards += avg_coinbase_reward_per_block;
1594+ total_supply += block_reward + avg_coinbase_reward_per_block;
1595+ latest_timetamp = timestamp;
1596+ }
1597+
1598+ println ! (
1599+ "At block height {block_height} (year {}, timestamp: {latest_timetamp}), total block rewards is {total_block_rewards}, total coinbase rewards is {total_coinbase_rewards}, total supply is {total_supply} credits" ,
1600+ block_height / blocks_per_year
1601+ ) ;
1602+
1603+ // Check that block height matches the expected max supply limit height.
1604+ assert_eq ! ( block_height, N :: MAX_SUPPLY_LIMIT_HEIGHT ) ;
1605+ assert_eq ! ( N :: MAX_SUPPLY_LIMIT_HEIGHT , 263_527_685 ) ;
1606+ }
1607+
1608+ #[ test]
1609+ fn test_total_supply_cap ( ) {
1610+ check_total_supply_cap :: < CanaryV0 > ( ) ;
1611+ check_total_supply_cap :: < TestnetV0 > ( ) ;
1612+ check_total_supply_cap :: < MainnetV0 > ( ) ;
1613+ }
1614+
15081615 #[ test]
15091616 fn test_targets ( ) {
15101617 let mut rng = TestRng :: default ( ) ;
0 commit comments