diff --git a/Cargo.toml b/Cargo.toml index 407ba36..bffb131 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,8 @@ const-crypto = "0.1.0" drillx = { version = "2.0.0-beta.1", features = ["solana"] } mpl-token-metadata = "4.1.2" num_enum = "0.7.2" -ore-api = { path = "api", version = "2.0.0-beta.2" } -ore-utils = { path = "utils", version = "2.0.0-beta.2", features = ["spl"] } +ore-api = { path = "api", version = "2.0.0-beta.3" } +ore-utils = { path = "utils", version = "2.0.0-beta.3", features = ["spl"] } shank = "0.3.0" solana-program = "^1.18" spl-token = { version = "^4", features = ["no-entrypoint"] } diff --git a/api/src/consts.rs b/api/src/consts.rs index c22a4d4..e45eabc 100644 --- a/api/src/consts.rs +++ b/api/src/consts.rs @@ -2,17 +2,23 @@ use array_const_fn_init::array_const_fn_init; use const_crypto::ed25519; use solana_program::{pubkey, pubkey::Pubkey}; -/// The reward rate to intialize the program with. -pub const INITIAL_BASE_REWARD_RATE: u64 = 10u64.pow(3u32); +/// The authority allowed to initialize the program. +pub const INITIALIZER_ADDRESS: Pubkey = pubkey!("HBUh9g46wk2X89CvaNN15UmsznP59rh6od1h8JwYAopk"); -/// The admin allowed to initialize the program. -pub const INITIAL_ADMIN: Pubkey = pubkey!("HBUh9g46wk2X89CvaNN15UmsznP59rh6od1h8JwYAopk"); +/// The base reward rate to intialize the program with. +pub const INITIAL_BASE_REWARD_RATE: u64 = 2u64.pow(6); + +/// The minimum allowed base reward rate, at which point the min difficulty should be increased +pub const BASE_REWARD_RATE_MIN_THRESHOLD: u64 = 2u64.pow(5); + +/// The maximum allowed base reward rate, at which point the min difficulty should be decreased. +pub const BASE_REWARD_RATE_MAX_THRESHOLD: u64 = 2u64.pow(8); /// The spam/liveness tolerance in seconds. pub const TOLERANCE: i64 = 5; -/// The minimum difficulty required of all submitted hashes. -pub const MIN_DIFFICULTY: u32 = 8; +/// The minimum difficulty to initialize the program with. +pub const INITIAL_MIN_DIFFICULTY: u32 = 8; /// The decimal precision of the ORE token. /// There are 100 billion indivisible units per ORE (called "grains"). diff --git a/api/src/state/config.rs b/api/src/state/config.rs index 970a5d1..3dacec7 100644 --- a/api/src/state/config.rs +++ b/api/src/state/config.rs @@ -10,20 +10,20 @@ use super::AccountDiscriminator; #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] pub struct Config { - // TODO Remove this - pub admin: Pubkey, - /// The base reward rate paid out for a hash of minimum difficulty. pub base_reward_rate: u64, /// The timestamp of the last reset. pub last_reset_at: i64, - /// The largest known stake balance on the network. - pub max_stake: u64, + /// The minimum accepted difficulty. + pub min_difficulty: u64, /// The address of the proof account with the highest stake balance. pub top_staker: Pubkey, + + /// The largest known stake balance on the network. + pub top_staker_balance: u64, } impl Discriminator for Config { diff --git a/program/src/crown.rs b/program/src/crown.rs index e9a2463..41c2999 100644 --- a/program/src/crown.rs +++ b/program/src/crown.rs @@ -56,8 +56,8 @@ pub fn process_crown<'a, 'info>( } // Crown the new top staker. - config.max_stake = proof_new.balance; config.top_staker = *proof_new_info.key; + config.top_staker_balance = proof_new.balance; Ok(()) } diff --git a/program/src/initialize.rs b/program/src/initialize.rs index dc81adf..6ccbf26 100644 --- a/program/src/initialize.rs +++ b/program/src/initialize.rs @@ -91,7 +91,7 @@ pub fn process_initialize<'a, 'info>( load_sysvar(rent_sysvar, sysvar::rent::id())?; // Check signer - if signer.key.ne(&INITIAL_ADMIN) { + if signer.key.ne(&INITIALIZER_ADDRESS) { return Err(ProgramError::MissingRequiredSignature); } @@ -140,8 +140,9 @@ pub fn process_initialize<'a, 'info>( let config = Config::try_from_bytes_mut(&mut config_data)?; config.base_reward_rate = INITIAL_BASE_REWARD_RATE; config.last_reset_at = 0; - config.max_stake = 0; + config.min_difficulty = INITIAL_MIN_DIFFICULTY as u64; config.top_staker = Pubkey::new_from_array([0; 32]); + config.top_staker_balance = 0; // Initialize treasury create_pda( diff --git a/program/src/mine.rs b/program/src/mine.rs index 4dcb1d0..b5f7a3c 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -97,27 +97,32 @@ pub fn process_mine<'a, 'info>( // Validate hash satisfies the minimnum difficulty. let hash = solution.to_hash(); let difficulty = hash.difficulty(); - sol_log(&format!("Diff {}", difficulty)); - if difficulty.lt(&MIN_DIFFICULTY) { + if difficulty.lt(&(config.min_difficulty as u32)) { return Err(OreError::HashTooEasy.into()); } + + // Normalize difficulty and calculate reward rate + let normalized_difficulty = difficulty + .checked_sub(config.min_difficulty as u32) + .unwrap(); let mut reward = config .base_reward_rate - .checked_mul(2u64.checked_pow(difficulty).unwrap()) + .checked_mul(2u64.checked_pow(normalized_difficulty).unwrap()) .unwrap(); + sol_log(&format!("Diff {}", difficulty)); // Apply staking multiplier. // If user has greater than or equal to the max stake on the network, they receive 2x multiplier. // Any stake less than this will receives between 1x and 2x multipler. The multipler is only active // if the miner's last stake deposit was more than one minute ago. - if config.max_stake.gt(&0) + if config.top_staker_balance.gt(&0) && proof.balance.gt(&0) && proof.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { let staking_reward = (reward as u128) - .checked_mul(proof.balance.min(config.max_stake) as u128) + .checked_mul(proof.balance.min(config.top_staker_balance) as u128) .unwrap() - .checked_div(config.max_stake as u128) + .checked_div(config.top_staker_balance as u128) .unwrap() as u64; reward = reward.checked_add(staking_reward).unwrap(); } diff --git a/program/src/reset.rs b/program/src/reset.rs index 47d151e..35b1a87 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -94,6 +94,18 @@ pub fn process_reset<'a, 'info>( config.base_reward_rate = calculate_new_reward_rate(config.base_reward_rate, total_theoretical_rewards); + // If base_reward_rate is too low, then increment 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, then decrement difficulty by 1 and halve base reward rate + if config.base_reward_rate.ge(&BASE_REWARD_RATE_MAX_THRESHOLD) && config.min_difficulty.gt(&0) { + 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 let mint = Mint::unpack(&mint_info.data.borrow()).expect("Failed to parse mint"); if mint.supply.ge(&MAX_SUPPLY) { @@ -160,7 +172,8 @@ mod tests { use crate::calculate_new_reward_rate; use ore_api::consts::{ - BUS_EPOCH_REWARDS, MAX_EPOCH_REWARDS, SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS, + BASE_REWARD_RATE_MIN_THRESHOLD, BUS_EPOCH_REWARDS, MAX_EPOCH_REWARDS, SMOOTHING_FACTOR, + TARGET_EPOCH_REWARDS, }; const FUZZ_SIZE: u64 = 10_000; @@ -189,6 +202,13 @@ mod tests { assert!(new_rate.lt(¤t_rate)); } + #[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); + assert!(new_rate.lt(¤t_rate)); + } + #[test] fn test_calculate_new_reward_rate_lower_fuzz() { let mut rng = rand::thread_rng();