From 618a15ae1bfb38d6fbfd6782228955c83b31aa84 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 24 Aug 2024 13:57:00 +0000 Subject: [PATCH 01/30] scaffolding boosts --- Cargo.lock | 16 ++++++++++++++++ Cargo.toml | 1 + program/Cargo.toml | 1 + program/src/mine.rs | 16 +++++++++++++++- 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index de7049f..877210a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1302,6 +1302,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ore-boost-api" +version = "0.1.0" +dependencies = [ + "array-const-fn-init", + "bytemuck", + "const-crypto", + "num_enum", + "ore-utils", + "solana-program", + "spl-token", + "static_assertions", + "thiserror", +] + [[package]] name = "ore-program" version = "2.1.9" @@ -1309,6 +1324,7 @@ dependencies = [ "drillx", "mpl-token-metadata", "ore-api", + "ore-boost-api", "ore-utils", "rand 0.8.5", "solana-program", diff --git a/Cargo.toml b/Cargo.toml index 6d4b8d8..eb9ee67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ drillx = { version = "2.0.0", features = ["solana"] } mpl-token-metadata = "4.1.2" num_enum = "0.7.2" ore-api = { path = "api", version = "2.1.9" } +ore-boost-api = { path = "../ore-boost/api", version = "0.1.0" } ore-utils = { path = "utils", features = ["spl"], version = "2.1.9" } solana-program = "^1.18" spl-token = { version = "^4", features = ["no-entrypoint"] } diff --git a/program/Cargo.toml b/program/Cargo.toml index 70af79c..36e57aa 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -21,6 +21,7 @@ default = [] drillx.workspace = true mpl-token-metadata.workspace = true ore-api.workspace = true +ore-boost-api.workspace = true ore-utils.workspace = true solana-program.workspace = true spl-token.workspace = true diff --git a/program/src/mine.rs b/program/src/mine.rs index 9efd3dc..0732de2 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -9,6 +9,7 @@ use ore_api::{ loaders::*, state::{Bus, Config, Proof}, }; +use ore_boost_api::state::{Boost, Stake}; use ore_utils::*; use solana_program::program::set_return_data; #[allow(deprecated)] @@ -31,8 +32,9 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult let args = Mine::try_from_bytes(data)?; // Load accounts. + let (required_accounts, optional_accounts) = accounts.split_at(6); let [signer, bus_info, config_info, proof_info, instructions_sysvar, slot_hashes_sysvar] = - accounts + required_accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -129,6 +131,18 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult } } + if !optional_accounts.is_empty() { + let [boost_info, stake_info] = optional_accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let boost_data = boost_info.data.borrow(); + let boost = Boost::try_from_bytes(&boost_data)?; + + let stake_data = stake_info.data.borrow(); + let stake = Stake::try_from_bytes(&stake_data)?; + } + // Apply liveness penalty. // // The liveness penalty exists to ensure there is no "invisible" hashpower on the network. It From 041efb2ff07fd59a611e10554d8c5db09db070ec Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 25 Aug 2024 12:01:53 +0000 Subject: [PATCH 02/30] reset update --- program/src/reset.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/program/src/reset.rs b/program/src/reset.rs index eda9489..b6ea0a7 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -63,9 +63,9 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul let bus = Bus::try_from_bytes_mut(&mut bus_data)?; // Track top balance. - if bus.top_balance.gt(&top_balance) { - top_balance = bus.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); From 66dd9e32ecb1a5a606f6fd2a30eb2f124364eed9 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 25 Aug 2024 12:32:26 +0000 Subject: [PATCH 03/30] update boosts --- program/src/mine.rs | 55 +++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/program/src/mine.rs b/program/src/mine.rs index 0732de2..59c3caa 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -107,41 +107,30 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult .checked_mul(2u64.checked_pow(normalized_difficulty).unwrap()) .unwrap(); - // Apply staking multiplier. + // Apply boosts. // - // 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 to protect against flash loan attacks. - let mut bus_data = bus_info.data.borrow_mut(); - let bus = Bus::try_from_bytes_mut(&mut bus_data)?; - if proof.balance.gt(&0) && proof.last_stake_at.saturating_add(ONE_MINUTE).lt(&t) { - // Calculate staking reward. - if config.top_balance.gt(&0) { - let staking_reward = (reward as u128) - .checked_mul(proof.balance.min(config.top_balance) as u128) - .unwrap() - .checked_div(config.top_balance as u128) - .unwrap() as u64; - reward = reward.checked_add(staking_reward).unwrap(); - } - - // Update bus stake tracker. - if proof.balance.gt(&bus.top_balance) { - bus.top_balance = proof.balance; + // Boosts are incentives that can multiply a miner's rewards by staking tokens in the ORE Boosts program. + // Up to 3 boosts can be applied on any given mine operation. + // Parse optional accounts + for i in 0..2 { + // Parse optional accounts, two at a time. + let (boost_accounts, optional_accounts) = optional_accounts.split_at(2); + if let [boost_info, stake_info] = boost_accounts { + // Only apply boost if last stake was greater than one minute ago. + let boost_data = boost_info.data.borrow(); + let boost = Boost::try_from_bytes(&boost_data)?; + let stake_data = stake_info.data.borrow(); + let stake = Stake::try_from_bytes(&stake_data)?; + if stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { + // TODO + } } } - - if !optional_accounts.is_empty() { - let [boost_info, stake_info] = optional_accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - let boost_data = boost_info.data.borrow(); - let boost = Boost::try_from_bytes(&boost_data)?; - - let stake_data = stake_info.data.borrow(); - let stake = Stake::try_from_bytes(&stake_data)?; - } + // if !optional_accounts.is_empty() { + // let [boost_info, stake_info] = optional_accounts else { + // return Err(ProgramError::NotEnoughAccountKeys); + // }; + // } // Apply liveness penalty. // @@ -175,6 +164,8 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // // Busses are limited to distributing 1 ORE per epoch. This is also the maximum amount that will be paid out // for any given hash. + let mut bus_data = bus_info.data.borrow_mut(); + let bus = Bus::try_from_bytes_mut(&mut bus_data)?; let reward_actual = reward.min(bus.rewards).min(ONE_ORE); // Update balances. From 803faa52f01305a3871b4d5aab18a2f9c1596ee1 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 25 Aug 2024 12:44:19 +0000 Subject: [PATCH 04/30] todo --- program/src/mine.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/program/src/mine.rs b/program/src/mine.rs index 59c3caa..d92f5c1 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -116,6 +116,8 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Parse optional accounts, two at a time. let (boost_accounts, optional_accounts) = optional_accounts.split_at(2); if let [boost_info, stake_info] = boost_accounts { + // TODO Load accounts + // Only apply boost if last stake was greater than one minute ago. let boost_data = boost_info.data.borrow(); let boost = Boost::try_from_bytes(&boost_data)?; From 66cb07244aa8447ec1c6276fc0724095998a4777 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Mon, 26 Aug 2024 21:58:42 +0000 Subject: [PATCH 05/30] implement three boost system --- api/src/error.rs | 2 ++ program/src/mine.rs | 46 +++++++++++++++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/api/src/error.rs b/api/src/error.rs index b0653ba..2e3d81e 100644 --- a/api/src/error.rs +++ b/api/src/error.rs @@ -21,6 +21,8 @@ pub enum OreError { MaxSupply = 6, #[error("The proof does not match the expected account")] AuthFailed = 7, + #[error("The same boost cannot be applied twice")] + DuplicateBoost = 8, } error!(OreError); diff --git a/program/src/mine.rs b/program/src/mine.rs index d92f5c1..069236b 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -9,7 +9,10 @@ use ore_api::{ loaders::*, state::{Bus, Config, Proof}, }; -use ore_boost_api::state::{Boost, Stake}; +use ore_boost_api::{ + loaders::{load_any_boost, load_stake}, + state::{Boost, Stake}, +}; use ore_utils::*; use solana_program::program::set_return_data; #[allow(deprecated)] @@ -111,28 +114,43 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // // Boosts are incentives that can multiply a miner's rewards by staking tokens in the ORE Boosts program. // Up to 3 boosts can be applied on any given mine operation. - // Parse optional accounts - for i in 0..2 { - // Parse optional accounts, two at a time. - let (boost_accounts, optional_accounts) = optional_accounts.split_at(2); - if let [boost_info, stake_info] = boost_accounts { - // TODO Load accounts + let mut boosts = [Pubkey::new_from_array([0; 32]); 3]; + for i in 0..3 { + if optional_accounts.len().gt(&(i * 2)) { + // Load optional accounts. + let boost_info = optional_accounts[i * 2].clone(); + let stake_info = optional_accounts[i * 2 + 1].clone(); + load_any_boost(&boost_info, false)?; + load_stake(&stake_info, &proof.authority, boost_info.key, false)?; - // Only apply boost if last stake was greater than one minute ago. + // Reject if boost is applied twice. + if boosts.contains(boost_info.key) { + return Err(OreError::DuplicateBoost.into()); + } + + // Record this boost has been used. + boosts[i] = *boost_info.key; + + // Parse account data. let boost_data = boost_info.data.borrow(); let boost = Boost::try_from_bytes(&boost_data)?; let stake_data = stake_info.data.borrow(); let stake = Stake::try_from_bytes(&stake_data)?; + + // Apply multiplier if last stake was greater than one minute ago. if stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { - // TODO + let multiplier = boost.multiplier.checked_sub(1).unwrap(); + let multiplier_reward = reward + .checked_mul(multiplier) + .unwrap() + .checked_mul(stake.balance) + .unwrap() + .checked_div(boost.total_stake) + .unwrap(); + reward = reward.checked_add(multiplier_reward).unwrap(); } } } - // if !optional_accounts.is_empty() { - // let [boost_info, stake_info] = optional_accounts else { - // return Err(ProgramError::NotEnoughAccountKeys); - // }; - // } // Apply liveness penalty. // From 834d93d3b2c33d63cfeda56cc3b159afa64906a7 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 27 Aug 2024 15:02:13 +0000 Subject: [PATCH 06/30] comment out unused var --- program/src/reset.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/program/src/reset.rs b/program/src/reset.rs index b6ea0a7..a507b62 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -56,7 +56,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul // Reset bus accounts and calculate actual rewards mined since last reset. let mut total_remaining_rewards = 0u64; let mut total_theoretical_rewards = 0u64; - let mut top_balance = 0u64; + // let mut top_balance = 0u64; for i in 0..BUS_COUNT { // Parse bus account. let mut bus_data = busses[i].data.borrow_mut(); @@ -80,7 +80,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_remaining_rewards); // Update global top balance. - config.top_balance = top_balance; + config.top_balance = 0; // top_balance; // Update base reward rate for next epoch. config.base_reward_rate = From 133b1d7f7349775f44ed05cf7875bee33b8ab214 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 27 Aug 2024 20:27:27 +0000 Subject: [PATCH 07/30] spl associated dep --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 877210a..08e25f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1312,6 +1312,7 @@ dependencies = [ "num_enum", "ore-utils", "solana-program", + "spl-associated-token-account", "spl-token", "static_assertions", "thiserror", From d1d9770c33fbc02f0396916229f070f10461b7b2 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 28 Aug 2024 16:37:56 +0000 Subject: [PATCH 08/30] fix overflow issue --- program/src/mine.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/program/src/mine.rs b/program/src/mine.rs index 069236b..627e4d3 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -14,7 +14,6 @@ use ore_boost_api::{ state::{Boost, Stake}, }; use ore_utils::*; -use solana_program::program::set_return_data; #[allow(deprecated)] use solana_program::{ account_info::AccountInfo, @@ -28,6 +27,7 @@ use solana_program::{ slot_hashes::SlotHash, sysvar::{self, Sysvar}, }; +use solana_program::{log, program::set_return_data}; /// Mine validates hashes and increments a miner's collectable balance. pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { @@ -105,15 +105,17 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult let normalized_difficulty = difficulty .checked_sub(config.min_difficulty as u32) .unwrap(); - let mut reward = config + let base_reward = config .base_reward_rate .checked_mul(2u64.checked_pow(normalized_difficulty).unwrap()) .unwrap(); + let mut reward = base_reward; // Apply boosts. // // Boosts are incentives that can multiply a miner's rewards by staking tokens in the ORE Boosts program. // Up to 3 boosts can be applied on any given mine operation. + log::sol_log(&format!("Base: {}", base_reward)); let mut boosts = [Pubkey::new_from_array([0; 32]); 3]; for i in 0..3 { if optional_accounts.len().gt(&(i * 2)) { @@ -140,14 +142,15 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Apply multiplier if last stake was greater than one minute ago. if stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { let multiplier = boost.multiplier.checked_sub(1).unwrap(); - let multiplier_reward = reward - .checked_mul(multiplier) + let boost_reward = (base_reward as u128) + .checked_mul(multiplier as u128) .unwrap() - .checked_mul(stake.balance) + .checked_mul(stake.balance as u128) .unwrap() - .checked_div(boost.total_stake) - .unwrap(); - reward = reward.checked_add(multiplier_reward).unwrap(); + .checked_div(boost.total_stake as u128) + .unwrap() as u64; + log::sol_log(&format!("Boost: {}", boost_reward)); + reward = reward.checked_add(boost_reward).unwrap(); } } } From 5bf5b4619bc0c2bcef2c1591be6d0c06313ce925 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 10 Sep 2024 05:01:08 +0000 Subject: [PATCH 09/30] boost expires at --- program/src/mine.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/program/src/mine.rs b/program/src/mine.rs index 627e4d3..0335e6c 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -105,17 +105,16 @@ 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 reward = config .base_reward_rate .checked_mul(2u64.checked_pow(normalized_difficulty).unwrap()) .unwrap(); - let mut reward = base_reward; // Apply boosts. // // Boosts are incentives that can multiply a miner's rewards by staking tokens in the ORE Boosts program. // Up to 3 boosts can be applied on any given mine operation. - log::sol_log(&format!("Base: {}", base_reward)); + log::sol_log(&format!("Base: {}", reward)); let mut boosts = [Pubkey::new_from_array([0; 32]); 3]; for i in 0..3 { if optional_accounts.len().gt(&(i * 2)) { @@ -139,10 +138,11 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult let stake_data = stake_info.data.borrow(); let stake = Stake::try_from_bytes(&stake_data)?; - // Apply multiplier if last stake was greater than one minute ago. - if stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { + // Apply multiplier if boost is not expired and + // last stake was greater than one minute ago. + if boost.expires_at.gt(&t) && stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { let multiplier = boost.multiplier.checked_sub(1).unwrap(); - let boost_reward = (base_reward as u128) + let boost_reward = (reward as u128) .checked_mul(multiplier as u128) .unwrap() .checked_mul(stake.balance as u128) From b828d36b75d4b6ddedeab980498d684a12a37d1e Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 10 Sep 2024 20:24:09 +0000 Subject: [PATCH 10/30] sdk --- api/src/instruction.rs | 247 ---------------------------------------- api/src/lib.rs | 1 + api/src/sdk.rs | 248 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 249 insertions(+), 247 deletions(-) create mode 100644 api/src/sdk.rs diff --git a/api/src/instruction.rs b/api/src/instruction.rs index 0ee2457..7010646 100644 --- a/api/src/instruction.rs +++ b/api/src/instruction.rs @@ -1,17 +1,6 @@ use bytemuck::{Pod, Zeroable}; -use drillx::Solution; use num_enum::TryFromPrimitive; use ore_utils::*; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, sysvar, -}; - -use crate::{ - consts::*, - state::{bus_pda, config_pda, proof_pda, treasury_pda}, -}; #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)] @@ -99,239 +88,3 @@ instruction!(OreInstruction, Stake); instruction!(OreInstruction, Update); instruction!(OreInstruction, Upgrade); instruction!(OreInstruction, Initialize); - -/// Builds an auth instruction. -pub fn auth(proof: Pubkey) -> Instruction { - Instruction { - program_id: NOOP_PROGRAM_ID, - accounts: vec![], - data: proof.to_bytes().to_vec(), - } -} - -/// Builds a claim instruction. -pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { - let proof = proof_pda(signer).0; - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(beneficiary, false), - AccountMeta::new(proof, false), - AccountMeta::new_readonly(TREASURY_ADDRESS, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: Claim { - amount: amount.to_le_bytes(), - } - .to_bytes(), - } -} - -/// Builds a close instruction. -pub fn close(signer: Pubkey) -> Instruction { - let proof = proof_pda(signer).0; - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(proof, false), - AccountMeta::new_readonly(solana_program::system_program::id(), false), - ], - data: Close {}.to_bytes(), - } -} - -/// Builds a mine instruction. -pub fn mine(signer: Pubkey, authority: Pubkey, bus: Pubkey, solution: Solution) -> Instruction { - let proof = proof_pda(authority).0; - Instruction { - program_id: crate::id(), - 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), - ], - data: Mine { - digest: solution.d, - nonce: solution.n, - } - .to_bytes(), - } -} - -/// Builds an open instruction. -pub fn open(signer: Pubkey, miner: Pubkey, payer: Pubkey) -> Instruction { - let proof_pda = proof_pda(signer); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new_readonly(miner, false), - AccountMeta::new(payer, true), - AccountMeta::new(proof_pda.0, false), - AccountMeta::new_readonly(solana_program::system_program::id(), false), - AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), - ], - data: Open { bump: proof_pda.1 }.to_bytes(), - } -} - -/// Builds a reset instruction. -pub fn reset(signer: Pubkey) -> Instruction { - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(BUS_ADDRESSES[0], false), - AccountMeta::new(BUS_ADDRESSES[1], false), - AccountMeta::new(BUS_ADDRESSES[2], false), - AccountMeta::new(BUS_ADDRESSES[3], false), - AccountMeta::new(BUS_ADDRESSES[4], false), - AccountMeta::new(BUS_ADDRESSES[5], false), - AccountMeta::new(BUS_ADDRESSES[6], false), - AccountMeta::new(BUS_ADDRESSES[7], false), - AccountMeta::new(CONFIG_ADDRESS, false), - AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: Reset {}.to_bytes(), - } -} - -/// Build a stake instruction. -pub fn stake(signer: Pubkey, sender: Pubkey, amount: u64) -> Instruction { - let proof = proof_pda(signer).0; - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(proof, false), - AccountMeta::new(sender, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: Stake { - amount: amount.to_le_bytes(), - } - .to_bytes(), - } -} - -// Build an update instruction. -pub fn update(signer: Pubkey, miner: Pubkey) -> Instruction { - let proof = proof_pda(signer).0; - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new_readonly(miner, false), - AccountMeta::new(proof, false), - ], - data: Update {}.to_bytes(), - } -} - -// Build an upgrade instruction. -pub fn upgrade(signer: Pubkey, beneficiary: Pubkey, sender: Pubkey, amount: u64) -> Instruction { - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(beneficiary, false), - AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(MINT_V1_ADDRESS, false), - AccountMeta::new(sender, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: Upgrade { - amount: amount.to_le_bytes(), - } - .to_bytes(), - } -} - -/// Builds an initialize instruction. -pub fn initialize(signer: Pubkey) -> Instruction { - let bus_pdas = [ - bus_pda(0), - bus_pda(1), - bus_pda(2), - bus_pda(3), - bus_pda(4), - bus_pda(5), - bus_pda(6), - bus_pda(7), - ]; - let config_pda = config_pda(); - let mint_pda = Pubkey::find_program_address(&[MINT, MINT_NOISE.as_slice()], &crate::id()); - let treasury_pda = treasury_pda(); - let treasury_tokens = - spl_associated_token_account::get_associated_token_address(&treasury_pda.0, &mint_pda.0); - let metadata_pda = Pubkey::find_program_address( - &[ - METADATA, - mpl_token_metadata::ID.as_ref(), - mint_pda.0.as_ref(), - ], - &mpl_token_metadata::ID, - ); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus_pdas[0].0, false), - AccountMeta::new(bus_pdas[1].0, false), - AccountMeta::new(bus_pdas[2].0, false), - AccountMeta::new(bus_pdas[3].0, false), - AccountMeta::new(bus_pdas[4].0, false), - AccountMeta::new(bus_pdas[5].0, false), - AccountMeta::new(bus_pdas[6].0, false), - AccountMeta::new(bus_pdas[7].0, false), - AccountMeta::new(config_pda.0, false), - AccountMeta::new(metadata_pda.0, false), - AccountMeta::new(mint_pda.0, false), - AccountMeta::new(treasury_pda.0, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(system_program::id(), false), - AccountMeta::new_readonly(spl_token::id(), false), - AccountMeta::new_readonly(spl_associated_token_account::id(), false), - AccountMeta::new_readonly(mpl_token_metadata::ID, false), - AccountMeta::new_readonly(sysvar::rent::id(), false), - ], - data: Initialize { - bus_0_bump: bus_pdas[0].1, - bus_1_bump: bus_pdas[1].1, - bus_2_bump: bus_pdas[2].1, - bus_3_bump: bus_pdas[3].1, - bus_4_bump: bus_pdas[4].1, - bus_5_bump: bus_pdas[5].1, - bus_6_bump: bus_pdas[6].1, - bus_7_bump: bus_pdas[7].1, - config_bump: config_pda.1, - metadata_bump: metadata_pda.1, - mint_bump: mint_pda.1, - treasury_bump: treasury_pda.1, - } - .to_bytes(), - } -} diff --git a/api/src/lib.rs b/api/src/lib.rs index 780b94f..75b6aba 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -3,6 +3,7 @@ pub mod error; pub mod event; pub mod instruction; pub mod loaders; +pub mod sdk; pub mod state; use solana_program::declare_id; diff --git a/api/src/sdk.rs b/api/src/sdk.rs new file mode 100644 index 0000000..67793dc --- /dev/null +++ b/api/src/sdk.rs @@ -0,0 +1,248 @@ +use drillx::Solution; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, sysvar, +}; + +use crate::{ + consts::*, + instruction::*, + state::{bus_pda, config_pda, proof_pda, treasury_pda}, +}; + +/// Builds an auth instruction. +pub fn auth(proof: Pubkey) -> Instruction { + Instruction { + program_id: NOOP_PROGRAM_ID, + accounts: vec![], + data: proof.to_bytes().to_vec(), + } +} + +/// Builds a claim instruction. +pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { + let proof = proof_pda(signer).0; + let treasury_tokens = spl_associated_token_account::get_associated_token_address( + &TREASURY_ADDRESS, + &MINT_ADDRESS, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(beneficiary, false), + AccountMeta::new(proof, false), + AccountMeta::new_readonly(TREASURY_ADDRESS, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: Claim { + amount: amount.to_le_bytes(), + } + .to_bytes(), + } +} + +/// Builds a close instruction. +pub fn close(signer: Pubkey) -> Instruction { + let proof = proof_pda(signer).0; + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(proof, false), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + ], + data: Close {}.to_bytes(), + } +} + +/// Builds a mine instruction. +pub fn mine(signer: Pubkey, authority: Pubkey, bus: Pubkey, solution: Solution) -> Instruction { + let proof = proof_pda(authority).0; + Instruction { + program_id: crate::id(), + 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), + ], + data: Mine { + digest: solution.d, + nonce: solution.n, + } + .to_bytes(), + } +} + +/// Builds an open instruction. +pub fn open(signer: Pubkey, miner: Pubkey, payer: Pubkey) -> Instruction { + let proof_pda = proof_pda(signer); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new_readonly(miner, false), + AccountMeta::new(payer, true), + AccountMeta::new(proof_pda.0, false), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), + ], + data: Open { bump: proof_pda.1 }.to_bytes(), + } +} + +/// Builds a reset instruction. +pub fn reset(signer: Pubkey) -> Instruction { + let treasury_tokens = spl_associated_token_account::get_associated_token_address( + &TREASURY_ADDRESS, + &MINT_ADDRESS, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(BUS_ADDRESSES[0], false), + AccountMeta::new(BUS_ADDRESSES[1], false), + AccountMeta::new(BUS_ADDRESSES[2], false), + AccountMeta::new(BUS_ADDRESSES[3], false), + AccountMeta::new(BUS_ADDRESSES[4], false), + AccountMeta::new(BUS_ADDRESSES[5], false), + AccountMeta::new(BUS_ADDRESSES[6], false), + AccountMeta::new(BUS_ADDRESSES[7], false), + AccountMeta::new(CONFIG_ADDRESS, false), + AccountMeta::new(MINT_ADDRESS, false), + AccountMeta::new(TREASURY_ADDRESS, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: Reset {}.to_bytes(), + } +} + +/// Build a stake instruction. +pub fn stake(signer: Pubkey, sender: Pubkey, amount: u64) -> Instruction { + let proof = proof_pda(signer).0; + let treasury_tokens = spl_associated_token_account::get_associated_token_address( + &TREASURY_ADDRESS, + &MINT_ADDRESS, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(proof, false), + AccountMeta::new(sender, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: Stake { + amount: amount.to_le_bytes(), + } + .to_bytes(), + } +} + +// Build an update instruction. +pub fn update(signer: Pubkey, miner: Pubkey) -> Instruction { + let proof = proof_pda(signer).0; + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new_readonly(miner, false), + AccountMeta::new(proof, false), + ], + data: Update {}.to_bytes(), + } +} + +// Build an upgrade instruction. +pub fn upgrade(signer: Pubkey, beneficiary: Pubkey, sender: Pubkey, amount: u64) -> Instruction { + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(beneficiary, false), + AccountMeta::new(MINT_ADDRESS, false), + AccountMeta::new(MINT_V1_ADDRESS, false), + AccountMeta::new(sender, false), + AccountMeta::new(TREASURY_ADDRESS, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: Upgrade { + amount: amount.to_le_bytes(), + } + .to_bytes(), + } +} + +/// Builds an initialize instruction. +pub fn initialize(signer: Pubkey) -> Instruction { + let bus_pdas = [ + bus_pda(0), + bus_pda(1), + bus_pda(2), + bus_pda(3), + bus_pda(4), + bus_pda(5), + bus_pda(6), + bus_pda(7), + ]; + let config_pda = config_pda(); + let mint_pda = Pubkey::find_program_address(&[MINT, MINT_NOISE.as_slice()], &crate::id()); + let treasury_pda = treasury_pda(); + let treasury_tokens = + spl_associated_token_account::get_associated_token_address(&treasury_pda.0, &mint_pda.0); + let metadata_pda = Pubkey::find_program_address( + &[ + METADATA, + mpl_token_metadata::ID.as_ref(), + mint_pda.0.as_ref(), + ], + &mpl_token_metadata::ID, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(bus_pdas[0].0, false), + AccountMeta::new(bus_pdas[1].0, false), + AccountMeta::new(bus_pdas[2].0, false), + AccountMeta::new(bus_pdas[3].0, false), + AccountMeta::new(bus_pdas[4].0, false), + AccountMeta::new(bus_pdas[5].0, false), + AccountMeta::new(bus_pdas[6].0, false), + AccountMeta::new(bus_pdas[7].0, false), + AccountMeta::new(config_pda.0, false), + AccountMeta::new(metadata_pda.0, false), + AccountMeta::new(mint_pda.0, false), + AccountMeta::new(treasury_pda.0, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(spl_associated_token_account::id(), false), + AccountMeta::new_readonly(mpl_token_metadata::ID, false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + ], + data: Initialize { + bus_0_bump: bus_pdas[0].1, + bus_1_bump: bus_pdas[1].1, + bus_2_bump: bus_pdas[2].1, + bus_3_bump: bus_pdas[3].1, + bus_4_bump: bus_pdas[4].1, + bus_5_bump: bus_pdas[5].1, + bus_6_bump: bus_pdas[6].1, + bus_7_bump: bus_pdas[7].1, + config_bump: config_pda.1, + metadata_bump: metadata_pda.1, + mint_bump: mint_pda.1, + treasury_bump: treasury_pda.1, + } + .to_bytes(), + } +} From 70e62a85813378ebd4c582e93031abdbd25ceab2 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 24 Sep 2024 05:28:14 +0000 Subject: [PATCH 11/30] reimplement staking multiplier --- program/src/mine.rs | 24 ++++++++++++++++++++++++ program/src/reset.rs | 10 +++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/program/src/mine.rs b/program/src/mine.rs index 0335e6c..0daba4b 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -110,6 +110,30 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult .checked_mul(2u64.checked_pow(normalized_difficulty).unwrap()) .unwrap(); + // 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 to protect against flash loan attacks. + let mut bus_data = bus_info.data.borrow_mut(); + let bus = Bus::try_from_bytes_mut(&mut bus_data)?; + if proof.balance.gt(&0) && proof.last_stake_at.saturating_add(ONE_MINUTE).lt(&t) { + // Calculate staking reward. + if config.top_balance.gt(&0) { + let staking_reward = (reward as u128) + .checked_mul(proof.balance.min(config.top_balance) as u128) + .unwrap() + .checked_div(config.top_balance as u128) + .unwrap() as u64; + reward = reward.checked_add(staking_reward).unwrap(); + } + + // Update bus stake tracker. + if proof.balance.gt(&bus.top_balance) { + bus.top_balance = proof.balance; + } + } + // Apply boosts. // // Boosts are incentives that can multiply a miner's rewards by staking tokens in the ORE Boosts program. diff --git a/program/src/reset.rs b/program/src/reset.rs index a507b62..eda9489 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -56,16 +56,16 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul // Reset bus accounts and calculate actual rewards mined since last reset. let mut total_remaining_rewards = 0u64; let mut total_theoretical_rewards = 0u64; - // let mut top_balance = 0u64; + let mut top_balance = 0u64; for i in 0..BUS_COUNT { // Parse bus account. let mut bus_data = busses[i].data.borrow_mut(); let bus = Bus::try_from_bytes_mut(&mut bus_data)?; // Track top balance. - // if bus.top_balance.gt(&top_balance) { - // top_balance = bus.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); @@ -80,7 +80,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_remaining_rewards); // Update global top balance. - config.top_balance = 0; // top_balance; + config.top_balance = top_balance; // Update base reward rate for next epoch. config.base_reward_rate = From cae0eeb480b75b16b3ea245c0f9cd924470939ee Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 24 Sep 2024 05:33:25 +0000 Subject: [PATCH 12/30] remove unneeded data borrow --- program/src/mine.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/program/src/mine.rs b/program/src/mine.rs index 0daba4b..d0fc76a 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -211,8 +211,6 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // // Busses are limited to distributing 1 ORE per epoch. This is also the maximum amount that will be paid out // for any given hash. - let mut bus_data = bus_info.data.borrow_mut(); - let bus = Bus::try_from_bytes_mut(&mut bus_data)?; let reward_actual = reward.min(bus.rewards).min(ONE_ORE); // Update balances. From fde50aae8a3f417d232a53ad547d8e7bc219d864 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 24 Aug 2024 13:57:00 +0000 Subject: [PATCH 13/30] scaffolding boosts --- Cargo.lock | 16 ++++++++++++++++ Cargo.toml | 1 + program/Cargo.toml | 1 + program/src/mine.rs | 16 +++++++++++++++- 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index de7049f..877210a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1302,6 +1302,21 @@ dependencies = [ "thiserror", ] +[[package]] +name = "ore-boost-api" +version = "0.1.0" +dependencies = [ + "array-const-fn-init", + "bytemuck", + "const-crypto", + "num_enum", + "ore-utils", + "solana-program", + "spl-token", + "static_assertions", + "thiserror", +] + [[package]] name = "ore-program" version = "2.1.9" @@ -1309,6 +1324,7 @@ dependencies = [ "drillx", "mpl-token-metadata", "ore-api", + "ore-boost-api", "ore-utils", "rand 0.8.5", "solana-program", diff --git a/Cargo.toml b/Cargo.toml index 6d4b8d8..eb9ee67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ drillx = { version = "2.0.0", features = ["solana"] } mpl-token-metadata = "4.1.2" num_enum = "0.7.2" ore-api = { path = "api", version = "2.1.9" } +ore-boost-api = { path = "../ore-boost/api", version = "0.1.0" } ore-utils = { path = "utils", features = ["spl"], version = "2.1.9" } solana-program = "^1.18" spl-token = { version = "^4", features = ["no-entrypoint"] } diff --git a/program/Cargo.toml b/program/Cargo.toml index 70af79c..36e57aa 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -21,6 +21,7 @@ default = [] drillx.workspace = true mpl-token-metadata.workspace = true ore-api.workspace = true +ore-boost-api.workspace = true ore-utils.workspace = true solana-program.workspace = true spl-token.workspace = true diff --git a/program/src/mine.rs b/program/src/mine.rs index 9efd3dc..0732de2 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -9,6 +9,7 @@ use ore_api::{ loaders::*, state::{Bus, Config, Proof}, }; +use ore_boost_api::state::{Boost, Stake}; use ore_utils::*; use solana_program::program::set_return_data; #[allow(deprecated)] @@ -31,8 +32,9 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult let args = Mine::try_from_bytes(data)?; // Load accounts. + let (required_accounts, optional_accounts) = accounts.split_at(6); let [signer, bus_info, config_info, proof_info, instructions_sysvar, slot_hashes_sysvar] = - accounts + required_accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -129,6 +131,18 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult } } + if !optional_accounts.is_empty() { + let [boost_info, stake_info] = optional_accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let boost_data = boost_info.data.borrow(); + let boost = Boost::try_from_bytes(&boost_data)?; + + let stake_data = stake_info.data.borrow(); + let stake = Stake::try_from_bytes(&stake_data)?; + } + // Apply liveness penalty. // // The liveness penalty exists to ensure there is no "invisible" hashpower on the network. It From 6504eebf3698054eab2e801c32fbc7459fd7c6b2 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 25 Aug 2024 12:01:53 +0000 Subject: [PATCH 14/30] reset update --- program/src/reset.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/program/src/reset.rs b/program/src/reset.rs index eda9489..b6ea0a7 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -63,9 +63,9 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul let bus = Bus::try_from_bytes_mut(&mut bus_data)?; // Track top balance. - if bus.top_balance.gt(&top_balance) { - top_balance = bus.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); From 1bed190d1200ac0fc8ea1ade4877b7e5352e5e78 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 25 Aug 2024 12:32:26 +0000 Subject: [PATCH 15/30] update boosts --- program/src/mine.rs | 55 +++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/program/src/mine.rs b/program/src/mine.rs index 0732de2..59c3caa 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -107,41 +107,30 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult .checked_mul(2u64.checked_pow(normalized_difficulty).unwrap()) .unwrap(); - // Apply staking multiplier. + // Apply boosts. // - // 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 to protect against flash loan attacks. - let mut bus_data = bus_info.data.borrow_mut(); - let bus = Bus::try_from_bytes_mut(&mut bus_data)?; - if proof.balance.gt(&0) && proof.last_stake_at.saturating_add(ONE_MINUTE).lt(&t) { - // Calculate staking reward. - if config.top_balance.gt(&0) { - let staking_reward = (reward as u128) - .checked_mul(proof.balance.min(config.top_balance) as u128) - .unwrap() - .checked_div(config.top_balance as u128) - .unwrap() as u64; - reward = reward.checked_add(staking_reward).unwrap(); - } - - // Update bus stake tracker. - if proof.balance.gt(&bus.top_balance) { - bus.top_balance = proof.balance; + // Boosts are incentives that can multiply a miner's rewards by staking tokens in the ORE Boosts program. + // Up to 3 boosts can be applied on any given mine operation. + // Parse optional accounts + for i in 0..2 { + // Parse optional accounts, two at a time. + let (boost_accounts, optional_accounts) = optional_accounts.split_at(2); + if let [boost_info, stake_info] = boost_accounts { + // Only apply boost if last stake was greater than one minute ago. + let boost_data = boost_info.data.borrow(); + let boost = Boost::try_from_bytes(&boost_data)?; + let stake_data = stake_info.data.borrow(); + let stake = Stake::try_from_bytes(&stake_data)?; + if stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { + // TODO + } } } - - if !optional_accounts.is_empty() { - let [boost_info, stake_info] = optional_accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - let boost_data = boost_info.data.borrow(); - let boost = Boost::try_from_bytes(&boost_data)?; - - let stake_data = stake_info.data.borrow(); - let stake = Stake::try_from_bytes(&stake_data)?; - } + // if !optional_accounts.is_empty() { + // let [boost_info, stake_info] = optional_accounts else { + // return Err(ProgramError::NotEnoughAccountKeys); + // }; + // } // Apply liveness penalty. // @@ -175,6 +164,8 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // // Busses are limited to distributing 1 ORE per epoch. This is also the maximum amount that will be paid out // for any given hash. + let mut bus_data = bus_info.data.borrow_mut(); + let bus = Bus::try_from_bytes_mut(&mut bus_data)?; let reward_actual = reward.min(bus.rewards).min(ONE_ORE); // Update balances. From ccc6293676ef7f1d0db03084cf358c09b7e7b7a5 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 25 Aug 2024 12:44:19 +0000 Subject: [PATCH 16/30] todo --- program/src/mine.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/program/src/mine.rs b/program/src/mine.rs index 59c3caa..d92f5c1 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -116,6 +116,8 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Parse optional accounts, two at a time. let (boost_accounts, optional_accounts) = optional_accounts.split_at(2); if let [boost_info, stake_info] = boost_accounts { + // TODO Load accounts + // Only apply boost if last stake was greater than one minute ago. let boost_data = boost_info.data.borrow(); let boost = Boost::try_from_bytes(&boost_data)?; From 771ba5eb0576c90a6c3d49be1608db72a2ea765a Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Mon, 26 Aug 2024 21:58:42 +0000 Subject: [PATCH 17/30] implement three boost system --- api/src/error.rs | 2 ++ program/src/mine.rs | 46 +++++++++++++++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/api/src/error.rs b/api/src/error.rs index b0653ba..2e3d81e 100644 --- a/api/src/error.rs +++ b/api/src/error.rs @@ -21,6 +21,8 @@ pub enum OreError { MaxSupply = 6, #[error("The proof does not match the expected account")] AuthFailed = 7, + #[error("The same boost cannot be applied twice")] + DuplicateBoost = 8, } error!(OreError); diff --git a/program/src/mine.rs b/program/src/mine.rs index d92f5c1..069236b 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -9,7 +9,10 @@ use ore_api::{ loaders::*, state::{Bus, Config, Proof}, }; -use ore_boost_api::state::{Boost, Stake}; +use ore_boost_api::{ + loaders::{load_any_boost, load_stake}, + state::{Boost, Stake}, +}; use ore_utils::*; use solana_program::program::set_return_data; #[allow(deprecated)] @@ -111,28 +114,43 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // // Boosts are incentives that can multiply a miner's rewards by staking tokens in the ORE Boosts program. // Up to 3 boosts can be applied on any given mine operation. - // Parse optional accounts - for i in 0..2 { - // Parse optional accounts, two at a time. - let (boost_accounts, optional_accounts) = optional_accounts.split_at(2); - if let [boost_info, stake_info] = boost_accounts { - // TODO Load accounts + let mut boosts = [Pubkey::new_from_array([0; 32]); 3]; + for i in 0..3 { + if optional_accounts.len().gt(&(i * 2)) { + // Load optional accounts. + let boost_info = optional_accounts[i * 2].clone(); + let stake_info = optional_accounts[i * 2 + 1].clone(); + load_any_boost(&boost_info, false)?; + load_stake(&stake_info, &proof.authority, boost_info.key, false)?; - // Only apply boost if last stake was greater than one minute ago. + // Reject if boost is applied twice. + if boosts.contains(boost_info.key) { + return Err(OreError::DuplicateBoost.into()); + } + + // Record this boost has been used. + boosts[i] = *boost_info.key; + + // Parse account data. let boost_data = boost_info.data.borrow(); let boost = Boost::try_from_bytes(&boost_data)?; let stake_data = stake_info.data.borrow(); let stake = Stake::try_from_bytes(&stake_data)?; + + // Apply multiplier if last stake was greater than one minute ago. if stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { - // TODO + let multiplier = boost.multiplier.checked_sub(1).unwrap(); + let multiplier_reward = reward + .checked_mul(multiplier) + .unwrap() + .checked_mul(stake.balance) + .unwrap() + .checked_div(boost.total_stake) + .unwrap(); + reward = reward.checked_add(multiplier_reward).unwrap(); } } } - // if !optional_accounts.is_empty() { - // let [boost_info, stake_info] = optional_accounts else { - // return Err(ProgramError::NotEnoughAccountKeys); - // }; - // } // Apply liveness penalty. // From f0f25d2c1f25dc7950e4a52445b53e7876df31f3 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 27 Aug 2024 15:02:13 +0000 Subject: [PATCH 18/30] comment out unused var --- program/src/reset.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/program/src/reset.rs b/program/src/reset.rs index b6ea0a7..a507b62 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -56,7 +56,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul // Reset bus accounts and calculate actual rewards mined since last reset. let mut total_remaining_rewards = 0u64; let mut total_theoretical_rewards = 0u64; - let mut top_balance = 0u64; + // let mut top_balance = 0u64; for i in 0..BUS_COUNT { // Parse bus account. let mut bus_data = busses[i].data.borrow_mut(); @@ -80,7 +80,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_remaining_rewards); // Update global top balance. - config.top_balance = top_balance; + config.top_balance = 0; // top_balance; // Update base reward rate for next epoch. config.base_reward_rate = From f88bb945ece653025e8e35e8e2f8e99edb8ceafb Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 27 Aug 2024 20:27:27 +0000 Subject: [PATCH 19/30] spl associated dep --- Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.lock b/Cargo.lock index 877210a..08e25f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1312,6 +1312,7 @@ dependencies = [ "num_enum", "ore-utils", "solana-program", + "spl-associated-token-account", "spl-token", "static_assertions", "thiserror", From 7e6dc82f45b9aafd00eaae33d5a98588420be7d6 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 28 Aug 2024 16:37:56 +0000 Subject: [PATCH 20/30] fix overflow issue --- program/src/mine.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/program/src/mine.rs b/program/src/mine.rs index 069236b..627e4d3 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -14,7 +14,6 @@ use ore_boost_api::{ state::{Boost, Stake}, }; use ore_utils::*; -use solana_program::program::set_return_data; #[allow(deprecated)] use solana_program::{ account_info::AccountInfo, @@ -28,6 +27,7 @@ use solana_program::{ slot_hashes::SlotHash, sysvar::{self, Sysvar}, }; +use solana_program::{log, program::set_return_data}; /// Mine validates hashes and increments a miner's collectable balance. pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { @@ -105,15 +105,17 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult let normalized_difficulty = difficulty .checked_sub(config.min_difficulty as u32) .unwrap(); - let mut reward = config + let base_reward = config .base_reward_rate .checked_mul(2u64.checked_pow(normalized_difficulty).unwrap()) .unwrap(); + let mut reward = base_reward; // Apply boosts. // // Boosts are incentives that can multiply a miner's rewards by staking tokens in the ORE Boosts program. // Up to 3 boosts can be applied on any given mine operation. + log::sol_log(&format!("Base: {}", base_reward)); let mut boosts = [Pubkey::new_from_array([0; 32]); 3]; for i in 0..3 { if optional_accounts.len().gt(&(i * 2)) { @@ -140,14 +142,15 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Apply multiplier if last stake was greater than one minute ago. if stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { let multiplier = boost.multiplier.checked_sub(1).unwrap(); - let multiplier_reward = reward - .checked_mul(multiplier) + let boost_reward = (base_reward as u128) + .checked_mul(multiplier as u128) .unwrap() - .checked_mul(stake.balance) + .checked_mul(stake.balance as u128) .unwrap() - .checked_div(boost.total_stake) - .unwrap(); - reward = reward.checked_add(multiplier_reward).unwrap(); + .checked_div(boost.total_stake as u128) + .unwrap() as u64; + log::sol_log(&format!("Boost: {}", boost_reward)); + reward = reward.checked_add(boost_reward).unwrap(); } } } From c2e6b83835cea5dff45c1f7715ccefe7e254715f Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 10 Sep 2024 05:01:08 +0000 Subject: [PATCH 21/30] boost expires at --- program/src/mine.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/program/src/mine.rs b/program/src/mine.rs index 627e4d3..0335e6c 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -105,17 +105,16 @@ 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 reward = config .base_reward_rate .checked_mul(2u64.checked_pow(normalized_difficulty).unwrap()) .unwrap(); - let mut reward = base_reward; // Apply boosts. // // Boosts are incentives that can multiply a miner's rewards by staking tokens in the ORE Boosts program. // Up to 3 boosts can be applied on any given mine operation. - log::sol_log(&format!("Base: {}", base_reward)); + log::sol_log(&format!("Base: {}", reward)); let mut boosts = [Pubkey::new_from_array([0; 32]); 3]; for i in 0..3 { if optional_accounts.len().gt(&(i * 2)) { @@ -139,10 +138,11 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult let stake_data = stake_info.data.borrow(); let stake = Stake::try_from_bytes(&stake_data)?; - // Apply multiplier if last stake was greater than one minute ago. - if stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { + // Apply multiplier if boost is not expired and + // last stake was greater than one minute ago. + if boost.expires_at.gt(&t) && stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { let multiplier = boost.multiplier.checked_sub(1).unwrap(); - let boost_reward = (base_reward as u128) + let boost_reward = (reward as u128) .checked_mul(multiplier as u128) .unwrap() .checked_mul(stake.balance as u128) From 7e931a1254d6a12a1c63ea5e48da7f306c85cd05 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 24 Sep 2024 05:28:14 +0000 Subject: [PATCH 22/30] reimplement staking multiplier --- program/src/mine.rs | 24 ++++++++++++++++++++++++ program/src/reset.rs | 10 +++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/program/src/mine.rs b/program/src/mine.rs index 0335e6c..0daba4b 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -110,6 +110,30 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult .checked_mul(2u64.checked_pow(normalized_difficulty).unwrap()) .unwrap(); + // 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 to protect against flash loan attacks. + let mut bus_data = bus_info.data.borrow_mut(); + let bus = Bus::try_from_bytes_mut(&mut bus_data)?; + if proof.balance.gt(&0) && proof.last_stake_at.saturating_add(ONE_MINUTE).lt(&t) { + // Calculate staking reward. + if config.top_balance.gt(&0) { + let staking_reward = (reward as u128) + .checked_mul(proof.balance.min(config.top_balance) as u128) + .unwrap() + .checked_div(config.top_balance as u128) + .unwrap() as u64; + reward = reward.checked_add(staking_reward).unwrap(); + } + + // Update bus stake tracker. + if proof.balance.gt(&bus.top_balance) { + bus.top_balance = proof.balance; + } + } + // Apply boosts. // // Boosts are incentives that can multiply a miner's rewards by staking tokens in the ORE Boosts program. diff --git a/program/src/reset.rs b/program/src/reset.rs index a507b62..eda9489 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -56,16 +56,16 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul // Reset bus accounts and calculate actual rewards mined since last reset. let mut total_remaining_rewards = 0u64; let mut total_theoretical_rewards = 0u64; - // let mut top_balance = 0u64; + let mut top_balance = 0u64; for i in 0..BUS_COUNT { // Parse bus account. let mut bus_data = busses[i].data.borrow_mut(); let bus = Bus::try_from_bytes_mut(&mut bus_data)?; // Track top balance. - // if bus.top_balance.gt(&top_balance) { - // top_balance = bus.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); @@ -80,7 +80,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_remaining_rewards); // Update global top balance. - config.top_balance = 0; // top_balance; + config.top_balance = top_balance; // Update base reward rate for next epoch. config.base_reward_rate = From 161ddd9cd2da04f048dd301c77ac81ad688d9d6e Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 24 Sep 2024 05:33:25 +0000 Subject: [PATCH 23/30] remove unneeded data borrow --- program/src/mine.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/program/src/mine.rs b/program/src/mine.rs index 0daba4b..d0fc76a 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -211,8 +211,6 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // // Busses are limited to distributing 1 ORE per epoch. This is also the maximum amount that will be paid out // for any given hash. - let mut bus_data = bus_info.data.borrow_mut(); - let bus = Bus::try_from_bytes_mut(&mut bus_data)?; let reward_actual = reward.min(bus.rewards).min(ONE_ORE); // Update balances. From dae14334d3a889ca6eff6719956120d409303545 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 24 Sep 2024 05:55:15 +0000 Subject: [PATCH 24/30] skip duplicate boost --- api/src/error.rs | 2 -- program/src/mine.rs | 7 +++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/api/src/error.rs b/api/src/error.rs index 2e3d81e..b0653ba 100644 --- a/api/src/error.rs +++ b/api/src/error.rs @@ -21,8 +21,6 @@ pub enum OreError { MaxSupply = 6, #[error("The proof does not match the expected account")] AuthFailed = 7, - #[error("The same boost cannot be applied twice")] - DuplicateBoost = 8, } error!(OreError); diff --git a/program/src/mine.rs b/program/src/mine.rs index d0fc76a..488a09b 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -148,9 +148,9 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult load_any_boost(&boost_info, false)?; load_stake(&stake_info, &proof.authority, boost_info.key, false)?; - // Reject if boost is applied twice. + // Skip if boost is applied twice. if boosts.contains(boost_info.key) { - return Err(OreError::DuplicateBoost.into()); + continue; } // Record this boost has been used. @@ -162,8 +162,7 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult let stake_data = stake_info.data.borrow(); let stake = Stake::try_from_bytes(&stake_data)?; - // Apply multiplier if boost is not expired and - // last stake was greater than one minute ago. + // Apply multiplier if boost is not expired and last stake at was more than one minute ago. if boost.expires_at.gt(&t) && stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { let multiplier = boost.multiplier.checked_sub(1).unwrap(); let boost_reward = (reward as u128) From 4d66db69dcfb9d073402a2a73ed624eb003aff11 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 24 Sep 2024 05:56:19 +0000 Subject: [PATCH 25/30] var name --- program/src/mine.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/program/src/mine.rs b/program/src/mine.rs index 488a09b..2d3aba2 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -139,7 +139,7 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Boosts are incentives that can multiply a miner's rewards by staking tokens in the ORE Boosts program. // Up to 3 boosts can be applied on any given mine operation. log::sol_log(&format!("Base: {}", reward)); - let mut boosts = [Pubkey::new_from_array([0; 32]); 3]; + let mut applied_boosts = [Pubkey::new_from_array([0; 32]); 3]; for i in 0..3 { if optional_accounts.len().gt(&(i * 2)) { // Load optional accounts. @@ -149,12 +149,12 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult load_stake(&stake_info, &proof.authority, boost_info.key, false)?; // Skip if boost is applied twice. - if boosts.contains(boost_info.key) { + if applied_boosts.contains(boost_info.key) { continue; } // Record this boost has been used. - boosts[i] = *boost_info.key; + applied_boosts[i] = *boost_info.key; // Parse account data. let boost_data = boost_info.data.borrow(); From 78ebb7a8d65b597fb7844db1975f679dc0f5bced Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 23 Sep 2024 23:54:24 -0700 Subject: [PATCH 26/30] rebased --- Cargo.lock | 18 +++++++++++++++--- program/src/mine.rs | 27 +++++++++++++++++++++------ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08e25f3..7e58d32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1294,7 +1294,7 @@ dependencies = [ "drillx", "mpl-token-metadata", "num_enum", - "ore-utils", + "ore-utils 2.1.9", "solana-program", "spl-associated-token-account", "spl-token", @@ -1310,7 +1310,7 @@ dependencies = [ "bytemuck", "const-crypto", "num_enum", - "ore-utils", + "ore-utils 2.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "solana-program", "spl-associated-token-account", "spl-token", @@ -1326,7 +1326,7 @@ dependencies = [ "mpl-token-metadata", "ore-api", "ore-boost-api", - "ore-utils", + "ore-utils 2.1.9", "rand 0.8.5", "solana-program", "spl-associated-token-account", @@ -1343,6 +1343,18 @@ dependencies = [ "spl-token", ] +[[package]] +name = "ore-utils" +version = "2.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d6933b14c84fb91ad7ab2e8fccff589884a81507c1a198dea4f03f3180ef4" +dependencies = [ + "bytemuck", + "solana-program", + "spl-associated-token-account", + "spl-token", +] + [[package]] name = "parking_lot" version = "0.12.3" diff --git a/program/src/mine.rs b/program/src/mine.rs index 2d3aba2..e2d02e7 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -13,7 +13,7 @@ use ore_boost_api::{ loaders::{load_any_boost, load_stake}, state::{Boost, Stake}, }; -use ore_utils::*; +use ore_utils::{load_signer, load_sysvar}; #[allow(deprecated)] use solana_program::{ account_info::AccountInfo, @@ -56,7 +56,10 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Validate epoch is active. let config_data = config_info.data.borrow(); - let config = Config::try_from_bytes(&config_data)?; + let config = { + use ore_utils::AccountDeserialize; + Config::try_from_bytes(&config_data)? + }; let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; if config .last_reset_at @@ -71,7 +74,10 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Here we use drillx to validate the provided solution is a valid hash of the challenge. // If invalid, we return an error. let mut proof_data = proof_info.data.borrow_mut(); - let proof = Proof::try_from_bytes_mut(&mut proof_data)?; + let proof = { + use ore_utils::AccountDeserialize; + Proof::try_from_bytes_mut(&mut proof_data)? + }; let solution = Solution::new(args.digest, args.nonce); if !solution.is_valid(&proof.challenge) { return Err(OreError::HashInvalid.into()); @@ -116,7 +122,10 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // 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 to protect against flash loan attacks. let mut bus_data = bus_info.data.borrow_mut(); - let bus = Bus::try_from_bytes_mut(&mut bus_data)?; + let bus = { + use ore_utils::AccountDeserialize; + Bus::try_from_bytes_mut(&mut bus_data)? + }; if proof.balance.gt(&0) && proof.last_stake_at.saturating_add(ONE_MINUTE).lt(&t) { // Calculate staking reward. if config.top_balance.gt(&0) { @@ -158,9 +167,15 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Parse account data. let boost_data = boost_info.data.borrow(); - let boost = Boost::try_from_bytes(&boost_data)?; + let boost = { + use ore_boost_api::ore_utils::AccountDeserialize; + Boost::try_from_bytes(&boost_data)? + }; let stake_data = stake_info.data.borrow(); - let stake = Stake::try_from_bytes(&stake_data)?; + let stake = { + use ore_boost_api::ore_utils::AccountDeserialize; + Stake::try_from_bytes(&stake_data)? + }; // Apply multiplier if boost is not expired and last stake at was more than one minute ago. if boost.expires_at.gt(&t) && stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { From 52e93f4af024d0d97c2128bbd31bbb14115a6559 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 24 Sep 2024 00:51:17 -0700 Subject: [PATCH 27/30] program-id --- api/src/consts.rs | 2 +- api/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/consts.rs b/api/src/consts.rs index 20ba668..d356170 100644 --- a/api/src/consts.rs +++ b/api/src/consts.rs @@ -3,7 +3,7 @@ use const_crypto::ed25519; use solana_program::{pubkey, pubkey::Pubkey}; /// The authority allowed to initialize the program. -pub const INITIALIZER_ADDRESS: Pubkey = pubkey!("HBUh9g46wk2X89CvaNN15UmsznP59rh6od1h8JwYAopk"); +pub const INITIALIZER_ADDRESS: Pubkey = pubkey!("DEuG4JnzvMVxMFPoBVvf2GH38mn3ybunMxtfmVU3ms86"); /// The base reward rate to intialize the program with. pub const INITIAL_BASE_REWARD_RATE: u64 = BASE_REWARD_RATE_MIN_THRESHOLD; diff --git a/api/src/lib.rs b/api/src/lib.rs index 75b6aba..e7bc19e 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -8,4 +8,4 @@ pub mod state; use solana_program::declare_id; -declare_id!("oreV2ZymfyeXgNgBdqMkumTqqAprVqgBWQfoYkrtKWQ"); +declare_id!("7dNmnKiRRazHV9c6qzy39iJMLobSAx487XvD3C1hVgvt"); From 44c83f0e725d60a704fd0408c5e363ccc16465d5 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 7 Oct 2024 01:38:29 -0700 Subject: [PATCH 28/30] boost event --- Cargo.lock | 25 ++++++++++--------------- api/src/event.rs | 8 ++++++++ api/src/sdk.rs | 30 +++++++++++++++++++++--------- program/Cargo.toml | 1 + program/src/mine.rs | 21 ++++++++++++++++++--- 5 files changed, 58 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e58d32..775dab6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -246,6 +246,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bincode" version = "1.3.3" @@ -1294,7 +1300,7 @@ dependencies = [ "drillx", "mpl-token-metadata", "num_enum", - "ore-utils 2.1.9", + "ore-utils", "solana-program", "spl-associated-token-account", "spl-token", @@ -1310,7 +1316,7 @@ dependencies = [ "bytemuck", "const-crypto", "num_enum", - "ore-utils 2.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "ore-utils", "solana-program", "spl-associated-token-account", "spl-token", @@ -1322,11 +1328,12 @@ dependencies = [ name = "ore-program" version = "2.1.9" dependencies = [ + "base64 0.22.1", "drillx", "mpl-token-metadata", "ore-api", "ore-boost-api", - "ore-utils 2.1.9", + "ore-utils", "rand 0.8.5", "solana-program", "spl-associated-token-account", @@ -1343,18 +1350,6 @@ dependencies = [ "spl-token", ] -[[package]] -name = "ore-utils" -version = "2.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d6933b14c84fb91ad7ab2e8fccff589884a81507c1a198dea4f03f3180ef4" -dependencies = [ - "bytemuck", - "solana-program", - "spl-associated-token-account", - "spl-token", -] - [[package]] name = "parking_lot" version = "0.12.3" diff --git a/api/src/event.rs b/api/src/event.rs index 9f41f25..096ee76 100644 --- a/api/src/event.rs +++ b/api/src/event.rs @@ -1,5 +1,6 @@ use bytemuck::{Pod, Zeroable}; use ore_utils::*; +use solana_program::pubkey::Pubkey; #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] @@ -9,4 +10,11 @@ pub struct MineEvent { pub timing: i64, } +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct BoostEvent { + pub mint: Pubkey, + pub reward: u64, +} event!(MineEvent); +event!(BoostEvent); diff --git a/api/src/sdk.rs b/api/src/sdk.rs index 67793dc..301d033 100644 --- a/api/src/sdk.rs +++ b/api/src/sdk.rs @@ -59,18 +59,30 @@ pub fn close(signer: Pubkey) -> Instruction { } /// Builds a mine instruction. -pub fn mine(signer: Pubkey, authority: Pubkey, bus: Pubkey, solution: Solution) -> Instruction { +pub fn mine( + signer: Pubkey, + authority: Pubkey, + bus: Pubkey, + solution: Solution, + boost_accounts: Vec, +) -> Instruction { let proof = proof_pda(authority).0; + 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), + ]; + let boost_accounts = boost_accounts + .into_iter() + .map(|pk| AccountMeta::new_readonly(pk, false)) + .collect(); + let accounts = [accounts, boost_accounts].concat(); Instruction { program_id: crate::id(), - 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), - ], + accounts, data: Mine { digest: solution.d, nonce: solution.n, diff --git a/program/Cargo.toml b/program/Cargo.toml index 36e57aa..acfae67 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -26,6 +26,7 @@ ore-utils.workspace = true solana-program.workspace = true spl-token.workspace = true spl-associated-token-account.workspace = true +base64 = "0.22.1" [dev-dependencies] rand = "0.8.5" diff --git a/program/src/mine.rs b/program/src/mine.rs index e2d02e7..bf49a88 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -1,10 +1,11 @@ use std::mem::size_of; +use base64::{prelude::BASE64_STANDARD, Engine}; use drillx::Solution; use ore_api::{ consts::*, error::OreError, - event::MineEvent, + event::{BoostEvent, MineEvent}, instruction::Mine, loaders::*, state::{Bus, Config, Proof}, @@ -27,7 +28,10 @@ use solana_program::{ slot_hashes::SlotHash, sysvar::{self, Sysvar}, }; -use solana_program::{log, program::set_return_data}; +use solana_program::{ + log::{self, sol_log}, + program::set_return_data, +}; /// Mine validates hashes and increments a miner's collectable balance. pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { @@ -149,8 +153,11 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Up to 3 boosts can be applied on any given mine operation. log::sol_log(&format!("Base: {}", reward)); let mut applied_boosts = [Pubkey::new_from_array([0; 32]); 3]; + sol_log(optional_accounts.len().to_string().as_str()); for i in 0..3 { + sol_log(i.to_string().as_str()); if optional_accounts.len().gt(&(i * 2)) { + sol_log("booooost"); // Load optional accounts. let boost_info = optional_accounts[i * 2].clone(); let stake_info = optional_accounts[i * 2 + 1].clone(); @@ -177,6 +184,8 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult Stake::try_from_bytes(&stake_data)? }; + sol_log(format!("expires at: {}", boost.expires_at).as_str()); + sol_log(format!("t: {}", t).as_str()); // Apply multiplier if boost is not expired and last stake at was more than one minute ago. if boost.expires_at.gt(&t) && stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { let multiplier = boost.multiplier.checked_sub(1).unwrap(); @@ -187,8 +196,14 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult .unwrap() .checked_div(boost.total_stake as u128) .unwrap() as u64; - log::sol_log(&format!("Boost: {}", boost_reward)); reward = reward.checked_add(boost_reward).unwrap(); + let boost_event = BoostEvent { + mint: boost.mint, + reward: boost_reward, + }; + let boost_event = boost_event.to_bytes(); + let boost_event = BASE64_STANDARD.encode(boost_event); + sol_log(format!("Boost event: {:}", boost_event).as_str()); } } } From f339895deb5cfe0da55f0f34b78a1a6aca5a4518 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 8 Oct 2024 00:06:02 -0700 Subject: [PATCH 29/30] log bas reward after boost rewards --- program/src/mine.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/program/src/mine.rs b/program/src/mine.rs index bf49a88..ac60892 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -151,13 +151,9 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // // Boosts are incentives that can multiply a miner's rewards by staking tokens in the ORE Boosts program. // Up to 3 boosts can be applied on any given mine operation. - log::sol_log(&format!("Base: {}", reward)); let mut applied_boosts = [Pubkey::new_from_array([0; 32]); 3]; - sol_log(optional_accounts.len().to_string().as_str()); for i in 0..3 { - sol_log(i.to_string().as_str()); if optional_accounts.len().gt(&(i * 2)) { - sol_log("booooost"); // Load optional accounts. let boost_info = optional_accounts[i * 2].clone(); let stake_info = optional_accounts[i * 2 + 1].clone(); @@ -184,10 +180,10 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult Stake::try_from_bytes(&stake_data)? }; - sol_log(format!("expires at: {}", boost.expires_at).as_str()); - sol_log(format!("t: {}", t).as_str()); + let not_expired = boost.expires_at.gt(&t); + let settled = stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t); // Apply multiplier if boost is not expired and last stake at was more than one minute ago. - if boost.expires_at.gt(&t) && stake.last_stake_at.saturating_add(ONE_MINUTE).le(&t) { + if not_expired && settled { let multiplier = boost.multiplier.checked_sub(1).unwrap(); let boost_reward = (reward as u128) .checked_mul(multiplier as u128) @@ -207,6 +203,8 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult } } } + // Log base reward after boost rewards for parsing. + log::sol_log(&format!("Base: {}", reward)); // Apply liveness penalty. // From 748e9317e100555a175aadb05a48af671e25a31b Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 8 Oct 2024 00:43:35 -0700 Subject: [PATCH 30/30] rollback --- api/src/consts.rs | 2 +- api/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/consts.rs b/api/src/consts.rs index d356170..20ba668 100644 --- a/api/src/consts.rs +++ b/api/src/consts.rs @@ -3,7 +3,7 @@ use const_crypto::ed25519; use solana_program::{pubkey, pubkey::Pubkey}; /// The authority allowed to initialize the program. -pub const INITIALIZER_ADDRESS: Pubkey = pubkey!("DEuG4JnzvMVxMFPoBVvf2GH38mn3ybunMxtfmVU3ms86"); +pub const INITIALIZER_ADDRESS: Pubkey = pubkey!("HBUh9g46wk2X89CvaNN15UmsznP59rh6od1h8JwYAopk"); /// The base reward rate to intialize the program with. pub const INITIAL_BASE_REWARD_RATE: u64 = BASE_REWARD_RATE_MIN_THRESHOLD; diff --git a/api/src/lib.rs b/api/src/lib.rs index 5389416..c0e78a6 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -18,4 +18,4 @@ pub mod prelude { use steel::*; -declare_id!("7dNmnKiRRazHV9c6qzy39iJMLobSAx487XvD3C1hVgvt"); +declare_id!("oreV2ZymfyeXgNgBdqMkumTqqAprVqgBWQfoYkrtKWQ");