From acbb1be65fe168b584b3ef78d4cba938a983c0fc Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 16 Jul 2025 17:21:20 -0700 Subject: [PATCH] flush out --- api/src/consts.rs | 3 + api/src/lib.rs | 1 + api/src/state/block.rs | 8 ++- api/src/state/config.rs | 3 + api/src/state/market/vaults.rs | 36 +++++----- program/src/close.rs | 27 +++----- program/src/initialize.rs | 6 +- program/src/lib.rs | 2 - program/src/mine.rs | 118 ++++++++------------------------- program/src/open.rs | 46 ++++++------- program/src/reset.rs | 34 ++++++++-- program/src/swap.rs | 36 +++++++--- 12 files changed, 144 insertions(+), 176 deletions(-) diff --git a/api/src/consts.rs b/api/src/consts.rs index 5276257..8ae0352 100644 --- a/api/src/consts.rs +++ b/api/src/consts.rs @@ -64,6 +64,9 @@ pub const FEE_RATE_BPS: u64 = 100; /// Denominator for fee calculations. pub const DENOMINATOR_BPS: u64 = 10_000; +/// Window to submit hashes, in slots. +pub const MINING_WINDOW: u64 = 1500; + /// Slot window size, used for sandwich resistance. pub const SLOT_WINDOW: u64 = 4; diff --git a/api/src/lib.rs b/api/src/lib.rs index 93852df..32d70d5 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -10,6 +10,7 @@ pub mod prelude { pub use crate::error::*; pub use crate::event::*; pub use crate::instruction::*; + pub use crate::sdk::*; pub use crate::state::*; } diff --git a/api/src/state/block.rs b/api/src/state/block.rs index 54e575f..31902a8 100644 --- a/api/src/state/block.rs +++ b/api/src/state/block.rs @@ -22,7 +22,13 @@ pub struct Block { /// The authority of the miner who submitted the best hash. pub best_hash_miner: Pubkey, - /// The hash of the starting slot, used for random number generation. + /// The slot at which the block starts trading. + pub start_slot: u64, + + /// The slot at which the block ends trading. + pub end_slot: u64, + + /// The hash of the end slot, provided by solana, used for random number generation. pub slot_hash: [u8; 32], /// The total amount of hashpower bought in the block. diff --git a/api/src/state/config.rs b/api/src/state/config.rs index 5e66cda..3fedf25 100644 --- a/api/src/state/config.rs +++ b/api/src/state/config.rs @@ -10,6 +10,9 @@ pub struct Config { // The address that can set the admin. pub admin: Pubkey, + // The block duration in slots. + pub block_duration: u64, + // The address that receives fees. pub fee_collector: Pubkey, diff --git a/api/src/state/market/vaults.rs b/api/src/state/market/vaults.rs index 2df5151..4212125 100644 --- a/api/src/state/market/vaults.rs +++ b/api/src/state/market/vaults.rs @@ -9,27 +9,27 @@ use super::Market; impl Market { /// Sanity check that vaults have reserves for all market debts. /// Assumes the token accounts have already been validated as the market's base and quote vaults. - pub fn check_vaults( - &self, - base_vault: &TokenAccount, - quote_vault: &TokenAccount, - ) -> Result<(), OreError> { - self.check_base_vault(base_vault)?; - self.check_quote_vault(quote_vault)?; - Ok(()) - } + // pub fn check_vaults( + // &self, + // base_vault: &TokenAccount, + // quote_vault: &TokenAccount, + // ) -> Result<(), OreError> { + // self.check_base_vault(base_vault)?; + // self.check_quote_vault(quote_vault)?; + // Ok(()) + // } /// Sanity check that base vault has reserves for all market debts. /// Assumes the token account has already been validated as the market's base vault. - pub fn check_base_vault(&self, base_vault: &TokenAccount) -> Result<(), OreError> { - if base_vault.amount() < self.base.balance { - sol_log(&format!("A base_vault.amount: {}", base_vault.amount())); - sol_log(&format!("A self.base.balance: {}", self.base.balance)); - sol_log("Insufficient base vault reserves"); - return Err(OreError::InsufficientVaultReserves.into()); - } - Ok(()) - } + // pub fn check_base_vault(&self, base_vault: &TokenAccount) -> Result<(), OreError> { + // if base_vault.amount() < self.base.balance { + // sol_log(&format!("A base_vault.amount: {}", base_vault.amount())); + // sol_log(&format!("A self.base.balance: {}", self.base.balance)); + // sol_log("Insufficient base vault reserves"); + // return Err(OreError::InsufficientVaultReserves.into()); + // } + // Ok(()) + // } /// Sanity check that quote vault has reserves for all market debts. /// Assumes the token account has already been validated as the market's quote vault. diff --git a/program/src/close.rs b/program/src/close.rs index a7270e4..b2082d0 100644 --- a/program/src/close.rs +++ b/program/src/close.rs @@ -1,4 +1,4 @@ -use ore_api::{prelude::*, sdk::program_log}; +use ore_api::prelude::*; use steel::*; /// Closes a block. @@ -11,9 +11,9 @@ pub fn process_close(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul return Err(ProgramError::NotEnoughAccountKeys); }; signer_info.is_signer()?; - let block = block_info.as_account_mut::(&ore_api::ID)?; - // TODO Ensure block is closed. - // .assert_mut(|b| clock.slot >= b.start_slot + 1500)?; + let block = block_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| clock.slot >= b.end_slot + MINING_WINDOW)?; // Window for submitting hashes has closed mint_info.has_address(&MINT_ADDRESS)?.as_mint()?; opener_info.is_writable()?.has_address(&block.opener)?; treasury_info.as_account::(&ore_api::ID)?; @@ -22,7 +22,7 @@ pub fn process_close(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul ore_program.is_program(&ore_api::ID)?; // Payout block reward. - if block.reward > 0 && block.best_hash_miner != Pubkey::default() { + if block.best_hash_miner != Pubkey::default() { // Load recipient. recipient_info.as_associated_token_account(&block.best_hash_miner, &mint_info.key)?; let miner = miner_info @@ -46,19 +46,11 @@ pub fn process_close(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul reward_amount, &[TREASURY], )?; - - // Emit event. - // RewardEvent { - // disc: OreEvent::Reward as u64, - // amount: reward_amount, - // authority: block.reward.lode_authority, - // block_id: block.id, - // rewards_type: RewardsType::Lode as u64, - // ts: clock.unix_timestamp, - // } - // .log(); } + // Close block. + block_info.close(opener_info)?; + // Emit event. // program_log( // block.id, @@ -73,8 +65,5 @@ pub fn process_close(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul // .to_bytes(), // )?; - // Close block. - block_info.close(opener_info)?; - Ok(()) } diff --git a/program/src/initialize.rs b/program/src/initialize.rs index e19944c..870dfea 100644 --- a/program/src/initialize.rs +++ b/program/src/initialize.rs @@ -2,11 +2,8 @@ use ore_api::prelude::*; use solana_program::program_pack::Pack; use steel::*; -/// Sets the admin. +/// Initializes the program. pub fn process_initialize(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { - // Parse data. - // let args = Initialize::try_from_bytes(data)?; - // Load accounts. let [signer_info, config_info, market_info, mint_info, treasury_info, vault_info, system_program, token_program] = accounts @@ -44,6 +41,7 @@ pub fn process_initialize(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program )?; let config = config_info.as_account_mut::(&ore_api::ID)?; config.admin = *signer_info.key; + config.block_duration = 1500; config.fee_collector = *signer_info.key; config.fee_rate = 0; diff --git a/program/src/lib.rs b/program/src/lib.rs index 3a81f0c..706f53d 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -44,8 +44,6 @@ pub fn process_instruction( OreInstruction::SetAdmin => process_set_admin(accounts, data)?, OreInstruction::SetFeeCollector => process_set_fee_collector(accounts, data)?, OreInstruction::SetFeeRate => process_set_fee_rate(accounts, data)?, - - _ => panic!("Not implemented"), } Ok(()) diff --git a/program/src/mine.rs b/program/src/mine.rs index f1ee027..d0a7d22 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -1,6 +1,5 @@ -use ore_api::{prelude::*, sdk::program_log}; +use ore_api::prelude::*; use solana_nostd_keccak::hash; -use solana_program::{log::sol_log, slot_hashes::SlotHashes}; use steel::*; /// Mine a block. @@ -11,92 +10,44 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Load accounts. let clock = Clock::get()?; - let [signer_info, authority_info, block_info, miner_info, mint_info, recipient_info, treasury_info, system_program, token_program, ore_program, slot_hashes_sysvar] = - accounts - else { + let [signer_info, authority_info, block_info, miner_info, ore_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; signer_info.is_signer()?; authority_info.is_writable()?; - let block = block_info.as_account_mut::(&ore_api::ID)?; - // .assert_mut(|b| clock.slot >= b.start_slot)? - // .assert_mut(|b| clock.slot < b.start_slot + 1500)?; - // let market = market_info - // .as_account::(&ore_api::ID)? - // .assert(|m| m.block_id == block.id)?; - mint_info.has_address(&MINT_ADDRESS)?.as_mint()?; + let block = block_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| clock.slot >= b.end_slot)? // Block has stopped trading + .assert_mut(|b| clock.slot < b.end_slot + MINING_WINDOW)? // Give 1500 slots to submit hashes + .assert_mut(|b| b.slot_hash != [0; 32])?; // Slot hash is set let miner = miner_info .as_account_mut::(&ore_api::ID)? - .assert_mut(|m| m.authority == *authority_info.key)?; - recipient_info - .is_writable()? - .as_associated_token_account(&miner.authority, &MINT_ADDRESS)?; - treasury_info.has_address(&TREASURY_ADDRESS)?; - system_program.is_program(&system_program::ID)?; - token_program.is_program(&spl_token::ID)?; + .assert_mut(|m| m.authority == *authority_info.key)? // Account belongs to authority + .assert_mut(|m| m.block_id <= block.id)?; // Only allow miner to submit hashes in forward bias ore_program.is_program(&ore_api::ID)?; - slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?; - - // Burn hash tokens. - // burn_signed( - // commitment_info, - // mint_hash_info, - // block_info, - // token_program, - // amount, - // &[BLOCK, &block.id.to_le_bytes()], - // )?; - - // Set block slot hash. - // if block.slot_hash == [0; 32] { - // let slot_hashes = - // bincode::deserialize::(slot_hashes_sysvar.data.borrow().as_ref()).unwrap(); - // let Some(slot_hash) = slot_hashes.get(&block.start_slot) else { - // // If mine is not called within ~2.5 minutes of the block starting, - // // then the slot hash will be unavailable and secure hashes cannot be generated. - // return Ok(()); - // }; - // block.slot_hash = slot_hash.to_bytes(); - // } // Reset miner hash if mining new block. - // if miner.block_id != block.id { - // let mut args = [0u8; 104]; - // args[..8].copy_from_slice(&block.id.to_le_bytes()); - // args[8..40].copy_from_slice(&block.slot_hash); - // args[40..72].copy_from_slice(&miner.authority.to_bytes()); - // args[72..].copy_from_slice(&miner.seed); - // miner.hash = hash(&args); - // miner.block_id = block.id; - // } + if miner.block_id != block.id { + let mut args = [0u8; 104]; + args[..8].copy_from_slice(&block.id.to_le_bytes()); + args[8..40].copy_from_slice(&block.slot_hash); + args[40..72].copy_from_slice(&miner.authority.to_bytes()); + args[72..].copy_from_slice(&miner.seed); + miner.hash = hash(&args); + miner.block_id = block.id; + } - // Mine and accumulate rewards. - // let mut nugget_reward = 0; - // for _ in 0..amount { - // // Update stats - // // block.total_deployed += 1; + // Generate secure hash with provided nonce. + let mut seed = [0u8; 40]; + seed[..8].copy_from_slice(&miner.hash.as_ref()); + seed[8..40].copy_from_slice(&nonce.to_le_bytes()); + let h = hash(&seed); - // // Generate hash. - // miner.hash = hash(miner.hash.as_ref()); - - // // Score and increment rewards. - // let score = difficulty(miner.hash) as u64; - // if score >= block.reward.nugget_threshold { - // nugget_reward += block.reward.nugget_reward; - // } - - // // If hash is best hash, update best hash. - // if miner.hash < block.reward.lode_hash { - // block.reward.lode_hash = miner.hash; - // block.reward.lode_authority = miner.authority; - // } - // } - - // Log mint. - // let ore_mint = mint_ore_info.as_mint()?; - // let mint_authority = ore_mint.mint_authority(); - // sol_log(format!("mint_authority: {:?}", mint_authority).as_str()); - // sol_log(format!("treasury: {:?}", treasury_info.key).as_str()); + // If hash is best hash, update best hash. + if h < block.best_hash { + block.best_hash = h; + block.best_hash_miner = miner.authority; + } // Emit event. // program_log( @@ -116,16 +67,3 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult Ok(()) } - -/// Returns the number of leading zeros on a 32 byte buffer. -pub fn difficulty(hash: [u8; 32]) -> u32 { - let mut count = 0; - for &byte in &hash { - let lz = byte.leading_zeros(); - count += lz; - if lz < 8 { - break; - } - } - count -} diff --git a/program/src/open.rs b/program/src/open.rs index 5f13b81..73239b4 100644 --- a/program/src/open.rs +++ b/program/src/open.rs @@ -9,30 +9,21 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult let id = u64::from_le_bytes(args.id); // Load accounts. - let clock = Clock::get()?; - let [signer_info, block_info, system_program, ore_program] = accounts else { + let [signer_info, block_info, market_info, system_program, ore_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; signer_info.is_signer()?; block_info - .is_empty()? - .is_writable()? - .has_seeds(&[BLOCK, &id.to_le_bytes()], &ore_api::ID)?; + .is_empty()? // Account has not been initialized + .is_writable()? // Account is writable + .has_seeds(&[BLOCK, &id.to_le_bytes()], &ore_api::ID)?; // Account has correct seeds + market_info + .as_account::(&ore_api::ID)? + .assert(|m| m.block_id < id)?; // Only allow opening blocks in forward bias system_program.is_program(&system_program::ID)?; ore_program.is_program(&ore_api::ID)?; - // TODO - - // Error out if start slot is within the current period. - let start_slot = id * 1500; - let current_block = clock.slot / 1500; - let current_period_start = current_block * 1500; - let current_period_end = current_period_start + 1500; - if start_slot < current_period_end { - return Err(ProgramError::InvalidArgument); - } - - // Initialize block. + // Create block account. create_program_account::( block_info, system_program, @@ -43,7 +34,12 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult let block = block_info.as_account_mut::(&ore_api::ID)?; block.id = id; block.opener = *signer_info.key; - block.reward = ONE_ORE * generate_lode(block.id) as u64; + block.reward = calculate_reward(block.id); + block.best_hash = [0; 32]; + block.best_hash_miner = Pubkey::default(); + block.start_slot = u64::MAX; // Set by reset + block.end_slot = u64::MAX; // Set by reset + block.slot_hash = [0; 32]; // Set by mine block.total_hashpower = 0; // Emit event. @@ -66,7 +62,7 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult Ok(()) } -fn generate_lode(block_id: u64) -> u8 { +fn calculate_reward(block_id: u64) -> u64 { // Generate noise. let noise_seed = block_id.to_le_bytes(); let noise = hash(&noise_seed); @@ -75,20 +71,18 @@ fn generate_lode(block_id: u64) -> u8 { let byte_value = noise[0]; // Map to 1-10 using integer division - let reward = (byte_value / 25) + 1; + let n = (byte_value / 25) + 1; // Ensure the value doesn't exceed 10 - if reward > 10 { - 10 - } else { - reward - } + let n = n.min(10); + + n as u64 * ONE_ORE } #[test] fn test_lode_rewards() { for i in 0u64..1000 { - let lode_reward = ONE_ORE * generate_lode(i) as u64; + let lode_reward = ONE_ORE * calculate_reward(i) as u64; let target_block_reward = ONE_ORE * 10; let expected_hashes_per_block = HASHPOWER_LIQUIDITY / 2; let expected_qualifying_hashes = diff --git a/program/src/reset.rs b/program/src/reset.rs index bf774cb..9410400 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -1,29 +1,49 @@ use ore_api::prelude::*; +use solana_program::slot_hashes::SlotHashes; use steel::*; /// Resets a block. pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { // Load accounts. - // let clock = Clock::get()?; - let [signer_info, block_info, config_info, fee_collector_info, market_info, mint_info, vault_info, system_program, token_program, ore_program] = + let clock = Clock::get()?; + let [signer_info, block_prev_info, block_next_info, config_info, fee_collector_info, market_info, mint_info, vault_info, system_program, token_program, ore_program, slot_hashes_sysvar] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; signer_info.is_signer()?; - let block = block_info.as_account_mut::(&ore_api::ID)?; + let block_next = block_next_info.as_account_mut::(&ore_api::ID)?; let config = config_info.as_account::(&ore_api::ID)?; fee_collector_info .is_writable()? .as_associated_token_account(&config.fee_collector, &mint_info.key)?; let market = market_info .as_account_mut::(&ore_api::ID)? - .assert_mut(|m| m.block_id == block.id - 1)?; + .assert_mut(|m| m.block_id == block_next.id - 1)?; mint_info.has_address(&MINT_ADDRESS)?.as_mint()?; let vault = vault_info.as_associated_token_account(&mint_info.key, &mint_info.key)?; system_program.is_program(&system_program::ID)?; token_program.is_program(&spl_token::ID)?; ore_program.is_program(&ore_api::ID)?; + slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?; + + // Load previous block if market block ID is not 0. + if market.block_id > 0 { + let block_prev = block_prev_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| b.id == market.block_id)? + .assert_mut(|b| b.end_slot <= clock.slot)?; + + // Set the slot hash of the previous block. + let slot_hashes = + bincode::deserialize::(slot_hashes_sysvar.data.borrow().as_ref()).unwrap(); + let Some(slot_hash) = slot_hashes.get(&block_prev.end_slot) else { + // If mine is not called within ~2.5 minutes of the block starting, + // then the slot hash will be unavailable and secure hashes cannot be generated. + return Ok(()); + }; + block_prev.slot_hash = slot_hash.to_bytes(); + } // Payout fee. if market.fee.uncollected > 0 { @@ -50,7 +70,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul )?; // Reset market account. - market.block_id = block.id; + market.block_id = block_next.id; market.base.balance = HASHPOWER_LIQUIDITY; market.base.balance_virtual = 0; market.quote.balance = 0; @@ -63,5 +83,9 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul market.fee.uncollected = 0; market.fee.cumulative = 0; + // Mark the block start and end slots. + block_next.start_slot = clock.slot; + block_next.end_slot = clock.slot + config.block_duration; + Ok(()) } diff --git a/program/src/swap.rs b/program/src/swap.rs index 53fdf86..e1a86f7 100644 --- a/program/src/swap.rs +++ b/program/src/swap.rs @@ -1,4 +1,4 @@ -use ore_api::{prelude::*, sdk::program_log}; +use ore_api::prelude::*; use steel::*; /// Swap in a hashpower market. @@ -11,25 +11,30 @@ pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Load accounts. let clock = Clock::get()?; - let [signer_info, block_info, market_info, mint_quote_info, tokens_info, vault_info, system_program, token_program, associated_token_program, ore_program] = + let [signer_info, block_info, market_info, miner_info, mint_info, tokens_info, vault_info, system_program, token_program, associated_token_program, ore_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; signer_info.is_signer()?; - let block: &mut Block = block_info.as_account_mut::(&ore_api::ID)?; - // .assert_mut(|b| clock.slot < b.start_slot)?; + let block: &mut Block = block_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|b| b.start_slot <= clock.slot)? + .assert_mut(|b| b.end_slot > clock.slot)?; let market = market_info .as_account_mut::(&ore_api::ID)? .assert_mut(|m| m.block_id == block.id)? .assert_mut(|m| m.base.liquidity() > 0)? .assert_mut(|m| m.quote.liquidity() > 0)?; - mint_quote_info.has_address(&market.quote.mint)?.as_mint()?; + let miner = miner_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|m| m.authority == *signer_info.key)?; + mint_info.has_address(&market.quote.mint)?.as_mint()?; vault_info .is_writable()? .has_address(&vault_pda().0)? .as_token_account()? - .assert(|t| t.mint() == *mint_quote_info.key)? + .assert(|t| t.mint() == *mint_info.key)? .assert(|t| t.owner() == *market_info.key)?; system_program.is_program(&system_program::ID)?; token_program.is_program(&spl_token::ID)?; @@ -42,7 +47,7 @@ pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult signer_info, signer_info, tokens_info, - mint_quote_info, + mint_info, system_program, token_program, associated_token_program, @@ -50,7 +55,7 @@ pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult } else { tokens_info .is_writable()? - .as_associated_token_account(signer_info.key, mint_quote_info.key)?; + .as_associated_token_account(signer_info.key, mint_info.key)?; } // Update market state. @@ -61,6 +66,11 @@ pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Transfer tokens match direction { SwapDirection::Buy => { + // Update hashpower. + block.total_hashpower += swap_event.base_to_transfer; + miner.total_hashpower += swap_event.base_to_transfer; + + // Transfer ORE from signer to market. transfer( signer_info, tokens_info, @@ -70,6 +80,11 @@ pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult )?; } SwapDirection::Sell => { + // Update hashpower. + block.total_hashpower -= swap_event.base_to_transfer; + miner.total_hashpower -= swap_event.base_to_transfer; + + // Trasnfer ORE from market to signer. transfer_signed( market_info, vault_info, @@ -82,9 +97,8 @@ pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult }; // Validate vault reserves. - // let vault_base = vault_base_info.as_token_account()?; - // let vault_quote = vault_quote_info.as_token_account()?; - // market.check_vaults(&vault_base, &vault_quote)?; + let vault = vault_info.as_token_account()?; + market.check_quote_vault(&vault)?; // Emit event. program_log(