From 54b7a354334c2ccb2b904e0cc10f6bdf3bb16de0 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 13 Feb 2024 23:24:57 +0000 Subject: [PATCH] optimization --- Cargo.lock | 1 + Cargo.toml | 1 + src/instruction.rs | 30 ++++- src/lib.rs | 33 ++++-- src/loaders.rs | 59 +++++----- src/processor/epoch.rs | 2 +- src/processor/initialize.rs | 19 +-- src/processor/mine.rs | 91 ++++++++++++++- src/processor/proof.rs | 48 +++++++- src/state/hash.rs | 2 + src/state/mod.rs | 2 + src/state/proof.rs | 32 ++++++ tests/test_epoch.rs | 2 + tests/test_initialize.rs | 9 ++ tests/test_mine.rs | 222 ++++++++++++++++++++++++++++++++++++ 15 files changed, 495 insertions(+), 58 deletions(-) create mode 100644 src/state/proof.rs create mode 100644 tests/test_mine.rs diff --git a/Cargo.lock b/Cargo.lock index 1ce5857..9d0e51c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2346,6 +2346,7 @@ dependencies = [ name = "ore" version = "0.1.0" dependencies = [ + "bincode", "bs64", "bytemuck", "num_enum 0.7.2", diff --git a/Cargo.toml b/Cargo.toml index f526aac..114535b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ no-entrypoint = [] default = [] [dependencies] +bincode = "1.3.3" bytemuck = "1.14.3" num_enum = "0.7.2" shank = "0.3.0" diff --git a/src/instruction.rs b/src/instruction.rs index df1387b..c2ecc14 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -2,6 +2,8 @@ use bytemuck::{Pod, Zeroable}; use num_enum::TryFromPrimitive; use shank::ShankInstruction; +use crate::state::Hash; + #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, ShankInstruction, TryFromPrimitive)] #[rustfmt::skip] @@ -33,8 +35,7 @@ pub enum OreInstruction { #[account(2, name = "bus", desc = "Ore bus account", writable)] #[account(3, name = "proof", desc = "Ore miner proof account", writable)] #[account(4, name = "treasury", desc = "Ore treasury account")] - #[account(5, name = "token_program", desc = "SPL token program")] - #[account(6, name = "slot_hashes", desc = "Solana slot hashes sysvar")] + #[account(5, name = "slot_hashes", desc = "Solana slot hashes sysvar")] Mine = 2, #[account(0, name = "ore_program", desc = "Ore program")] @@ -101,3 +102,28 @@ impl InitializeArgs { bytemuck::bytes_of(self) } } + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct ProofArgs { + pub bump: u8, +} + +impl ProofArgs { + pub fn to_bytes(&self) -> &[u8] { + bytemuck::bytes_of(self) + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct MineArgs { + pub hash: Hash, + pub nonce: [u8; 8], +} + +impl MineArgs { + pub fn to_bytes(&self) -> &[u8] { + bytemuck::bytes_of(self) + } +} diff --git a/src/lib.rs b/src/lib.rs index c7519d8..de3f032 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,22 @@ pub const PROOF: &[u8] = b"proof"; /// The seed of the treasury account PDA. pub const TREASURY: &[u8] = b"treasury"; +/// Treasury address +pub const TREASURY_ADDRESS: Pubkey = pubkey!("67PLJej6iZm915WbEu6NLeZtRZtnHc5nSVQvkHRZyPiC"); + +// SHA2 const stable +/// Bus pubkeys +pub const BUS_ADDRESSES: [Pubkey; 8] = [ + pubkey!("2uwqyH2gKqstgAFCSniirx73X4iQek5ETc2vVJKUiNMg"), + pubkey!("FRMC6jVczm1cRaEs5EhDsfw7X8vsmSDpf3bJWVkawngu"), + pubkey!("9nWyycs4GHjnLujPR2sbA1A8K8CkiLc5VzxWUD4hg2uM"), + pubkey!("Kt7kqD3MyvxLbj4ek9urXUxkDoxaMuQn82K2VdYD1jM"), + pubkey!("8r9mXYnFQXhwrNfvatGUTxbbNSqxScuCwp4sBTSxDVTJ"), + pubkey!("D9cEH32k8p9uWc4w5RrStK9rWssU8NuX1Dg5YaUim4wL"), + pubkey!("H1RKMYADPzd4C1j1RZu51NvRSVktoTYEJyeVy98Kmdyu"), + pubkey!("3XbdZNbBjjp8qnDJjv1RxaKisyfx6ahznYkSigs6dayy"), +]; + /// Processes the incoming instruction pub fn process_instruction( program_id: &Pubkey, @@ -93,13 +109,16 @@ pub fn process_instruction( .ok_or(ProgramError::InvalidInstructionData)?; let ix = OreInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))?; - match ix { - OreInstruction::Epoch => process_epoch(program_id, accounts, data)?, - OreInstruction::Proof => process_proof(program_id, accounts, data)?, - OreInstruction::Mine => process_mine(program_id, accounts, data)?, - OreInstruction::Claim => process_claim(program_id, accounts, data)?, - OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, - } + // if ix.eq(&OreInstruction::Proof) { + // process_proof(program_id, accounts, data)? + // } + // match ix { + // OreInstruction::Epoch => process_epoch(program_id, accounts, data)?, + // OreInstruction::Proof => process_proof(program_id, accounts, data)?, + // OreInstruction::Mine => process_mine(program_id, accounts, data)?, + // OreInstruction::Claim => process_claim(program_id, accounts, data)?, + // OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, + // } Ok(()) } diff --git a/src/loaders.rs b/src/loaders.rs index c5b2b7f..a11d3db 100644 --- a/src/loaders.rs +++ b/src/loaders.rs @@ -1,12 +1,12 @@ use solana_program::{ account_info::AccountInfo, program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, - system_program, + syscalls, system_program, }; use spl_token::state::Mint; use crate::{ - state::{Bus, Treasury}, - BUS, MINT_ADDRESS, TREASURY, + state::{Bus, Proof}, + BUS_COUNT, MINT_ADDRESS, TREASURY_ADDRESS, }; pub fn load_signer<'a, 'info>( @@ -18,26 +18,6 @@ pub fn load_signer<'a, 'info>( Ok(info) } -pub fn load_pda<'a, 'info>( - info: &'a AccountInfo<'info>, - seeds: &[&[u8]], - writable: bool, -) -> Result<&'a AccountInfo<'info>, ProgramError> { - let key = Pubkey::create_program_address(seeds, &crate::id())?; - if !info.key.eq(&key) { - return Err(ProgramError::InvalidSeeds); - } - if !info.owner.eq(&crate::id()) { - return Err(ProgramError::InvalidAccountOwner); - } - if writable { - if !info.is_writable { - return Err(ProgramError::InvalidAccountData); - } - } - Ok(info) -} - pub fn load_uninitialized_pda<'a, 'info>( info: &'a AccountInfo<'info>, seeds: &[&[u8]], @@ -62,10 +42,29 @@ pub fn load_bus<'a, 'info>( let bus_data = info.data.borrow(); let bus = bytemuck::try_from_bytes::(&bus_data).unwrap(); - let key = - Pubkey::create_program_address(&[BUS, &[bus.id as u8], &[bus.bump as u8]], &crate::id())?; - if !info.key.eq(&key) { - return Err(ProgramError::InvalidSeeds); + if !(0..BUS_COUNT).contains(&(bus.id as usize)) { + return Err(ProgramError::InvalidAccountData); + } + + Ok(info) +} + +pub fn load_proof<'a, 'info>( + info: &'a AccountInfo<'info>, + signer: &Pubkey, +) -> Result<&'a AccountInfo<'info>, ProgramError> { + if !info.owner.eq(&crate::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + let proof_data = info.data.borrow(); + let proof = bytemuck::try_from_bytes::(&proof_data).unwrap(); + + if !proof.authority.eq(&signer) { + return Err(ProgramError::InvalidAccountData); } Ok(info) @@ -81,11 +80,7 @@ pub fn load_treasury<'a, 'info>( return Err(ProgramError::UninitializedAccount); } - let treasury_data = info.data.borrow(); - let treasury = bytemuck::try_from_bytes::(&treasury_data).unwrap(); - - let key = Pubkey::create_program_address(&[TREASURY, &[treasury.bump as u8]], &crate::id())?; - if !info.key.eq(&key) { + if !info.key.eq(&TREASURY_ADDRESS) { return Err(ProgramError::InvalidSeeds); } diff --git a/src/processor/epoch.rs b/src/processor/epoch.rs index e829be3..85e9e03 100644 --- a/src/processor/epoch.rs +++ b/src/processor/epoch.rs @@ -10,7 +10,7 @@ use solana_program::{ use crate::{ loaders::*, state::{Bus, Treasury}, - BUS, BUS_COUNT, BUS_EPOCH_REWARDS, EPOCH_DURATION, MAX_EPOCH_REWARDS, SMOOTHING_FACTOR, + BUS_COUNT, BUS_EPOCH_REWARDS, EPOCH_DURATION, MAX_EPOCH_REWARDS, SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS, TREASURY, }; diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 5c998ab..9ae3160 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -11,7 +11,7 @@ use solana_program::{ }; use spl_token::state::Mint; -use crate::{instruction::*, BUS, INITIAL_DIFFICULTY, MINT_ADDRESS}; +use crate::{instruction::*, BUS, INITIAL_DIFFICULTY, MINT_ADDRESS, TREASURY_ADDRESS}; use crate::{ loaders::*, state::{Bus, Treasury}, @@ -77,10 +77,13 @@ pub fn process_initialize<'a, 'info>( } // Account 11: Treasury - let treasury_account_info = load_uninitialized_pda( + let treasury_info = load_uninitialized_pda( next_account_info(accounts_iter)?, &[TREASURY, &[args.treasury_bump]], )?; + if !treasury_info.key.eq(&TREASURY_ADDRESS) { + return Err(ProgramError::InvalidSeeds); + } // Account 12: Treasury tokens let treasury_tokens = load_uninitialized_account(next_account_info(accounts_iter)?)?; @@ -132,14 +135,14 @@ pub fn process_initialize<'a, 'info>( // Initialize treasury create_pda( - treasury_account_info, + treasury_info, &crate::id(), size_of::(), &[TREASURY, &[args.treasury_bump]], system_program, signer, )?; - let mut treasury_data = treasury_account_info.data.borrow_mut(); + let mut treasury_data = treasury_info.data.borrow_mut(); let mut treasury = bytemuck::try_from_bytes_mut::(&mut treasury_data).unwrap(); treasury.bump = args.treasury_bump as u64; treasury.admin = *signer.key; @@ -162,14 +165,14 @@ pub fn process_initialize<'a, 'info>( &spl_token::instruction::initialize_mint( &spl_token::id(), mint.key, - treasury_account_info.key, + treasury_info.key, None, TOKEN_DECIMALS, )?, &[ token_program.clone(), mint.clone(), - treasury_account_info.clone(), + treasury_info.clone(), rent_sysvar.clone(), ], &[&[MINT, &[args.mint_bump]]], @@ -179,7 +182,7 @@ pub fn process_initialize<'a, 'info>( solana_program::program::invoke( &spl_associated_token_account::instruction::create_associated_token_account( signer.key, - treasury_account_info.key, + treasury_info.key, mint.key, &spl_token::id(), ), @@ -187,7 +190,7 @@ pub fn process_initialize<'a, 'info>( associated_token_program.clone(), signer.clone(), treasury_tokens.clone(), - treasury_account_info.clone(), + treasury_info.clone(), mint.clone(), system_program.clone(), token_program.clone(), diff --git a/src/processor/mine.rs b/src/processor/mine.rs index e49df35..b3b1ad4 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -1,9 +1,21 @@ +use std::mem::size_of; + use solana_program::{ - account_info::AccountInfo, + account_info::{next_account_info, AccountInfo}, + clock::Clock, entrypoint::ProgramResult, - keccak::{hashv, Hash}, + keccak::{hashv, Hash as KeccakHash}, program_error::ProgramError, pubkey::Pubkey, + slot_hashes::SlotHash, + sysvar::{self, Sysvar}, +}; + +use crate::{ + instruction::MineArgs, + loaders::*, + state::{Bus, Proof, Treasury}, + EPOCH_DURATION, }; pub fn process_mine<'a, 'info>( @@ -11,16 +23,83 @@ pub fn process_mine<'a, 'info>( accounts: &'a [AccountInfo<'info>], data: &[u8], ) -> ProgramResult { - // TODO + // let accounts_iter = &mut accounts.iter(); + // let args = + // bytemuck::try_from_bytes::(data).or(Err(ProgramError::InvalidInstructionData))?; + + // let [signer, bus_info, proof_info, treasury_info, slot_hashes_info] = accounts else { + // return Err(ProgramError::NotEnoughAccountKeys); + // }; + // let _ = load_signer(signer)?; + // let _ = load_bus(bus_info)?; + // let _ = load_proof(proof_info, signer.key)?; + // let _ = load_treasury(treasury_info)?; + // let _ = load_account(slot_hashes_info, sysvar::slot_hashes::id())?; + + // Account 1: Signer + // let signer = load_signer(next_account_info(accounts_iter)?)?; + + // Account 2: Bus + // let bus_info = load_bus(next_account_info(accounts_iter)?)?; + + // Account 3: Proof + // let proof_info = load_proof(next_account_info(accounts_iter)?, signer.key)?; + + // Account 4: Treasury + // let treasury_info = load_treasury(next_account_info(accounts_iter)?)?; + + // Account 5: Slot hashes svsvar + // let slot_hashes_info = + // load_account(next_account_info(accounts_iter)?, sysvar::slot_hashes::id())?; + + // Validate epoch is active + // let clock = Clock::get().unwrap(); + // let treasury_data = treasury_info.data.borrow(); + // let treasury = bytemuck::try_from_bytes::(&treasury_data).unwrap(); + // let epoch_end_at = treasury.epoch_start_at.saturating_add(EPOCH_DURATION); + // if !clock.unix_timestamp.lt(&epoch_end_at) { + // return Err(ProgramError::Custom(1)); + // } + + // Validate provided hash + // let mut proof_data = proof_info.data.borrow_mut(); + // let mut proof = bytemuck::try_from_bytes_mut::(&mut proof_data).unwrap(); + // validate_hash( + // proof.hash.into(), + // args.hash.into(), + // *signer.key, + // u64::from_le_bytes(args.nonce), + // treasury.difficulty.into(), + // )?; + + // Update claimable rewards + // let mut bus_data = bus_info.data.borrow_mut(); + // let mut bus = bytemuck::try_from_bytes_mut::(&mut bus_data).unwrap(); + // if !bus.available_rewards.ge(&treasury.reward_rate) { + // return Err(ProgramError::Custom(1)); + // } + // bus.available_rewards = bus.available_rewards.saturating_sub(treasury.reward_rate); + // proof.claimable_rewards = proof.claimable_rewards.saturating_add(treasury.reward_rate); + + // Hash most recent slot hash into the next challenge to prevent pre-mining attacks + // let slot_hash_bytes = &slot_hashes_info.data.borrow()[0..size_of::()]; + // proof.hash = hashv(&[KeccakHash::from(args.hash).as_ref(), slot_hash_bytes]).into(); + + // Update lifetime stats + // proof.total_hashes = proof.total_hashes.saturating_add(1); + // proof.total_rewards = proof.total_rewards.saturating_add(1); + + // TODO Log? + Ok(()) } pub(crate) fn validate_hash( - current_hash: Hash, - hash: Hash, + current_hash: KeccakHash, + hash: KeccakHash, signer: Pubkey, nonce: u64, - difficulty: Hash, + difficulty: KeccakHash, ) -> Result<(), ProgramError> { // Validate hash correctness. let hash_ = hashv(&[ diff --git a/src/processor/proof.rs b/src/processor/proof.rs index 784e7a8..35ca8b2 100644 --- a/src/processor/proof.rs +++ b/src/processor/proof.rs @@ -1,10 +1,54 @@ -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; +use std::mem::size_of; + +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + keccak::hashv, + program_error::ProgramError, + pubkey::Pubkey, + system_program, +}; + +use crate::{instruction::ProofArgs, loaders::*, state::Proof, utils::create_pda, PROOF}; pub fn process_proof<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], data: &[u8], ) -> ProgramResult { - // TODO + let accounts_iter = &mut accounts.iter(); + let args = bytemuck::try_from_bytes::(data) + .or(Err(ProgramError::InvalidInstructionData))?; + + // Account 1: Signer + let signer = load_signer(next_account_info(accounts_iter)?)?; + + // Account 2: Proof + let proof_info = load_uninitialized_pda( + next_account_info(accounts_iter)?, + &[PROOF, signer.key.as_ref(), &[args.bump]], + )?; + + // Account 3: System program + let system_program = load_account(next_account_info(accounts_iter)?, system_program::id())?; + + // Initialize proof + create_pda( + proof_info, + &crate::id(), + size_of::(), + &[PROOF, signer.key.as_ref(), &[args.bump]], + system_program, + signer, + )?; + let mut proof_data = proof_info.data.borrow_mut(); + let mut proof = bytemuck::try_from_bytes_mut::(&mut proof_data).unwrap(); + proof.bump = args.bump as u64; + proof.authority = *signer.key; + proof.claimable_rewards = 0; + proof.hash = hashv(&[&signer.key.to_bytes()]).into(); + proof.total_hashes = 0; + proof.total_rewards = 0; + Ok(()) } diff --git a/src/state/hash.rs b/src/state/hash.rs index 94d7902..7864c59 100644 --- a/src/state/hash.rs +++ b/src/state/hash.rs @@ -8,12 +8,14 @@ use solana_program::keccak::{Hash as KeccakHash, HASH_BYTES}; pub struct Hash(pub [u8; HASH_BYTES]); impl From for Hash { + #[inline(always)] fn from(value: KeccakHash) -> Self { unsafe { transmute(value) } } } impl From for KeccakHash { + #[inline(always)] fn from(value: Hash) -> Self { unsafe { transmute(value) } } diff --git a/src/state/mod.rs b/src/state/mod.rs index 3a6040b..64809b4 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -1,7 +1,9 @@ mod bus; mod hash; +mod proof; mod treasury; pub use bus::*; pub use hash::*; +pub use proof::*; pub use treasury::*; diff --git a/src/state/proof.rs b/src/state/proof.rs new file mode 100644 index 0000000..c87dae8 --- /dev/null +++ b/src/state/proof.rs @@ -0,0 +1,32 @@ +use bytemuck::{Pod, Zeroable}; +use solana_program::pubkey::Pubkey; + +use super::Hash; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct Proof { + /// The bump of the proof account PDA. + pub bump: u64, + + /// The account (i.e. miner) authorized to use this proof. + pub authority: Pubkey, + + /// The quantity of tokens this miner may claim from the treasury. + pub claimable_rewards: u64, + + /// The proof's current hash. + pub hash: Hash, + + /// The total lifetime hashes provided by this miner. + pub total_hashes: u64, + + /// The total lifetime rewards distributed to this miner. + pub total_rewards: u64, +} + +impl Proof { + pub fn to_bytes(&self) -> &[u8] { + bytemuck::bytes_of(self) + } +} diff --git a/tests/test_epoch.rs b/tests/test_epoch.rs index 9a1b0a8..3fe672b 100644 --- a/tests/test_epoch.rs +++ b/tests/test_epoch.rs @@ -144,6 +144,8 @@ async fn test_epoch() { treasury_tokens_account, bs64::encode(&treasury_tokens_account.data) ); + + // assert!(false); } async fn setup_program_test_env() -> (BanksClient, Keypair, Hash) { diff --git a/tests/test_initialize.rs b/tests/test_initialize.rs index 7e782a3..d72dea8 100644 --- a/tests/test_initialize.rs +++ b/tests/test_initialize.rs @@ -132,6 +132,15 @@ async fn test_initialize() { assert_eq!(treasury_tokens.is_native, COption::None); assert_eq!(treasury_tokens.delegated_amount, 0); assert_eq!(treasury_tokens.close_authority, COption::None); + + // println!( + // "Treasury {:?} {:?} {:?}", + // treasury_pda.0, + // treasury_account, + // bs64::encode(&treasury_account.data) + // ); + + // assert!(false); } async fn setup_program_test_env() -> (BanksClient, Keypair, Hash) { diff --git a/tests/test_mine.rs b/tests/test_mine.rs new file mode 100644 index 0000000..66e2f45 --- /dev/null +++ b/tests/test_mine.rs @@ -0,0 +1,222 @@ +use std::str::FromStr; + +use ore::{ + instruction::{MineArgs, OreInstruction, ProofArgs}, + state::{Proof, Treasury}, + BUS, PROOF, TREASURY, +}; +use solana_program::{ + clock::Clock, + epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, + instruction::{AccountMeta, Instruction}, + keccak::{hashv, Hash as KeccakHash}, + pubkey::Pubkey, + system_program, sysvar, +}; +use solana_program_test::{processor, BanksClient, ProgramTest}; +use solana_sdk::{ + signature::{Keypair, Signer}, + transaction::Transaction, +}; + +#[tokio::test] +async fn test_mine() { + // Setup + let (mut banks, payer, hash) = setup_program_test_env().await; + + // Build proof ix + let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); + let ix_0 = Instruction { + program_id: ore::id(), + accounts: vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new(proof_pda.0, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + data: [ + OreInstruction::Proof.to_vec(), + ProofArgs { bump: proof_pda.1 }.to_bytes().to_vec(), + ] + .concat(), + }; + + // Submit tx + let tx = Transaction::new_signed_with_payer(&[ix_0], Some(&payer.pubkey()), &[&payer], hash); + let res = banks.process_transaction(tx).await; + assert!(res.is_ok()); + + // Assert proof state + // let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); + // assert_eq!(proof_account.owner, ore::id()); + // let proof = bytemuck::try_from_bytes::(&proof_account.data).unwrap(); + + // Assert proof state + let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); + let treasury_account = banks.get_account(treasury_pda.0).await.unwrap().unwrap(); + let treasury = bytemuck::try_from_bytes::(&treasury_account.data).unwrap(); + + // Find next hash + // let (next_hash, nonce) = find_next_hash( + // proof.hash.into(), + // treasury.difficulty.into(), + // payer.pubkey(), + // ); + + // println!("Hash: {:?}", next_hash); + // println!("None: {:?}", nonce); + // let args = MineArgs { + // hash: next_hash.into(), + // nonce, + // }; + // let bytes = args.to_bytes(); + // let parsed_args = bytemuck::try_from_bytes::(bytes); + // println!("Args: {:?}", args.to_bytes()); + // assert!(parsed_args.is_ok()); + + // Build mine ix + let bus_pda = Pubkey::find_program_address(&[BUS, &[0]], &ore::id()); + let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); + let ix_1 = Instruction { + program_id: ore::id(), + accounts: vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new(bus_pda.0, false), + AccountMeta::new(proof_pda.0, false), + AccountMeta::new_readonly(treasury_pda.0, false), + AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), + ], + data: [ + OreInstruction::Mine.to_vec(), + MineArgs { + // hash: next_hash.into(), + // nonce: nonce.to_le_bytes(), + hash: KeccakHash::new_unique().into(), + nonce: 42u64.to_le_bytes(), + } + .to_bytes() + .to_vec(), + ] + .concat(), + }; + + // Submit tx + let tx = Transaction::new_signed_with_payer(&[ix_1], Some(&payer.pubkey()), &[&payer], hash); + let res = banks.process_transaction(tx).await; + assert!(res.is_ok()); + + // TODO Assert proof state + // TODO Assert bus state +} + +fn find_next_hash(hash: KeccakHash, difficulty: KeccakHash, signer: Pubkey) -> (KeccakHash, u64) { + let mut next_hash: KeccakHash; + let mut nonce = 0u64; + loop { + next_hash = hashv(&[ + hash.to_bytes().as_slice(), + signer.to_bytes().as_slice(), + nonce.to_be_bytes().as_slice(), + ]); + if next_hash.le(&difficulty) { + break; + } else { + println!("Invalid hash: {} Nonce: {:?}", next_hash.to_string(), nonce); + } + nonce += 1; + } + (next_hash, nonce) +} + +async fn setup_program_test_env() -> (BanksClient, Keypair, solana_program::hash::Hash) { + let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); + program_test.prefer_bpf(true); + + // Busses + program_test.add_account_with_base64_data( + Pubkey::from_str("2uwqyH2gKqstgAFCSniirx73X4iQek5ETc2vVJKUiNMg").unwrap(), + 1002240, + ore::id(), + "/wAAAAAAAACAsuYOAAAAAA==", + ); + program_test.add_account_with_base64_data( + Pubkey::from_str("FRMC6jVczm1cRaEs5EhDsfw7X8vsmSDpf3bJWVkawngu").unwrap(), + 1002240, + ore::id(), + "/gAAAAEAAACAsuYOAAAAAA==", + ); + program_test.add_account_with_base64_data( + Pubkey::from_str("9nWyycs4GHjnLujPR2sbA1A8K8CkiLc5VzxWUD4hg2uM").unwrap(), + 1002240, + ore::id(), + "/wAAAAIAAACAsuYOAAAAAA==", + ); + program_test.add_account_with_base64_data( + Pubkey::from_str("Kt7kqD3MyvxLbj4ek9urXUxkDoxaMuQn82K2VdYD1jM").unwrap(), + 1002240, + ore::id(), + "+gAAAAMAAACAsuYOAAAAAA==", + ); + program_test.add_account_with_base64_data( + Pubkey::from_str("8r9mXYnFQXhwrNfvatGUTxbbNSqxScuCwp4sBTSxDVTJ").unwrap(), + 1002240, + ore::id(), + "/QAAAAQAAACAsuYOAAAAAA==", + ); + program_test.add_account_with_base64_data( + Pubkey::from_str("D9cEH32k8p9uWc4w5RrStK9rWssU8NuX1Dg5YaUim4wL").unwrap(), + 1002240, + ore::id(), + "/wAAAAUAAACAsuYOAAAAAA==", + ); + program_test.add_account_with_base64_data( + Pubkey::from_str("H1RKMYADPzd4C1j1RZu51NvRSVktoTYEJyeVy98Kmdyu").unwrap(), + 1002240, + ore::id(), + "/wAAAAYAAACAsuYOAAAAAA==", + ); + program_test.add_account_with_base64_data( + Pubkey::from_str("3XbdZNbBjjp8qnDJjv1RxaKisyfx6ahznYkSigs6dayy").unwrap(), + 1002240, + ore::id(), + "+QAAAAcAAACAsuYOAAAAAA==", + ); + + // Treasury (difficulty = MAX) + program_test.add_account_with_base64_data( + Pubkey::from_str("67PLJej6iZm915WbEu6NLeZtRZtnHc5nSVQvkHRZyPiC").unwrap(), + 1559040, + ore::id(), + "/wAAAAAAAADHPztpT4Jpqy1n9x6y1psKOUdDt07/OgR6noRFAOuOcP//////////////////////////////////////////ZAAAAAAAAAD0AQAAAAAAAAAAAAAAAAAA" + // "/wAAAAAAAACO+OozfX3xTr9I8U/aRel4qp0ixaw9/PjyseBa6CcLyv//////////////////////////////////////////AAAAAAAAAADoAwAAAAAAAAAAAAAAAAAA" + ); + + // Mint + program_test.add_account_with_base64_data( + Pubkey::from_str("DY4JVebraRXg9BGt4MRU4mvqHGDzmi2Ay1HGjDU5YeNf").unwrap(), + 1461600, + spl_token::id(), + "AQAAAEvtK9pjA/sPMEl3rhUgX8iz4/q0A5icrVGp0GdL3satAJQ1dwAAAAAJAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + ); + + // Treasury tokens + program_test.add_account_with_base64_data( + Pubkey::from_str("EH4tskvkeNqX5ce3FBr4oJob3FKSns9th7NvP28ZHsNL").unwrap(), + 2039280, + spl_token::id(), + "ukD7Oc0QjzbigRIB1x9/XLzAT3w7X0UTZ1NVeB85lRRL7SvaYwP7DzBJd64VIF/Is+P6tAOYnK1RqdBnS97GrQCUNXcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + ); + + // Set sysvar + program_test.add_sysvar_account( + sysvar::clock::id(), + &Clock { + slot: 10, + epoch_start_timestamp: 0, + epoch: 0, + leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, + unix_timestamp: 100, + }, + ); + + program_test.start().await +}