From 495dd4ad4b74042c6a196fac7e657c4da66db29e Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 23 May 2025 18:41:54 -0700 Subject: [PATCH] scaffolding --- Cargo.lock | 2 + Cargo.toml | 1 + api/Cargo.toml | 1 + api/src/sdk.rs | 137 ++++++++++++++++++++++++++++++++++---- api/src/state/mod.rs | 7 +- program/Cargo.toml | 1 + program/src/bet.rs | 20 ++---- program/src/bury.rs | 6 +- program/src/close.rs | 1 + program/src/initialize.rs | 2 +- program/src/payout.rs | 40 ++++++----- program/src/reset.rs | 5 +- 12 files changed, 169 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 58d201f..64caa4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1283,6 +1283,7 @@ dependencies = [ "array-const-fn-init", "bytemuck", "const-crypto", + "meteora-pools-sdk", "mpl-token-metadata", "num_enum", "solana-program", @@ -1317,6 +1318,7 @@ dependencies = [ name = "ore-program" version = "3.7.0" dependencies = [ + "bincode", "drillx", "meteora-pools-sdk", "mpl-token-metadata", diff --git a/Cargo.toml b/Cargo.toml index ec902ea..8f347d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ keywords = ["solana", "crypto", "mining"] [workspace.dependencies] array-const-fn-init = "0.1.1" +bincode = "1.3.3" bytemuck = "1.14.3" bytemuck_derive = "1.7.0" const-crypto = "0.1.0" diff --git a/api/Cargo.toml b/api/Cargo.toml index 09d136d..746e119 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -13,6 +13,7 @@ keywords.workspace = true array-const-fn-init.workspace = true bytemuck.workspace = true const-crypto.workspace = true +meteora-pools-sdk.workspace = true mpl-token-metadata.workspace = true num_enum.workspace = true solana-program.workspace = true diff --git a/api/src/sdk.rs b/api/src/sdk.rs index c7d9694..7046812 100644 --- a/api/src/sdk.rs +++ b/api/src/sdk.rs @@ -1,19 +1,18 @@ +use meteora_pools_sdk::instructions::Swap; +use spl_token::native_mint; use steel::*; -use crate::{instruction::*, state::*}; +use crate::{ + consts::{MINT_ADDRESS, TREASURY_ADDRESS, TREASURY_TOKENS_ADDRESS}, + instruction::*, + state::*, +}; -pub fn bet( - signer: Pubkey, - mint: Pubkey, - amount: u64, - id: u64, - round: u64, - seed: Option<[u8; 32]>, -) -> Instruction { +pub fn bet(signer: Pubkey, mint: Pubkey, amount: u64, round: u64, seed: [u8; 32]) -> Instruction { let sender = spl_associated_token_account::get_associated_token_address(&signer, &mint); let block = block_pda().0; let block_bets = spl_associated_token_account::get_associated_token_address(&signer, &mint); - let wager = wager_pda(round, id).0; + let wager = wager_pda(round, seed).0; Instruction { program_id: crate::ID, accounts: vec![ @@ -22,14 +21,128 @@ pub fn bet( AccountMeta::new(block_bets, false), AccountMeta::new(sender, false), AccountMeta::new(wager, false), - AccountMeta::new_readonly(spl_token::ID, false), AccountMeta::new_readonly(spl_associated_token_account::ID, false), + AccountMeta::new_readonly(spl_token::ID, false), AccountMeta::new_readonly(sysvar::slot_hashes::ID, false), ], data: Bet { amount: amount.to_le_bytes(), - seed: seed.unwrap_or([0; 32]), + seed, } .to_bytes(), } } + +pub fn bury(signer: Pubkey, swap: Swap) -> Instruction { + let block = block_pda().0; + let block_bets = spl_associated_token_account::get_associated_token_address( + &signer, + &spl_token::native_mint::ID, + ); + let block_ore = + spl_associated_token_account::get_associated_token_address(&signer, &MINT_ADDRESS); + Instruction { + program_id: crate::ID, + accounts: vec![ + // required accounts + AccountMeta::new(signer, true), + AccountMeta::new(block, false), + AccountMeta::new(block_bets, false), + AccountMeta::new(block_ore, false), + AccountMeta::new(spl_token::native_mint::ID, false), + AccountMeta::new(MINT_ADDRESS, false), + // swap accounts + AccountMeta::new(swap.pool, false), + AccountMeta::new(swap.user_source_token, false), + AccountMeta::new(swap.user_destination_token, false), + AccountMeta::new(swap.a_vault, false), + AccountMeta::new(swap.b_vault, false), + AccountMeta::new(swap.a_token_vault, false), + AccountMeta::new(swap.b_token_vault, false), + AccountMeta::new(swap.a_vault_lp_mint, false), + AccountMeta::new(swap.b_vault_lp_mint, false), + AccountMeta::new(swap.a_vault_lp, false), + AccountMeta::new(swap.b_vault_lp, false), + AccountMeta::new(swap.protocol_token_fee, false), + AccountMeta::new_readonly(swap.vault_program, false), + AccountMeta::new_readonly(spl_token::ID, false), + AccountMeta::new_readonly(meteora_pools_sdk::programs::AMM_ID, false), + ], + data: Close {}.to_bytes(), + } +} + +pub fn close(signer: Pubkey, wager: Pubkey) -> Instruction { + let block = block_pda().0; + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(block, false), + AccountMeta::new(wager, false), + AccountMeta::new_readonly(system_program::ID, false), + ], + data: Close {}.to_bytes(), + } +} + +pub fn initialize(signer: Pubkey, mint: Pubkey, ore_mint: Pubkey) -> Instruction { + let block = block_pda().0; + let block_bets = + spl_associated_token_account::get_associated_token_address(&signer, &native_mint::ID); + let block_ore = + spl_associated_token_account::get_associated_token_address(&signer, &MINT_ADDRESS); + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(block, false), + AccountMeta::new(block_bets, false), + AccountMeta::new(block_ore, false), + AccountMeta::new(mint, false), + AccountMeta::new(ore_mint, 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: Initialize {}.to_bytes(), + } +} + +pub fn payout(signer: Pubkey, wager: Pubkey, recipient: Pubkey) -> Instruction { + let block = block_pda().0; + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(block, false), + AccountMeta::new(wager, false), + AccountMeta::new(recipient, false), + AccountMeta::new(TREASURY_ADDRESS, false), + AccountMeta::new(TREASURY_TOKENS_ADDRESS, false), + AccountMeta::new_readonly(system_program::ID, false), + AccountMeta::new_readonly(spl_token::ID, false), + AccountMeta::new_readonly(sysvar::slot_hashes::ID, false), + ], + data: Payout {}.to_bytes(), + } +} + +pub fn reset(signer: Pubkey, boost_config: Pubkey) -> Instruction { + let block = block_pda().0; + let boost_proof = proof_pda(boost_config).0; + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(block, false), + AccountMeta::new(MINT_ADDRESS, false), + AccountMeta::new(TREASURY_ADDRESS, false), + AccountMeta::new(TREASURY_TOKENS_ADDRESS, false), + AccountMeta::new_readonly(spl_token::ID, false), + AccountMeta::new_readonly(boost_config, false), + AccountMeta::new(boost_proof, false), + ], + data: Reset {}.to_bytes(), + } +} diff --git a/api/src/state/mod.rs b/api/src/state/mod.rs index e0ffd2c..e09c6fb 100644 --- a/api/src/state/mod.rs +++ b/api/src/state/mod.rs @@ -29,11 +29,8 @@ pub fn proof_pda(authority: Pubkey) -> (Pubkey, u8) { Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()) } -pub fn wager_pda(round: u64, id: u64) -> (Pubkey, u8) { - Pubkey::find_program_address( - &[WAGER, &round.to_le_bytes(), &id.to_le_bytes()], - &crate::ID, - ) +pub fn wager_pda(round: u64, seed: [u8; 32]) -> (Pubkey, u8) { + Pubkey::find_program_address(&[WAGER, &round.to_le_bytes(), &seed], &crate::ID) } pub fn treasury_pda() -> (Pubkey, u8) { diff --git a/program/Cargo.toml b/program/Cargo.toml index 4e68d0b..df0f6d8 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -18,6 +18,7 @@ name = "ore" default = [] [dependencies] +bincode.workspace = true drillx.workspace = true meteora-pools-sdk.workspace = true mpl-token-metadata.workspace = true diff --git a/program/src/bet.rs b/program/src/bet.rs index be6fd5e..a6132b9 100644 --- a/program/src/bet.rs +++ b/program/src/bet.rs @@ -4,10 +4,12 @@ use ore_api::prelude::*; use solana_program::{keccak::hashv, slot_hashes::SlotHash}; use steel::*; +/// Places a bet. pub fn process_bet(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { // Parse data. let args = Bet::try_from_bytes(data)?; let amount = u64::from_le_bytes(args.amount); + let seed = args.seed; // Load accounts. let clock = Clock::get()?; @@ -24,11 +26,7 @@ pub fn process_bet(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { block_bets_info.as_associated_token_account(block_info.key, &block.mint)?; sender_info.as_associated_token_account(signer_info.key, &block.mint)?; wager_info.is_writable()?.is_empty()?.has_seeds( - &[ - WAGER, - &block.current_round.to_le_bytes(), - &block.bet_count.to_le_bytes(), - ], + &[WAGER, &block.current_round.to_le_bytes(), &seed], &ore_api::ID, )?; system_program.is_program(&system_program::ID)?; @@ -55,18 +53,12 @@ pub fn process_bet(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { wager.timestamp = clock.unix_timestamp as u64; wager.cumulative_bets = block.total_bets; - // Update block. + // Update block stats. block.total_bets += amount; block.bet_count += 1; - // Hash client seed into block noise. Use a recent slot hash if no seed is provided. - // This follows the scheme for provable randomness. - let seed: &[u8] = if args.seed == [0; 32] { - &slot_hashes_sysvar.data.borrow()[0..size_of::()] - } else { - args.seed.as_slice() - }; - block.noise = hashv(&[&block.noise, seed]).to_bytes(); + // Hash client seed into block noise. This follows the scheme for provable randomness. + block.noise = hashv(&[&block.noise, &seed]).to_bytes(); // Transfer wagers. transfer( diff --git a/program/src/bury.rs b/program/src/bury.rs index 6dcff58..4684092 100644 --- a/program/src/bury.rs +++ b/program/src/bury.rs @@ -2,6 +2,7 @@ use meteora_pools_sdk::instructions::{SwapCpi, SwapCpiAccounts, SwapInstructionA use ore_api::prelude::*; use steel::*; +/// Swaps bets into ORE and buries the ORE. pub fn process_bury(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { // Load accounts. let (required_accounts, meteora_accounts) = accounts.split_at(5); @@ -56,13 +57,14 @@ pub fn process_bury(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult // Burn (bury) the purchased ORE. let block_ore = block_ore_info.as_associated_token_account(block_info.key, &MINT_ADDRESS)?; - burn_signed( + burn_signed_with_bump( block_ore_info, ore_mint_info, block_info, token_program_info, block_ore.amount(), - &[BLOCK, &[block_bump]], + &[BLOCK], + block_bump, )?; Ok(()) diff --git a/program/src/close.rs b/program/src/close.rs index fa1b754..a0d3ea6 100644 --- a/program/src/close.rs +++ b/program/src/close.rs @@ -1,6 +1,7 @@ use ore_api::prelude::*; use steel::*; +/// Closes a wager account. pub fn process_close(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { // Load accounts. let [signer_info, block_info, wager_info, system_program] = accounts else { diff --git a/program/src/initialize.rs b/program/src/initialize.rs index f74c69a..19f183e 100644 --- a/program/src/initialize.rs +++ b/program/src/initialize.rs @@ -1,7 +1,7 @@ use ore_api::prelude::*; use steel::*; -/// Initialize sets up the ORE program to begin mining. +/// Initialize the program. pub fn process_initialize(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { // Load accounts. let [signer_info, block_info, block_bets_info, block_ore_info, ore_mint_info, sol_mint_info, system_program, token_program, associated_token_program] = diff --git a/program/src/payout.rs b/program/src/payout.rs index 7d1ea4c..365aff9 100644 --- a/program/src/payout.rs +++ b/program/src/payout.rs @@ -1,9 +1,9 @@ -use std::mem::size_of; - use ore_api::prelude::*; -use solana_program::{keccak::hashv, slot_hashes::SlotHash}; +use solana_program::keccak::hashv; use steel::*; +use sysvar::slot_hashes::SlotHashes; +/// Pays out a block reward to the winning. pub fn process_payout(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { // Load accounts. let clock = Clock::get()?; @@ -17,8 +17,6 @@ pub fn process_payout(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResu .as_account_mut::(&ore_api::ID)? .assert_mut(|b| b.ends_at < clock.slot)? .assert_mut(|b| b.payed_out == 0)?; - let wager = wager_info.as_account::(&ore_api::ID)?; - recipient_info.as_associated_token_account(&wager.authority, &MINT_ADDRESS)?; treasury_info.has_address(&TREASURY_ADDRESS)?; treasury_tokens_info .has_address(&TREASURY_TOKENS_ADDRESS)? @@ -27,13 +25,20 @@ pub fn process_payout(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResu token_program.is_program(&spl_token::ID)?; slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?; + // Mark the block as paid out. + block.payed_out = 1; + + // Skip payout if no bets were placed. + if block.total_bets == 0 { + return Ok(()); + } + // Select the slothash from the slot at when the round ended. - // The represents the server seed for the provably fair random number. - let offset = clock.slot - block.ends_at; - let size = size_of::(); - let i = offset as usize * size; - let slot_hash = &slot_hashes_sysvar.data.borrow()[i..i + size]; - block.noise = hashv(&[&block.noise, slot_hash]).to_bytes(); + // The represents the server seed for a provably fair random number. + let slot_hashes = + bincode::deserialize::(slot_hashes_sysvar.data.borrow().as_ref()).unwrap(); + let slot_hash = slot_hashes.get(&block.ends_at).unwrap(); + block.noise = hashv(&[&block.noise, slot_hash.as_ref()]).to_bytes(); // Calculate the random number. let x = u64::from_le_bytes(block.noise[0..8].try_into().unwrap()); @@ -42,11 +47,12 @@ pub fn process_payout(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResu let w = u64::from_le_bytes(block.noise[24..32].try_into().unwrap()); let roll = (x ^ y ^ z ^ w) % block.total_bets; - // Assert that the wager account passed in is the winner. - assert!(roll >= wager.cumulative_bets && roll < wager.cumulative_bets + wager.amount); - - // Mark the block as paid out. - block.payed_out = 1; + // Validate the wager account. + let wager = wager_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|w| roll >= w.cumulative_bets)? + .assert_mut(|w| roll < w.cumulative_bets + w.amount)?; + recipient_info.as_associated_token_account(&wager.authority, &MINT_ADDRESS)?; // Transfer the winnings to the recipient. transfer_signed( @@ -54,7 +60,7 @@ pub fn process_payout(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResu &treasury_tokens_info, &recipient_info, &token_program, - ONE_ORE / 2, + block.reward, &[TREASURY], )?; diff --git a/program/src/reset.rs b/program/src/reset.rs index 18401c4..7fbbbef 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -6,8 +6,8 @@ use steel::*; pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { // Load accounts. let clock = Clock::get()?; - let (required_accounts, boost_accounts) = accounts.split_at(6); - let [signer_info, block_info, mint_info, treasury_info, treasury_tokens_info, token_program, slot_hashes_sysvar] = + let (required_accounts, boost_accounts) = accounts.split_at(5); + let [signer_info, block_info, mint_info, treasury_info, treasury_tokens_info, token_program] = required_accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -24,7 +24,6 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul treasury_info.has_address(&TREASURY_ADDRESS)?; treasury_tokens_info.has_address(&TREASURY_TOKENS_ADDRESS)?; token_program.is_program(&spl_token::ID)?; - slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?; // Load boost accounts. let [boost_config_info, boost_proof_info] = boost_accounts else {