From b54c542925b1e6810f94acffaef18cc6e6086c9c Mon Sep 17 00:00:00 2001 From: Hardhat Chad <155858888+HardhatChad@users.noreply.github.com> Date: Sun, 23 Mar 2025 13:46:15 -0500 Subject: [PATCH] Reduce emissions by 10% every year (#108) * deprecate top balance * stub dynamic emissions impl * impl 90% curve * scaffold emissions rate function * emissions curve * implement new supply curve * remove comments * update comment * bump version * test reset * add tests for emissions reduction * test * fix test * bump version --- Cargo.lock | 6 +- Cargo.toml | 2 +- api/src/consts.rs | 15 -- api/src/state/bus.rs | 1 + api/src/state/config.rs | 4 +- program/src/initialize.rs | 1 - program/src/reset.rs | 518 ++++++++++++++++++++++++++++++++------ 7 files changed, 454 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 67a60eb..9569fd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1266,7 +1266,7 @@ dependencies = [ [[package]] name = "ore-api" -version = "3.3.0" +version = "3.4.0" dependencies = [ "array-const-fn-init", "bytemuck", @@ -1323,11 +1323,11 @@ dependencies = [ [[package]] name = "ore-program" -version = "3.3.0" +version = "3.4.0" dependencies = [ "drillx", "mpl-token-metadata", - "ore-api 3.3.0", + "ore-api 3.4.0", "ore-boost-api 3.0.0", "rand 0.8.5", "solana-program", diff --git a/Cargo.toml b/Cargo.toml index cd575e5..55cc63d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["api", "program"] [workspace.package] -version = "3.3.0" +version = "3.4.0" edition = "2021" license = "Apache-2.0" homepage = "https://ore.supply" diff --git a/api/src/consts.rs b/api/src/consts.rs index 116f5ce..46f51b2 100644 --- a/api/src/consts.rs +++ b/api/src/consts.rs @@ -42,16 +42,6 @@ pub const EPOCH_DURATION: i64 = ONE_MINUTE * EPOCH_MINUTES; /// The maximum token supply (5 million). pub const MAX_SUPPLY: u64 = ONE_ORE * 5_000_000; -/// The target quantity of ORE to be mined per epoch. -pub const TARGET_EPOCH_REWARDS: u64 = ONE_ORE * EPOCH_MINUTES as u64; - -/// The maximum quantity of ORE that can be mined per epoch. -/// Inflation target ≈ 1 ORE / min -pub const MAX_EPOCH_REWARDS: u64 = TARGET_EPOCH_REWARDS * BUS_COUNT as u64; - -/// The quantity of ORE each bus is allowed to issue per epoch. -pub const BUS_EPOCH_REWARDS: u64 = MAX_EPOCH_REWARDS / BUS_COUNT as u64; - /// The number of bus accounts, for parallelizing mine operations. pub const BUS_COUNT: usize = 8; @@ -59,11 +49,6 @@ pub const BUS_COUNT: usize = 8; /// than a factor of this constant from one epoch to the next. pub const SMOOTHING_FACTOR: u64 = 2; -// Assert MAX_EPOCH_REWARDS is evenly divisible by BUS_COUNT. -static_assertions::const_assert!( - (MAX_EPOCH_REWARDS / BUS_COUNT as u64) * BUS_COUNT as u64 == MAX_EPOCH_REWARDS -); - /// The seed of the bus account PDA. pub const BUS: &[u8] = b"bus"; diff --git a/api/src/state/bus.rs b/api/src/state/bus.rs index bf32edc..71504b3 100644 --- a/api/src/state/bus.rs +++ b/api/src/state/bus.rs @@ -18,6 +18,7 @@ pub struct Bus { pub theoretical_rewards: u64, /// The largest known stake balance seen by the bus this epoch. + #[deprecated(since = "2.8.0", note = "Top balance is no longer tracked or used")] pub top_balance: u64, } diff --git a/api/src/state/config.rs b/api/src/state/config.rs index 9f61c2b..c60dede 100644 --- a/api/src/state/config.rs +++ b/api/src/state/config.rs @@ -15,8 +15,8 @@ pub struct Config { /// The minimum accepted difficulty. pub min_difficulty: u64, - /// Buffer for possible future use. - pub _buffer: [u8; 8], + /// The target emissions rate in ORE/min. + pub target_emmissions_rate: u64, } account!(OreAccount, Config); diff --git a/program/src/initialize.rs b/program/src/initialize.rs index 8d031ee..32fe733 100644 --- a/program/src/initialize.rs +++ b/program/src/initialize.rs @@ -88,7 +88,6 @@ pub fn process_initialize(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program bus.id = i as u64; bus.rewards = 0; bus.theoretical_rewards = 0; - bus.top_balance = 0; } // Initialize config. diff --git a/program/src/reset.rs b/program/src/reset.rs index dc47c76..b34df7e 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -1,7 +1,7 @@ use ore_api::prelude::*; use steel::*; -/// Reset tops up the bus balances, updates the base reward rate, and sets up the ORE program for the next epoch. +/// Reset tops up the bus balances and updates the emissions and reward rates. pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { // Load accounts. let [signer_info, bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, bus_7_info, config_info, mint_info, treasury_info, treasury_tokens_info, token_program] = @@ -55,69 +55,94 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul return Ok(()); } - // Update timestamp. - config.last_reset_at = clock.unix_timestamp; - - // Reset bus accounts and calculate actual rewards mined since last reset. + // Process epoch. let busses = [bus_0, bus_1, bus_2, bus_3, bus_4, bus_5, bus_6, bus_7]; - let mut total_remaining_rewards = 0u64; - let mut total_theoretical_rewards = 0u64; - let mut top_balance = 0u64; - for bus in busses { - // Track top balance. - if bus.top_balance.gt(&top_balance) { - top_balance = bus.top_balance; - } - - // Track accumulators. - total_remaining_rewards = total_remaining_rewards.saturating_add(bus.rewards); - total_theoretical_rewards = - total_theoretical_rewards.saturating_add(bus.theoretical_rewards); - - // Reset bus account for new epoch. - bus.rewards = BUS_EPOCH_REWARDS; - bus.theoretical_rewards = 0; - bus.top_balance = 0; - } - let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_remaining_rewards); - - // Update base reward rate for next epoch. - config.base_reward_rate = - calculate_new_reward_rate(config.base_reward_rate, total_theoretical_rewards); - - // If base reward rate is too low, increment min difficulty by 1 and double base reward rate. - if config.base_reward_rate.le(&BASE_REWARD_RATE_MIN_THRESHOLD) { - config.min_difficulty = config.min_difficulty.checked_add(1).unwrap(); - config.base_reward_rate = config.base_reward_rate.checked_mul(2).unwrap(); - } - - // If base reward rate is too high, decrement min difficulty by 1 and halve base reward rate. - if config.base_reward_rate.ge(&BASE_REWARD_RATE_MAX_THRESHOLD) && config.min_difficulty.gt(&1) { - config.min_difficulty = config.min_difficulty.checked_sub(1).unwrap(); - config.base_reward_rate = config.base_reward_rate.checked_div(2).unwrap(); - } - - // Max supply check. - if mint.supply().ge(&MAX_SUPPLY) { - return Err(OreError::MaxSupply.into()); - } + let amount_to_mint = config.process_epoch(busses, &clock, &mint)?; // Fund the treasury token account. - let amount = MAX_SUPPLY - .saturating_sub(mint.supply()) - .min(total_epoch_rewards); mint_to_signed( mint_info, treasury_tokens_info, treasury_info, token_program, - amount, + amount_to_mint, &[TREASURY], )?; Ok(()) } +trait EpochProcessor { + fn process_epoch( + &mut self, + busses: [&mut Bus; 8], + clock: &Clock, + mint: &Mint, + ) -> Result; +} + +impl EpochProcessor for Config { + fn process_epoch( + &mut self, + busses: [&mut Bus; 8], + clock: &Clock, + mint: &Mint, + ) -> Result { + // Max supply check. + if mint.supply() >= MAX_SUPPLY { + return Err(OreError::MaxSupply.into()); + } + + // Update timestamp. + self.last_reset_at = clock.unix_timestamp; + + // Adjust emissions curve based on current supply. + self.target_emmissions_rate = get_target_emissions_rate(mint.supply()); + + // Calculate target rewards to distribute in coming epoch (emissions rate multiplied by epoch duration). + let target_epoch_rewards = self.target_emmissions_rate * EPOCH_MINUTES as u64; + + // Reset bus counters and calculate theoretical rewards mined in the last epoch. + let mut amount_to_mint = 0u64; + let mut remaining_supply = MAX_SUPPLY.saturating_sub(mint.supply()); + let mut theoretical_epoch_rewards = 0u64; + for bus in busses { + // Reset theoretical rewards. + theoretical_epoch_rewards += bus.theoretical_rewards; + bus.theoretical_rewards = 0; + + // Reset bus rewards. + let topup_amount = target_epoch_rewards + .saturating_sub(bus.rewards) + .min(remaining_supply); + remaining_supply -= topup_amount; + amount_to_mint += topup_amount; + bus.rewards += topup_amount; + } + + // Update base reward rate for next epoch. + self.base_reward_rate = calculate_new_reward_rate( + self.base_reward_rate, + theoretical_epoch_rewards, + target_epoch_rewards, + ); + + // If base reward rate is too low, increment min difficulty by 1 and double base reward rate. + if self.base_reward_rate < BASE_REWARD_RATE_MIN_THRESHOLD { + self.min_difficulty += 1; + self.base_reward_rate *= 2; + } + + // If base reward rate is too high, decrement min difficulty by 1 and halve base reward rate. + if self.base_reward_rate >= BASE_REWARD_RATE_MAX_THRESHOLD && self.min_difficulty > 1 { + self.min_difficulty -= 1; + self.base_reward_rate /= 2; + } + + Ok(amount_to_mint) + } +} + /// This function calculates what the new reward rate should be based on how many total rewards /// were mined in the prior epoch. The math is largely identitical to function used by the Bitcoin /// network to update the difficulty between each epoch. @@ -127,7 +152,11 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul /// The new rate is then smoothed by a constant factor to avoid large fluctuations. In Ore's case, /// the epochs are short (60 seconds) so a smoothing factor of 2 has been chosen. That is, the reward rate /// can at most double or halve from one epoch to the next. -pub(crate) fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) -> u64 { +pub(crate) fn calculate_new_reward_rate( + current_rate: u64, + epoch_rewards: u64, + target_epoch_rewards: u64, +) -> u64 { // Avoid division by zero. Leave the reward rate unchanged, if detected. if epoch_rewards.eq(&0) { return current_rate; @@ -135,7 +164,7 @@ pub(crate) fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) - // Calculate new reward rate. let new_rate = (current_rate as u128) - .saturating_mul(TARGET_EPOCH_REWARDS as u128) + .saturating_mul(target_epoch_rewards as u128) .saturating_div(epoch_rewards as u128) as u64; // Smooth reward rate so it cannot change by more than a constant factor from one epoch to the next. @@ -144,32 +173,77 @@ pub(crate) fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) - let new_rate_smoothed = new_rate.min(new_rate_max).max(new_rate_min); // Prevent reward rate from dropping below 1 or exceeding BUS_EPOCH_REWARDS and return. - new_rate_smoothed.max(1).min(BUS_EPOCH_REWARDS) + new_rate_smoothed.max(1).min(target_epoch_rewards) +} + +/// This function calculates the target emissions rate (ORE / min) based on the current supply. +/// It is designed to reduce emissions by 10% approximately every 12 months with a hardcap at 5 million ORE. +pub(crate) fn get_target_emissions_rate(current_supply: u64) -> u64 { + match current_supply { + n if n < ONE_ORE * 525_600 => 100_000_000_000, // Year ~1 + n if n < ONE_ORE * 998_640 => 90_000_000_000, // Year ~2 + n if n < ONE_ORE * 1_424_376 => 81_000_000_000, // Year ~3 + n if n < ONE_ORE * 1_807_538 => 72_900_000_000, // Year ~4 + n if n < ONE_ORE * 2_152_384 => 65_610_000_000, // Year ~5 + n if n < ONE_ORE * 2_462_746 => 59_049_000_000, // Year ~6 + n if n < ONE_ORE * 2_742_071 => 53_144_100_000, // Year ~7 + n if n < ONE_ORE * 2_993_464 => 47_829_690_000, // Year ~8 + n if n < ONE_ORE * 3_219_717 => 43_046_721_000, // Year ~9 + n if n < ONE_ORE * 3_423_346 => 38_742_048_900, // Year ~10 + n if n < ONE_ORE * 3_606_611 => 34_867_844_010, // Year ~11 + n if n < ONE_ORE * 3_771_550 => 31_381_059_609, // Year ~12 + n if n < ONE_ORE * 3_919_995 => 28_242_953_648, // Year ~13 + n if n < ONE_ORE * 4_053_595 => 25_418_658_283, // Year ~14 + n if n < ONE_ORE * 4_173_836 => 22_876_792_454, // Year ~15 + n if n < ONE_ORE * 4_282_052 => 20_589_113_208, // Year ~16 + n if n < ONE_ORE * 4_379_447 => 18_530_201_887, // Year ~17 + n if n < ONE_ORE * 4_467_102 => 16_677_181_698, // Year ~18 + n if n < ONE_ORE * 4_545_992 => 15_009_463_528, // Year ~19 + n if n < ONE_ORE * 4_616_993 => 13_508_517_175, // Year ~20 + n if n < ONE_ORE * 4_680_893 => 12_157_665_457, // Year ~21 + n if n < ONE_ORE * 4_738_404 => 10_941_898_911, // Year ~22 + n if n < ONE_ORE * 4_790_164 => 9_847_709_019, // Year ~23 + n if n < ONE_ORE * 4_836_747 => 8_862_938_117, // Year ~24 + n if n < ONE_ORE * 4_878_672 => 7_976_644_305, // Year ~25 + n if n < ONE_ORE * 4_916_405 => 7_178_979_874, // Year ~26 + n if n < ONE_ORE * 4_950_365 => 6_461_081_886, // Year ~27 + n if n < ONE_ORE * 4_980_928 => 5_814_973_607, // Year ~28 + n if n < ONE_ORE * 5_000_000 => 5_233_476_327, // Year ~29 + _ => 0, + } } #[cfg(test)] mod tests { use rand::{distributions::Uniform, Rng}; + use solana_program::program_option::COption; + use steel::{Clock, Mint}; - use crate::calculate_new_reward_rate; - use ore_api::consts::{ - BASE_REWARD_RATE_MIN_THRESHOLD, BUS_EPOCH_REWARDS, MAX_EPOCH_REWARDS, SMOOTHING_FACTOR, - TARGET_EPOCH_REWARDS, + use crate::{calculate_new_reward_rate, reset::EpochProcessor}; + use ore_api::{ + consts::{ + BASE_REWARD_RATE_MIN_THRESHOLD, BUS_COUNT, EPOCH_MINUTES, ONE_ORE, SMOOTHING_FACTOR, + TOKEN_DECIMALS, + }, + state::{Bus, Config}, }; const FUZZ_SIZE: u64 = 10_000; + const TARGET_EPOCH_REWARDS: u64 = ONE_ORE * EPOCH_MINUTES as u64; + const MAX_EPOCH_REWARDS: u64 = TARGET_EPOCH_REWARDS * BUS_COUNT as u64; #[test] fn test_calculate_new_reward_rate_target() { let current_rate = 1000; - let new_rate = calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS); + let new_rate = + calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS, TARGET_EPOCH_REWARDS); assert!(new_rate.eq(¤t_rate)); } #[test] fn test_calculate_new_reward_rate_div_by_zero() { let current_rate = 1000; - let new_rate = calculate_new_reward_rate(current_rate, 0); + let new_rate = calculate_new_reward_rate(current_rate, 0, TARGET_EPOCH_REWARDS); assert!(new_rate.eq(¤t_rate)); } @@ -178,7 +252,8 @@ mod tests { let current_rate = 1000; let new_rate = calculate_new_reward_rate( current_rate, - TARGET_EPOCH_REWARDS.saturating_add(1_000_000_000), + TARGET_EPOCH_REWARDS.saturating_add(10_000_000_000), + TARGET_EPOCH_REWARDS, ); assert!(new_rate.lt(¤t_rate)); } @@ -186,7 +261,8 @@ mod tests { #[test] fn test_calculate_new_reward_rate_lower_edge() { let current_rate = BASE_REWARD_RATE_MIN_THRESHOLD; - let new_rate = calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS + 1); + let new_rate = + calculate_new_reward_rate(current_rate, TARGET_EPOCH_REWARDS + 1, TARGET_EPOCH_REWARDS); assert!(new_rate.lt(¤t_rate)); } @@ -194,10 +270,11 @@ mod tests { fn test_calculate_new_reward_rate_lower_fuzz() { let mut rng = rand::thread_rng(); for _ in 0..FUZZ_SIZE { - let current_rate: u64 = rng.sample(Uniform::new(1, BUS_EPOCH_REWARDS)); + let current_rate: u64 = rng.sample(Uniform::new(1, TARGET_EPOCH_REWARDS)); let actual_rewards: u64 = rng.sample(Uniform::new(TARGET_EPOCH_REWARDS, MAX_EPOCH_REWARDS)); - let new_rate = calculate_new_reward_rate(current_rate, actual_rewards); + let new_rate = + calculate_new_reward_rate(current_rate, actual_rewards, TARGET_EPOCH_REWARDS); assert!(new_rate.lt(¤t_rate)); } } @@ -207,7 +284,8 @@ mod tests { let current_rate = 1000; let new_rate = calculate_new_reward_rate( current_rate, - TARGET_EPOCH_REWARDS.saturating_sub(1_000_000_000), + TARGET_EPOCH_REWARDS.saturating_sub(10_000_000_000), + TARGET_EPOCH_REWARDS, ); assert!(new_rate.gt(¤t_rate)); } @@ -216,9 +294,10 @@ mod tests { fn test_calculate_new_reward_rate_higher_fuzz() { let mut rng = rand::thread_rng(); for _ in 0..FUZZ_SIZE { - let current_rate: u64 = rng.sample(Uniform::new(1, BUS_EPOCH_REWARDS)); + let current_rate: u64 = rng.sample(Uniform::new(1, TARGET_EPOCH_REWARDS)); let actual_rewards: u64 = rng.sample(Uniform::new(1, TARGET_EPOCH_REWARDS)); - let new_rate = calculate_new_reward_rate(current_rate, actual_rewards); + let new_rate = + calculate_new_reward_rate(current_rate, actual_rewards, TARGET_EPOCH_REWARDS); assert!(new_rate.gt(¤t_rate)); } } @@ -226,26 +305,323 @@ mod tests { #[test] fn test_calculate_new_reward_rate_max_smooth() { let current_rate = 1000; - let new_rate = calculate_new_reward_rate(current_rate, 1); + let new_rate = calculate_new_reward_rate(current_rate, 1, TARGET_EPOCH_REWARDS); assert!(new_rate.eq(¤t_rate.saturating_mul(SMOOTHING_FACTOR))); } #[test] fn test_calculate_new_reward_rate_min_smooth() { let current_rate = 1000; - let new_rate = calculate_new_reward_rate(current_rate, u64::MAX); + let new_rate = calculate_new_reward_rate(current_rate, u64::MAX, TARGET_EPOCH_REWARDS); assert!(new_rate.eq(¤t_rate.saturating_div(SMOOTHING_FACTOR))); } #[test] fn test_calculate_new_reward_rate_max_inputs() { - let new_rate = calculate_new_reward_rate(BUS_EPOCH_REWARDS, MAX_EPOCH_REWARDS); - assert!(new_rate.eq(&BUS_EPOCH_REWARDS.saturating_div(SMOOTHING_FACTOR))); + let new_rate = calculate_new_reward_rate( + TARGET_EPOCH_REWARDS, + MAX_EPOCH_REWARDS, + TARGET_EPOCH_REWARDS, + ); + assert!(new_rate.eq(&TARGET_EPOCH_REWARDS.saturating_div(SMOOTHING_FACTOR))); } #[test] fn test_calculate_new_reward_rate_min_inputs() { - let new_rate = calculate_new_reward_rate(1, 1); + let new_rate = calculate_new_reward_rate(1, 1, TARGET_EPOCH_REWARDS); assert!(new_rate.eq(&1u64.saturating_mul(SMOOTHING_FACTOR))); } + + #[allow(deprecated)] + #[test] + fn test_process_epoch_simple() { + let mut config = Config { + base_reward_rate: 1024, + last_reset_at: 0, + min_difficulty: 1, + target_emmissions_rate: ONE_ORE, + }; + let bus_0 = &mut Bus { + id: 0, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_1 = &mut Bus { + id: 1, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_2 = &mut Bus { + id: 2, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_3 = &mut Bus { + id: 3, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_4 = &mut Bus { + id: 4, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_5 = &mut Bus { + id: 5, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_6 = &mut Bus { + id: 6, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_7 = &mut Bus { + id: 7, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let busses = [bus_0, bus_1, bus_2, bus_3, bus_4, bus_5, bus_6, bus_7]; + let clock = Clock::default(); + let mint = Mint::V0(spl_token::state::Mint { + mint_authority: COption::None, + supply: ONE_ORE * 100, + decimals: TOKEN_DECIMALS, + is_initialized: true, + freeze_authority: COption::None, + }); + + let amount_to_mint = config.process_epoch(busses, &clock, &mint).unwrap(); + assert_eq!(config.target_emmissions_rate, ONE_ORE); + assert_eq!( + ONE_ORE * EPOCH_MINUTES as u64 * BUS_COUNT as u64, + amount_to_mint + ); + } + + #[allow(deprecated)] + #[test] + fn test_process_epoch_emissions_boundary() { + let mut config = Config { + base_reward_rate: 1024, + last_reset_at: 0, + min_difficulty: 1, + target_emmissions_rate: ONE_ORE, + }; + let bus_0 = &mut Bus { + id: 0, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_1 = &mut Bus { + id: 1, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_2 = &mut Bus { + id: 2, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_3 = &mut Bus { + id: 3, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_4 = &mut Bus { + id: 4, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_5 = &mut Bus { + id: 5, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_6 = &mut Bus { + id: 6, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_7 = &mut Bus { + id: 7, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let busses = [bus_0, bus_1, bus_2, bus_3, bus_4, bus_5, bus_6, bus_7]; + let clock = Clock::default(); + let mint = Mint::V0(spl_token::state::Mint { + mint_authority: COption::None, + supply: ONE_ORE * 525_600, + decimals: TOKEN_DECIMALS, + is_initialized: true, + freeze_authority: COption::None, + }); + + let amount_to_mint = config.process_epoch(busses, &clock, &mint).unwrap(); + assert_eq!(config.target_emmissions_rate, 90_000_000_000); + assert_eq!( + 90_000_000_000 * EPOCH_MINUTES as u64 * BUS_COUNT as u64, + amount_to_mint + ); + } + + #[allow(deprecated)] + #[test] + fn test_process_epoch_max_supply() { + let mut config = Config { + base_reward_rate: 1024, + last_reset_at: 0, + min_difficulty: 1, + target_emmissions_rate: 5_233_476_327, + }; + let bus_0 = &mut Bus { + id: 0, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_1 = &mut Bus { + id: 1, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_2 = &mut Bus { + id: 2, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_3 = &mut Bus { + id: 3, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_4 = &mut Bus { + id: 4, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_5 = &mut Bus { + id: 5, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_6 = &mut Bus { + id: 6, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_7 = &mut Bus { + id: 7, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let busses = [bus_0, bus_1, bus_2, bus_3, bus_4, bus_5, bus_6, bus_7]; + let clock = Clock::default(); + let mint = Mint::V0(spl_token::state::Mint { + mint_authority: COption::None, + supply: ONE_ORE * 4_999_999, + decimals: TOKEN_DECIMALS, + is_initialized: true, + freeze_authority: COption::None, + }); + + let amount_to_mint = config.process_epoch(busses, &clock, &mint).unwrap(); + assert_eq!(config.target_emmissions_rate, 5_233_476_327); + assert_eq!(ONE_ORE, amount_to_mint); + } + + #[allow(deprecated)] + #[test] + fn test_process_epoch_zero_emissions() { + let mut config = Config { + base_reward_rate: 1024, + last_reset_at: 0, + min_difficulty: 1, + target_emmissions_rate: 5_233_476_327, + }; + let bus_0 = &mut Bus { + id: 0, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_1 = &mut Bus { + id: 1, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_2 = &mut Bus { + id: 2, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_3 = &mut Bus { + id: 3, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_4 = &mut Bus { + id: 4, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_5 = &mut Bus { + id: 5, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_6 = &mut Bus { + id: 6, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let bus_7 = &mut Bus { + id: 7, + rewards: 0, + theoretical_rewards: 0, + top_balance: 0, + }; + let busses = [bus_0, bus_1, bus_2, bus_3, bus_4, bus_5, bus_6, bus_7]; + let clock = Clock::default(); + let mint = Mint::V0(spl_token::state::Mint { + mint_authority: COption::None, + supply: ONE_ORE * 5_000_000, + decimals: TOKEN_DECIMALS, + is_initialized: true, + freeze_authority: COption::None, + }); + + let amount_to_mint = config.process_epoch(busses, &clock, &mint); + assert!(amount_to_mint.is_err()); + } }