From 8c96e239816f64ef7b9fbe895b65a606acb347fb Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 18 Jul 2025 16:11:47 -0700 Subject: [PATCH] e2e --- api/src/sdk.rs | 36 ++++++-- api/src/state/mod.rs | 11 +-- cli/src/main.rs | 158 ++++++++++++++++++++++++++++++++-- program/src/initialize.rs | 175 ++++++++++++++++---------------------- program/src/mine.rs | 2 +- program/src/open.rs | 2 +- program/src/reset.rs | 5 +- program/src/swap.rs | 3 +- 8 files changed, 263 insertions(+), 129 deletions(-) diff --git a/api/src/sdk.rs b/api/src/sdk.rs index 756dbe1..7bf19e6 100644 --- a/api/src/sdk.rs +++ b/api/src/sdk.rs @@ -27,7 +27,7 @@ pub fn initialize(signer: Pubkey) -> Instruction { let mint_address = MINT_ADDRESS; let treasury_address = TREASURY_ADDRESS; let treasury_tokens_address = treasury_tokens_address(); - let vault_address = vault_pda().0; + let vault_address = vault_address(); Instruction { program_id: crate::ID, accounts: vec![ @@ -82,6 +82,29 @@ pub fn open(signer: Pubkey, id: u64) -> Instruction { } } +pub fn claim(signer: Pubkey, amount: u64) -> Instruction { + let miner_address = miner_pda(signer).0; + let miner_tokens_address = get_associated_token_address(&signer, &MINT_ADDRESS); + let recipient_address = get_associated_token_address(&signer, &MINT_ADDRESS); + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(miner_address, false), + AccountMeta::new(miner_tokens_address, false), + AccountMeta::new(recipient_address, false), + AccountMeta::new(MINT_ADDRESS, false), + AccountMeta::new_readonly(system_program::ID, false), + AccountMeta::new_readonly(spl_token::ID, false), + AccountMeta::new_readonly(spl_associated_token_account::ID, false), + ], + data: Claim { + amount: amount.to_le_bytes(), + } + .to_bytes(), + } +} + pub fn close(signer: Pubkey, opener: Pubkey, winner: Pubkey, id: u64) -> Instruction { let block_adddress = block_pda(id).0; let miner_tokens_address = get_associated_token_address(&winner, &MINT_ADDRESS); @@ -107,7 +130,7 @@ pub fn close(signer: Pubkey, opener: Pubkey, winner: Pubkey, id: u64) -> Instruc } } -pub fn reset(signer: Pubkey, fee_collector: Pubkey, id: u64) -> Instruction { +pub fn reset(signer: Pubkey, id: u64) -> Instruction { let block_prev_adddress = block_pda(id).0; let block_next_adddress = block_pda(id + 1).0; let config_address = config_pda().0; @@ -115,7 +138,7 @@ pub fn reset(signer: Pubkey, fee_collector: Pubkey, id: u64) -> Instruction { let mint_address = MINT_ADDRESS; let treasury_address = TREASURY_ADDRESS; let treasury_tokens_address = treasury_tokens_address(); - let vault_address = vault_pda().0; + let vault_address = vault_address(); Instruction { program_id: crate::ID, accounts: vec![ @@ -123,7 +146,6 @@ pub fn reset(signer: Pubkey, fee_collector: Pubkey, id: u64) -> Instruction { AccountMeta::new(block_prev_adddress, false), AccountMeta::new(block_next_adddress, false), AccountMeta::new_readonly(config_address, false), - AccountMeta::new(fee_collector, false), AccountMeta::new(market_address, false), AccountMeta::new(mint_address, false), AccountMeta::new(treasury_address, false), @@ -152,8 +174,8 @@ pub fn swap( let config_address = config_pda().0; let market_address = market_pda().0; let miner_address = miner_pda(signer).0; - let tokens_quote_address = get_associated_token_address(&signer, &MINT_ADDRESS); - let vault_address = vault_pda().0; + let tokens_address = get_associated_token_address(&signer, &MINT_ADDRESS); + let vault_address = vault_address(); Instruction { program_id: crate::ID, accounts: vec![ @@ -164,7 +186,7 @@ pub fn swap( AccountMeta::new(market_address, false), AccountMeta::new(miner_address, false), AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(tokens_quote_address, false), + AccountMeta::new(tokens_address, false), AccountMeta::new(vault_address, false), AccountMeta::new_readonly(system_program::ID, false), AccountMeta::new_readonly(spl_token::ID, false), diff --git a/api/src/state/mod.rs b/api/src/state/mod.rs index 7b82ef9..7b25496 100644 --- a/api/src/state/mod.rs +++ b/api/src/state/mod.rs @@ -40,16 +40,9 @@ pub fn miner_pda(authority: Pubkey) -> (Pubkey, u8) { Pubkey::find_program_address(&[MINER, &authority.to_bytes()], &crate::ID) } -pub fn vault_pda() -> (Pubkey, u8) { +pub fn vault_address() -> Pubkey { let market_address = market_pda().0; - Pubkey::find_program_address( - &[ - &market_address.to_bytes(), - &spl_token::ID.to_bytes(), - &MINT_ADDRESS.to_bytes(), - ], - &crate::ID, - ) + spl_associated_token_account::get_associated_token_address(&market_address, &MINT_ADDRESS) } pub fn treasury_pda() -> (Pubkey, u8) { diff --git a/cli/src/main.rs b/cli/src/main.rs index 2d385e4..f6a38a0 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -8,10 +8,12 @@ use solana_client::{ }; use solana_sdk::{ compute_budget::ComputeBudgetInstruction, + keccak::hash, pubkey::Pubkey, signature::{read_keypair_file, Signer}, transaction::Transaction, }; +use spl_token::amount_to_ui_amount; use steel::{AccountDeserialize, Clock, Discriminator}; #[tokio::main] @@ -29,12 +31,27 @@ async fn main() { "clock" => { log_clock(&rpc).await.unwrap(); } + "claim" => { + claim(&rpc, &payer).await.unwrap(); + } + "close" => { + close(&rpc, &payer).await.unwrap(); + } + "close_all" => { + close_all(&rpc, &payer).await.unwrap(); + } + "market" => { + log_market(&rpc).await.unwrap(); + } "block" => { log_block(&rpc).await.unwrap(); } "blocks" => { log_blocks(&rpc).await.unwrap(); } + "mine" => { + mine(&rpc, &payer).await.unwrap(); + } "initialize" => { initialize(&rpc, &payer).await.unwrap(); } @@ -44,6 +61,9 @@ async fn main() { "swap" => { swap(&rpc, &payer).await.unwrap(); } + "reset" => { + reset(&rpc, &payer).await.unwrap(); + } "miner" => { log_miner(&rpc, payer.pubkey()).await.unwrap(); } @@ -66,6 +86,96 @@ async fn initialize( Ok(()) } +async fn close( + rpc: &RpcClient, + payer: &solana_sdk::signer::keypair::Keypair, +) -> Result<(), anyhow::Error> { + let id_str = std::env::var("ID").expect("Missing ID env var"); + let id = id_str.parse::()?; + let block = get_block(rpc, id).await?; + let ix = ore_api::sdk::close(payer.pubkey(), block.opener, block.best_hash_miner, id); + submit_transaction(rpc, payer, &[ix]).await?; + Ok(()) +} + +async fn claim( + rpc: &RpcClient, + payer: &solana_sdk::signer::keypair::Keypair, +) -> Result<(), anyhow::Error> { + let ix = ore_api::sdk::claim(payer.pubkey(), u64::MAX); + submit_transaction(rpc, payer, &[ix]).await?; + Ok(()) +} + +async fn close_all( + rpc: &RpcClient, + payer: &solana_sdk::signer::keypair::Keypair, +) -> Result<(), anyhow::Error> { + let clock = get_clock(rpc).await?; + let blocks = get_blocks(rpc).await?; + for (_, block) in blocks { + if clock.slot > block.end_slot + MINING_WINDOW { + let ix = ore_api::sdk::close( + payer.pubkey(), + block.opener, + block.best_hash_miner, + block.id, + ); + submit_transaction(rpc, payer, &[ix]).await?; + } + } + Ok(()) +} + +async fn mine( + rpc: &RpcClient, + payer: &solana_sdk::signer::keypair::Keypair, +) -> Result<(), anyhow::Error> { + let miner = get_miner(rpc, payer.pubkey()).await?; + let block = get_block(rpc, miner.block_id).await?; + let clock = get_clock(rpc).await?; + if clock.slot < block.end_slot { + return Err(anyhow::anyhow!("Mining window is not yet open.")); + } + if clock.slot >= block.end_slot + MINING_WINDOW { + return Err(anyhow::anyhow!("Mining window is closed.")); + } + let mut best_hash = [u8::MAX; 32]; + let mut best_nonce = 0; + for i in 0..miner.hashpower { + let mut seed = [0u8; 112]; + seed[..8].copy_from_slice(&block.id.to_le_bytes()); + seed[8..40].copy_from_slice(&block.slot_hash); + seed[40..72].copy_from_slice(&miner.authority.to_bytes()); + seed[72..104].copy_from_slice(&miner.seed); + seed[104..].copy_from_slice(&i.to_le_bytes()); + let h = hash(&seed).to_bytes(); + if h < best_hash { + best_hash = h; + best_nonce = i; + } + } + if block.best_hash < best_hash { + return Err(anyhow::anyhow!("A better hash was already found.")); + } + println!("Found best hash: {:?}", best_hash.to_ascii_lowercase()); + let ix = ore_api::sdk::mine(payer.pubkey(), payer.pubkey(), block.id, best_nonce); + submit_transaction(rpc, payer, &[ix]).await?; + Ok(()) +} + +async fn reset( + rpc: &RpcClient, + payer: &solana_sdk::signer::keypair::Keypair, +) -> Result<(), anyhow::Error> { + let market = get_market(rpc).await?; + let id = market.block_id; + let open_ix = ore_api::sdk::open(payer.pubkey(), id + 1); + let reset_ix = ore_api::sdk::reset(payer.pubkey(), id); + submit_transaction(rpc, payer, &[open_ix, reset_ix]).await?; + Ok(()) +} + async fn open( rpc: &RpcClient, payer: &solana_sdk::signer::keypair::Keypair, @@ -81,15 +191,15 @@ async fn swap( rpc: &RpcClient, payer: &solana_sdk::signer::keypair::Keypair, ) -> Result<(), anyhow::Error> { - let id_str = std::env::var("ID").expect("Missing ID env var"); - let id = id_str.parse::()?; + let market = get_market(rpc).await?; + let id = market.block_id; let config = get_config(rpc).await?; let fee_collector = config.fee_collector; let ix = ore_api::sdk::swap( payer.pubkey(), id, fee_collector, - 100_000_000, + 1_000_000_000, SwapDirection::Buy, SwapPrecision::ExactIn, [0; 32], @@ -129,6 +239,16 @@ async fn log_clock(rpc: &RpcClient) -> Result<(), anyhow::Error> { Ok(()) } +async fn log_market(rpc: &RpcClient) -> Result<(), anyhow::Error> { + let market = get_market(&rpc).await?; + let block = get_block(&rpc, market.block_id).await?; + let clock = get_clock(rpc).await?; + print_market(market); + println!(""); + print_block(block, &clock); + Ok(()) +} + async fn log_block(rpc: &RpcClient) -> Result<(), anyhow::Error> { let id_str = std::env::var("ID").expect("Missing ID env var"); let id = id_str.parse::()?; @@ -139,11 +259,32 @@ async fn log_block(rpc: &RpcClient) -> Result<(), anyhow::Error> { } fn print_block(block: Block, clock: &Clock) { - let address = block_pda(block.id).0; let current_slot = clock.slot; - println!("Address: {:?}", address); + println!("Block"); println!(" Id: {:?}", block.id); println!(" Slot hash: {:?}", block.slot_hash); + println!(" Total hashpower: {}", block.total_hashpower); + println!(" Best hash: {:?}", block.best_hash); + println!(" Best hash miner: {:?}", block.best_hash_miner); + println!(" Start slot: {}", block.start_slot); + println!(" End slot: {}", block.end_slot); + println!(" Reward: {}", block.reward); + println!( + " Time remaining: {} sec", + (block.end_slot.saturating_sub(current_slot) as f64) * 0.4 + ); +} + +fn print_market(market: Market) { + println!("Market"); + println!(" Block id: {}", market.block_id); + println!(" Base token: {:?}", market.base); + println!(" Quote token: {:?}", market.quote); + println!(" Fee: {:?}", market.fee); + println!(" Snapshot: {:?}", market.snapshot); + let price = amount_to_ui_amount(market.quote.liquidity() as u64, TOKEN_DECIMALS) + / market.base.liquidity() as f64; + println!(" Price: {:.11?} ORE / hash", price); } async fn log_blocks(rpc: &RpcClient) -> Result<(), anyhow::Error> { @@ -170,6 +311,13 @@ async fn get_config(rpc: &RpcClient) -> Result { Ok(*config) } +async fn get_market(rpc: &RpcClient) -> Result { + let market_pda = ore_api::state::market_pda(); + let account = rpc.get_account(&market_pda.0).await?; + let market = Market::try_from_bytes(&account.data)?; + Ok(*market) +} + async fn get_miner(rpc: &RpcClient, authority: Pubkey) -> Result { let miner_pda = ore_api::state::miner_pda(authority); let account = rpc.get_account(&miner_pda.0).await?; diff --git a/program/src/initialize.rs b/program/src/initialize.rs index 6507e9e..ded3d47 100644 --- a/program/src/initialize.rs +++ b/program/src/initialize.rs @@ -1,5 +1,4 @@ use ore_api::prelude::*; -use solana_program::program_pack::Pack; use steel::*; /// Initializes the program. @@ -11,84 +10,83 @@ pub fn process_initialize(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program return Err(ProgramError::NotEnoughAccountKeys); }; signer_info.is_signer()?.has_address(&ADMIN_ADDRESS)?; - config_info - .is_writable()? - .is_empty()? - .has_seeds(&[CONFIG], &ore_api::ID)?; - market_info - .is_writable()? - .is_empty()? - .has_seeds(&[MARKET], &ore_api::ID)?; + config_info.has_seeds(&[CONFIG], &ore_api::ID)?; + market_info.has_seeds(&[MARKET], &ore_api::ID)?; mint_info.has_address(&MINT_ADDRESS)?.as_mint()?; - treasury_info - .is_writable()? - .is_empty()? - .has_seeds(&[TREASURY], &ore_api::ID)?; - treasury_tokens_info.is_writable()?.is_empty()?; - vault_info - .is_writable()? - .is_empty()? - .has_address(&vault_pda().0)?; + treasury_info.has_seeds(&[TREASURY], &ore_api::ID)?; + treasury_tokens_info.is_writable()?; + vault_info.has_address(&vault_address())?; system_program.is_program(&system_program::ID)?; token_program.is_program(&spl_token::ID)?; associated_token_program.is_program(&spl_associated_token_account::ID)?; // Create config account. - create_program_account::( - config_info, - system_program, - signer_info, - &ore_api::ID, - &[CONFIG], - )?; - let config = config_info.as_account_mut::(&ore_api::ID)?; - config.admin = *signer_info.key; - config.block_duration = INITIAL_BLOCK_DURATION; - config.sniper_fee_duration = INITIAL_SNIPER_FEE_DURATION; - config.fee_collector = *signer_info.key; - config.fee_rate = FEE_LAMPORTS; + if config_info.data_is_empty() { + create_program_account::( + config_info, + system_program, + signer_info, + &ore_api::ID, + &[CONFIG], + )?; + let config = config_info.as_account_mut::(&ore_api::ID)?; + config.admin = *signer_info.key; + config.block_duration = INITIAL_BLOCK_DURATION; + config.sniper_fee_duration = INITIAL_SNIPER_FEE_DURATION; + config.fee_collector = *signer_info.key; + config.fee_rate = FEE_LAMPORTS; + } else { + config_info.as_account::(&ore_api::ID)?; + } // Initialize market. - let initial_id: u64 = 0; - create_program_account::( - market_info, - system_program, - signer_info, - &ore_api::ID, - &[MARKET], - )?; - let market = market_info.as_account_mut::(&ore_api::ID)?; - market.base = TokenParams { - mint: Pubkey::default(), // Virtual token - balance: 0, - balance_virtual: 0, - }; - market.quote = TokenParams { - mint: MINT_ADDRESS, - balance: 0, - balance_virtual: 0, - }; - market.fee = FeeParams { - rate: 0, - uncollected: 0, - cumulative: 0, - }; - market.snapshot = Snapshot { - enabled: 1, - base_balance: 0, - quote_balance: 0, - slot: 0, - }; - market.block_id = initial_id; + if market_info.data_is_empty() { + create_program_account::( + market_info, + system_program, + signer_info, + &ore_api::ID, + &[MARKET], + )?; + let market = market_info.as_account_mut::(&ore_api::ID)?; + market.base = TokenParams { + mint: Pubkey::default(), // Virtual token + balance: 0, + balance_virtual: 0, + }; + market.quote = TokenParams { + mint: MINT_ADDRESS, + balance: 0, + balance_virtual: 0, + }; + market.fee = FeeParams { + rate: 0, + uncollected: 0, + cumulative: 0, + }; + market.snapshot = Snapshot { + enabled: 1, + base_balance: 0, + quote_balance: 0, + slot: 0, + }; + market.block_id = 0; + } else { + market_info.as_account::(&ore_api::ID)?; + } // Create treasury account. - create_program_account::( - treasury_info, - system_program, - signer_info, - &ore_api::ID, - &[TREASURY], - )?; + if treasury_info.data_is_empty() { + create_program_account::( + treasury_info, + system_program, + signer_info, + &ore_api::ID, + &[TREASURY], + )?; + } else { + treasury_info.as_account::(&ore_api::ID)?; + } // Load treasury tokens. if treasury_tokens_info.data_is_empty() { @@ -107,40 +105,17 @@ pub fn process_initialize(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program // Initialize vault token account. if vault_info.data_is_empty() { - let vault_pda = vault_pda(); - allocate_account_with_bump( - vault_info, - system_program, + create_associated_token_account( signer_info, - spl_token::state::Account::LEN, - &spl_token::ID, - &[ - market_info.key.as_ref(), - token_program.key.as_ref(), - mint_info.key.as_ref(), - ], - vault_pda.1, - )?; - solana_program::program::invoke( - &spl_token_2022::instruction::initialize_account3( - &spl_token::ID, - &vault_pda.0, - &mint_info.key, - &market_info.key, - )?, - &[ - vault_info.clone(), - mint_info.clone(), - market_info.clone(), - token_program.clone(), - ], + market_info, + vault_info, + mint_info, + system_program, + token_program, + associated_token_program, )?; } else { - vault_info - .has_address(&vault_pda().0)? - .as_token_account()? - .assert(|t| t.mint() == *mint_info.key)? - .assert(|t| t.owner() == *market_info.key)?; + vault_info.as_associated_token_account(market_info.key, mint_info.key)?; } Ok(()) diff --git a/program/src/mine.rs b/program/src/mine.rs index 74eb7ea..f6d8124 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -31,7 +31,7 @@ pub fn process_mine(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult seed[..8].copy_from_slice(&block.id.to_le_bytes()); seed[8..40].copy_from_slice(&block.slot_hash); seed[40..72].copy_from_slice(&miner.authority.to_bytes()); - seed[72..].copy_from_slice(&miner.seed); + seed[72..104].copy_from_slice(&miner.seed); seed[104..].copy_from_slice(&nonce.to_le_bytes()); let h = hash(&seed); diff --git a/program/src/open.rs b/program/src/open.rs index f2cde77..01d5229 100644 --- a/program/src/open.rs +++ b/program/src/open.rs @@ -35,7 +35,7 @@ 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.best_hash = [0; 32]; + block.best_hash = [u8::MAX; 32]; block.best_hash_miner = Pubkey::default(); block.reward = 0; // Set by reset instruction block.start_slot = u64::MAX; // Set by reset instruction diff --git a/program/src/reset.rs b/program/src/reset.rs index 104b253..55cb101 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -8,7 +8,7 @@ use steel::*; pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { // Load accounts. let clock = Clock::get()?; - let [signer_info, block_prev_info, block_next_info, config_info, fee_collector_info, market_info, mint_info, treasury_info, treasury_tokens_info, vault_info, system_program, token_program, ore_program, slot_hashes_sysvar] = + let [signer_info, block_prev_info, block_next_info, config_info, market_info, mint_info, treasury_info, treasury_tokens_info, vault_info, system_program, token_program, ore_program, slot_hashes_sysvar] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -16,9 +16,6 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul signer_info.is_signer()?; 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_next.id - 1)?; diff --git a/program/src/swap.rs b/program/src/swap.rs index 7ae8627..35c3fa0 100644 --- a/program/src/swap.rs +++ b/program/src/swap.rs @@ -36,7 +36,6 @@ pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult .as_mint()?; vault_info .is_writable()? - .has_address(&vault_pda().0)? .as_associated_token_account(market_info.key, mint_info.key)?; system_program.is_program(&system_program::ID)?; token_program.is_program(&spl_token::ID)?; @@ -76,7 +75,7 @@ pub fn process_swap(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult // Pay swap fee. if config.fee_rate > 0 { - signer_info.send(config.fee_rate, fee_collector_info); + fee_collector_info.collect(config.fee_rate, &signer_info)?; } // Load token acccounts.