Skip to content

Commit af911b8

Browse files
authored
Merge branch 'staging' into feat/improve-register-index-error
2 parents 7056e9b + c980e97 commit af911b8

File tree

3 files changed

+210
-4
lines changed

3 files changed

+210
-4
lines changed

console/network/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ pub trait Network:
129129

130130
/// The starting supply of Aleo credits.
131131
const STARTING_SUPPLY: u64 = 1_500_000_000_000_000; // 1.5B credits
132+
/// The maximum supply of Aleo credits.
133+
/// This value represents the absolute upper bound on all ALEO created over the lifetime of the network.
134+
const MAX_SUPPLY: u64 = 5_000_000_000_000_000; // 5B credits
135+
/// The block height that upper bounds the total supply of Aleo credits to 5 billion.
136+
#[cfg(not(feature = "test"))]
137+
const MAX_SUPPLY_LIMIT_HEIGHT: u32 = 263_527_685;
138+
/// The block height that upper bounds the total supply of Aleo credits to 5 billion.
139+
/// This is deliberately set to a low value for testing purposes only.
140+
#[cfg(feature = "test")]
141+
const MAX_SUPPLY_LIMIT_HEIGHT: u32 = 5;
132142
/// The cost in microcredits per byte for the deployment transaction.
133143
const DEPLOYMENT_FEE_MULTIPLIER: u64 = 1_000; // 1 millicredit per byte
134144
/// The multiplier in microcredits for each command in the constructor.

ledger/block/src/helpers/target.rs

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const V2_MIN_BLOCK_INTERVAL: i64 = 1; // 1 second.
2929
const 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.
3233
pub 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.
99106
pub 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();

ledger/src/tests.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3235,6 +3235,95 @@ mod valid_solutions {
32353235
let block_aborted_solution_id = block.aborted_solution_ids().first().unwrap();
32363236
assert_eq!(*block_aborted_solution_id, invalid_solution.id(), "Aborted solutions do not match");
32373237
}
3238+
3239+
#[test]
3240+
fn test_no_rewards_after_limit_height() {
3241+
let rng = &mut TestRng::default();
3242+
3243+
// Initialize the test environment.
3244+
let crate::test_helpers::TestEnv { ledger, private_key, address, .. } =
3245+
crate::test_helpers::sample_test_env(rng);
3246+
3247+
// Advance the ledger to the reward limit height.
3248+
let supply_limit_height = CurrentNetwork::MAX_SUPPLY_LIMIT_HEIGHT;
3249+
3250+
// Advance until before the supply limit height.
3251+
while ledger.latest_height() + 1 < supply_limit_height {
3252+
let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![], rng).unwrap();
3253+
ledger.advance_to_next_block(&block).unwrap();
3254+
3255+
// Check that there exists rewards in the block.
3256+
assert!(!block.ratifications().is_empty());
3257+
let ratifications: Vec<_> = block.ratifications().iter().collect();
3258+
match ratifications[0] {
3259+
Ratify::BlockReward(block_reward) => {
3260+
assert!(*block_reward > 0);
3261+
}
3262+
_ => panic!("Expected a block reward ratification"),
3263+
}
3264+
}
3265+
3266+
// Create one additional block at the supply limit height.
3267+
let next_block =
3268+
ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![], rng).unwrap();
3269+
ledger.advance_to_next_block(&next_block).unwrap();
3270+
3271+
// Check that the block and puzzle rewards are 0.
3272+
assert!(!next_block.ratifications().is_empty());
3273+
let ratifications: Vec<_> = next_block.ratifications().iter().collect();
3274+
match ratifications[0] {
3275+
Ratify::BlockReward(block_reward) => {
3276+
assert_eq!(*block_reward, 0);
3277+
}
3278+
_ => panic!("Expected a block reward ratification"),
3279+
}
3280+
match ratifications[1] {
3281+
Ratify::PuzzleReward(puzzle_reward) => {
3282+
assert_eq!(*puzzle_reward, 0);
3283+
}
3284+
_ => panic!("Expected a puzzle reward ratification"),
3285+
}
3286+
3287+
// Create another block with a valid solution that does not give any rewards.
3288+
3289+
// Retrieve the puzzle parameters.
3290+
let puzzle = ledger.puzzle();
3291+
let latest_epoch_hash = ledger.latest_epoch_hash().unwrap();
3292+
let minimum_proof_target = ledger.latest_proof_target();
3293+
3294+
// Create solutions that are greater than the minimum proof target.
3295+
let valid_solution = loop {
3296+
let solution = puzzle.prove(latest_epoch_hash, address, rng.r#gen(), None).unwrap();
3297+
if puzzle.get_proof_target(&solution).unwrap() >= minimum_proof_target {
3298+
break solution;
3299+
}
3300+
};
3301+
3302+
// Create a block with the valid solution.
3303+
let next_block_with_solution = ledger
3304+
.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![valid_solution], vec![], rng)
3305+
.unwrap();
3306+
ledger.advance_to_next_block(&next_block_with_solution).unwrap();
3307+
3308+
// Check that the block and puzzle rewards are 0.
3309+
assert!(!next_block.ratifications().is_empty());
3310+
let ratifications: Vec<_> = next_block.ratifications().iter().collect();
3311+
match ratifications[0] {
3312+
Ratify::BlockReward(block_reward) => {
3313+
assert_eq!(*block_reward, 0);
3314+
}
3315+
_ => panic!("Expected a block reward ratification"),
3316+
}
3317+
match ratifications[1] {
3318+
Ratify::PuzzleReward(puzzle_reward) => {
3319+
assert_eq!(*puzzle_reward, 0);
3320+
}
3321+
_ => panic!("Expected a puzzle reward ratification"),
3322+
}
3323+
3324+
// Check that the solution was accepted.
3325+
assert_eq!(next_block_with_solution.solutions().len(), 1);
3326+
}
32383327
}
32393328

32403329
/// Tests multiple attacks where the subDAG of a block is invalid

0 commit comments

Comments
 (0)