diff --git a/api/src/instruction.rs b/api/src/instruction.rs index 2a2f052..70fbfd0 100644 --- a/api/src/instruction.rs +++ b/api/src/instruction.rs @@ -13,6 +13,7 @@ pub enum OreInstruction { // Admin Initialize = 100, + Migrate = 101, } #[repr(C)] @@ -47,6 +48,10 @@ pub struct Update {} #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct Initialize {} +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct Migrate {} + instruction!(OreInstruction, Claim); instruction!(OreInstruction, Close); instruction!(OreInstruction, Mine); @@ -54,3 +59,4 @@ instruction!(OreInstruction, Open); instruction!(OreInstruction, Reset); instruction!(OreInstruction, Update); instruction!(OreInstruction, Initialize); +instruction!(OreInstruction, Migrate); diff --git a/api/src/state/bus.rs b/api/src/state/bus.rs new file mode 100644 index 0000000..71504b3 --- /dev/null +++ b/api/src/state/bus.rs @@ -0,0 +1,25 @@ +use steel::*; + +use super::OreAccount; + +/// Bus accounts are responsible for distributing mining rewards. There are 8 busses total +/// to minimize write-lock contention and allow Solana to process mine instructions in parallel. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct Bus { + /// The ID of the bus account. + pub id: u64, + + /// The remaining rewards this bus has left to payout in the current epoch. + pub rewards: u64, + + /// The rewards this bus would have paid out in the current epoch if there no limit. + /// This is used to calculate the updated reward rate. + pub theoretical_rewards: u64, + + /// The largest known stake balance seen by the bus this epoch. + #[deprecated(since = "2.8.0", note = "Top balance is no longer tracked or used")] + pub top_balance: u64, +} + +account!(OreAccount, Bus); diff --git a/api/src/state/config.rs b/api/src/state/config.rs index d87d9f1..08ff279 100644 --- a/api/src/state/config.rs +++ b/api/src/state/config.rs @@ -1,6 +1,6 @@ use steel::*; -use super::OreAccount; +use super::{OldOreAccount, OreAccount}; /// Config is a singleton account which manages program global variables. #[repr(C)] @@ -22,4 +22,22 @@ pub struct Config { pub block_reward: u64, } +/// Config is a singleton account which manages program global variables. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct OldConfig { + /// 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 minimum accepted difficulty. + pub min_difficulty: u64, + + /// The target emissions rate in ORE/min. + pub target_emmissions_rate: u64, +} + account!(OreAccount, Config); +account!(OldOreAccount, OldConfig); diff --git a/api/src/state/mod.rs b/api/src/state/mod.rs index 1017310..489760a 100644 --- a/api/src/state/mod.rs +++ b/api/src/state/mod.rs @@ -1,7 +1,9 @@ +mod bus; mod config; mod proof; mod treasury; +pub use bus::*; pub use config::*; pub use proof::*; pub use treasury::*; @@ -13,6 +15,7 @@ use crate::consts::*; #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] pub enum OreAccount { + Bus = 100, Config = 101, Proof = 102, Treasury = 103, @@ -32,3 +35,9 @@ pub fn proof_pda(authority: Pubkey) -> (Pubkey, u8) { pub fn treasury_pda() -> (Pubkey, u8) { Pubkey::find_program_address(&[TREASURY], &crate::id()) } + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] +pub enum OldOreAccount { + OldConfig = 101, +} diff --git a/program/src/lib.rs b/program/src/lib.rs index ce9d5f0..15aaa3d 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -1,6 +1,7 @@ mod claim; mod close; mod initialize; +mod migrate; mod mine; mod open; mod reset; @@ -9,13 +10,13 @@ mod update; use claim::*; use close::*; use initialize::*; +use migrate::*; use mine::*; use open::*; -use reset::*; -use update::*; - use ore_api::instruction::*; +use reset::*; use steel::*; +use update::*; pub fn process_instruction( program_id: &Pubkey, @@ -32,6 +33,7 @@ pub fn process_instruction( OreInstruction::Reset => process_reset(accounts, data)?, OreInstruction::Update => process_update(accounts, data)?, OreInstruction::Initialize => process_initialize(accounts, data)?, + OreInstruction::Migrate => process_migrate(accounts, data)?, } Ok(()) diff --git a/program/src/migrate.rs b/program/src/migrate.rs new file mode 100644 index 0000000..c14cc5a --- /dev/null +++ b/program/src/migrate.rs @@ -0,0 +1,124 @@ +use ore_api::prelude::*; +use solana_program::hash; +use steel::*; + +/// Mine validates hashes and increments a miner's claimable balance. +pub fn process_migrate(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + // Parse args. + let args = Migrate::try_from_bytes(data)?; + + // Load accounts. + let clock = Clock::get()?; + let t: i64 = clock.unix_timestamp; + let [signer_info, bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, bus_7_info, config_info, mint_info, treasury_info, treasury_tokens_info, token_program, system_program, slot_hashes_sysvar] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + signer_info.is_signer()?.has_address(&INITIALIZER_ADDRESS); + let config = config_info + .as_account_mut::(&ore_api::ID)? + .assert_mut_err( + |c| t < c.last_reset_at + EPOCH_DURATION, + OreError::NeedsReset.into(), + )?; + let bus_0 = bus_0_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| b.id == 0)?; + let bus_1 = bus_1_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| b.id == 1)?; + let bus_2 = bus_2_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| b.id == 2)?; + let bus_3 = bus_3_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| b.id == 3)?; + let bus_4 = bus_4_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| b.id == 4)?; + let bus_5 = bus_5_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| b.id == 5)?; + let bus_6 = bus_6_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| b.id == 6)?; + let bus_7 = bus_7_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| b.id == 7)?; + mint_info + .is_writable()? + .has_address(&MINT_ADDRESS)? + .as_mint()?; + treasury_info + .has_address(&TREASURY_ADDRESS)? + .is_writable()?; + treasury_tokens_info + .has_address(&TREASURY_TOKENS_ADDRESS)? + .as_associated_token_account(&TREASURY_ADDRESS, &MINT_ADDRESS)?; + token_program.is_program(&spl_token::ID)?; + system_program.is_program(&system_program::ID)?; + slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?; + + let mut total_bus_balance = 0; + total_bus_balance += bus_0.rewards; + total_bus_balance += bus_1.rewards; + total_bus_balance += bus_2.rewards; + total_bus_balance += bus_3.rewards; + total_bus_balance += bus_4.rewards; + total_bus_balance += bus_5.rewards; + total_bus_balance += bus_6.rewards; + total_bus_balance += bus_7.rewards; + + // Reset bus balances + bus_0.rewards = 0; + bus_1.rewards = 0; + bus_2.rewards = 0; + bus_3.rewards = 0; + bus_4.rewards = 0; + bus_5.rewards = 0; + bus_6.rewards = 0; + bus_7.rewards = 0; + + // Delete bus accounts + bus_0_info.close(signer_info)?; + bus_1_info.close(signer_info)?; + bus_2_info.close(signer_info)?; + bus_3_info.close(signer_info)?; + bus_4_info.close(signer_info)?; + bus_5_info.close(signer_info)?; + bus_6_info.close(signer_info)?; + bus_7_info.close(signer_info)?; + + // Burn all tokens in the bus balances + burn_signed( + treasury_tokens_info, + mint_info, + treasury_info, + token_program, + total_bus_balance, + &[TREASURY], + )?; + + // let proof = proof_info + // .as_account_mut::(&ore_api::ID)? + // .assert_mut_err( + // |p| p.miner == *signer_info.key, + // ProgramError::MissingRequiredSignature, + // )?; + + // Compute the hash. + // let solution = hash::hashv(&[ + // args.nonce.as_slice(), + // config.challenge.as_slice(), + // proof.authority.to_bytes().as_slice(), + // ]); + + // // Update the best solution. + // if solution.to_bytes() < config.best_hash { + // config.best_hash = solution.to_bytes(); + // config.best_proof = *proof_info.key; + // } + + Ok(()) +} diff --git a/program/src/mine.rs b/program/src/mine.rs index b6e720d..465680e 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -14,12 +14,7 @@ pub fn process_mine(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { return Err(ProgramError::NotEnoughAccountKeys); }; signer_info.is_signer()?; - let config = config_info - .as_account_mut::(&ore_api::ID)? - .assert_mut_err( - |c| t < c.last_reset_at + EPOCH_DURATION, - OreError::NeedsReset.into(), - )?; + let config = config_info.as_account_mut::(&ore_api::ID)?; let proof = proof_info .as_account_mut::(&ore_api::ID)? .assert_mut_err( @@ -40,5 +35,10 @@ pub fn process_mine(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { config.best_proof = *proof_info.key; } + // Update the proof. + proof.last_hash = solution.to_bytes(); + proof.last_hash_at = t; + proof.total_hashes += 1; + Ok(()) } diff --git a/program/src/reset.rs b/program/src/reset.rs index 5b37bc9..9428f56 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -40,11 +40,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul .assert_mut(|p| p.authority == *boost_config_info.key)?; // Validate enough time has passed since the last reset. - if config - .last_reset_at - .saturating_add(EPOCH_DURATION) - .gt(&clock.unix_timestamp) - { + if clock.unix_timestamp < config.last_reset_at + EPOCH_DURATION { return Ok(()); } @@ -72,7 +68,9 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul // Update proof balances. proof.balance += miner_reward; + proof.total_rewards += miner_reward; boost_proof.balance += boost_reward; + boost_proof.total_rewards += boost_reward; // Fund the treasury. mint_to_signed(