From 8ab3c274925b749b8ed789204ee7cdc1626338ac Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 17:43:04 +0000 Subject: [PATCH] 11 decimals --- src/consts.rs | 16 ++++++++-------- src/error.rs | 12 ++++-------- src/lib.rs | 6 ++---- src/processor/initialize.rs | 7 ++++--- src/processor/mine.rs | 15 ++++++++------- src/processor/reset.rs | 21 ++++++++++++++------- src/processor/upgrade.rs | 7 +++++-- src/state/bus.rs | 4 ++++ 8 files changed, 49 insertions(+), 39 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 48448a7..9b3906f 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -5,23 +5,23 @@ use solana_program::{pubkey, pubkey::Pubkey}; /// The reward rate to intialize the program with. pub const INITIAL_BASE_REWARD_RATE: u64 = 10u64.pow(3u32); -// TODO Migrate to 11 decimals? -/// The decimal precision of the ORE token. -/// Using SI prefixes, the smallest indivisible unit of ORE is a nanoORE. -/// 1 nanoORE = 0.000000001 ORE = one billionth of an ORE -pub const TOKEN_DECIMALS: u8 = 9; +/// The minimum difficulty to initialize the program with. +pub const INITIAL_MIN_DIFFICULTY: u32 = 12; -/// One ORE token, denominated in units of nanoORE. +/// The decimal precision of the ORE token. +pub const TOKEN_DECIMALS: u8 = 11; + +/// One ORE token, denominated in indivisible units. pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); /// The duration of an epoch, in units of seconds. pub const EPOCH_DURATION: i64 = 60; -/// The target quantity of ORE to be mined per epoch, in units of nanoORE. +/// The target quantity of ORE to be mined per epoch. /// Inflation rate ≈ 1 ORE / epoch (min 0, max 2) pub const TARGET_EPOCH_REWARDS: u64 = ONE_ORE; -/// The maximum quantity of ORE that can be mined per epoch, in units of nanoORE. +/// The maximum quantity of ORE that can be mined per epoch. pub const MAX_EPOCH_REWARDS: u64 = ONE_ORE.saturating_mul(2); /// The quantity of ORE each bus is allowed to issue per epoch. diff --git a/src/error.rs b/src/error.rs index 8880788..5fdaf61 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,17 +10,13 @@ pub enum OreError { #[error("The epoch has ended and needs reset")] NeedsReset = 1, #[error("The provided hash did not satisfy the minimum required difficulty")] - DifficultyInsufficient = 2, - #[error("The bus does not have enough rewards to issue at this time")] - BusRewardsInsufficient = 3, + HashTooEasy = 2, #[error("The claim amount cannot be greater than the claimable rewards")] - ClaimTooLarge = 4, + ClaimTooLarge = 3, #[error("The stake amount cannot exceed u64 size")] - StakeTooLarge = 5, + StakeTooLarge = 4, #[error("The clock time is invalid")] - ClockInvalid = 6, - #[error("The noise account is as large as it can get")] - NoiseSizeExceeded = 7, + ClockInvalid = 5, } impl From for ProgramError { diff --git a/src/lib.rs b/src/lib.rs index 724c880..c15417e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,10 +14,8 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; -// TODO u128 for internal rewards representation? -// TODO Admin fn for min difficulty? What if this were set automatically by u128 base reward rate? -// TODO Increase bus count? -// TODO Is downgrade necessary or no? +// TODO Admin fn for min difficulty? +// TODO Is downgrade necessary? declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index be483fe..3e9a083 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -17,8 +17,9 @@ use crate::{ utils::create_pda, utils::AccountDeserialize, utils::Discriminator, - BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, METADATA, METADATA_NAME, METADATA_SYMBOL, - METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, + BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, INITIAL_MIN_DIFFICULTY, METADATA, + METADATA_NAME, METADATA_SYMBOL, METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, + TREASURY, }; /// Initialize sets up the Ore program. Its responsibilities include: @@ -134,7 +135,7 @@ pub fn process_initialize<'a, 'info>( config.admin = *signer.key; config.base_reward_rate = INITIAL_BASE_REWARD_RATE; config.last_reset_at = 0; - config.min_difficulty = 8; + config.min_difficulty = INITIAL_MIN_DIFFICULTY; config.paused = 0; // Initialize treasury diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 68cdaa0..16bcddf 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -18,7 +18,7 @@ use crate::{ loaders::*, state::{Bus, Config, Proof}, utils::AccountDeserialize, - BUS_EPOCH_REWARDS, EPOCH_DURATION, + EPOCH_DURATION, }; // TODO Look into tx introspection to require 1 hash per tx @@ -69,7 +69,7 @@ pub fn process_mine<'a, 'info>( return Err(OreError::ClockInvalid.into()); } - // Validate epoch is active + // TODO Validate epoch is active // let treasury_data = treasury_info.data.borrow(); // let treasury = Treasury::try_from_bytes(&treasury_data)?; // let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION); @@ -84,7 +84,7 @@ pub fn process_mine<'a, 'info>( let difficulty = drillx::difficulty(hx); sol_log(&format!("Diff {}", difficulty)); if difficulty.le(&config.min_difficulty) { - return Err(OreError::DifficultyInsufficient.into()); + return Err(OreError::HashTooEasy.into()); } // Calculate base reward rate @@ -143,16 +143,17 @@ pub fn process_mine<'a, 'info>( // Set upper bound to whatever is left in the bus let mut bus_data = bus_info.data.borrow_mut(); let bus = Bus::try_from_bytes_mut(&mut bus_data)?; - reward = reward.min(bus.rewards); + let actual_reward = reward.min(bus.rewards); // Update balances sol_log(&format!("Total {}", reward)); sol_log(&format!("Bus {}", bus.rewards)); + bus.theoretical_rewards = bus.theoretical_rewards.saturating_add(reward); bus.rewards = bus .rewards - .checked_sub(reward) - .ok_or(OreError::BusRewardsInsufficient)?; - proof.balance = proof.balance.saturating_add(reward); + .checked_sub(actual_reward) + .expect("This should not happen"); + proof.balance = proof.balance.saturating_add(actual_reward); // Hash recent slot hash into the next challenge to prevent pre-mining attacks proof.challenge = hashv(&[ diff --git a/src/processor/reset.rs b/src/processor/reset.rs index 96b66b8..2b27b6f 100644 --- a/src/processor/reset.rs +++ b/src/processor/reset.rs @@ -18,7 +18,7 @@ use crate::{ /// Reset sets up the Ore program for the next epoch. Its responsibilities include: /// 1. Reset bus account rewards counters. /// 2. Adjust the reward rate to stabilize inflation. -/// 3. Top up the treasury token account to backup claims. +/// 3. Top up the treasury token account to fund claims. /// /// Safety requirements: /// - Reset is a permissionless instruction and can be invoked by any signer. @@ -29,8 +29,11 @@ use crate::{ /// Discussion: /// - It is important that `reset` can only be invoked once per 60 second period to ensure the supply growth rate /// stays within the guaranteed bounds of 0 ≤ R ≤ 2 ORE/min. -/// - The reward rate is dynamically adjusted based on last epoch's actual reward rate (proxy for hashpower) to -/// target an average supply growth rate of 1 ORE/min. +/// - The reward rate is dynamically adjusted based on last epoch's theoretical reward rate to target an average +/// supply growth rate of 1 ORE/min. +/// - The "theoretical" reward rate refers to the amount that would have been paid out if rewards were not capped by +/// the bus limits. It's necessary to use this value to ensure the reward rate update calculation accurately +/// accounts for the difficulty of submitted hashes. pub fn process_reset<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], @@ -85,17 +88,21 @@ pub fn process_reset<'a, 'info>( // Reset bus accounts and calculate actual rewards mined since last reset let mut total_remaining_rewards = 0u64; + let mut total_theoretical_rewards = 0u64; for i in 0..BUS_COUNT { let mut bus_data = busses[i].data.borrow_mut(); let bus = Bus::try_from_bytes_mut(&mut bus_data)?; total_remaining_rewards = total_remaining_rewards.saturating_add(bus.rewards); + total_theoretical_rewards = + total_theoretical_rewards.saturating_add(bus.theoretical_rewards); bus.rewards = BUS_EPOCH_REWARDS; + bus.theoretical_rewards = 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_epoch_rewards); + calculate_new_reward_rate(config.base_reward_rate, total_theoretical_rewards); // Fund treasury token account solana_program::program::invoke_signed( @@ -135,9 +142,9 @@ pub(crate) fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) - } // Calculate new reward rate. - let new_rate = (current_rate) - .saturating_mul(TARGET_EPOCH_REWARDS) - .saturating_div(epoch_rewards) as u64; + let new_rate = (current_rate 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. let new_rate_min = current_rate.saturating_div(SMOOTHING_FACTOR); diff --git a/src/processor/upgrade.rs b/src/processor/upgrade.rs index fa4aeef..4c149f8 100644 --- a/src/processor/upgrade.rs +++ b/src/processor/upgrade.rs @@ -53,8 +53,11 @@ pub fn process_upgrade<'a, 'info>( ], )?; + // Account for decimals change. + // v1 token has 9 decimals. v2 token has 11. + let amount_to_mint = amount.saturating_mul(100); + // Mint to the beneficiary account - // TODO Account for decimals change! let treasury_data = treasury_info.data.borrow(); let treasury = Treasury::try_from_bytes(&treasury_data)?; let treasury_bump = treasury.bump as u8; @@ -66,7 +69,7 @@ pub fn process_upgrade<'a, 'info>( beneficiary_info.key, treasury_info.key, &[treasury_info.key], - amount, + amount_to_mint, )?, &[ token_program.clone(), diff --git a/src/state/bus.rs b/src/state/bus.rs index 849fe36..a01fd49 100644 --- a/src/state/bus.rs +++ b/src/state/bus.rs @@ -17,6 +17,10 @@ pub struct Bus { /// The quantity of rewards this bus can issue in the current epoch epoch. pub rewards: u64, + + // TODO Come up with better name + /// The rewards that would have been paid out this epoch if the bus had no limit. + pub theoretical_rewards: u64, } impl Discriminator for Bus {