Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions console/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ pub trait Network:

/// The starting supply of Aleo credits.
const STARTING_SUPPLY: u64 = 1_500_000_000_000_000; // 1.5B credits
/// The maximum supply of Aleo credits.
/// This value represents the absolute upper bound on all ALEO created over the lifetime of the network.
const MAX_SUPPLY: u64 = 5_000_000_000_000_000; // 5B credits
/// The block height that upper bounds the total supply of Aleo credits to 5 billion.
#[cfg(not(any(test, feature = "test")))]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be using the test feature or the test_consensus_heights feature? I am still hoping we can phase out the former, as its name is not very descriptive.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want this to be for tests only rather than test_consensus_heights, because test_consensus_heights can be used for devnets.

I've kept the feature flag consistent with the other network variables, so if we would like to cleanup, we should do in a subsequent PR.

const MAX_SUPPLY_LIMIT_HEIGHT: u32 = 263_527_685;
/// The block height that upper bounds the total supply of Aleo credits to 5 billion.
/// This is deliberately set to a low value for testing purposes only.
#[cfg(any(test, feature = "test"))]
const MAX_SUPPLY_LIMIT_HEIGHT: u32 = 5;
/// The cost in microcredits per byte for the deployment transaction.
const DEPLOYMENT_FEE_MULTIPLIER: u64 = 1_000; // 1 millicredit per byte
/// The multiplier in microcredits for each command in the constructor.
Expand Down
61 changes: 61 additions & 0 deletions ledger/block/src/helpers/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1505,6 +1505,67 @@ mod tests {
}
}

fn check_total_supply_cap<N: Network>() {
const AVG_BLOCK_TIME: i64 = 3;

let blocks_per_year = block_height_at_year(AVG_BLOCK_TIME as u16, 1);

// The tracking state for the simluation
let mut total_supply = N::STARTING_SUPPLY;
let mut total_block_rewards = 0u64;
let mut total_coinbase_rewards = 0u64;
let mut block_height = 1u32;
let mut latest_timetamp = 0;

// Iterate until we reach 5 billion credits
while total_supply < N::MAX_SUPPLY {
// Calculate the block reward.
let block_reward =
block_reward::<N>(block_height, N::STARTING_SUPPLY, N::BLOCK_TIME, AVG_BLOCK_TIME, 0, 0).unwrap();

// Calculate the coinbase reward.
let timestamp = N::GENESIS_TIMESTAMP + (block_height as i64 * AVG_BLOCK_TIME);
let coinbase_reward = coinbase_reward::<N>(
block_height,
timestamp,
N::GENESIS_TIMESTAMP,
N::STARTING_SUPPLY,
N::ANCHOR_TIME,
N::ANCHOR_HEIGHT,
N::BLOCK_TIME,
1,
0,
1,
)
.unwrap();

// Calculate the average expected coinbase reward per block based on the retargeting interval.
// This is the upper bound, because we consider hitting 50% of the coinbase target eligible for retargeting.
let avg_coinbase_reward_per_block = coinbase_reward * AVG_BLOCK_TIME as u64 / N::ANCHOR_TIME as u64;

// Update the trackers.
block_height += 1;
total_block_rewards += block_reward;
total_coinbase_rewards += avg_coinbase_reward_per_block;
total_supply += block_reward + avg_coinbase_reward_per_block;
latest_timetamp = timestamp;
}

println!(
"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",
block_height / blocks_per_year
);

assert_eq!(block_height, N::MAX_SUPPLY_LIMIT_HEIGHT);
}

#[test]
fn test_total_supply_cap() {
check_total_supply_cap::<CanaryV0>();
check_total_supply_cap::<TestnetV0>();
check_total_supply_cap::<MainnetV0>();
}

#[test]
fn test_targets() {
let mut rng = TestRng::default();
Expand Down
10 changes: 8 additions & 2 deletions ledger/block/src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ impl<N: Network> Block<N> {
// Calculate the time since last block.
let time_since_last_block = timestamp.saturating_sub(previous_block.timestamp());
// Compute the expected block reward.
let expected_block_reward = block_reward::<N>(
let mut expected_block_reward = block_reward::<N>(
Comment thread
raychu86 marked this conversation as resolved.
Outdated
height,
N::STARTING_SUPPLY,
N::BLOCK_TIME,
Expand All @@ -422,7 +422,13 @@ impl<N: Network> Block<N> {
expected_transaction_fees,
)?;
// Compute the expected puzzle reward.
let expected_puzzle_reward = puzzle_reward(expected_coinbase_reward);
let mut expected_puzzle_reward = puzzle_reward(expected_coinbase_reward);

// If the height is at or beyond the max supply limit height, set rewards to zero.
if height >= N::MAX_SUPPLY_LIMIT_HEIGHT {
expected_block_reward = 0;
expected_puzzle_reward = 0;
}

Ok((
expected_cumulative_weight,
Expand Down
86 changes: 86 additions & 0 deletions ledger/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2515,6 +2515,92 @@ function foo:
}
}

#[test]
fn test_no_rewards_after_limit_height() {
let rng = &mut TestRng::default();

// Initialize the test environment.
let crate::test_helpers::TestEnv { ledger, private_key, address, .. } = crate::test_helpers::sample_test_env(rng);

// Advance the ledger to the reward limit height.
let supply_limit_height = CurrentNetwork::MAX_SUPPLY_LIMIT_HEIGHT;

// Advance until before the supply limit height.
while ledger.latest_height() + 1 < supply_limit_height {
let block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![], rng).unwrap();
ledger.advance_to_next_block(&block).unwrap();

// Check that there exists rewards in the block.
assert!(!block.ratifications().is_empty());
let ratifications: Vec<_> = block.ratifications().iter().collect();
match ratifications[0] {
Ratify::BlockReward(block_reward) => {
assert!(*block_reward > 0);
}
_ => panic!("Expected a block reward ratification"),
}
}

// Create one additional block at the supply limit height.
let next_block = ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![], rng).unwrap();
ledger.advance_to_next_block(&next_block).unwrap();

// Check that the block and puzzle rewards are 0.
assert!(!next_block.ratifications().is_empty());
let ratifications: Vec<_> = next_block.ratifications().iter().collect();
match ratifications[0] {
Ratify::BlockReward(block_reward) => {
assert_eq!(*block_reward, 0);
}
_ => panic!("Expected a block reward ratification"),
}
match ratifications[1] {
Ratify::PuzzleReward(puzzle_reward) => {
assert_eq!(*puzzle_reward, 0);
}
_ => panic!("Expected a puzzle reward ratification"),
}

// Create another block with a valid solution that does not give any rewards.

// Retrieve the puzzle parameters.
let puzzle = ledger.puzzle();
let latest_epoch_hash = ledger.latest_epoch_hash().unwrap();
let minimum_proof_target = ledger.latest_proof_target();

// Create solutions that are greater than the minimum proof target.
let valid_solution = loop {
let solution = puzzle.prove(latest_epoch_hash, address, rng.r#gen(), None).unwrap();
if puzzle.get_proof_target(&solution).unwrap() >= minimum_proof_target {
break solution;
}
};

// Create a block with the valid solution.
let next_block_with_solution =
ledger.prepare_advance_to_next_beacon_block(&private_key, vec![], vec![valid_solution], vec![], rng).unwrap();
ledger.advance_to_next_block(&next_block_with_solution).unwrap();

// Check that the block and puzzle rewards are 0.
assert!(!next_block.ratifications().is_empty());
let ratifications: Vec<_> = next_block.ratifications().iter().collect();
match ratifications[0] {
Ratify::BlockReward(block_reward) => {
assert_eq!(*block_reward, 0);
}
_ => panic!("Expected a block reward ratification"),
}
match ratifications[1] {
Ratify::PuzzleReward(puzzle_reward) => {
Comment thread
raychu86 marked this conversation as resolved.
Outdated
assert_eq!(*puzzle_reward, 0);
}
_ => panic!("Expected a puzzle reward ratification"),
}

// Check that the solution was accepted.
assert_eq!(next_block_with_solution.solutions().len(), 1);
}

// These tests require the proof targets to be low enough to be able to generate **valid** solutions.
// This requires the 'test' feature to be enabled for the `console` dependency.
#[cfg(feature = "test")]
Expand Down
7 changes: 6 additions & 1 deletion synthesizer/src/vm/finalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,12 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
let puzzle_reward = snarkvm_ledger_block::puzzle_reward(coinbase_reward);

// Output the reward ratifications.
vec![Ratify::BlockReward(block_reward), Ratify::PuzzleReward(puzzle_reward)]
match state.block_height() >= N::MAX_SUPPLY_LIMIT_HEIGHT {
// If the maximum supply limit height has been reached, then no rewards are given.
true => vec![Ratify::BlockReward(0), Ratify::PuzzleReward(0)],
// Otherwise, provide the computed rewards.
false => vec![Ratify::BlockReward(block_reward), Ratify::PuzzleReward(puzzle_reward)],
}
}
};

Expand Down