diff --git a/pallets/mining-rewards/src/lib.rs b/pallets/mining-rewards/src/lib.rs index cd2a8a32..d60557c1 100644 --- a/pallets/mining-rewards/src/lib.rs +++ b/pallets/mining-rewards/src/lib.rs @@ -17,7 +17,7 @@ pub use weights::*; pub mod pallet { use super::*; use codec::Decode; - use core::marker::PhantomData; + use core::{convert::TryInto, marker::PhantomData}; use frame_support::{ pallet_prelude::*, traits::{ @@ -33,6 +33,8 @@ pub mod pallet { traits::{AccountIdConversion, Saturating}, }; + const UNIT: u128 = 1_000_000_000_000u128; + pub(crate) type BalanceOf = <::Currency as Inspect<::AccountId>>::Balance; @@ -52,13 +54,17 @@ pub mod pallet { type Currency: Mutate + qp_wormhole::TransferProofs, Self::AccountId>; - /// The base block reward given to miners + /// The maximum total supply of tokens + #[pallet::constant] + type MaxSupply: Get>; + + /// The divisor used to calculate block rewards from remaining supply #[pallet::constant] - type MinerBlockReward: Get>; + type EmissionDivisor: Get>; - /// The base block reward given to treasury + /// The portion of rewards that goes to treasury (out of 100) #[pallet::constant] - type TreasuryBlockReward: Get>; + type TreasuryPortion: Get; /// The treasury pallet ID #[pallet::constant] @@ -101,24 +107,61 @@ pub mod pallet { } fn on_finalize(_block_number: BlockNumberFor) { - // Get the block rewards - let miner_reward = T::MinerBlockReward::get(); - let treasury_reward = T::TreasuryBlockReward::get(); + // Calculate dynamic block reward based on remaining supply + let max_supply = T::MaxSupply::get(); + let current_supply = T::Currency::total_issuance(); + let emission_divisor = T::EmissionDivisor::get(); + + let remaining_supply = max_supply.saturating_sub(current_supply); + + if remaining_supply == BalanceOf::::zero() { + log::warn!( + "💰 Emission completed: current supply has reached the configured maximum, \ + no further block rewards will be minted." + ); + } + + let total_reward = remaining_supply + .checked_div(&emission_divisor) + .unwrap_or_else(|| BalanceOf::::zero()); + + // Split the reward between treasury and miner + let treasury_portion = T::TreasuryPortion::get(); + let treasury_reward = + total_reward.saturating_mul(treasury_portion.into()) / 100u32.into(); + let miner_reward = total_reward.saturating_sub(treasury_reward); + let tx_fees = >::take(); // Extract miner ID from the pre-runtime digest let miner = Self::extract_miner_from_digest(); - log::debug!(target: "mining-rewards", "💰 Base reward: {:?}", miner_reward); - log::debug!(target: "mining-rewards", "💰 Original Tx_fees: {:?}", tx_fees); + // Log readable amounts (convert to tokens by dividing by 1e12) + if let (Ok(total), Ok(treasury), Ok(miner_amt), Ok(current), Ok(fees)) = ( + TryInto::::try_into(total_reward), + TryInto::::try_into(treasury_reward), + TryInto::::try_into(miner_reward), + TryInto::::try_into(current_supply), + TryInto::::try_into(tx_fees), + ) { + let remaining: u128 = + TryInto::::try_into(max_supply.saturating_sub(current_supply)) + .unwrap_or(0); + log::debug!(target: "mining-rewards", "💰 Total reward: {:.6}", total as f64 / UNIT as f64); + log::debug!(target: "mining-rewards", "💰 Treasury reward: {:.6}", treasury as f64 / UNIT as f64); + log::debug!(target: "mining-rewards", "💰 Miner reward: {:.6}", miner_amt as f64 / UNIT as f64); + log::debug!(target: "mining-rewards", "💰 Current supply: {:.2}", current as f64 / UNIT as f64); + log::debug!(target: "mining-rewards", "💰 Remaining supply: {:.2}", remaining as f64 / UNIT as f64); + log::debug!(target: "mining-rewards", "💰 Transaction fees: {:.6}", fees as f64 / UNIT as f64); + } // Send fees to miner if any Self::mint_reward(miner.clone(), tx_fees); - // Send rewards separately for accounting + // Send block rewards to miner Self::mint_reward(miner, miner_reward); - // Send treasury reward + // Send treasury portion to treasury Self::mint_reward(None, treasury_reward); } } @@ -168,13 +211,6 @@ pub mod pallet { T::Currency::store_transfer_proof(&mint_account, &miner, reward); Self::deposit_event(Event::MinerRewarded { miner: miner.clone(), reward }); - - log::debug!( - target: "mining-rewards", - "💰 Rewards sent to miner: {:?} {:?}", - reward, - miner - ); }, None => { let treasury = T::TreasuryPalletId::get().into_account_truncating(); @@ -183,12 +219,6 @@ pub mod pallet { T::Currency::store_transfer_proof(&mint_account, &treasury, reward); Self::deposit_event(Event::TreasuryRewarded { reward }); - - log::debug!( - target: "mining-rewards", - "💰 Rewards sent to Treasury: {:?}", - reward - ); }, }; } diff --git a/pallets/mining-rewards/src/mock.rs b/pallets/mining-rewards/src/mock.rs index 03ce2a4c..2883832d 100644 --- a/pallets/mining-rewards/src/mock.rs +++ b/pallets/mining-rewards/src/mock.rs @@ -24,11 +24,13 @@ frame_support::construct_runtime!( pub type Balance = u128; pub type Block = frame_system::mocking::MockBlock; +const UNIT: u128 = 1_000_000_000_000u128; parameter_types! { pub const BlockHashCount: u64 = 250; pub const SS58Prefix: u8 = 189; - pub const BlockReward: u128 = 50; + pub const MaxSupply: u128 = 21_000_000 * UNIT; + pub const EmissionDivisor: u128 = 26_280_000; pub const ExistentialDeposit: Balance = 1; pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); } @@ -83,15 +85,16 @@ impl pallet_balances::Config for Test { } parameter_types! { - pub const TreasuryBlockReward: u128 = 50; + pub const TreasuryPortion: u8 = 50; // 50% goes to treasury in tests (matching runtime) pub const MintingAccount: sp_core::crypto::AccountId32 = sp_core::crypto::AccountId32::new([99u8; 32]); } impl pallet_mining_rewards::Config for Test { type Currency = Balances; type WeightInfo = (); - type MinerBlockReward = BlockReward; - type TreasuryBlockReward = TreasuryBlockReward; + type MaxSupply = MaxSupply; + type EmissionDivisor = EmissionDivisor; + type TreasuryPortion = TreasuryPortion; type TreasuryPalletId = TreasuryPalletId; type MintingAccount = MintingAccount; } diff --git a/pallets/mining-rewards/src/tests.rs b/pallets/mining-rewards/src/tests.rs index 04d9ef13..04513ca6 100644 --- a/pallets/mining-rewards/src/tests.rs +++ b/pallets/mining-rewards/src/tests.rs @@ -2,8 +2,6 @@ use crate::{mock::*, weights::WeightInfo, Event}; use frame_support::traits::{Currency, Hooks}; use sp_runtime::traits::AccountIdConversion; -const UNIT: u128 = 1_000_000_000_000; - #[test] fn miner_reward_works() { new_test_ext().execute_with(|| { @@ -13,17 +11,26 @@ fn miner_reward_works() { // Add a miner to the pre-runtime digest set_miner_digest(miner()); + // Calculate expected rewards with treasury portion + // Initial supply is just the existential deposits (2 accounts * 1 unit each = 2) + let current_supply = Balances::total_issuance(); + let total_reward = (MaxSupply::get() - current_supply) / EmissionDivisor::get(); + let treasury_reward = total_reward * TreasuryPortion::get() as u128 / 100; + let miner_reward = total_reward - treasury_reward; + // Run the on_finalize hook MiningRewards::on_finalize(1); - // Check that the miner received the block reward (no fees in this test) - assert_eq!( - Balances::free_balance(miner()), - initial_balance + 50 // Initial + base reward only + // Check that the miner received the calculated block reward (minus treasury portion) + assert_eq!(Balances::free_balance(miner()), initial_balance + miner_reward); + + // Check the miner reward event was emitted + System::assert_has_event( + Event::MinerRewarded { miner: miner(), reward: miner_reward }.into(), ); - // Check the event was emitted - System::assert_has_event(Event::MinerRewarded { miner: miner(), reward: 50 }.into()); + // Check the treasury reward event was emitted + System::assert_has_event(Event::TreasuryRewarded { reward: treasury_reward }.into()); }); } @@ -43,25 +50,20 @@ fn miner_reward_with_transaction_fees_works() { // Check fees collection event System::assert_has_event(Event::FeesCollected { amount: 25, total: 25 }.into()); + // Calculate expected rewards with treasury portion + let current_supply = Balances::total_issuance(); + let total_block_reward = (MaxSupply::get() - current_supply) / EmissionDivisor::get(); + let treasury_reward = total_block_reward * TreasuryPortion::get() as u128 / 100; + let miner_block_reward = total_block_reward - treasury_reward; + // Run the on_finalize hook MiningRewards::on_finalize(1); - // Check that the miner received the block reward + all fees - // Current implementation: miner gets base reward (50) + all fees (25) - assert_eq!( - Balances::free_balance(miner()), - initial_balance + 50 + 25 // Initial + base + all fees - ); + // Check that the miner received the miner portion of block reward + all fees + assert_eq!(Balances::free_balance(miner()), initial_balance + miner_block_reward + fees); // Check the events were emitted with the correct amounts - // First event: treasury block reward - System::assert_has_event( - Event::TreasuryRewarded { - reward: 50, // treasury block reward - } - .into(), - ); - // Second event: miner reward for fees + // First event: miner reward for fees System::assert_has_event( Event::MinerRewarded { miner: miner(), @@ -69,14 +71,12 @@ fn miner_reward_with_transaction_fees_works() { } .into(), ); - // Third event: miner reward for base reward + // Second event: miner reward for block reward System::assert_has_event( - Event::MinerRewarded { - miner: miner(), - reward: 50, // base reward - } - .into(), + Event::MinerRewarded { miner: miner(), reward: miner_block_reward }.into(), ); + // Third event: treasury reward + System::assert_has_event(Event::TreasuryRewarded { reward: treasury_reward }.into()); }); } @@ -92,17 +92,18 @@ fn on_unbalanced_collects_fees() { // Check that fees were collected assert_eq!(MiningRewards::collected_fees(), 30); + // Calculate expected rewards with treasury portion + let current_supply = Balances::total_issuance(); + let total_block_reward = (MaxSupply::get() - current_supply) / EmissionDivisor::get(); + let treasury_reward = total_block_reward * TreasuryPortion::get() as u128 / 100; + let miner_block_reward = total_block_reward - treasury_reward; + // Add a miner to the pre-runtime digest and distribute rewards set_miner_digest(miner()); MiningRewards::on_finalize(1); - // Check that the miner received the block reward + all fees - // Check miner received rewards - // Current implementation: miner gets base reward (50) + all fees (30) - assert_eq!( - Balances::free_balance(miner()), - initial_balance + 50 + 30 // Initial + base + all fees - ); + // Check that the miner received the miner portion of block reward + all fees + assert_eq!(Balances::free_balance(miner()), initial_balance + miner_block_reward + 30); }); } @@ -115,22 +116,35 @@ fn multiple_blocks_accumulate_rewards() { // Block 1 set_miner_digest(miner()); MiningRewards::collect_transaction_fees(10); + + // Calculate rewards for block 1 with treasury portion + let current_supply_block1 = Balances::total_issuance(); + let total_block1_reward = + (MaxSupply::get() - current_supply_block1) / EmissionDivisor::get(); + let miner_block1_reward = + total_block1_reward - (total_block1_reward * TreasuryPortion::get() as u128 / 100); + MiningRewards::on_finalize(1); - // Current implementation: miner gets base reward (50) + all fees (10) - let balance_after_block_1 = initial_balance + 50 + 10; // Initial + base + all fees + let balance_after_block_1 = initial_balance + miner_block1_reward + 10; assert_eq!(Balances::free_balance(miner()), balance_after_block_1); - // Block 2 + // Block 2 - supply has increased after block 1, so reward will be different set_miner_digest(miner()); MiningRewards::collect_transaction_fees(15); + + let current_supply_block2 = Balances::total_issuance(); + let total_block2_reward = + (MaxSupply::get() - current_supply_block2) / EmissionDivisor::get(); + let miner_block2_reward = + total_block2_reward - (total_block2_reward * TreasuryPortion::get() as u128 / 100); + MiningRewards::on_finalize(2); // Check total rewards for both blocks - // Block 1: 50 + 10 = 60, Block 2: 50 + 15 = 65, Total: 125 assert_eq!( Balances::free_balance(miner()), - initial_balance + 50 + 10 + 50 + 15 // Initial + block1 + block2 + initial_balance + miner_block1_reward + 10 + miner_block2_reward + 15 ); }); } @@ -145,24 +159,35 @@ fn different_miners_get_different_rewards() { // Block 1 - First miner set_miner_digest(miner()); MiningRewards::collect_transaction_fees(10); + + let current_supply_block1 = Balances::total_issuance(); + let total_block1_reward = + (MaxSupply::get() - current_supply_block1) / EmissionDivisor::get(); + let miner_block1_reward = + total_block1_reward - (total_block1_reward * TreasuryPortion::get() as u128 / 100); + MiningRewards::on_finalize(1); - // Check first miner balance - // Current implementation: miner gets base reward (50) + all fees (10) - let balance_after_block_1 = initial_balance_miner1 + 50 + 10; // Initial + base + all fees + let balance_after_block_1 = initial_balance_miner1 + miner_block1_reward + 10; assert_eq!(Balances::free_balance(miner()), balance_after_block_1); // Block 2 - Second miner System::set_block_number(2); set_miner_digest(miner2()); MiningRewards::collect_transaction_fees(20); + + let current_supply_block2 = Balances::total_issuance(); + let total_block2_reward = + (MaxSupply::get() - current_supply_block2) / EmissionDivisor::get(); + let miner_block2_reward = + total_block2_reward - (total_block2_reward * TreasuryPortion::get() as u128 / 100); + MiningRewards::on_finalize(2); // Check second miner balance - // Current implementation: miner gets base reward (50) + all fees (20) assert_eq!( Balances::free_balance(miner2()), - initial_balance_miner2 + 50 + 20 // Initial + base + all fees + initial_balance_miner2 + miner_block2_reward + 20 ); // First miner balance should remain unchanged @@ -184,17 +209,18 @@ fn transaction_fees_collector_works() { // Check accumulated fees assert_eq!(MiningRewards::collected_fees(), 30); + // Calculate expected rewards with treasury portion + let current_supply = Balances::total_issuance(); + let total_block_reward = (MaxSupply::get() - current_supply) / EmissionDivisor::get(); + let miner_block_reward = + total_block_reward - (total_block_reward * TreasuryPortion::get() as u128 / 100); + // Reward miner set_miner_digest(miner()); MiningRewards::on_finalize(1); - // Check miner got base reward + 90% of all fees - // Check that the miner received the block reward + all collected fees - // Base reward: 50, Fees: 30 (from the collect_transaction_fees call) - assert_eq!( - Balances::free_balance(miner()), - initial_balance + 50 + 30 // Initial + base + all fees - ); + // Check that the miner received the miner portion of block reward + all collected fees + assert_eq!(Balances::free_balance(miner()), initial_balance + miner_block_reward + 30); }); } @@ -213,16 +239,18 @@ fn block_lifecycle_works() { // 2. Add some transaction fees during block execution MiningRewards::collect_transaction_fees(15); + // Calculate expected rewards with treasury portion + let current_supply = Balances::total_issuance(); + let total_block_reward = (MaxSupply::get() - current_supply) / EmissionDivisor::get(); + let miner_block_reward = + total_block_reward - (total_block_reward * TreasuryPortion::get() as u128 / 100); + // 3. on_finalize - should reward the miner set_miner_digest(miner()); MiningRewards::on_finalize(1); // Check miner received rewards - // Current implementation: miner gets base reward (50) + all fees (15 in this test) - assert_eq!( - Balances::free_balance(miner()), - initial_balance + 50 + 15 // Initial + base reward + fees - ); + assert_eq!(Balances::free_balance(miner()), initial_balance + miner_block_reward + 15); }); } @@ -238,20 +266,23 @@ fn test_run_to_block_helper() { // Add fees for block 1 MiningRewards::collect_transaction_fees(10); + // Note: This test is complex with run_to_block as rewards change with supply + // We'll just verify the mechanism works and final balance is reasonable + let initial_supply = Balances::total_issuance(); + // Run to block 3 (this should process blocks 1 and 2) run_to_block(3); - // Check that miner received rewards for blocks 1 and 2 - // Block 1: 50 (base) + 10 (fees) = 60 - // Block 2: 50 (base) + 0 (no new fees) = 50 - // Total: 110 - assert_eq!( - Balances::free_balance(miner()), - initial_balance + 110 // Initial + 50 + 10 + 50 - ); - // Verify we're at the expected block number assert_eq!(System::block_number(), 3); + + // Check that miner balance increased (should have rewards from both blocks + fees) + let final_balance = Balances::free_balance(miner()); + assert!(final_balance > initial_balance, "Miner should have received rewards"); + + // Verify supply increased due to minted rewards + let final_supply = Balances::total_issuance(); + assert!(final_supply > initial_supply, "Total supply should have increased"); }); } @@ -262,40 +293,32 @@ fn rewards_go_to_treasury_when_no_miner() { let treasury_account = TreasuryPalletId::get().into_account_truncating(); let initial_treasury_balance = Balances::free_balance(&treasury_account); - // Fund Treasury - let treasury_funding = 1000 * UNIT; - let _ = Balances::deposit_creating(&treasury_account, treasury_funding); + // Calculate expected rewards - when no miner, all rewards go to treasury + let current_supply = Balances::total_issuance(); + let total_reward = (MaxSupply::get() - current_supply) / EmissionDivisor::get(); + let treasury_portion_reward = total_reward * TreasuryPortion::get() as u128 / 100; + let miner_portion_reward = total_reward - treasury_portion_reward; - // Create a block without a miner + // Create a block without a miner (no digest set) System::set_block_number(1); MiningRewards::on_finalize(System::block_number()); - // Check that Treasury received the rewards - // When no miner, treasury gets both miner reward and treasury block reward - let expected_reward = BlockReward::get() + TreasuryBlockReward::get(); // 50 + 50 = 100 + // Check that Treasury received both its portion and the miner's portion (since no miner) assert_eq!( Balances::free_balance(treasury_account), - initial_treasury_balance + treasury_funding + expected_reward + initial_treasury_balance + treasury_portion_reward + miner_portion_reward ); - // Check that the events were emitted - treasury gets both miner reward and treasury reward + // Check that the events were emitted System::assert_has_event( - Event::TreasuryRewarded { - reward: 50, // treasury block reward - } - .into(), - ); - System::assert_has_event( - Event::TreasuryRewarded { - reward: 50, // miner reward (goes to treasury when no miner) - } - .into(), + Event::TreasuryRewarded { reward: treasury_portion_reward }.into(), ); + System::assert_has_event(Event::TreasuryRewarded { reward: miner_portion_reward }.into()); }); } #[test] -fn test_fees_split_between_treasury_and_miner() { +fn test_fees_and_rewards_to_miner() { new_test_ext().execute_with(|| { // Set up initial balances let miner = account_id(1); @@ -306,6 +329,12 @@ fn test_fees_split_between_treasury_and_miner() { let tx_fees = 100; MiningRewards::collect_transaction_fees(tx_fees); + // Calculate expected rewards with treasury portion + let current_supply = Balances::total_issuance(); + let total_block_reward = (MaxSupply::get() - current_supply) / EmissionDivisor::get(); + let treasury_reward = total_block_reward * TreasuryPortion::get() as u128 / 100; + let miner_block_reward = total_block_reward - treasury_reward; + // Create a block with a miner System::set_block_number(1); set_miner_digest(miner.clone()); @@ -313,41 +342,17 @@ fn test_fees_split_between_treasury_and_miner() { // Run on_finalize MiningRewards::on_finalize(System::block_number()); - // Get Treasury account - let treasury_account: sp_core::crypto::AccountId32 = - TreasuryPalletId::get().into_account_truncating(); - // Get actual values from the system AFTER on_finalize - let treasury_balance_after_finalize = Balances::free_balance(&treasury_account); let miner_balance_after_finalize = Balances::free_balance(&miner); - // Calculate expected values using the same method as in the implementation - // Current implementation: miner gets all fees, treasury gets block reward - let expected_reward_component_for_miner = BlockReward::get().saturating_add(tx_fees); - - // Check Treasury balance - it should have the treasury block reward - assert_eq!( - treasury_balance_after_finalize, - 50, // TreasuryBlockReward - "Treasury should receive block reward" - ); - - // Check miner balance + // Check miner balance - should get miner portion of block reward + all fees assert_eq!( miner_balance_after_finalize, - actual_initial_balance_after_creation + expected_reward_component_for_miner, - "Miner should receive base reward + all fees" + actual_initial_balance_after_creation + miner_block_reward + tx_fees, + "Miner should receive miner portion of block reward + all fees" ); // Verify events - // Check events for proper reward distribution - System::assert_has_event( - Event::TreasuryRewarded { - reward: 50, // treasury block reward - } - .into(), - ); - System::assert_has_event( Event::MinerRewarded { miner: miner.clone(), @@ -356,12 +361,153 @@ fn test_fees_split_between_treasury_and_miner() { .into(), ); - System::assert_has_event( - Event::MinerRewarded { - miner, - reward: BlockReward::get(), // base reward - } - .into(), + System::assert_has_event(Event::MinerRewarded { miner, reward: miner_block_reward }.into()); + + System::assert_has_event(Event::TreasuryRewarded { reward: treasury_reward }.into()); + }); +} + +#[test] +fn test_emission_simulation_120m_blocks() { + new_test_ext().execute_with(|| { + // Add realistic initial supply similar to genesis + let treasury_account = TreasuryPalletId::get().into_account_truncating(); + let _ = Balances::deposit_creating(&treasury_account, 3_600_000 * UNIT); + + println!("=== Mining Rewards Emission Simulation ==="); + println!("Max Supply: {:.0} tokens", MaxSupply::get() as f64 / UNIT as f64); + println!("Emission Divisor: {:?}", EmissionDivisor::get()); + println!("Treasury Portion: {}%", TreasuryPortion::get()); + println!(); + + const MAX_BLOCKS: u32 = 130_000_000; + const REPORT_INTERVAL: u32 = 1_000_000; // Report every 1M blocks + const UNIT: u128 = 1_000_000_000_000; // For readable output + + let initial_supply = Balances::total_issuance(); + let mut current_supply = initial_supply; + let mut total_miner_rewards = 0u128; + let mut total_treasury_rewards = 0u128; + let mut block = 0u32; + + println!("Block Supply %MaxSupply BlockReward ToTreasury ToMiner Remaining"); + println!("{}", "-".repeat(90)); + + // Print initial state + let remaining = MaxSupply::get() - current_supply; + let block_reward = if remaining > 0 { remaining / EmissionDivisor::get() } else { 0 }; + let treasury_reward = block_reward * TreasuryPortion::get() as u128 / 100; + let miner_reward = block_reward - treasury_reward; + + println!( + "{:<11} {:<13} {:<11.2}% {:<13.6} {:<12.6} {:<12.6} {:<13}", + block, + current_supply / UNIT, + (current_supply as f64 / MaxSupply::get() as f64) * 100.0, + block_reward as f64 / UNIT as f64, + treasury_reward as f64 / UNIT as f64, + miner_reward as f64 / UNIT as f64, + remaining / UNIT ); + + // Set up a consistent miner + set_miner_digest(miner()); + + while block < MAX_BLOCKS && current_supply < MaxSupply::get() { + // Simulate REPORT_INTERVAL blocks + for _ in 0..REPORT_INTERVAL { + if current_supply >= MaxSupply::get() { + break; + } + + // Calculate reward for this block + let remaining_supply = MaxSupply::get().saturating_sub(current_supply); + if remaining_supply == 0 { + break; + } + + let block_reward = remaining_supply / EmissionDivisor::get(); + let treasury_reward = block_reward * TreasuryPortion::get() as u128 / 100; + let miner_reward = block_reward - treasury_reward; + + // Update totals (simulate the minting) + current_supply += block_reward; + total_treasury_rewards += treasury_reward; + total_miner_rewards += miner_reward; + block += 1; + + // Early exit if rewards become negligible + if block_reward < 1000 { // Less than 1000 raw units (very small) + break; + } + } + + // Print progress report + let remaining = MaxSupply::get().saturating_sub(current_supply); + let next_block_reward = if remaining > 0 { remaining / EmissionDivisor::get() } else { 0 }; + let next_treasury = next_block_reward * TreasuryPortion::get() as u128 / 100; + let next_miner = next_block_reward - next_treasury; + + println!( + "{:<11} {:<13} {:<11.2}% {:<13.6} {:<12.6} {:<12.6} {:<13}", + block, + current_supply / UNIT, + (current_supply as f64 / MaxSupply::get() as f64) * 100.0, + next_block_reward as f64 / UNIT as f64, + next_treasury as f64 / UNIT as f64, + next_miner as f64 / UNIT as f64, + remaining / UNIT + ); + + // Stop if rewards become negligible or we've reached max supply + if current_supply >= MaxSupply::get() || next_block_reward < 1000 { + break; + } + } + + println!("{}", "-".repeat(90)); + println!(); + println!("=== Final Summary ==="); + println!("Total Blocks Processed: {}", block); + println!("Final Supply: {:.6} tokens", current_supply as f64 / UNIT as f64); + println!("Percentage of Max Supply: {:.4}%", (current_supply as f64 / MaxSupply::get() as f64) * 100.0); + println!("Remaining Supply: {:.6} tokens", (MaxSupply::get() - current_supply) as f64 / UNIT as f64); + println!(); + println!("Total Miner Rewards: {:.6} tokens", total_miner_rewards as f64 / UNIT as f64); + println!("Total Treasury Rewards: {:.6} tokens", total_treasury_rewards as f64 / UNIT as f64); + println!("Total Rewards Distributed: {:.6} tokens", (total_miner_rewards + total_treasury_rewards) as f64 / UNIT as f64); + println!(); + println!("Miner Share: {:.1}%", (total_miner_rewards as f64 / (total_miner_rewards + total_treasury_rewards) as f64) * 100.0); + println!("Treasury Share: {:.1}%", (total_treasury_rewards as f64 / (total_miner_rewards + total_treasury_rewards) as f64) * 100.0); + + // Time estimates (assuming 12 second blocks) + let total_seconds = block as f64 * 12.0; + let days = total_seconds / (24.0 * 3600.0); + let years = days / 365.25; + + println!(); + println!("=== Time Estimates (12s blocks) ==="); + println!("Total Time: {:.1} days ({:.1} years)", days, years); + + // === Comprehensive Emission Validation === + + assert!(current_supply >= initial_supply, "Supply should have increased"); + assert!(current_supply <= MaxSupply::get(), "Supply should not exceed max supply"); + + let emitted_tokens = current_supply - initial_supply; + let emission_percentage = (emitted_tokens as f64 / (MaxSupply::get() - initial_supply) as f64) * 100.0; + assert!(emission_percentage > 99.0, "Should have emitted >99% of available supply, got {:.2}%", emission_percentage); + + assert!(total_miner_rewards > 0, "Miners should have received rewards"); + assert!(total_treasury_rewards > 0, "Treasury should have received rewards"); + assert_eq!(total_miner_rewards + total_treasury_rewards, emitted_tokens, "Total rewards should equal emitted tokens"); + + let remaining_percentage = ((MaxSupply::get() - current_supply) as f64 / MaxSupply::get() as f64) * 100.0; + assert!(remaining_percentage < 1.0, "Should have <10% supply remaining, got {:.2}%", remaining_percentage); + assert!(remaining_percentage > 0.0, "Should still have some supply remaining for future emission"); + + println!(); + println!("✅ All emission validation checks passed!"); + println!("✅ Emission simulation completed successfully!"); }); } diff --git a/runtime/src/configs/mod.rs b/runtime/src/configs/mod.rs index e76063a3..ea2b6bc1 100644 --- a/runtime/src/configs/mod.rs +++ b/runtime/src/configs/mod.rs @@ -131,8 +131,9 @@ parameter_types! { impl pallet_mining_rewards::Config for Runtime { type Currency = Balances; type WeightInfo = pallet_mining_rewards::weights::SubstrateWeight; - type MinerBlockReward = ConstU128<{ 10 * UNIT }>; // 10 tokens - type TreasuryBlockReward = ConstU128<0>; // 0 tokens + type MaxSupply = ConstU128<{ 21_000_000 * UNIT }>; // 21 million tokens + type EmissionDivisor = ConstU128<26_280_000>; // Divide remaining supply by this amount + type TreasuryPortion = ConstU8<50>; // % of rewards go to treasury type TreasuryPalletId = TreasuryPalletId; type MintingAccount = MintingAccount; } diff --git a/runtime/src/genesis_config_presets.rs b/runtime/src/genesis_config_presets.rs index d7e835b4..3f42f727 100644 --- a/runtime/src/genesis_config_presets.rs +++ b/runtime/src/genesis_config_presets.rs @@ -54,12 +54,15 @@ fn dilithium_default_accounts() -> Vec { } // Returns the genesis config presets populated with given parameters. fn genesis_template(endowed_accounts: Vec, root: AccountId) -> Value { - let mut balances = - endowed_accounts.iter().cloned().map(|k| (k, 1u128 << 60)).collect::>(); + let mut balances = endowed_accounts + .iter() + .cloned() + .map(|k| (k, 100_000 * UNIT)) + .collect::>(); - const ONE_BILLION: u128 = 1_000_000_000; + const INITIAL_TREASURY: u128 = 21_000_000 * 30 * UNIT / 100; // 30% tokens go to investors let treasury_account = TreasuryPalletId::get().into_account_truncating(); - balances.push((treasury_account, ONE_BILLION * UNIT)); + balances.push((treasury_account, INITIAL_TREASURY)); let config = RuntimeGenesisConfig { balances: BalancesConfig { balances },