diff --git a/Cargo.lock b/Cargo.lock index 9569fd8..6ddcb7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1266,7 +1266,7 @@ dependencies = [ [[package]] name = "ore-api" -version = "3.4.0" +version = "3.5.0" dependencies = [ "array-const-fn-init", "bytemuck", @@ -1323,11 +1323,11 @@ dependencies = [ [[package]] name = "ore-program" -version = "3.4.0" +version = "3.5.0" dependencies = [ "drillx", "mpl-token-metadata", - "ore-api 3.4.0", + "ore-api 3.5.0", "ore-boost-api 3.0.0", "rand 0.8.5", "solana-program", diff --git a/Cargo.toml b/Cargo.toml index 55cc63d..bb418bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["api", "program"] [workspace.package] -version = "3.4.0" +version = "3.5.0" edition = "2021" license = "Apache-2.0" homepage = "https://ore.supply" diff --git a/api/src/sdk.rs b/api/src/sdk.rs index 728ba74..7ade8a1 100644 --- a/api/src/sdk.rs +++ b/api/src/sdk.rs @@ -56,22 +56,21 @@ pub fn mine( authority: Pubkey, bus: Pubkey, solution: Solution, - boost_keys: Option<[Pubkey; 2]>, + boost: Pubkey, + boost_config: Pubkey, ) -> Instruction { let proof = proof_pda(authority).0; - let mut accounts = vec![ + let accounts = vec![ AccountMeta::new(signer, true), AccountMeta::new(bus, false), AccountMeta::new_readonly(CONFIG_ADDRESS, false), AccountMeta::new(proof, false), AccountMeta::new_readonly(sysvar::instructions::ID, false), AccountMeta::new_readonly(sysvar::slot_hashes::ID, false), + AccountMeta::new_readonly(boost, false), + AccountMeta::new(proof_pda(boost).0, false), + AccountMeta::new_readonly(boost_config, false), ]; - if let Some([boost_address, boost_config_address]) = boost_keys { - accounts.push(AccountMeta::new_readonly(boost_address, false)); - accounts.push(AccountMeta::new(proof_pda(boost_address).0, false)); - accounts.push(AccountMeta::new_readonly(boost_config_address, false)); - } Instruction { program_id: crate::ID, accounts, diff --git a/program/src/mine.rs b/program/src/mine.rs index 5b94597..7e26cf6 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -22,7 +22,7 @@ pub fn process_mine(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { // Load accounts. let clock = Clock::get()?; let t: i64 = clock.unix_timestamp; - let (required_accounts, optional_accounts) = accounts.split_at(6); + let (required_accounts, boost_accounts) = accounts.split_at(6); let [signer_info, bus_info, config_info, proof_info, instructions_sysvar, slot_hashes_sysvar] = required_accounts else { @@ -34,7 +34,7 @@ pub fn process_mine(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { .is_config()? .as_account::(&ore_api::ID)? .assert_err( - |c| c.last_reset_at.saturating_add(EPOCH_DURATION) > t, + |c| t < c.last_reset_at + EPOCH_DURATION, OreError::NeedsReset.into(), )?; let proof = proof_info @@ -46,6 +46,16 @@ pub fn process_mine(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { instructions_sysvar.is_sysvar(&sysvar::instructions::ID)?; slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?; + // Load boost accounts. + let [boost_info, boost_proof_info, boost_config_info] = boost_accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + let boost = boost_info.as_account::(&ore_boost_api::ID)?; + let boost_config = boost_config_info.as_account::(&ore_boost_api::ID)?; + let boost_proof = boost_proof_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|p| p.authority == *boost_info.key)?; + // Authenticate the proof account. // // Only one proof account can be used for any given transaction. All `mine` instructions @@ -88,32 +98,27 @@ pub fn process_mine(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { let normalized_difficulty = difficulty .checked_sub(config.min_difficulty as u32) .unwrap(); - let base_reward = config + let mut base_reward = config .base_reward_rate .checked_mul(2u64.checked_pow(normalized_difficulty).unwrap()) .unwrap(); + // Nullify base reward if boost is invalid. + if boost_config.current != *boost_info.key || t >= boost_config.ts + ROTATION_DURATION { + base_reward = 0; + } + // Apply boosts. // // Boosts are staking incentives that can multiply a miner's rewards. The boost rewards are // split between the miner and staker. let mut boost_reward = 0; - if let [boost_info, _boost_proof_info, boost_config_info] = optional_accounts { - // Load boost accounts. - let boost = boost_info.as_account::(&ore_boost_api::ID)?; - let boost_config = boost_config_info.as_account::(&ore_boost_api::ID)?; - - // Apply multiplier if boost is active, not expired, and last rotation was less than one minute ago - if boost_config.current == *boost_info.key - && t < boost_config.ts + ROTATION_DURATION - && t < boost.expires_at - { - boost_reward = (base_reward as u128) - .checked_mul(boost.multiplier as u128) - .unwrap() - .checked_div(DENOMINATOR_MULTIPLIER as u128) - .unwrap() as u64; - } + if t < boost.expires_at { + boost_reward = (base_reward as u128) + .checked_mul(boost.multiplier as u128) + .unwrap() + .checked_div(DENOMINATOR_MULTIPLIER as u128) + .unwrap() as u64; } // Apply liveness penalty. @@ -198,19 +203,14 @@ pub fn process_mine(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { // Update staker balances. if net_staker_boost_reward > 0 { - if let [boost_info, boost_proof_info, _boost_config_info] = optional_accounts { - let boost_proof = boost_proof_info - .as_account_mut::(&ore_api::ID)? - .assert_mut(|p| p.authority == *boost_info.key)?; - boost_proof.balance = boost_proof - .balance - .checked_add(net_staker_boost_reward) - .unwrap(); - boost_proof.total_rewards = boost_proof - .total_rewards - .checked_add(net_staker_boost_reward) - .unwrap(); - } + boost_proof.balance = boost_proof + .balance + .checked_add(net_staker_boost_reward) + .unwrap(); + boost_proof.total_rewards = boost_proof + .total_rewards + .checked_add(net_staker_boost_reward) + .unwrap(); } // Hash a recent slot hash into the next challenge to prevent pre-mining attacks.