From ca1820606d2f91d10851687d4014e76c85893e66 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 14 Apr 2024 06:03:32 +0000 Subject: [PATCH 001/111] remove hash arg and move nonce to front of digest --- Cargo.lock | 2 +- src/instruction.rs | 4 +--- src/processor/mine.rs | 51 +++++++++---------------------------------- tests/test_mine.rs | 34 ++++++++++------------------- 4 files changed, 24 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c1f4937..60533b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2366,7 +2366,7 @@ dependencies = [ [[package]] name = "ore-program" -version = "1.2.0" +version = "1.2.1" dependencies = [ "bs58 0.5.0", "bs64", diff --git a/src/instruction.rs b/src/instruction.rs index ca052d7..fa60d43 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -118,7 +118,6 @@ pub struct RegisterArgs { #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct MineArgs { - pub hash: Hash, pub nonce: [u8; 8], } @@ -208,7 +207,7 @@ pub fn register(signer: Pubkey) -> Instruction { } /// Builds a mine instruction. -pub fn mine(signer: Pubkey, bus: Pubkey, hash: Hash, nonce: u64) -> Instruction { +pub fn mine(signer: Pubkey, bus: Pubkey, nonce: u64) -> Instruction { let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; Instruction { program_id: crate::id(), @@ -222,7 +221,6 @@ pub fn mine(signer: Pubkey, bus: Pubkey, hash: Hash, nonce: u64) -> Instruction data: [ OreInstruction::Mine.to_vec(), MineArgs { - hash, nonce: nonce.to_le_bytes(), } .to_bytes() diff --git a/src/processor/mine.rs b/src/processor/mine.rs index f318e19..d7ee705 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -4,10 +4,9 @@ use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, - keccak::{hashv, Hash as KeccakHash, HASH_BYTES}, + keccak::{hashv, Hash as KeccakHash}, program::set_return_data, program_error::ProgramError, - program_memory::sol_memcmp, pubkey::Pubkey, slot_hashes::SlotHash, sysvar::{self, Sysvar}, @@ -70,8 +69,7 @@ pub fn process_mine<'a, 'info>( // Validate provided hash let mut proof_data = proof_info.data.borrow_mut(); let proof = Proof::try_from_bytes_mut(&mut proof_data)?; - validate_hash( - args.hash.into(), + let hash = validate_hash( proof.hash.into(), *signer.key, u64::from_le_bytes(args.nonce), @@ -89,7 +87,7 @@ pub fn process_mine<'a, 'info>( // Hash recent slot hash into the next challenge to prevent pre-mining attacks proof.hash = hashv(&[ - KeccakHash::from(args.hash).as_ref(), + hash.as_ref(), &slot_hashes_info.data.borrow()[0..size_of::()], ]) .into(); @@ -107,34 +105,26 @@ pub fn process_mine<'a, 'info>( /// Validates the provided hash, ensursing it is equal to SHA3(current_hash, singer, nonce). /// Fails if the provided hash is valid but does not satisfy the required difficulty. pub(crate) fn validate_hash( - hash: KeccakHash, current_hash: KeccakHash, signer: Pubkey, nonce: u64, difficulty: KeccakHash, -) -> Result<(), ProgramError> { - // Validate hash correctness - let hash_ = hashv(&[ +) -> Result { + let hash = hashv(&[ + nonce.to_le_bytes().as_slice(), current_hash.as_ref(), signer.as_ref(), - nonce.to_le_bytes().as_slice(), ]); - if sol_memcmp(hash.as_ref(), hash_.as_ref(), HASH_BYTES) != 0 { - return Err(OreError::HashInvalid.into()); - } - - // Validate hash difficulty if hash.gt(&difficulty) { return Err(OreError::DifficultyNotSatisfied.into()); } - - Ok(()) + Ok(hash) } #[cfg(test)] mod tests { use solana_program::{ - keccak::{hashv, Hash, HASH_BYTES}, + keccak::{Hash, HASH_BYTES}, pubkey::Pubkey, }; @@ -146,38 +136,17 @@ mod tests { let signer = Pubkey::new_unique(); let nonce = 10u64; let difficulty = Hash::new_from_array([255; HASH_BYTES]); - let h2 = hashv(&[ - h1.to_bytes().as_slice(), - signer.to_bytes().as_slice(), - nonce.to_le_bytes().as_slice(), - ]); - let res = validate_hash(h2, h1, signer, nonce, difficulty); + let res = validate_hash(h1, signer, nonce, difficulty); assert!(res.is_ok()); } #[test] fn test_validate_hash_fail() { - let h1 = Hash::new_from_array([1; HASH_BYTES]); - let signer = Pubkey::new_unique(); - let nonce = 10u64; - let difficulty = Hash::new_from_array([255; HASH_BYTES]); - let h2 = Hash::new_from_array([2; HASH_BYTES]); - let res = validate_hash(h2, h1, signer, nonce, difficulty); - assert!(res.is_err()); - } - - #[test] - fn test_validate_hash_fail_difficulty() { let h1 = Hash::new_from_array([1; HASH_BYTES]); let signer = Pubkey::new_unique(); let nonce = 10u64; let difficulty = Hash::new_from_array([0; HASH_BYTES]); - let h2 = hashv(&[ - h1.to_bytes().as_slice(), - signer.to_bytes().as_slice(), - nonce.to_le_bytes().as_slice(), - ]); - let res = validate_hash(h2, h1, signer, nonce, difficulty); + let res = validate_hash(h1, signer, nonce, difficulty); assert!(res.is_err()); } } diff --git a/tests/test_mine.rs b/tests/test_mine.rs index 727eb2a..04faf3d 100644 --- a/tests/test_mine.rs +++ b/tests/test_mine.rs @@ -62,7 +62,7 @@ async fn test_mine() { ); // Submit mine tx - let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], next_hash.into(), nonce); + let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); let res = banks.process_transaction(tx).await; assert!(res.is_ok()); @@ -161,7 +161,7 @@ async fn test_mine_alt_proof() { // Submit mine tx with invalid proof let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (next_hash, nonce) = find_next_hash( + let (_next_hash, nonce) = find_next_hash( proof.hash.into(), KeccakHash::new_from_array([u8::MAX; 32]), payer.pubkey(), @@ -178,7 +178,6 @@ async fn test_mine_alt_proof() { data: [ OreInstruction::Mine.to_vec(), MineArgs { - hash: next_hash.into(), nonce: nonce.to_le_bytes(), } .to_bytes() @@ -213,7 +212,7 @@ async fn test_mine_correct_hash_alt_proof() { // Submit with correct hash for invalid proof let proof_alt_account = banks.get_account(proof_alt_pda.0).await.unwrap().unwrap(); let proof_alt = Proof::try_from_bytes(&proof_alt_account.data).unwrap(); - let (next_hash, nonce) = find_next_hash( + let (_next_hash, nonce) = find_next_hash( proof_alt.hash.into(), KeccakHash::new_from_array([u8::MAX; 32]), payer_alt.pubkey(), @@ -230,7 +229,6 @@ async fn test_mine_correct_hash_alt_proof() { data: [ OreInstruction::Mine.to_vec(), MineArgs { - hash: next_hash.into(), nonce: nonce.to_le_bytes(), } .to_bytes() @@ -258,14 +256,14 @@ async fn test_mine_bus_rewards_insufficient() { // Find next hash let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (next_hash, nonce) = find_next_hash( + let (_next_hash, nonce) = find_next_hash( proof.hash.into(), KeccakHash::new_from_array([u8::MAX; 32]), payer.pubkey(), ); // Submit mine tx - let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], next_hash.into(), nonce); + let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); let res = banks.process_transaction(tx).await; assert!(res.is_err()); @@ -348,14 +346,14 @@ async fn test_mine_not_enough_accounts() { // Find next hash let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (next_hash, nonce) = find_next_hash( + let (_next_hash, nonce) = find_next_hash( proof.hash.into(), KeccakHash::new_from_array([u8::MAX; 32]), payer.pubkey(), ); // Submit mine tx - let mut ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], next_hash.into(), nonce); + let mut ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); ix.accounts.remove(1); let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); let res = banks.process_transaction(tx).await; @@ -377,14 +375,14 @@ async fn test_mine_too_early() { // Find next hash let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (next_hash, nonce) = find_next_hash( + let (_next_hash, nonce) = find_next_hash( proof.hash.into(), KeccakHash::new_from_array([u8::MAX; 32]), payer.pubkey(), ); // Submit mine tx - let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], next_hash.into(), nonce); + let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); let res = banks.process_transaction(tx).await; assert!(res.is_err()); @@ -406,14 +404,14 @@ async fn test_mine_needs_reset() { // Find next hash let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (next_hash, nonce) = find_next_hash( + let (_next_hash, nonce) = find_next_hash( proof.hash.into(), KeccakHash::new_from_array([u8::MAX; 32]), payer.pubkey(), ); // Submit mine tx - let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], next_hash.into(), nonce); + let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); let res = banks.process_transaction(tx).await; assert!(res.is_err()); @@ -469,7 +467,7 @@ async fn test_mine_fail_bad_data() { // Shared variables for tests. let mut rng = rand::thread_rng(); - let (next_hash, nonce) = find_next_hash( + let (_next_hash, nonce) = find_next_hash( proof.hash.into(), KeccakHash::new_from_array([u8::MAX; 32]), payer.pubkey(), @@ -501,7 +499,6 @@ async fn test_mine_fail_bad_data() { // Fuzz test random hashes and nonces for _ in 0..FUZZ { - let next_hash = KeccakHash::new_unique(); let nonce: u64 = rng.gen(); assert_mine_tx_err( &mut banks, @@ -512,7 +509,6 @@ async fn test_mine_fail_bad_data() { proof_address, TREASURY_ADDRESS, sysvar::slot_hashes::id(), - next_hash, nonce, ) .await; @@ -529,7 +525,6 @@ async fn test_mine_fail_bad_data() { proof_address, TREASURY_ADDRESS, sysvar::slot_hashes::id(), - next_hash, nonce, ) .await; @@ -546,7 +541,6 @@ async fn test_mine_fail_bad_data() { Pubkey::new_unique(), TREASURY_ADDRESS, sysvar::slot_hashes::id(), - next_hash, nonce, ) .await; @@ -562,7 +556,6 @@ async fn test_mine_fail_bad_data() { TREASURY_ADDRESS, proof_address, sysvar::slot_hashes::id(), - next_hash, nonce, ) .await; @@ -577,7 +570,6 @@ async fn test_mine_fail_bad_data() { proof_address, TREASURY_ADDRESS, sysvar::clock::id(), - next_hash, nonce, ) .await; @@ -592,7 +584,6 @@ async fn assert_mine_tx_err( proof: Pubkey, treasury: Pubkey, slot_hash: Pubkey, - next_hash: KeccakHash, nonce: u64, ) { let ix = Instruction { @@ -607,7 +598,6 @@ async fn assert_mine_tx_err( data: [ OreInstruction::Mine.to_vec(), MineArgs { - hash: next_hash.into(), nonce: nonce.to_le_bytes(), } .to_bytes() From 91224291401c3d3f3852618fa27c3ce70a21a454 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 14 Apr 2024 06:12:35 +0000 Subject: [PATCH 002/111] randomize hash --- src/error.rs | 6 ++---- src/instruction.rs | 1 + src/processor/mine.rs | 2 +- src/processor/register.rs | 11 ++++++++--- tests/test_mine.rs | 20 +------------------- 5 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/error.rs b/src/error.rs index 597331b..63a657d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,12 +13,10 @@ pub enum OreError { ResetTooEarly = 2, #[error("The provided hash was invalid")] HashInvalid = 3, - #[error("The provided hash does not satisfy the difficulty requirement")] - DifficultyNotSatisfied = 4, #[error("The bus does not have enough rewards to issue at this time")] - BusRewardsInsufficient = 5, + BusRewardsInsufficient = 4, #[error("The claim amount cannot be greater than the claimable rewards")] - ClaimTooLarge = 6, + ClaimTooLarge = 5, } impl From for ProgramError { diff --git a/src/instruction.rs b/src/instruction.rs index fa60d43..3f049e8 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -197,6 +197,7 @@ pub fn register(signer: Pubkey) -> Instruction { AccountMeta::new(signer, true), AccountMeta::new(proof_pda.0, false), AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), ], data: [ OreInstruction::Register.to_vec(), diff --git a/src/processor/mine.rs b/src/processor/mine.rs index d7ee705..9241ad4 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -116,7 +116,7 @@ pub(crate) fn validate_hash( signer.as_ref(), ]); if hash.gt(&difficulty) { - return Err(OreError::DifficultyNotSatisfied.into()); + return Err(OreError::HashInvalid.into()); } Ok(hash) } diff --git a/src/processor/register.rs b/src/processor/register.rs index 94fc9fd..5451996 100644 --- a/src/processor/register.rs +++ b/src/processor/register.rs @@ -2,7 +2,7 @@ use std::mem::size_of; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, keccak::hashv, - program_error::ProgramError, pubkey::Pubkey, system_program, + program_error::ProgramError, pubkey::Pubkey, slot_hashes::SlotHash, system_program, sysvar, }; use crate::{ @@ -32,7 +32,7 @@ pub fn process_register<'a, 'info>( let args = RegisterArgs::try_from_bytes(data)?; // Load accounts - let [signer, proof_info, system_program] = accounts else { + let [signer, proof_info, system_program, slot_hashes_info] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; @@ -43,6 +43,7 @@ pub fn process_register<'a, 'info>( &crate::id(), )?; load_program(system_program, system_program::id())?; + load_sysvar(slot_hashes_info, sysvar::slot_hashes::id())?; // Initialize proof create_pda( @@ -58,7 +59,11 @@ pub fn process_register<'a, 'info>( let proof = Proof::try_from_bytes_mut(&mut proof_data)?; proof.authority = *signer.key; proof.claimable_rewards = 0; - proof.hash = hashv(&[signer.key.as_ref()]).into(); + proof.hash = hashv(&[ + signer.key.as_ref(), + &slot_hashes_info.data.borrow()[0..size_of::()], + ]) + .into(); proof.total_hashes = 0; proof.total_rewards = 0; diff --git a/tests/test_mine.rs b/tests/test_mine.rs index 04faf3d..6494d5c 100644 --- a/tests/test_mine.rs +++ b/tests/test_mine.rs @@ -50,7 +50,6 @@ async fn test_mine() { let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); assert_eq!(proof.authority, payer.pubkey()); assert_eq!(proof.claimable_rewards, 0); - assert_eq!(proof.hash, hashv(&[payer.pubkey().as_ref()]).into()); assert_eq!(proof.total_hashes, 0); assert_eq!(proof.total_rewards, 0); @@ -497,23 +496,6 @@ async fn test_mine_fail_bad_data() { assert!(res.is_err()); } - // Fuzz test random hashes and nonces - for _ in 0..FUZZ { - let nonce: u64 = rng.gen(); - assert_mine_tx_err( - &mut banks, - &payer, - blockhash, - payer.pubkey(), - BUS_ADDRESSES[0], - proof_address, - TREASURY_ADDRESS, - sysvar::slot_hashes::id(), - nonce, - ) - .await; - } - // Fuzz test random bus addresses for _ in 0..FUZZ { assert_mine_tx_err( @@ -615,9 +597,9 @@ fn find_next_hash(hash: KeccakHash, difficulty: KeccakHash, signer: Pubkey) -> ( let mut nonce = 0u64; loop { next_hash = hashv(&[ + nonce.to_le_bytes().as_slice(), hash.to_bytes().as_slice(), signer.to_bytes().as_slice(), - nonce.to_le_bytes().as_slice(), ]); if next_hash.le(&difficulty) { break; From 3a109e7a11601f961b8e19a8815c04c5053f633a Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 16 Apr 2024 03:26:40 +0000 Subject: [PATCH 003/111] groundwork for pick your difficulty --- src/consts.rs | 7 + src/instruction.rs | 42 +- src/lib.rs | 1 - src/loaders.rs | 1826 ++++++++++++++-------------- src/processor/claim.rs | 6 +- src/processor/initialize.rs | 24 +- src/processor/mine.rs | 21 +- src/processor/mod.rs | 2 - src/processor/register.rs | 6 +- src/processor/update_admin.rs | 14 +- src/processor/update_difficulty.rs | 55 - src/state/bus.rs | 6 + src/state/config.rs | 29 + src/state/mod.rs | 2 + src/state/proof.rs | 11 +- src/state/treasury.rs | 10 +- src/utils.rs | 5 +- tests/test_initialize.rs | 306 ++--- tests/test_mine.rs | 1368 ++++++++++----------- tests/test_register.rs | 346 +++--- tests/test_reset.rs | 810 ++++++------ tests/test_update_admin.rs | 224 ++-- tests/test_update_difficulty.rs | 220 ++-- 23 files changed, 2687 insertions(+), 2654 deletions(-) delete mode 100644 src/processor/update_difficulty.rs create mode 100644 src/state/config.rs diff --git a/src/consts.rs b/src/consts.rs index 095c39e..1a2916b 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -48,6 +48,9 @@ static_assertions::const_assert!( /// The seed of the bus account PDA. pub const BUS: &[u8] = b"bus"; +/// The seed of the config account PDA. +pub const CONFIG: &[u8] = b"config"; + /// The seed of the metadata account PDA. pub const METADATA: &[u8] = b"metadata"; @@ -86,6 +89,10 @@ pub const BUS_ADDRESSES: [Pubkey; BUS_COUNT] = [ pubkey!("Ay5N9vKS2Tyo2M9u9TFt59N1XbxdW93C7UrFZW3h8sMC"), ]; +// TODO +/// The address of the config account. +pub const CONFIG_ADDRESS: Pubkey = pubkey!("FTap9fv2GPpWGqrLj3o4c9nHH7p36ih7NbSWHnrkQYqa"); + /// The address of the mint metadata account. pub const METADATA_ADDRESS: Pubkey = pubkey!("2nXZSxfjELuRatcoY64yHdFLZFi3mtesxobHmsoU3Dag"); diff --git a/src/instruction.rs b/src/instruction.rs index 3f049e8..041e254 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -8,10 +8,19 @@ use solana_program::{ }; use crate::{ - impl_instruction_from_bytes, impl_to_bytes, state::Hash, BUS, METADATA, MINT, MINT_ADDRESS, - MINT_NOISE, PROOF, TREASURY, TREASURY_ADDRESS, + impl_instruction_from_bytes, impl_to_bytes, state::Hash, BUS, CONFIG, CONFIG_ADDRESS, METADATA, + MINT, MINT_ADDRESS, MINT_NOISE, PROOF, TREASURY, TREASURY_ADDRESS, }; +// TODO Stake +// TODO Unstake + +// TODO Upgrade (v1 to v2 token) +// TODO Downgrade (v2 to v1 token) + +// TODO Personalized difficulty +// TODO Payout rewards based on multiplier and difficulty setting + #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, ShankInstruction, TryFromPrimitive)] #[rustfmt::skip] @@ -80,11 +89,6 @@ pub enum OreInstruction { #[account(1, name = "signer", desc = "Admin signer", signer)] #[account(2, name = "treasury", desc = "Ore treasury account")] UpdateAdmin = 101, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "signer", desc = "Admin signer", signer)] - #[account(2, name = "treasury", desc = "Ore treasury account")] - UpdateDifficulty = 102, } impl OreInstruction { @@ -104,6 +108,7 @@ pub struct InitializeArgs { pub bus_5_bump: u8, pub bus_6_bump: u8, pub bus_7_bump: u8, + pub config_bump: u8, pub metadata_bump: u8, pub mint_bump: u8, pub treasury_bump: u8, @@ -144,14 +149,12 @@ impl_to_bytes!(RegisterArgs); impl_to_bytes!(MineArgs); impl_to_bytes!(ClaimArgs); impl_to_bytes!(UpdateAdminArgs); -impl_to_bytes!(UpdateDifficultyArgs); impl_instruction_from_bytes!(InitializeArgs); impl_instruction_from_bytes!(RegisterArgs); impl_instruction_from_bytes!(MineArgs); impl_instruction_from_bytes!(ClaimArgs); impl_instruction_from_bytes!(UpdateAdminArgs); -impl_instruction_from_bytes!(UpdateDifficultyArgs); /// Builds a reset instruction. pub fn reset(signer: Pubkey) -> Instruction { @@ -215,8 +218,8 @@ pub fn mine(signer: Pubkey, bus: Pubkey, nonce: u64) -> Instruction { accounts: vec![ AccountMeta::new(signer, true), AccountMeta::new(bus, false), + AccountMeta::new_readonly(CONFIG_ADDRESS, false), AccountMeta::new(proof, false), - AccountMeta::new_readonly(TREASURY_ADDRESS, false), AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), ], data: [ @@ -272,6 +275,7 @@ pub fn initialize(signer: Pubkey) -> Instruction { Pubkey::find_program_address(&[BUS, &[6]], &crate::id()), Pubkey::find_program_address(&[BUS, &[7]], &crate::id()), ]; + let config_pda = Pubkey::find_program_address(&[CONFIG], &crate::id()); let mint_pda = Pubkey::find_program_address(&[MINT, MINT_NOISE.as_slice()], &crate::id()); let treasury_pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); let treasury_tokens = @@ -296,6 +300,7 @@ pub fn initialize(signer: Pubkey) -> Instruction { AccountMeta::new(bus_pdas[5].0, false), AccountMeta::new(bus_pdas[6].0, false), AccountMeta::new(bus_pdas[7].0, false), + AccountMeta::new(config_pda.0, false), AccountMeta::new(metadata_pda.0, false), AccountMeta::new(mint_pda.0, false), AccountMeta::new(treasury_pda.0, false), @@ -317,6 +322,7 @@ pub fn initialize(signer: Pubkey) -> Instruction { bus_5_bump: bus_pdas[5].1, bus_6_bump: bus_pdas[6].1, bus_7_bump: bus_pdas[7].1, + config_bump: config_pda.1, metadata_bump: metadata_pda.1, mint_bump: mint_pda.1, treasury_bump: treasury_pda.1, @@ -343,19 +349,3 @@ pub fn update_admin(signer: Pubkey, new_admin: Pubkey) -> Instruction { .concat(), } } - -/// Builds an update_difficulty instruction. -pub fn update_difficulty(signer: Pubkey, new_difficulty: Hash) -> Instruction { - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(TREASURY_ADDRESS, false), - ], - data: [ - OreInstruction::UpdateDifficulty.to_vec(), - UpdateDifficultyArgs { new_difficulty }.to_bytes().to_vec(), - ] - .concat(), - } -} diff --git a/src/lib.rs b/src/lib.rs index 1cc357d..dca6482 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,7 +39,6 @@ pub fn process_instruction( OreInstruction::Claim => process_claim(program_id, accounts, data)?, OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, OreInstruction::UpdateAdmin => process_update_admin(program_id, accounts, data)?, - OreInstruction::UpdateDifficulty => process_update_difficulty(program_id, accounts, data)?, } Ok(()) diff --git a/src/loaders.rs b/src/loaders.rs index 902b996..aa7b20d 100644 --- a/src/loaders.rs +++ b/src/loaders.rs @@ -5,9 +5,9 @@ use solana_program::{ use spl_token::state::Mint; use crate::{ - state::{Bus, Proof, Treasury}, + state::{Bus, Config, Proof, Treasury}, utils::AccountDeserialize, - BUS_ADDRESSES, BUS_COUNT, MINT_ADDRESS, TREASURY_ADDRESS, + BUS_ADDRESSES, BUS_COUNT, CONFIG_ADDRESS, MINT_ADDRESS, TREASURY_ADDRESS, }; /// Errors if: @@ -95,6 +95,38 @@ pub fn load_any_bus<'a, 'info>( Ok(()) } +/// Errors if: +/// - Owner is not Ore program. +/// - Address does not match the expected address. +/// - Data is empty. +/// - Data cannot deserialize into a config account. +/// - Expected to be writable, but is not. +pub fn load_config<'a, 'info>( + info: &'a AccountInfo<'info>, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&crate::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.key.ne(&CONFIG_ADDRESS) { + return Err(ProgramError::InvalidSeeds); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + let config_data = info.data.borrow(); + let _ = Config::try_from_bytes(&config_data)?; + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + /// Errors if: /// - Owner is not Ore program. /// - Data is empty. @@ -331,941 +363,943 @@ pub fn load_program<'a, 'info>( Ok(()) } -#[cfg(test)] -mod tests { - use solana_program::{ - account_info::AccountInfo, keccak::Hash as KeccakHash, program_option::COption, - program_pack::Pack, pubkey::Pubkey, system_program, - }; - use spl_token::state::{AccountState, Mint}; +// #[cfg(test)] +// mod tests { +// use solana_program::{ +// account_info::AccountInfo, keccak::Hash as KeccakHash, program_option::COption, +// program_pack::Pack, pubkey::Pubkey, system_program, +// }; +// use spl_token::state::{AccountState, Mint}; - use crate::{ - loaders::{ - load_account, load_any_bus, load_bus, load_mint, load_proof, load_signer, load_sysvar, - load_token_account, load_treasury, load_uninitialized_account, load_uninitialized_pda, - }, - state::{Bus, Proof, Treasury}, - utils::Discriminator, - BUS, BUS_ADDRESSES, BUS_COUNT, MINT_ADDRESS, PROOF, TOKEN_DECIMALS, TREASURY, - TREASURY_ADDRESS, - }; +// use crate::{ +// loaders::{ +// load_account, load_any_bus, load_bus, load_mint, load_proof, load_signer, load_sysvar, +// load_token_account, load_treasury, load_uninitialized_account, load_uninitialized_pda, +// }, +// state::{Bus, Proof, Treasury}, +// utils::Discriminator, +// BUS, BUS_ADDRESSES, BUS_COUNT, MINT_ADDRESS, PROOF, TOKEN_DECIMALS, TREASURY, +// TREASURY_ADDRESS, +// }; - use super::load_program; +// use super::load_program; - #[test] - pub fn test_signer_not_signer() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_signer(&info).is_err()); - } +// #[test] +// pub fn test_signer_not_signer() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &key, +// false, +// false, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_signer(&info).is_err()); +// } - #[test] - pub fn test_load_bus_bad_account_owner() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_bus(&info, 0, true).is_err()); - } +// #[test] +// pub fn test_load_bus_bad_account_owner() { +// let key = BUS_ADDRESSES[0]; +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_bus(&info, 0, true).is_err()); +// } - #[test] - pub fn test_load_bus_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_bus(&info, 0, true).is_err()); - } +// #[test] +// pub fn test_load_bus_bad_key() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_bus(&info, 0, true).is_err()); +// } - #[test] - pub fn test_load_bus_empty_data() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_bus(&info, 0, true).is_err()); - } +// #[test] +// pub fn test_load_bus_empty_data() { +// let key = BUS_ADDRESSES[0]; +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_bus(&info, 0, true).is_err()); +// } - #[test] - pub fn test_load_bus_bad_data() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Treasury::discriminator() as u64).to_le_bytes(), // Bus discriminator - Bus { id: 0, rewards: 0 }.to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_bus(&info, 0, true).is_err()); - } +// #[test] +// pub fn test_load_bus_bad_data() { +// let key = BUS_ADDRESSES[0]; +// let mut lamports = 1_000_000_000; +// let mut data = [ +// &(Treasury::discriminator() as u64).to_le_bytes(), // Bus discriminator +// Bus { id: 0, rewards: 0 }.to_bytes(), +// ] +// .concat(); +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_bus(&info, 0, true).is_err()); +// } - #[test] - pub fn test_load_bus_bad_id() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), // Bus discriminator - Bus { id: 1, rewards: 0 }.to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_bus(&info, 0, true).is_err()); - } +// #[test] +// pub fn test_load_bus_bad_id() { +// let key = BUS_ADDRESSES[0]; +// let mut lamports = 1_000_000_000; +// let mut data = [ +// &(Bus::discriminator() as u64).to_le_bytes(), // Bus discriminator +// Bus { id: 1, rewards: 0 }.to_bytes(), +// ] +// .concat(); +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_bus(&info, 0, true).is_err()); +// } - #[test] - pub fn test_load_bus_not_writeable() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { id: 0, rewards: 0 }.to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_bus(&info, 0, true).is_err()); - } +// #[test] +// pub fn test_load_bus_not_writeable() { +// let key = BUS_ADDRESSES[0]; +// let mut lamports = 1_000_000_000; +// let mut data = [ +// &(Bus::discriminator() as u64).to_le_bytes(), +// Bus { id: 0, rewards: 0 }.to_bytes(), +// ] +// .concat(); +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// false, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_bus(&info, 0, true).is_err()); +// } - #[test] - pub fn test_load_any_bus_bad_account_owner() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } +// #[test] +// pub fn test_load_any_bus_bad_account_owner() { +// let key = BUS_ADDRESSES[0]; +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_any_bus(&info, true).is_err()); +// } - #[test] - pub fn test_load_any_bus_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } +// #[test] +// pub fn test_load_any_bus_bad_key() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_any_bus(&info, true).is_err()); +// } - #[test] - pub fn test_load_any_bus_empty_data() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } +// #[test] +// pub fn test_load_any_bus_empty_data() { +// let key = BUS_ADDRESSES[0]; +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_any_bus(&info, true).is_err()); +// } - #[test] - pub fn test_load_any_bus_bad_data() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Treasury::discriminator() as u64).to_le_bytes(), // Treasury discriminator - Bus { id: 0, rewards: 0 }.to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } +// #[test] +// pub fn test_load_any_bus_bad_data() { +// let key = BUS_ADDRESSES[0]; +// let mut lamports = 1_000_000_000; +// let mut data = [ +// &(Treasury::discriminator() as u64).to_le_bytes(), // Treasury discriminator +// Bus { id: 0, rewards: 0 }.to_bytes(), +// ] +// .concat(); +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_any_bus(&info, true).is_err()); +// } - #[test] - pub fn test_load_any_bus_bad_id() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { - id: (BUS_COUNT + 1) as u64, - rewards: 0, - } - .to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } +// #[test] +// pub fn test_load_any_bus_bad_id() { +// let key = BUS_ADDRESSES[0]; +// let mut lamports = 1_000_000_000; +// let mut data = [ +// &(Bus::discriminator() as u64).to_le_bytes(), +// Bus { +// id: (BUS_COUNT + 1) as u64, +// rewards: 0, +// } +// .to_bytes(), +// ] +// .concat(); +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_any_bus(&info, true).is_err()); +// } - #[test] - pub fn test_load_any_bus_mismatch_id() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { - id: 1 as u64, - rewards: 0, - } - .to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } +// #[test] +// pub fn test_load_any_bus_mismatch_id() { +// let key = BUS_ADDRESSES[0]; +// let mut lamports = 1_000_000_000; +// let mut data = [ +// &(Bus::discriminator() as u64).to_le_bytes(), +// Bus { +// id: 1 as u64, +// rewards: 0, +// } +// .to_bytes(), +// ] +// .concat(); +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_any_bus(&info, true).is_err()); +// } - #[test] - pub fn test_load_any_bus_not_writeable() { - let key = BUS_ADDRESSES[0]; - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { id: 0, rewards: 0 }.to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_any_bus(&info, true).is_err()); - } +// #[test] +// pub fn test_load_any_bus_not_writeable() { +// let key = BUS_ADDRESSES[0]; +// let mut lamports = 1_000_000_000; +// let mut data = [ +// &(Bus::discriminator() as u64).to_le_bytes(), +// Bus { id: 0, rewards: 0 }.to_bytes(), +// ] +// .concat(); +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// false, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_any_bus(&info, true).is_err()); +// } - #[test] - pub fn test_load_proof_bad_account_owner() { - let authority = Pubkey::new_unique(); - let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &pda.0, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_proof(&info, &authority, true).is_err()); - } +// #[test] +// pub fn test_load_proof_bad_account_owner() { +// let authority = Pubkey::new_unique(); +// let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &pda.0, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_proof(&info, &authority, true).is_err()); +// } - #[test] - pub fn test_load_proof_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_proof(&info, &Pubkey::new_unique(), true).is_err()); - } +// #[test] +// pub fn test_load_proof_bad_key() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_proof(&info, &Pubkey::new_unique(), true).is_err()); +// } - #[test] - pub fn test_load_proof_empty_data() { - let authority = Pubkey::new_unique(); - let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &pda.0, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_proof(&info, &authority, true).is_err()); - } +// #[test] +// pub fn test_load_proof_empty_data() { +// let authority = Pubkey::new_unique(); +// let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = crate::id(); +// let info = AccountInfo::new( +// &pda.0, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_proof(&info, &authority, true).is_err()); +// } - #[test] - pub fn test_load_proof_bad_data() { - let authority = Pubkey::new_unique(); - let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), // Bus discriminator - Proof { - authority, - claimable_rewards: 0, - hash: KeccakHash::new_from_array([u8::MAX; 32]).into(), - total_hashes: 0, - total_rewards: 0, - } - .to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &pda.0, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_proof(&info, &authority, true).is_err()); - } +// #[test] +// pub fn test_load_proof_bad_data() { +// let authority = Pubkey::new_unique(); +// let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); +// let mut lamports = 1_000_000_000; +// let mut data = [ +// &(Bus::discriminator() as u64).to_le_bytes(), // Bus discriminator +// Proof { +// authority, +// balance: 0, +// hash: KeccakHash::new_from_array([u8::MAX; 32]).into(), +// total_hashes: 0, +// total_rewards: 0, +// multiplier: 1, // TODO +// last_hash_at: 0, +// } +// .to_bytes(), +// ] +// .concat(); +// let owner = crate::id(); +// let info = AccountInfo::new( +// &pda.0, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_proof(&info, &authority, true).is_err()); +// } - #[test] - pub fn test_load_proof_not_writeable() { - let authority = Pubkey::new_unique(); - let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = [ - &(Proof::discriminator() as u64).to_le_bytes(), - Proof { - authority, - claimable_rewards: 0, - hash: KeccakHash::new_from_array([u8::MAX; 32]).into(), - total_hashes: 0, - total_rewards: 0, - } - .to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &pda.0, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_proof(&info, &authority, true).is_err()); - } +// #[test] +// pub fn test_load_proof_not_writeable() { +// let authority = Pubkey::new_unique(); +// let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); +// let mut lamports = 1_000_000_000; +// let mut data = [ +// &(Proof::discriminator() as u64).to_le_bytes(), +// Proof { +// authority, +// claimable_rewards: 0, +// hash: KeccakHash::new_from_array([u8::MAX; 32]).into(), +// total_hashes: 0, +// total_rewards: 0, +// } +// .to_bytes(), +// ] +// .concat(); +// let owner = crate::id(); +// let info = AccountInfo::new( +// &pda.0, +// false, +// false, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_proof(&info, &authority, true).is_err()); +// } - #[test] - pub fn test_load_treasury_bad_account_owner() { - let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &pda.0, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_treasury(&info, true).is_err()); - } +// #[test] +// pub fn test_load_treasury_bad_account_owner() { +// let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &pda.0, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_treasury(&info, true).is_err()); +// } - #[test] - pub fn test_load_treasury_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_treasury(&info, true).is_err()); - } +// #[test] +// pub fn test_load_treasury_bad_key() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_treasury(&info, true).is_err()); +// } - #[test] - pub fn test_load_treasury_empty_data() { - let key = TREASURY_ADDRESS; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = crate::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_treasury(&info, true).is_err()); - } +// #[test] +// pub fn test_load_treasury_empty_data() { +// let key = TREASURY_ADDRESS; +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = crate::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_treasury(&info, true).is_err()); +// } - #[test] - pub fn test_load_treasury_bad_data() { - let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = [ - &(Bus::discriminator() as u64).to_le_bytes(), // Bus discriminator - Treasury { - bump: pda.1 as u64, - admin: Pubkey::new_unique(), - difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), - last_reset_at: 0, - reward_rate: 100, - total_claimed_rewards: 0, - } - .to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &pda.0, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_treasury(&info, true).is_err()); - } +// #[test] +// pub fn test_load_treasury_bad_data() { +// let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); +// let mut lamports = 1_000_000_000; +// let mut data = [ +// &(Bus::discriminator() as u64).to_le_bytes(), // Bus discriminator +// Treasury { +// bump: pda.1 as u64, +// // admin: Pubkey::new_unique(), +// // difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), +// last_reset_at: 0, +// reward_rate: 100, +// total_claimed_rewards: 0, +// } +// .to_bytes(), +// ] +// .concat(); +// let owner = crate::id(); +// let info = AccountInfo::new( +// &pda.0, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_treasury(&info, true).is_err()); +// } - #[test] - pub fn test_load_treasury_not_writeable() { - let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = [ - &(Treasury::discriminator() as u64).to_le_bytes(), - Treasury { - bump: pda.1 as u64, - admin: Pubkey::new_unique(), - difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), - last_reset_at: 0, - reward_rate: 100, - total_claimed_rewards: 0, - } - .to_bytes(), - ] - .concat(); - let owner = crate::id(); - let info = AccountInfo::new( - &pda.0, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_treasury(&info, true).is_err()); - } +// #[test] +// pub fn test_load_treasury_not_writeable() { +// let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); +// let mut lamports = 1_000_000_000; +// let mut data = [ +// &(Treasury::discriminator() as u64).to_le_bytes(), +// Treasury { +// bump: pda.1 as u64, +// // admin: Pubkey::new_unique(), +// // difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), +// last_reset_at: 0, +// reward_rate: 100, +// total_claimed_rewards: 0, +// } +// .to_bytes(), +// ] +// .concat(); +// let owner = crate::id(); +// let info = AccountInfo::new( +// &pda.0, +// false, +// false, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_treasury(&info, true).is_err()); +// } - #[test] - pub fn test_load_mint_bad_account_owner() { - let key = MINT_ADDRESS; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); - } +// #[test] +// pub fn test_load_mint_bad_account_owner() { +// let key = MINT_ADDRESS; +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); +// } - #[test] - pub fn test_load_mint_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_mint(&info, true).is_err()); - } +// #[test] +// pub fn test_load_mint_bad_key() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = spl_token::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_mint(&info, true).is_err()); +// } - #[test] - pub fn test_load_mint_empty_data() { - let key = MINT_ADDRESS; - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_mint(&info, true).is_err()); - } +// #[test] +// pub fn test_load_mint_empty_data() { +// let key = MINT_ADDRESS; +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = spl_token::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_mint(&info, true).is_err()); +// } - #[test] - pub fn test_load_mint_bad_data() { - let key = MINT_ADDRESS; - let mut lamports = 1_000_000_000; - let mut data = [1]; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_mint(&info, true).is_err()); - } +// #[test] +// pub fn test_load_mint_bad_data() { +// let key = MINT_ADDRESS; +// let mut lamports = 1_000_000_000; +// let mut data = [1]; +// let owner = spl_token::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_mint(&info, true).is_err()); +// } - #[test] - pub fn test_load_mint_not_writeable() { - let mut data: [u8; Mint::LEN] = [0; Mint::LEN]; - Mint { - mint_authority: COption::Some(TREASURY_ADDRESS), - supply: 0, - decimals: TOKEN_DECIMALS, - is_initialized: true, - freeze_authority: COption::None, - } - .pack_into_slice(&mut data); - let key = MINT_ADDRESS; - let mut lamports = 1_000_000_000; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_mint(&info, true).is_err()); - } +// #[test] +// pub fn test_load_mint_not_writeable() { +// let mut data: [u8; Mint::LEN] = [0; Mint::LEN]; +// Mint { +// mint_authority: COption::Some(TREASURY_ADDRESS), +// supply: 0, +// decimals: TOKEN_DECIMALS, +// is_initialized: true, +// freeze_authority: COption::None, +// } +// .pack_into_slice(&mut data); +// let key = MINT_ADDRESS; +// let mut lamports = 1_000_000_000; +// let owner = spl_token::id(); +// let info = AccountInfo::new( +// &key, +// false, +// false, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_mint(&info, true).is_err()); +// } - #[test] - pub fn test_load_token_account_bad_account_owner() { - let mut data: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; - spl_token::state::Account { - mint: MINT_ADDRESS, - owner: TREASURY_ADDRESS, - amount: 2_000_000_000, - delegate: COption::None, - state: AccountState::Initialized, - is_native: COption::None, - delegated_amount: 0, - close_authority: COption::None, - } - .pack_into_slice(&mut data); - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); - } +// #[test] +// pub fn test_load_token_account_bad_account_owner() { +// let mut data: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; +// spl_token::state::Account { +// mint: MINT_ADDRESS, +// owner: TREASURY_ADDRESS, +// amount: 2_000_000_000, +// delegate: COption::None, +// state: AccountState::Initialized, +// is_native: COption::None, +// delegated_amount: 0, +// close_authority: COption::None, +// } +// .pack_into_slice(&mut data); +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); +// } - #[test] - pub fn test_load_token_account_empty_data() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); - } +// #[test] +// pub fn test_load_token_account_empty_data() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = spl_token::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); +// } - #[test] - pub fn test_load_token_account_bad_data() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = [1]; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); - } +// #[test] +// pub fn test_load_token_account_bad_data() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = [1]; +// let owner = spl_token::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); +// } - #[test] - pub fn test_load_token_account_bad_owner_mint() { - let mut data: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; - spl_token::state::Account { - mint: MINT_ADDRESS, - owner: TREASURY_ADDRESS, - amount: 2_000_000_000, - delegate: COption::None, - state: AccountState::Initialized, - is_native: COption::None, - delegated_amount: 0, - close_authority: COption::None, - } - .pack_into_slice(&mut data); - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_token_account(&info, Some(&key), &MINT_ADDRESS, false).is_err()); - assert!(load_token_account(&info, None, &Pubkey::new_unique(), false).is_err()); - assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); - } +// #[test] +// pub fn test_load_token_account_bad_owner_mint() { +// let mut data: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; +// spl_token::state::Account { +// mint: MINT_ADDRESS, +// owner: TREASURY_ADDRESS, +// amount: 2_000_000_000, +// delegate: COption::None, +// state: AccountState::Initialized, +// is_native: COption::None, +// delegated_amount: 0, +// close_authority: COption::None, +// } +// .pack_into_slice(&mut data); +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let owner = spl_token::id(); +// let info = AccountInfo::new( +// &key, +// false, +// false, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_token_account(&info, Some(&key), &MINT_ADDRESS, false).is_err()); +// assert!(load_token_account(&info, None, &Pubkey::new_unique(), false).is_err()); +// assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); +// } - #[test] - pub fn test_load_uninitialized_pda_bad_key_bump() { - let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &pda.0, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_uninitialized_pda(&info, &[BUS], pda.1, &crate::id()).is_err()); - assert!(load_uninitialized_pda(&info, &[TREASURY], 0, &crate::id()).is_err()); - } +// #[test] +// pub fn test_load_uninitialized_pda_bad_key_bump() { +// let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &pda.0, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_uninitialized_pda(&info, &[BUS], pda.1, &crate::id()).is_err()); +// assert!(load_uninitialized_pda(&info, &[TREASURY], 0, &crate::id()).is_err()); +// } - #[test] - pub fn test_load_uninitialized_account_bad_owner() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = spl_token::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_uninitialized_account(&info).is_err()); - } +// #[test] +// pub fn test_load_uninitialized_account_bad_owner() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = spl_token::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_uninitialized_account(&info).is_err()); +// } - #[test] - pub fn test_load_uninitialized_account_data_not_empty() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = [0]; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - true, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_uninitialized_account(&info).is_err()); - } +// #[test] +// pub fn test_load_uninitialized_account_data_not_empty() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = [0]; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &key, +// false, +// true, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_uninitialized_account(&info).is_err()); +// } - #[test] - pub fn test_load_uninitialized_account_not_writeable() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_uninitialized_account(&info).is_err()); - } +// #[test] +// pub fn test_load_uninitialized_account_not_writeable() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &key, +// false, +// false, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_uninitialized_account(&info).is_err()); +// } - #[test] - pub fn test_load_sysvar_bad_owner() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_sysvar(&info, key).is_err()); - } +// #[test] +// pub fn test_load_sysvar_bad_owner() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &key, +// false, +// false, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_sysvar(&info, key).is_err()); +// } - #[test] - pub fn test_load_account_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_account(&info, Pubkey::new_unique(), false).is_err()); - } +// #[test] +// pub fn test_load_account_bad_key() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &key, +// false, +// false, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_account(&info, Pubkey::new_unique(), false).is_err()); +// } - #[test] - pub fn test_load_account_not_writeable() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_account(&info, key, true).is_err()); - } +// #[test] +// pub fn test_load_account_not_writeable() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &key, +// false, +// false, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_account(&info, key, true).is_err()); +// } - #[test] - pub fn test_load_program_bad_key() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - true, - 0, - ); - assert!(load_program(&info, Pubkey::new_unique()).is_err()); - } +// #[test] +// pub fn test_load_program_bad_key() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &key, +// false, +// false, +// &mut lamports, +// &mut data, +// &owner, +// true, +// 0, +// ); +// assert!(load_program(&info, Pubkey::new_unique()).is_err()); +// } - #[test] - pub fn test_load_program_not_executable() { - let key = Pubkey::new_unique(); - let mut lamports = 1_000_000_000; - let mut data = []; - let owner = system_program::id(); - let info = AccountInfo::new( - &key, - false, - false, - &mut lamports, - &mut data, - &owner, - false, - 0, - ); - assert!(load_program(&info, key).is_err()); - } -} +// #[test] +// pub fn test_load_program_not_executable() { +// let key = Pubkey::new_unique(); +// let mut lamports = 1_000_000_000; +// let mut data = []; +// let owner = system_program::id(); +// let info = AccountInfo::new( +// &key, +// false, +// false, +// &mut lamports, +// &mut data, +// &owner, +// false, +// 0, +// ); +// assert!(load_program(&info, key).is_err()); +// } +// } diff --git a/src/processor/claim.rs b/src/processor/claim.rs index 849297a..9ba5b29 100644 --- a/src/processor/claim.rs +++ b/src/processor/claim.rs @@ -48,11 +48,11 @@ pub fn process_claim<'a, 'info>( )?; load_program(token_program, spl_token::id())?; - // Update claimable amount + // Update miner balance let mut proof_data = proof_info.data.borrow_mut(); let proof = Proof::try_from_bytes_mut(&mut proof_data)?; - proof.claimable_rewards = proof - .claimable_rewards + proof.balance = proof + .balance .checked_sub(amount) .ok_or(OreError::ClaimTooLarge)?; diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index cd29e63..dfcb3ca 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -13,11 +13,11 @@ use spl_token::state::Mint; use crate::{ instruction::*, loaders::*, - state::{Bus, Treasury}, + state::{Bus, Config, Treasury}, utils::create_pda, utils::AccountDeserialize, utils::Discriminator, - BUS, BUS_COUNT, INITIAL_DIFFICULTY, INITIAL_REWARD_RATE, METADATA, METADATA_NAME, + BUS, BUS_COUNT, CONFIG, INITIAL_DIFFICULTY, INITIAL_REWARD_RATE, METADATA, METADATA_NAME, METADATA_SYMBOL, METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, }; @@ -48,7 +48,7 @@ pub fn process_initialize<'a, 'info>( let args = InitializeArgs::try_from_bytes(data)?; // Load accounts - let [signer, bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, bus_7_info, metadata_info, mint_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program, metadata_program, rent_sysvar] = + let [signer, 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, metadata_info, mint_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program, metadata_program, rent_sysvar] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -62,6 +62,7 @@ pub fn process_initialize<'a, 'info>( load_uninitialized_pda(bus_5_info, &[BUS, &[5]], args.bus_5_bump, &crate::id())?; load_uninitialized_pda(bus_6_info, &[BUS, &[6]], args.bus_6_bump, &crate::id())?; load_uninitialized_pda(bus_7_info, &[BUS, &[7]], args.bus_7_bump, &crate::id())?; + load_uninitialized_pda(config_info, &[CONFIG], args.config_bump, &crate::id())?; load_uninitialized_pda( metadata_info, &[ @@ -117,6 +118,21 @@ pub fn process_initialize<'a, 'info>( bus.rewards = 0; } + // Initialize config + create_pda( + config_info, + &crate::id(), + 8 + size_of::(), + &[CONFIG, &[args.config_bump]], + system_program, + signer, + )?; + let mut config_data = config_info.data.borrow_mut(); + config_data[0] = Config::discriminator() as u8; + let config = Config::try_from_bytes_mut(&mut config_data)?; + config.admin = *signer.key; + config.difficulty = INITIAL_DIFFICULTY.into(); + // Initialize treasury create_pda( treasury_info, @@ -129,9 +145,7 @@ pub fn process_initialize<'a, 'info>( let mut treasury_data = treasury_info.data.borrow_mut(); treasury_data[0] = Treasury::discriminator() as u8; let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; - treasury.admin = *signer.key; treasury.bump = args.treasury_bump as u64; - treasury.difficulty = INITIAL_DIFFICULTY.into(); treasury.last_reset_at = 0; treasury.reward_rate = INITIAL_REWARD_RATE; treasury.total_claimed_rewards = 0; diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 9241ad4..6a15a68 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -69,12 +69,16 @@ pub fn process_mine<'a, 'info>( // Validate provided hash let mut proof_data = proof_info.data.borrow_mut(); let proof = Proof::try_from_bytes_mut(&mut proof_data)?; - let hash = validate_hash( - proof.hash.into(), - *signer.key, - u64::from_le_bytes(args.nonce), - treasury.difficulty.into(), - )?; + // let hash = validate_hash( + // proof.hash.into(), + // *signer.key, + // u64::from_le_bytes(args.nonce), + // proof.difficulty.into(), + // )?; + + // TODO Calculate reward based on difficulty + // TODO Calculate rewards multiplier + // TODO Calculate reward payout amount // Update claimable rewards let mut bus_data = bus_info.data.borrow_mut(); @@ -83,11 +87,12 @@ pub fn process_mine<'a, 'info>( .rewards .checked_sub(treasury.reward_rate) .ok_or(OreError::BusRewardsInsufficient)?; - proof.claimable_rewards = proof.claimable_rewards.saturating_add(treasury.reward_rate); + proof.balance = proof.balance.saturating_add(treasury.reward_rate); // Hash recent slot hash into the next challenge to prevent pre-mining attacks proof.hash = hashv(&[ - hash.as_ref(), + // TODO + // hash.as_ref(), &slot_hashes_info.data.borrow()[0..size_of::()], ]) .into(); diff --git a/src/processor/mod.rs b/src/processor/mod.rs index 532584e..e5730a0 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -4,7 +4,6 @@ mod mine; mod register; mod reset; mod update_admin; -mod update_difficulty; pub use claim::*; pub use initialize::*; @@ -12,4 +11,3 @@ pub use mine::*; pub use register::*; pub use reset::*; pub use update_admin::*; -pub use update_difficulty::*; diff --git a/src/processor/register.rs b/src/processor/register.rs index 5451996..2c1d810 100644 --- a/src/processor/register.rs +++ b/src/processor/register.rs @@ -14,6 +14,8 @@ use crate::{ PROOF, }; +// TODO Create a stake account + /// Register generates a new hash chain for a prospective miner. Its responsibilities include: /// 1. Initialize a new proof account. /// 2. Generate an initial hash from the signer's key. @@ -58,12 +60,14 @@ pub fn process_register<'a, 'info>( proof_data[0] = Proof::discriminator() as u8; let proof = Proof::try_from_bytes_mut(&mut proof_data)?; proof.authority = *signer.key; - proof.claimable_rewards = 0; + proof.balance = 0; proof.hash = hashv(&[ signer.key.as_ref(), &slot_hashes_info.data.borrow()[0..size_of::()], ]) .into(); + proof.last_hash_at = 0; + proof.multiplier = 1; // TODO proof.total_hashes = 0; proof.total_rewards = 0; diff --git a/src/processor/update_admin.rs b/src/processor/update_admin.rs index a006b55..1b88148 100644 --- a/src/processor/update_admin.rs +++ b/src/processor/update_admin.rs @@ -3,7 +3,7 @@ use solana_program::{ pubkey::Pubkey, }; -use crate::{instruction::UpdateAdminArgs, loaders::*, state::Treasury, utils::AccountDeserialize}; +use crate::{instruction::UpdateAdminArgs, loaders::*, state::Config, utils::AccountDeserialize}; /// UpdateAdmin updates the program's admin account. Its responsibilities include: /// 1. Update the treasury admin address. @@ -34,21 +34,21 @@ pub fn process_update_admin<'a, 'info>( let args = UpdateAdminArgs::try_from_bytes(data)?; // Load accounts - let [signer, treasury_info] = accounts else { + let [signer, config_info] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; - load_treasury(treasury_info, true)?; + load_config(config_info, true)?; // Validate signer is admin - let mut treasury_data = treasury_info.data.borrow_mut(); - let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; - if treasury.admin.ne(&signer.key) { + let mut config_data = config_info.data.borrow_mut(); + let config = Config::try_from_bytes_mut(&mut config_data)?; + if config.admin.ne(&signer.key) { return Err(ProgramError::MissingRequiredSignature); } // Update admin - treasury.admin = args.new_admin; + config.admin = args.new_admin; Ok(()) } diff --git a/src/processor/update_difficulty.rs b/src/processor/update_difficulty.rs deleted file mode 100644 index 8b77d53..0000000 --- a/src/processor/update_difficulty.rs +++ /dev/null @@ -1,55 +0,0 @@ -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, -}; - -use crate::{ - instruction::UpdateDifficultyArgs, loaders::*, state::Treasury, utils::AccountDeserialize, -}; - -/// UpdateDifficulty updates the program's global difficulty value. Its responsibilities include: -/// 1. Update the mining difficulty. -/// -/// Safety requirements: -/// - Can only succeed if the signer is the program admin. -/// - Can only succeed if the provided treasury is valid. -/// -/// Discussion: -/// - Ore subdivides into 1 billion indivisible atomic units. Therefore if global hashpower -/// were to increase to the point where >1B valid hashes were submitted to the protocol for -/// validation per epoch, the Ore inflation rate could be pushed above the 1 ORE / min target. -/// - The strict limits on bus reward counters guarantee inflation can never exceed 2 ORE / min, -/// but it is the responsibility of the admin to adjust mining difficulty if needed to maintain -/// the 1 ORE / min target average. -/// - It is worth noting that Solana today processes well below 1 million real TPS or -/// (60 * 1,000,000) = 60,000,000 transactions per minute. Even if every transaction on Solana -/// were a mine operation, this would still be two orders of magnitude below the boundary -/// condition where Ore inflation targets would be challenged. So in practice, Solana is likely -/// to reach its network saturation point long before Ore ever hits its theoretical limits. -pub fn process_update_difficulty<'a, 'info>( - _program_id: &Pubkey, - accounts: &'a [AccountInfo<'info>], - data: &[u8], -) -> ProgramResult { - // Parse args - let args = UpdateDifficultyArgs::try_from_bytes(data)?; - - // Load accounts - let [signer, treasury_info] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - load_signer(signer)?; - load_treasury(treasury_info, true)?; - - // Validate signer is admin - let mut treasury_data = treasury_info.data.borrow_mut(); - let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; - if treasury.admin.ne(&signer.key) { - return Err(ProgramError::MissingRequiredSignature); - } - - // Update admin - treasury.difficulty = args.new_difficulty; - - Ok(()) -} diff --git a/src/state/bus.rs b/src/state/bus.rs index 849fe36..b98bd51 100644 --- a/src/state/bus.rs +++ b/src/state/bus.rs @@ -17,6 +17,12 @@ pub struct Bus { /// The quantity of rewards this bus can issue in the current epoch epoch. pub rewards: u64, + + /// Histogram of hash count per difficulty + pub hash_hist: [u64; 32], + + /// Cumulative sum of applied multipliers per difficulty + pub multiplier_hist: [u64; 32], } impl Discriminator for Bus { diff --git a/src/state/config.rs b/src/state/config.rs new file mode 100644 index 0000000..c992c12 --- /dev/null +++ b/src/state/config.rs @@ -0,0 +1,29 @@ +use bytemuck::{Pod, Zeroable}; +use shank::ShankAccount; +use solana_program::pubkey::Pubkey; + +use crate::{ + impl_account_from_bytes, impl_to_bytes, + state::Hash, + utils::{AccountDiscriminator, Discriminator}, +}; + +/// Config is a singleton account which manages admin configurable variables. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] +pub struct Config { + /// The admin authority with permission to update the difficulty. + pub admin: Pubkey, + + /// The hash difficulty. + pub difficulty: Hash, +} + +impl Discriminator for Config { + fn discriminator() -> AccountDiscriminator { + AccountDiscriminator::Config + } +} + +impl_to_bytes!(Config); +impl_account_from_bytes!(Config); diff --git a/src/state/mod.rs b/src/state/mod.rs index 64809b4..b828008 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -1,9 +1,11 @@ mod bus; +mod config; mod hash; mod proof; mod treasury; pub use bus::*; +pub use config::*; pub use hash::*; pub use proof::*; pub use treasury::*; diff --git a/src/state/proof.rs b/src/state/proof.rs index f3c1cc2..8d3699c 100644 --- a/src/state/proof.rs +++ b/src/state/proof.rs @@ -13,15 +13,22 @@ use crate::{ #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] pub struct Proof { - /// The account (i.e. miner) authorized to use this proof. + /// The signer authorized to use this proof. pub authority: Pubkey, /// The quantity of tokens this miner may claim from the treasury. - pub claimable_rewards: u64, + pub balance: u64, /// The proof's current hash. pub hash: Hash, + /// The last time this account provided a hash. + pub last_hash_at: u64, + + // TODO Figure out multiplier representation + /// The rewards multiplier for this account. + pub multiplier: u64, + /// The total lifetime hashes provided by this miner. pub total_hashes: u64, diff --git a/src/state/treasury.rs b/src/state/treasury.rs index 9ffbfc5..26ae195 100644 --- a/src/state/treasury.rs +++ b/src/state/treasury.rs @@ -1,10 +1,8 @@ use bytemuck::{Pod, Zeroable}; use shank::ShankAccount; -use solana_program::pubkey::Pubkey; use crate::{ impl_account_from_bytes, impl_to_bytes, - state::Hash, utils::{AccountDiscriminator, Discriminator}, }; @@ -13,22 +11,16 @@ use crate::{ #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] pub struct Treasury { - /// The admin authority with permission to update the difficulty. - pub admin: Pubkey, - /// The bump of the treasury account PDA, for signing CPIs. pub bump: u64, - /// The hash difficulty. - pub difficulty: Hash, - /// The timestamp of the reset invocation. pub last_reset_at: i64, /// The reward rate to payout to miners for submiting valid hashes. pub reward_rate: u64, - /// The total lifetime claimed rewards. + /// The total lifetime claimed rewards of the program. pub total_claimed_rewards: u64, } diff --git a/src/utils.rs b/src/utils.rs index 82445ae..df3eac4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -82,8 +82,9 @@ pub(crate) fn create_pda<'a, 'info>( #[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] pub enum AccountDiscriminator { Bus = 100, - Proof = 101, - Treasury = 102, + Config = 101, + Proof = 102, + Treasury = 103, } pub trait Discriminator { diff --git a/tests/test_initialize.rs b/tests/test_initialize.rs index 66afdd9..9e6a840 100644 --- a/tests/test_initialize.rs +++ b/tests/test_initialize.rs @@ -1,170 +1,170 @@ -use mpl_token_metadata::{ - accounts::Metadata, - types::{Key, TokenStandard}, -}; -use ore::{ - state::{Bus, Treasury}, - utils::AccountDeserialize, - BUS_ADDRESSES, BUS_COUNT, INITIAL_DIFFICULTY, INITIAL_REWARD_RATE, METADATA_ADDRESS, - METADATA_NAME, METADATA_SYMBOL, METADATA_URI, MINT_ADDRESS, TREASURY, -}; -use solana_program::{ - hash::Hash, program_option::COption, program_pack::Pack, pubkey::Pubkey, rent::Rent, -}; -use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; -use solana_sdk::{ - account::Account, - signature::{Keypair, Signer}, - transaction::Transaction, -}; -use spl_token::state::{AccountState, Mint}; +// use mpl_token_metadata::{ +// accounts::Metadata, +// types::{Key, TokenStandard}, +// }; +// use ore::{ +// state::{Bus, Treasury}, +// utils::AccountDeserialize, +// BUS_ADDRESSES, BUS_COUNT, INITIAL_DIFFICULTY, INITIAL_REWARD_RATE, METADATA_ADDRESS, +// METADATA_NAME, METADATA_SYMBOL, METADATA_URI, MINT_ADDRESS, TREASURY, +// }; +// use solana_program::{ +// hash::Hash, program_option::COption, program_pack::Pack, pubkey::Pubkey, rent::Rent, +// }; +// use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; +// use solana_sdk::{ +// account::Account, +// signature::{Keypair, Signer}, +// transaction::Transaction, +// }; +// use spl_token::state::{AccountState, Mint}; -#[tokio::test] -async fn test_initialize() { - // Setup - let (mut banks, payer, blockhash) = setup_program_test_env().await; +// #[tokio::test] +// async fn test_initialize() { +// // Setup +// let (mut banks, payer, blockhash) = setup_program_test_env().await; - // Pdas - let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); - let treasury_tokens_address = - spl_associated_token_account::get_associated_token_address(&treasury_pda.0, &MINT_ADDRESS); +// // Pdas +// let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); +// let treasury_tokens_address = +// spl_associated_token_account::get_associated_token_address(&treasury_pda.0, &MINT_ADDRESS); - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit tx +// let ix = ore::instruction::initialize(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Test bus state - for i in 0..BUS_COUNT { - let bus_account = banks.get_account(BUS_ADDRESSES[i]).await.unwrap().unwrap(); - assert_eq!(bus_account.owner, ore::id()); - let bus = Bus::try_from_bytes(&bus_account.data).unwrap(); - assert_eq!(bus.id as u8, i as u8); - assert_eq!(bus.rewards, 0); - } +// // Test bus state +// for i in 0..BUS_COUNT { +// let bus_account = banks.get_account(BUS_ADDRESSES[i]).await.unwrap().unwrap(); +// assert_eq!(bus_account.owner, ore::id()); +// let bus = Bus::try_from_bytes(&bus_account.data).unwrap(); +// assert_eq!(bus.id as u8, i as u8); +// assert_eq!(bus.rewards, 0); +// } - // Test treasury state - let treasury_account = banks.get_account(treasury_pda.0).await.unwrap().unwrap(); - assert_eq!(treasury_account.owner, ore::id()); - let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - assert_eq!(treasury.bump as u8, treasury_pda.1); - assert_eq!(treasury.admin, payer.pubkey()); - assert_eq!(treasury.difficulty, INITIAL_DIFFICULTY.into()); - assert_eq!(treasury.last_reset_at as u8, 0); - assert_eq!(treasury.reward_rate, INITIAL_REWARD_RATE); - assert_eq!(treasury.total_claimed_rewards as u8, 0); +// // Test treasury state +// let treasury_account = banks.get_account(treasury_pda.0).await.unwrap().unwrap(); +// assert_eq!(treasury_account.owner, ore::id()); +// let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); +// assert_eq!(treasury.bump as u8, treasury_pda.1); +// // assert_eq!(treasury.admin, payer.pubkey()); +// // assert_eq!(treasury.difficulty, INITIAL_DIFFICULTY.into()); +// assert_eq!(treasury.last_reset_at as u8, 0); +// assert_eq!(treasury.reward_rate, INITIAL_REWARD_RATE); +// assert_eq!(treasury.total_claimed_rewards as u8, 0); - // Test mint state - let mint_account = banks.get_account(MINT_ADDRESS).await.unwrap().unwrap(); - assert_eq!(mint_account.owner, spl_token::id()); - let mint = Mint::unpack(&mint_account.data).unwrap(); - assert_eq!(mint.mint_authority, COption::Some(treasury_pda.0)); - assert_eq!(mint.supply, 0); - assert_eq!(mint.decimals, ore::TOKEN_DECIMALS); - assert_eq!(mint.is_initialized, true); - assert_eq!(mint.freeze_authority, COption::None); +// // Test mint state +// let mint_account = banks.get_account(MINT_ADDRESS).await.unwrap().unwrap(); +// assert_eq!(mint_account.owner, spl_token::id()); +// let mint = Mint::unpack(&mint_account.data).unwrap(); +// assert_eq!(mint.mint_authority, COption::Some(treasury_pda.0)); +// assert_eq!(mint.supply, 0); +// assert_eq!(mint.decimals, ore::TOKEN_DECIMALS); +// assert_eq!(mint.is_initialized, true); +// assert_eq!(mint.freeze_authority, COption::None); - // Test metadata state - let metadata_account = banks.get_account(METADATA_ADDRESS).await.unwrap().unwrap(); - assert_eq!(metadata_account.owner, mpl_token_metadata::ID); - let metadata = Metadata::from_bytes(&metadata_account.data).unwrap(); - assert_eq!(metadata.key, Key::MetadataV1); - assert_eq!(metadata.update_authority, payer.pubkey()); - assert_eq!(metadata.mint, MINT_ADDRESS); - assert_eq!(metadata.name.trim_end_matches('\0'), METADATA_NAME); - assert_eq!(metadata.symbol.trim_end_matches('\0'), METADATA_SYMBOL); - assert_eq!(metadata.uri.trim_end_matches('\0'), METADATA_URI); - assert_eq!(metadata.seller_fee_basis_points, 0); - assert_eq!(metadata.creators, None); - assert_eq!(metadata.primary_sale_happened, false); - assert_eq!(metadata.is_mutable, true); - assert_eq!(metadata.token_standard, Some(TokenStandard::Fungible)); - assert_eq!(metadata.collection, None); - assert_eq!(metadata.uses, None); - assert_eq!(metadata.collection_details, None); - assert_eq!(metadata.programmable_config, None); +// // Test metadata state +// let metadata_account = banks.get_account(METADATA_ADDRESS).await.unwrap().unwrap(); +// assert_eq!(metadata_account.owner, mpl_token_metadata::ID); +// let metadata = Metadata::from_bytes(&metadata_account.data).unwrap(); +// assert_eq!(metadata.key, Key::MetadataV1); +// assert_eq!(metadata.update_authority, payer.pubkey()); +// assert_eq!(metadata.mint, MINT_ADDRESS); +// assert_eq!(metadata.name.trim_end_matches('\0'), METADATA_NAME); +// assert_eq!(metadata.symbol.trim_end_matches('\0'), METADATA_SYMBOL); +// assert_eq!(metadata.uri.trim_end_matches('\0'), METADATA_URI); +// assert_eq!(metadata.seller_fee_basis_points, 0); +// assert_eq!(metadata.creators, None); +// assert_eq!(metadata.primary_sale_happened, false); +// assert_eq!(metadata.is_mutable, true); +// assert_eq!(metadata.token_standard, Some(TokenStandard::Fungible)); +// assert_eq!(metadata.collection, None); +// assert_eq!(metadata.uses, None); +// assert_eq!(metadata.collection_details, None); +// assert_eq!(metadata.programmable_config, None); - // Test treasury token state - let treasury_tokens_account = banks - .get_account(treasury_tokens_address) - .await - .unwrap() - .unwrap(); - assert_eq!(treasury_tokens_account.owner, spl_token::id()); - let treasury_tokens = spl_token::state::Account::unpack(&treasury_tokens_account.data).unwrap(); - assert_eq!(treasury_tokens.mint, MINT_ADDRESS); - assert_eq!(treasury_tokens.owner, treasury_pda.0); - assert_eq!(treasury_tokens.amount, 0); - assert_eq!(treasury_tokens.delegate, COption::None); - assert_eq!(treasury_tokens.state, AccountState::Initialized); - assert_eq!(treasury_tokens.is_native, COption::None); - assert_eq!(treasury_tokens.delegated_amount, 0); - assert_eq!(treasury_tokens.close_authority, COption::None); -} +// // Test treasury token state +// let treasury_tokens_account = banks +// .get_account(treasury_tokens_address) +// .await +// .unwrap() +// .unwrap(); +// assert_eq!(treasury_tokens_account.owner, spl_token::id()); +// let treasury_tokens = spl_token::state::Account::unpack(&treasury_tokens_account.data).unwrap(); +// assert_eq!(treasury_tokens.mint, MINT_ADDRESS); +// assert_eq!(treasury_tokens.owner, treasury_pda.0); +// assert_eq!(treasury_tokens.amount, 0); +// assert_eq!(treasury_tokens.delegate, COption::None); +// assert_eq!(treasury_tokens.state, AccountState::Initialized); +// assert_eq!(treasury_tokens.is_native, COption::None); +// assert_eq!(treasury_tokens.delegated_amount, 0); +// assert_eq!(treasury_tokens.close_authority, COption::None); +// } -#[tokio::test] -async fn test_initialize_not_enough_accounts() { - // Setup - let (mut banks, payer, blockhash) = setup_program_test_env().await; +// #[tokio::test] +// async fn test_initialize_not_enough_accounts() { +// // Setup +// let (mut banks, payer, blockhash) = setup_program_test_env().await; - // Submit tx - let mut ix = ore::instruction::initialize(payer.pubkey()); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit tx +// let mut ix = ore::instruction::initialize(payer.pubkey()); +// ix.accounts.remove(1); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_initialize_bad_key() { - // Setup - let (mut banks, payer, blockhash) = setup_program_test_env().await; +// #[tokio::test] +// async fn test_initialize_bad_key() { +// // Setup +// let (mut banks, payer, blockhash) = setup_program_test_env().await; - // Bad addresses - let bad_pda = Pubkey::find_program_address(&[b"t"], &ore::id()); - for i in 1..12 { - let mut ix = ore::instruction::initialize(payer.pubkey()); - ix.accounts[i].pubkey = bad_pda.0; - let tx = - Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); - } -} +// // Bad addresses +// let bad_pda = Pubkey::find_program_address(&[b"t"], &ore::id()); +// for i in 1..12 { +// let mut ix = ore::instruction::initialize(payer.pubkey()); +// ix.accounts[i].pubkey = bad_pda.0; +// let tx = +// Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } +// } -#[tokio::test] -async fn test_initialize_bad_programs() { - // Setup - let (mut banks, payer, blockhash) = setup_program_test_env().await; +// #[tokio::test] +// async fn test_initialize_bad_programs() { +// // Setup +// let (mut banks, payer, blockhash) = setup_program_test_env().await; - // Bad addresses - for i in 13..18 { - let mut ix = ore::instruction::initialize(payer.pubkey()); - ix.accounts[i].pubkey = Pubkey::new_unique(); - let tx = - Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); - } -} +// // Bad addresses +// for i in 13..18 { +// let mut ix = ore::instruction::initialize(payer.pubkey()); +// ix.accounts[i].pubkey = Pubkey::new_unique(); +// let tx = +// Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } +// } -async fn setup_program_test_env() -> (BanksClient, Keypair, Hash) { - let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); - program_test.prefer_bpf(true); +// async fn setup_program_test_env() -> (BanksClient, Keypair, Hash) { +// let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); +// program_test.prefer_bpf(true); - // Setup metadata program - let data = read_file(&"tests/buffers/metadata_program.bpf"); - program_test.add_account( - mpl_token_metadata::ID, - Account { - lamports: Rent::default().minimum_balance(data.len()).max(1), - data, - owner: solana_sdk::bpf_loader::id(), - executable: true, - rent_epoch: 0, - }, - ); +// // Setup metadata program +// let data = read_file(&"tests/buffers/metadata_program.bpf"); +// program_test.add_account( +// mpl_token_metadata::ID, +// Account { +// lamports: Rent::default().minimum_balance(data.len()).max(1), +// data, +// owner: solana_sdk::bpf_loader::id(), +// executable: true, +// rent_epoch: 0, +// }, +// ); - program_test.start().await -} +// program_test.start().await +// } diff --git a/tests/test_mine.rs b/tests/test_mine.rs index 6494d5c..ae10349 100644 --- a/tests/test_mine.rs +++ b/tests/test_mine.rs @@ -1,746 +1,746 @@ -use std::{mem::size_of, str::FromStr}; +// use std::{mem::size_of, str::FromStr}; -use ore::{ - instruction::{MineArgs, OreInstruction}, - state::{Bus, Proof, Treasury}, - utils::{AccountDeserialize, Discriminator}, - BUS_ADDRESSES, BUS_COUNT, EPOCH_DURATION, INITIAL_REWARD_RATE, MINT_ADDRESS, PROOF, START_AT, - TOKEN_DECIMALS, TREASURY, TREASURY_ADDRESS, -}; -use rand::{distributions::Uniform, Rng}; -use solana_program::{ - clock::Clock, - epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, - hash::Hash, - instruction::{AccountMeta, Instruction}, - keccak::{hashv, Hash as KeccakHash}, - native_token::LAMPORTS_PER_SOL, - program_option::COption, - program_pack::Pack, - pubkey::Pubkey, - slot_hashes::SlotHash, - system_program, sysvar, -}; -use solana_program_test::{processor, BanksClient, ProgramTest}; -use solana_sdk::{ - account::Account, - signature::{Keypair, Signer}, - transaction::Transaction, -}; -use spl_associated_token_account::{ - get_associated_token_address, instruction::create_associated_token_account, -}; -use spl_token::state::{AccountState, Mint}; +// use ore::{ +// instruction::{MineArgs, OreInstruction}, +// state::{Bus, Proof, Treasury}, +// utils::{AccountDeserialize, Discriminator}, +// BUS_ADDRESSES, BUS_COUNT, EPOCH_DURATION, INITIAL_REWARD_RATE, MINT_ADDRESS, PROOF, START_AT, +// TOKEN_DECIMALS, TREASURY, TREASURY_ADDRESS, +// }; +// use rand::{distributions::Uniform, Rng}; +// use solana_program::{ +// clock::Clock, +// epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, +// hash::Hash, +// instruction::{AccountMeta, Instruction}, +// keccak::{hashv, Hash as KeccakHash}, +// native_token::LAMPORTS_PER_SOL, +// program_option::COption, +// program_pack::Pack, +// pubkey::Pubkey, +// slot_hashes::SlotHash, +// system_program, sysvar, +// }; +// use solana_program_test::{processor, BanksClient, ProgramTest}; +// use solana_sdk::{ +// account::Account, +// signature::{Keypair, Signer}, +// transaction::Transaction, +// }; +// use spl_associated_token_account::{ +// get_associated_token_address, instruction::create_associated_token_account, +// }; +// use spl_token::state::{AccountState, Mint}; -#[tokio::test] -async fn test_mine() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; +// #[tokio::test] +// async fn test_mine() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit register tx +// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); +// let ix = ore::instruction::register(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// 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 = Proof::try_from_bytes(&proof_account.data).unwrap(); - assert_eq!(proof.authority, payer.pubkey()); - assert_eq!(proof.claimable_rewards, 0); - assert_eq!(proof.total_hashes, 0); - assert_eq!(proof.total_rewards, 0); +// // Assert proof state +// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); +// assert_eq!(proof_account.owner, ore::id()); +// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); +// assert_eq!(proof.authority, payer.pubkey()); +// assert_eq!(proof.claimable_rewards, 0); +// assert_eq!(proof.total_hashes, 0); +// assert_eq!(proof.total_rewards, 0); - // Find next hash - let (next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); +// // Find next hash +// let (next_hash, nonce) = find_next_hash( +// proof.hash.into(), +// KeccakHash::new_from_array([u8::MAX; 32]), +// payer.pubkey(), +// ); - // Submit mine tx - let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit mine tx +// let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Assert proof state - let slot_hashes_account = banks - .get_account(sysvar::slot_hashes::id()) - .await - .unwrap() - .unwrap(); - let slot_hash_bytes = &slot_hashes_account.data[0..size_of::()]; - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - assert_eq!(proof_account.owner, ore::id()); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - assert_eq!(proof.authority, payer.pubkey()); - assert_eq!(proof.claimable_rewards, INITIAL_REWARD_RATE); - assert_eq!( - proof.hash, - hashv(&[&next_hash.as_ref(), slot_hash_bytes,]).into() - ); - assert_eq!(proof.total_hashes, 1); - assert_eq!(proof.total_rewards, INITIAL_REWARD_RATE); +// // Assert proof state +// let slot_hashes_account = banks +// .get_account(sysvar::slot_hashes::id()) +// .await +// .unwrap() +// .unwrap(); +// let slot_hash_bytes = &slot_hashes_account.data[0..size_of::()]; +// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); +// assert_eq!(proof_account.owner, ore::id()); +// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); +// assert_eq!(proof.authority, payer.pubkey()); +// assert_eq!(proof.claimable_rewards, INITIAL_REWARD_RATE); +// assert_eq!( +// proof.hash, +// hashv(&[&next_hash.as_ref(), slot_hash_bytes,]).into() +// ); +// assert_eq!(proof.total_hashes, 1); +// assert_eq!(proof.total_rewards, INITIAL_REWARD_RATE); - // Submit claim tx - let amount = proof.claimable_rewards; - let beneficiary_address = get_associated_token_address(&payer.pubkey(), &ore::MINT_ADDRESS); - let token_ix = create_associated_token_account( - &payer.pubkey(), - &payer.pubkey(), - &ore::MINT_ADDRESS, - &spl_token::id(), - ); - let ix = ore::instruction::claim(payer.pubkey(), beneficiary_address, amount); - let tx = Transaction::new_signed_with_payer( - &[token_ix, ix], - Some(&payer.pubkey()), - &[&payer], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit claim tx +// let amount = proof.claimable_rewards; +// let beneficiary_address = get_associated_token_address(&payer.pubkey(), &ore::MINT_ADDRESS); +// let token_ix = create_associated_token_account( +// &payer.pubkey(), +// &payer.pubkey(), +// &ore::MINT_ADDRESS, +// &spl_token::id(), +// ); +// let ix = ore::instruction::claim(payer.pubkey(), beneficiary_address, amount); +// let tx = Transaction::new_signed_with_payer( +// &[token_ix, ix], +// Some(&payer.pubkey()), +// &[&payer], +// blockhash, +// ); +// 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(); - let proof_ = Proof::try_from_bytes(&proof_account.data).unwrap(); - assert_eq!(proof_.authority, proof.authority); - assert_eq!(proof_.claimable_rewards, 0); - assert_eq!(proof_.hash, proof.hash); - assert_eq!(proof_.total_hashes, proof.total_hashes); - assert_eq!(proof_.total_rewards, proof.total_rewards); +// // Assert proof state +// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); +// let proof_ = Proof::try_from_bytes(&proof_account.data).unwrap(); +// assert_eq!(proof_.authority, proof.authority); +// assert_eq!(proof_.claimable_rewards, 0); +// assert_eq!(proof_.hash, proof.hash); +// assert_eq!(proof_.total_hashes, proof.total_hashes); +// assert_eq!(proof_.total_rewards, proof.total_rewards); - // Assert beneficiary state - let beneficiary_account = banks - .get_account(beneficiary_address) - .await - .unwrap() - .unwrap(); - assert_eq!(beneficiary_account.owner, spl_token::id()); - let beneficiary = spl_token::state::Account::unpack(&beneficiary_account.data).unwrap(); - assert_eq!(beneficiary.mint, ore::MINT_ADDRESS); - assert_eq!(beneficiary.owner, payer.pubkey()); - assert_eq!(beneficiary.amount, amount); - assert_eq!(beneficiary.delegate, COption::None); - assert_eq!(beneficiary.state, AccountState::Initialized); - assert_eq!(beneficiary.is_native, COption::None); - assert_eq!(beneficiary.delegated_amount, 0); - assert_eq!(beneficiary.close_authority, COption::None); -} +// // Assert beneficiary state +// let beneficiary_account = banks +// .get_account(beneficiary_address) +// .await +// .unwrap() +// .unwrap(); +// assert_eq!(beneficiary_account.owner, spl_token::id()); +// let beneficiary = spl_token::state::Account::unpack(&beneficiary_account.data).unwrap(); +// assert_eq!(beneficiary.mint, ore::MINT_ADDRESS); +// assert_eq!(beneficiary.owner, payer.pubkey()); +// assert_eq!(beneficiary.amount, amount); +// assert_eq!(beneficiary.delegate, COption::None); +// assert_eq!(beneficiary.state, AccountState::Initialized); +// assert_eq!(beneficiary.is_native, COption::None); +// assert_eq!(beneficiary.delegated_amount, 0); +// assert_eq!(beneficiary.close_authority, COption::None); +// } -#[tokio::test] -async fn test_mine_alt_proof() { - // Setup - let (mut banks, payer, payer_alt, blockhash) = - setup_program_test_env(true, ClockState::Normal).await; +// #[tokio::test] +// async fn test_mine_alt_proof() { +// // Setup +// let (mut banks, payer, payer_alt, blockhash) = +// setup_program_test_env(true, ClockState::Normal).await; - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit register tx +// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); +// let ix = ore::instruction::register(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Submit register alt tx - let proof_alt_pda = - Pubkey::find_program_address(&[PROOF, payer_alt.pubkey().as_ref()], &ore::id()); - let ix_alt = ore::instruction::register(payer_alt.pubkey()); - let tx = Transaction::new_signed_with_payer( - &[ix_alt], - Some(&payer_alt.pubkey()), - &[&payer_alt], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit register alt tx +// let proof_alt_pda = +// Pubkey::find_program_address(&[PROOF, payer_alt.pubkey().as_ref()], &ore::id()); +// let ix_alt = ore::instruction::register(payer_alt.pubkey()); +// let tx = Transaction::new_signed_with_payer( +// &[ix_alt], +// Some(&payer_alt.pubkey()), +// &[&payer_alt], +// blockhash, +// ); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Submit mine tx with invalid proof - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (_next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new(BUS_ADDRESSES[0], false), - AccountMeta::new(proof_alt_pda.0, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), - ], - data: [ - OreInstruction::Mine.to_vec(), - MineArgs { - nonce: nonce.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), - }; - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit mine tx with invalid proof +// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); +// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); +// let (_next_hash, nonce) = find_next_hash( +// proof.hash.into(), +// KeccakHash::new_from_array([u8::MAX; 32]), +// payer.pubkey(), +// ); +// let ix = Instruction { +// program_id: ore::id(), +// accounts: vec![ +// AccountMeta::new(payer.pubkey(), true), +// AccountMeta::new(BUS_ADDRESSES[0], false), +// AccountMeta::new(proof_alt_pda.0, false), +// AccountMeta::new(TREASURY_ADDRESS, false), +// AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), +// ], +// data: [ +// OreInstruction::Mine.to_vec(), +// MineArgs { +// nonce: nonce.to_le_bytes(), +// } +// .to_bytes() +// .to_vec(), +// ] +// .concat(), +// }; +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_mine_correct_hash_alt_proof() { - // Setup - let (mut banks, payer, payer_alt, blockhash) = - setup_program_test_env(true, ClockState::Normal).await; +// #[tokio::test] +// async fn test_mine_correct_hash_alt_proof() { +// // Setup +// let (mut banks, payer, payer_alt, blockhash) = +// setup_program_test_env(true, ClockState::Normal).await; - // Submit register alt tx - let proof_alt_pda = - Pubkey::find_program_address(&[PROOF, payer_alt.pubkey().as_ref()], &ore::id()); - let ix_alt = ore::instruction::register(payer_alt.pubkey()); - let tx = Transaction::new_signed_with_payer( - &[ix_alt], - Some(&payer_alt.pubkey()), - &[&payer_alt], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit register alt tx +// let proof_alt_pda = +// Pubkey::find_program_address(&[PROOF, payer_alt.pubkey().as_ref()], &ore::id()); +// let ix_alt = ore::instruction::register(payer_alt.pubkey()); +// let tx = Transaction::new_signed_with_payer( +// &[ix_alt], +// Some(&payer_alt.pubkey()), +// &[&payer_alt], +// blockhash, +// ); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Submit with correct hash for invalid proof - let proof_alt_account = banks.get_account(proof_alt_pda.0).await.unwrap().unwrap(); - let proof_alt = Proof::try_from_bytes(&proof_alt_account.data).unwrap(); - let (_next_hash, nonce) = find_next_hash( - proof_alt.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer_alt.pubkey(), - ); - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new(BUS_ADDRESSES[0], false), - AccountMeta::new(proof_alt_pda.0, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), - ], - data: [ - OreInstruction::Mine.to_vec(), - MineArgs { - nonce: nonce.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), - }; - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit with correct hash for invalid proof +// let proof_alt_account = banks.get_account(proof_alt_pda.0).await.unwrap().unwrap(); +// let proof_alt = Proof::try_from_bytes(&proof_alt_account.data).unwrap(); +// let (_next_hash, nonce) = find_next_hash( +// proof_alt.hash.into(), +// KeccakHash::new_from_array([u8::MAX; 32]), +// payer_alt.pubkey(), +// ); +// let ix = Instruction { +// program_id: ore::id(), +// accounts: vec![ +// AccountMeta::new(payer.pubkey(), true), +// AccountMeta::new(BUS_ADDRESSES[0], false), +// AccountMeta::new(proof_alt_pda.0, false), +// AccountMeta::new(TREASURY_ADDRESS, false), +// AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), +// ], +// data: [ +// OreInstruction::Mine.to_vec(), +// MineArgs { +// nonce: nonce.to_le_bytes(), +// } +// .to_bytes() +// .to_vec(), +// ] +// .concat(), +// }; +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_mine_bus_rewards_insufficient() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(false, ClockState::Normal).await; +// #[tokio::test] +// async fn test_mine_bus_rewards_insufficient() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env(false, ClockState::Normal).await; - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit register tx +// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); +// let ix = ore::instruction::register(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Find next hash - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (_next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); +// // Find next hash +// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); +// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); +// let (_next_hash, nonce) = find_next_hash( +// proof.hash.into(), +// KeccakHash::new_from_array([u8::MAX; 32]), +// payer.pubkey(), +// ); - // Submit mine tx - let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit mine tx +// let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_claim_too_large() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; +// #[tokio::test] +// async fn test_claim_too_large() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - // Submit register tx - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit register tx +// let ix = ore::instruction::register(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Submit claim tx - let beneficiary = get_associated_token_address(&payer.pubkey(), &ore::MINT_ADDRESS); - let token_ix = create_associated_token_account( - &payer.pubkey(), - &payer.pubkey(), - &ore::MINT_ADDRESS, - &spl_token::id(), - ); - let ix = ore::instruction::claim(payer.pubkey(), beneficiary, 1); - let tx = Transaction::new_signed_with_payer( - &[token_ix, ix], - Some(&payer.pubkey()), - &[&payer], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit claim tx +// let beneficiary = get_associated_token_address(&payer.pubkey(), &ore::MINT_ADDRESS); +// let token_ix = create_associated_token_account( +// &payer.pubkey(), +// &payer.pubkey(), +// &ore::MINT_ADDRESS, +// &spl_token::id(), +// ); +// let ix = ore::instruction::claim(payer.pubkey(), beneficiary, 1); +// let tx = Transaction::new_signed_with_payer( +// &[token_ix, ix], +// Some(&payer.pubkey()), +// &[&payer], +// blockhash, +// ); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_claim_other_proof() { - // Setup - let (mut banks, payer, alt_payer, blockhash) = - setup_program_test_env(true, ClockState::Normal).await; +// #[tokio::test] +// async fn test_claim_other_proof() { +// // Setup +// let (mut banks, payer, alt_payer, blockhash) = +// setup_program_test_env(true, ClockState::Normal).await; - // Submit register tx - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit register tx +// let ix = ore::instruction::register(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Submit claim tx - let beneficiary = get_associated_token_address(&alt_payer.pubkey(), &ore::MINT_ADDRESS); - let token_ix = create_associated_token_account( - &alt_payer.pubkey(), - &alt_payer.pubkey(), - &ore::MINT_ADDRESS, - &spl_token::id(), - ); - let mut ix = ore::instruction::claim(payer.pubkey(), beneficiary, 0); - ix.accounts[0].pubkey = alt_payer.pubkey(); - let tx = Transaction::new_signed_with_payer( - &[token_ix, ix], - Some(&alt_payer.pubkey()), - &[&alt_payer], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit claim tx +// let beneficiary = get_associated_token_address(&alt_payer.pubkey(), &ore::MINT_ADDRESS); +// let token_ix = create_associated_token_account( +// &alt_payer.pubkey(), +// &alt_payer.pubkey(), +// &ore::MINT_ADDRESS, +// &spl_token::id(), +// ); +// let mut ix = ore::instruction::claim(payer.pubkey(), beneficiary, 0); +// ix.accounts[0].pubkey = alt_payer.pubkey(); +// let tx = Transaction::new_signed_with_payer( +// &[token_ix, ix], +// Some(&alt_payer.pubkey()), +// &[&alt_payer], +// blockhash, +// ); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_mine_not_enough_accounts() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; +// #[tokio::test] +// async fn test_mine_not_enough_accounts() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit register tx +// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); +// let ix = ore::instruction::register(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Find next hash - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (_next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); +// // Find next hash +// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); +// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); +// let (_next_hash, nonce) = find_next_hash( +// proof.hash.into(), +// KeccakHash::new_from_array([u8::MAX; 32]), +// payer.pubkey(), +// ); - // Submit mine tx - let mut ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit mine tx +// let mut ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); +// ix.accounts.remove(1); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_mine_too_early() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::TooEarly).await; +// #[tokio::test] +// async fn test_mine_too_early() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::TooEarly).await; - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit register tx +// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); +// let ix = ore::instruction::register(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Find next hash - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (_next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); +// // Find next hash +// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); +// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); +// let (_next_hash, nonce) = find_next_hash( +// proof.hash.into(), +// KeccakHash::new_from_array([u8::MAX; 32]), +// payer.pubkey(), +// ); - // Submit mine tx - let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit mine tx +// let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_mine_needs_reset() { - // Setup - let (mut banks, payer, _, blockhash) = - setup_program_test_env(true, ClockState::NeedsReset).await; +// #[tokio::test] +// async fn test_mine_needs_reset() { +// // Setup +// let (mut banks, payer, _, blockhash) = +// setup_program_test_env(true, ClockState::NeedsReset).await; - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit register tx +// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); +// let ix = ore::instruction::register(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Find next hash - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - let (_next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); +// // Find next hash +// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); +// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); +// let (_next_hash, nonce) = find_next_hash( +// proof.hash.into(), +// KeccakHash::new_from_array([u8::MAX; 32]), +// payer.pubkey(), +// ); - // Submit mine tx - let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit mine tx +// let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_claim_not_enough_accounts() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; +// #[tokio::test] +// async fn test_claim_not_enough_accounts() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - // Submit register tx - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit register tx +// let ix = ore::instruction::register(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Submit claim tx - let beneficiary = get_associated_token_address(&payer.pubkey(), &ore::MINT_ADDRESS); - let token_ix = create_associated_token_account( - &payer.pubkey(), - &payer.pubkey(), - &ore::MINT_ADDRESS, - &spl_token::id(), - ); - let mut ix = ore::instruction::claim(payer.pubkey(), beneficiary, 0); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer( - &[token_ix, ix], - Some(&payer.pubkey()), - &[&payer], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit claim tx +// let beneficiary = get_associated_token_address(&payer.pubkey(), &ore::MINT_ADDRESS); +// let token_ix = create_associated_token_account( +// &payer.pubkey(), +// &payer.pubkey(), +// &ore::MINT_ADDRESS, +// &spl_token::id(), +// ); +// let mut ix = ore::instruction::claim(payer.pubkey(), beneficiary, 0); +// ix.accounts.remove(1); +// let tx = Transaction::new_signed_with_payer( +// &[token_ix, ix], +// Some(&payer.pubkey()), +// &[&payer], +// blockhash, +// ); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_mine_fail_bad_data() { - // Setup - const FUZZ: usize = 10; - let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; +// #[tokio::test] +// async fn test_mine_fail_bad_data() { +// // Setup +// const FUZZ: usize = 10; +// let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - // Submit register tx - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let ix = ore::instruction::register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit register tx +// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); +// let ix = ore::instruction::register(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Get proof - let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); - let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); +// // Get proof +// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); +// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - // Shared variables for tests. - let mut rng = rand::thread_rng(); - let (_next_hash, nonce) = find_next_hash( - proof.hash.into(), - KeccakHash::new_from_array([u8::MAX; 32]), - payer.pubkey(), - ); - let signer = payer.pubkey(); - let proof_address = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &ore::id()).0; +// // Shared variables for tests. +// let mut rng = rand::thread_rng(); +// let (_next_hash, nonce) = find_next_hash( +// proof.hash.into(), +// KeccakHash::new_from_array([u8::MAX; 32]), +// payer.pubkey(), +// ); +// let signer = payer.pubkey(); +// let proof_address = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &ore::id()).0; - // Fuzz randomized instruction data - for _ in 0..FUZZ { - let length_range = Uniform::from(5..=256); - let length = rng.sample(length_range); - let random_bytes: Vec = (0..length).map(|_| rng.gen()).collect(); - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(BUS_ADDRESSES[0], false), - AccountMeta::new(proof_address, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), - ], - data: [OreInstruction::Mine.to_vec(), random_bytes].concat(), - }; - let tx = - Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); - } +// // Fuzz randomized instruction data +// for _ in 0..FUZZ { +// let length_range = Uniform::from(5..=256); +// let length = rng.sample(length_range); +// let random_bytes: Vec = (0..length).map(|_| rng.gen()).collect(); +// let ix = Instruction { +// program_id: ore::id(), +// accounts: vec![ +// AccountMeta::new(signer, true), +// AccountMeta::new(BUS_ADDRESSES[0], false), +// AccountMeta::new(proof_address, false), +// AccountMeta::new(TREASURY_ADDRESS, false), +// AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), +// ], +// data: [OreInstruction::Mine.to_vec(), random_bytes].concat(), +// }; +// let tx = +// Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } - // Fuzz test random bus addresses - for _ in 0..FUZZ { - assert_mine_tx_err( - &mut banks, - &payer, - blockhash, - payer.pubkey(), - Pubkey::new_unique(), - proof_address, - TREASURY_ADDRESS, - sysvar::slot_hashes::id(), - nonce, - ) - .await; - } +// // Fuzz test random bus addresses +// for _ in 0..FUZZ { +// assert_mine_tx_err( +// &mut banks, +// &payer, +// blockhash, +// payer.pubkey(), +// Pubkey::new_unique(), +// proof_address, +// TREASURY_ADDRESS, +// sysvar::slot_hashes::id(), +// nonce, +// ) +// .await; +// } - // Fuzz test random proof addresses - for _ in 0..FUZZ { - assert_mine_tx_err( - &mut banks, - &payer, - blockhash, - payer.pubkey(), - BUS_ADDRESSES[0], - Pubkey::new_unique(), - TREASURY_ADDRESS, - sysvar::slot_hashes::id(), - nonce, - ) - .await; - } +// // Fuzz test random proof addresses +// for _ in 0..FUZZ { +// assert_mine_tx_err( +// &mut banks, +// &payer, +// blockhash, +// payer.pubkey(), +// BUS_ADDRESSES[0], +// Pubkey::new_unique(), +// TREASURY_ADDRESS, +// sysvar::slot_hashes::id(), +// nonce, +// ) +// .await; +// } - // Mix up the proof and treasury addresses - assert_mine_tx_err( - &mut banks, - &payer, - blockhash, - payer.pubkey(), - BUS_ADDRESSES[0], - TREASURY_ADDRESS, - proof_address, - sysvar::slot_hashes::id(), - nonce, - ) - .await; +// // Mix up the proof and treasury addresses +// assert_mine_tx_err( +// &mut banks, +// &payer, +// blockhash, +// payer.pubkey(), +// BUS_ADDRESSES[0], +// TREASURY_ADDRESS, +// proof_address, +// sysvar::slot_hashes::id(), +// nonce, +// ) +// .await; - // Pass an invalid sysvar - assert_mine_tx_err( - &mut banks, - &payer, - blockhash, - payer.pubkey(), - BUS_ADDRESSES[0], - proof_address, - TREASURY_ADDRESS, - sysvar::clock::id(), - nonce, - ) - .await; -} +// // Pass an invalid sysvar +// assert_mine_tx_err( +// &mut banks, +// &payer, +// blockhash, +// payer.pubkey(), +// BUS_ADDRESSES[0], +// proof_address, +// TREASURY_ADDRESS, +// sysvar::clock::id(), +// nonce, +// ) +// .await; +// } -async fn assert_mine_tx_err( - banks: &mut BanksClient, - payer: &Keypair, - blockhash: Hash, - signer: Pubkey, - bus: Pubkey, - proof: Pubkey, - treasury: Pubkey, - slot_hash: Pubkey, - nonce: u64, -) { - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus, false), - AccountMeta::new(proof, false), - AccountMeta::new(treasury, false), - AccountMeta::new_readonly(slot_hash, false), - ], - data: [ - OreInstruction::Mine.to_vec(), - MineArgs { - nonce: nonce.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), - }; - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// async fn assert_mine_tx_err( +// banks: &mut BanksClient, +// payer: &Keypair, +// blockhash: Hash, +// signer: Pubkey, +// bus: Pubkey, +// proof: Pubkey, +// treasury: Pubkey, +// slot_hash: Pubkey, +// nonce: u64, +// ) { +// let ix = Instruction { +// program_id: ore::id(), +// accounts: vec![ +// AccountMeta::new(signer, true), +// AccountMeta::new(bus, false), +// AccountMeta::new(proof, false), +// AccountMeta::new(treasury, false), +// AccountMeta::new_readonly(slot_hash, false), +// ], +// data: [ +// OreInstruction::Mine.to_vec(), +// MineArgs { +// nonce: nonce.to_le_bytes(), +// } +// .to_bytes() +// .to_vec(), +// ] +// .concat(), +// }; +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -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(&[ - nonce.to_le_bytes().as_slice(), - hash.to_bytes().as_slice(), - signer.to_bytes().as_slice(), - ]); - if next_hash.le(&difficulty) { - break; - } else { - println!("Invalid hash: {} Nonce: {:?}", next_hash.to_string(), nonce); - } - nonce += 1; - } - (next_hash, nonce) -} +// 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(&[ +// nonce.to_le_bytes().as_slice(), +// hash.to_bytes().as_slice(), +// signer.to_bytes().as_slice(), +// ]); +// if next_hash.le(&difficulty) { +// break; +// } else { +// println!("Invalid hash: {} Nonce: {:?}", next_hash.to_string(), nonce); +// } +// nonce += 1; +// } +// (next_hash, nonce) +// } -enum ClockState { - Normal, - TooEarly, - NeedsReset, -} +// enum ClockState { +// Normal, +// TooEarly, +// NeedsReset, +// } -async fn setup_program_test_env( - funded_busses: bool, - clock_state: ClockState, -) -> (BanksClient, Keypair, Keypair, solana_program::hash::Hash) { - let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); - program_test.prefer_bpf(true); +// async fn setup_program_test_env( +// funded_busses: bool, +// clock_state: ClockState, +// ) -> (BanksClient, Keypair, Keypair, solana_program::hash::Hash) { +// let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); +// program_test.prefer_bpf(true); - // Busses - for i in 0..BUS_COUNT { - program_test.add_account_with_base64_data( - BUS_ADDRESSES[i], - 1057920, - ore::id(), - bs64::encode( - &[ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { - id: i as u64, - rewards: if funded_busses { 250_000_000 } else { 0 }, - } - .to_bytes(), - ] - .concat(), - ) - .as_str(), - ); - } +// // Busses +// for i in 0..BUS_COUNT { +// program_test.add_account_with_base64_data( +// BUS_ADDRESSES[i], +// 1057920, +// ore::id(), +// bs64::encode( +// &[ +// &(Bus::discriminator() as u64).to_le_bytes(), +// Bus { +// id: i as u64, +// rewards: if funded_busses { 250_000_000 } else { 0 }, +// } +// .to_bytes(), +// ] +// .concat(), +// ) +// .as_str(), +// ); +// } - // Treasury - let admin_address = Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap(); - let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); - program_test.add_account_with_base64_data( - treasury_pda.0, - 1614720, - ore::id(), - bs64::encode( - &[ - &(Treasury::discriminator() as u64).to_le_bytes(), - Treasury { - bump: treasury_pda.1 as u64, - admin: admin_address, - difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), - last_reset_at: START_AT, - reward_rate: INITIAL_REWARD_RATE, - total_claimed_rewards: 0, - } - .to_bytes(), - ] - .concat(), - ) - .as_str(), - ); +// // Treasury +// let admin_address = Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap(); +// let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); +// program_test.add_account_with_base64_data( +// treasury_pda.0, +// 1614720, +// ore::id(), +// bs64::encode( +// &[ +// &(Treasury::discriminator() as u64).to_le_bytes(), +// Treasury { +// bump: treasury_pda.1 as u64, +// admin: admin_address, +// difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), +// last_reset_at: START_AT, +// reward_rate: INITIAL_REWARD_RATE, +// total_claimed_rewards: 0, +// } +// .to_bytes(), +// ] +// .concat(), +// ) +// .as_str(), +// ); - // Mint - let mut mint_src: [u8; Mint::LEN] = [0; Mint::LEN]; - Mint { - mint_authority: COption::Some(TREASURY_ADDRESS), - supply: 2_000_000_000, - decimals: TOKEN_DECIMALS, - is_initialized: true, - freeze_authority: COption::None, - } - .pack_into_slice(&mut mint_src); - program_test.add_account_with_base64_data( - MINT_ADDRESS, - 1461600, - spl_token::id(), - bs64::encode(&mint_src).as_str(), - ); +// // Mint +// let mut mint_src: [u8; Mint::LEN] = [0; Mint::LEN]; +// Mint { +// mint_authority: COption::Some(TREASURY_ADDRESS), +// supply: 2_000_000_000, +// decimals: TOKEN_DECIMALS, +// is_initialized: true, +// freeze_authority: COption::None, +// } +// .pack_into_slice(&mut mint_src); +// program_test.add_account_with_base64_data( +// MINT_ADDRESS, +// 1461600, +// spl_token::id(), +// bs64::encode(&mint_src).as_str(), +// ); - // Treasury tokens - let tokens_address = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - let mut tokens_src: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; - spl_token::state::Account { - mint: MINT_ADDRESS, - owner: TREASURY_ADDRESS, - amount: 2_000_000_000, - delegate: COption::None, - state: AccountState::Initialized, - is_native: COption::None, - delegated_amount: 0, - close_authority: COption::None, - } - .pack_into_slice(&mut tokens_src); - program_test.add_account_with_base64_data( - tokens_address, - 2039280, - spl_token::id(), - bs64::encode(&tokens_src).as_str(), - ); +// // Treasury tokens +// let tokens_address = spl_associated_token_account::get_associated_token_address( +// &TREASURY_ADDRESS, +// &MINT_ADDRESS, +// ); +// let mut tokens_src: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; +// spl_token::state::Account { +// mint: MINT_ADDRESS, +// owner: TREASURY_ADDRESS, +// amount: 2_000_000_000, +// delegate: COption::None, +// state: AccountState::Initialized, +// is_native: COption::None, +// delegated_amount: 0, +// close_authority: COption::None, +// } +// .pack_into_slice(&mut tokens_src); +// program_test.add_account_with_base64_data( +// tokens_address, +// 2039280, +// spl_token::id(), +// bs64::encode(&tokens_src).as_str(), +// ); - // Set sysvar - let ts = match clock_state { - ClockState::Normal => START_AT + 1, - ClockState::TooEarly => START_AT - 1, - ClockState::NeedsReset => START_AT + EPOCH_DURATION, - }; - program_test.add_sysvar_account( - sysvar::clock::id(), - &Clock { - slot: 0, - epoch_start_timestamp: 0, - epoch: 0, - leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, - unix_timestamp: ts, - }, - ); +// // Set sysvar +// let ts = match clock_state { +// ClockState::Normal => START_AT + 1, +// ClockState::TooEarly => START_AT - 1, +// ClockState::NeedsReset => START_AT + EPOCH_DURATION, +// }; +// program_test.add_sysvar_account( +// sysvar::clock::id(), +// &Clock { +// slot: 0, +// epoch_start_timestamp: 0, +// epoch: 0, +// leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, +// unix_timestamp: ts, +// }, +// ); - // Setup alt payer - let payer_alt = Keypair::new(); - program_test.add_account( - payer_alt.pubkey(), - Account { - lamports: LAMPORTS_PER_SOL, - data: vec![], - owner: system_program::id(), - executable: false, - rent_epoch: 0, - }, - ); +// // Setup alt payer +// let payer_alt = Keypair::new(); +// program_test.add_account( +// payer_alt.pubkey(), +// Account { +// lamports: LAMPORTS_PER_SOL, +// data: vec![], +// owner: system_program::id(), +// executable: false, +// rent_epoch: 0, +// }, +// ); - let (banks, payer, blockhash) = program_test.start().await; - (banks, payer, payer_alt, blockhash) -} +// let (banks, payer, blockhash) = program_test.start().await; +// (banks, payer, payer_alt, blockhash) +// } diff --git a/tests/test_register.rs b/tests/test_register.rs index 454baa2..084b2fe 100644 --- a/tests/test_register.rs +++ b/tests/test_register.rs @@ -1,188 +1,188 @@ -use std::str::FromStr; +// use std::str::FromStr; -use ore::{ - instruction::{register, OreInstruction, RegisterArgs}, - state::{Bus, Treasury}, - utils::Discriminator, - BUS_ADDRESSES, BUS_COUNT, INITIAL_REWARD_RATE, MINT_ADDRESS, PROOF, TOKEN_DECIMALS, TREASURY, - TREASURY_ADDRESS, -}; -use solana_program::{ - clock::Clock, - epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, - instruction::{AccountMeta, Instruction}, - keccak::Hash as KeccakHash, - program_option::COption, - program_pack::Pack, - pubkey::Pubkey, - rent::Rent, - sysvar, -}; -use solana_program_test::{processor, BanksClient, ProgramTest}; -use solana_sdk::{ - signature::{Keypair, Signer}, - system_transaction::transfer, - transaction::Transaction, -}; -use spl_token::state::{AccountState, Mint}; +// use ore::{ +// instruction::{register, OreInstruction, RegisterArgs}, +// state::{Bus, Treasury}, +// utils::Discriminator, +// BUS_ADDRESSES, BUS_COUNT, INITIAL_REWARD_RATE, MINT_ADDRESS, PROOF, TOKEN_DECIMALS, TREASURY, +// TREASURY_ADDRESS, +// }; +// use solana_program::{ +// clock::Clock, +// epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, +// instruction::{AccountMeta, Instruction}, +// keccak::Hash as KeccakHash, +// program_option::COption, +// program_pack::Pack, +// pubkey::Pubkey, +// rent::Rent, +// sysvar, +// }; +// use solana_program_test::{processor, BanksClient, ProgramTest}; +// use solana_sdk::{ +// signature::{Keypair, Signer}, +// system_transaction::transfer, +// transaction::Transaction, +// }; +// use spl_token::state::{AccountState, Mint}; -#[tokio::test] -async fn test_register_account_with_lamports() { - let (mut banks, payer, blockhash) = setup_program_test_env().await; +// #[tokio::test] +// async fn test_register_account_with_lamports() { +// let (mut banks, payer, blockhash) = setup_program_test_env().await; - // Send lamports to the proof pda - let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); - let lamports = Rent::default().minimum_balance(0); - let tx = transfer(&payer, &proof_pda.0, lamports, blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Send lamports to the proof pda +// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); +// let lamports = Rent::default().minimum_balance(0); +// let tx = transfer(&payer, &proof_pda.0, lamports, blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Assert register succeeds - let ix = register(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); -} +// // Assert register succeeds +// let ix = register(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); +// } -#[tokio::test] -async fn test_register_not_enough_accounts() { - let (mut banks, payer, blockhash) = setup_program_test_env().await; +// #[tokio::test] +// async fn test_register_not_enough_accounts() { +// let (mut banks, payer, blockhash) = setup_program_test_env().await; - // Assert register fails - let mut ix = register(payer.pubkey()); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Assert register fails +// let mut ix = register(payer.pubkey()); +// ix.accounts.remove(1); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_register_fail_other() { - let (mut banks, payer, blockhash) = setup_program_test_env().await; +// #[tokio::test] +// async fn test_register_fail_other() { +// let (mut banks, payer, blockhash) = setup_program_test_env().await; - // Try register for another keypair - let other = Keypair::new(); - let proof_pda = Pubkey::find_program_address(&[PROOF, other.pubkey().as_ref()], &ore::id()); - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new(proof_pda.0, false), - AccountMeta::new_readonly(solana_program::system_program::id(), false), - ], - data: [ - OreInstruction::Register.to_vec(), - RegisterArgs { bump: proof_pda.1 }.to_bytes().to_vec(), - ] - .concat(), - }; - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Try register for another keypair +// let other = Keypair::new(); +// let proof_pda = Pubkey::find_program_address(&[PROOF, other.pubkey().as_ref()], &ore::id()); +// let ix = Instruction { +// program_id: ore::id(), +// accounts: vec![ +// AccountMeta::new(payer.pubkey(), true), +// AccountMeta::new(proof_pda.0, false), +// AccountMeta::new_readonly(solana_program::system_program::id(), false), +// ], +// data: [ +// OreInstruction::Register.to_vec(), +// RegisterArgs { bump: proof_pda.1 }.to_bytes().to_vec(), +// ] +// .concat(), +// }; +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -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); +// 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 - for i in 0..BUS_COUNT { - program_test.add_account_with_base64_data( - BUS_ADDRESSES[i], - 1057920, - ore::id(), - bs64::encode( - &[ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { - id: i as u64, - rewards: 250_000_000, - } - .to_bytes(), - ] - .concat(), - ) - .as_str(), - ); - } +// // Busses +// for i in 0..BUS_COUNT { +// program_test.add_account_with_base64_data( +// BUS_ADDRESSES[i], +// 1057920, +// ore::id(), +// bs64::encode( +// &[ +// &(Bus::discriminator() as u64).to_le_bytes(), +// Bus { +// id: i as u64, +// rewards: 250_000_000, +// } +// .to_bytes(), +// ] +// .concat(), +// ) +// .as_str(), +// ); +// } - // Treasury - let admin_address = Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap(); - let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); - program_test.add_account_with_base64_data( - treasury_pda.0, - 1614720, - ore::id(), - bs64::encode( - &[ - &(Treasury::discriminator() as u64).to_le_bytes(), - Treasury { - bump: treasury_pda.1 as u64, - admin: admin_address, - difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), - last_reset_at: 100, - reward_rate: INITIAL_REWARD_RATE, - total_claimed_rewards: 0, - } - .to_bytes(), - ] - .concat(), - ) - .as_str(), - ); +// // Treasury +// let admin_address = Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap(); +// let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); +// program_test.add_account_with_base64_data( +// treasury_pda.0, +// 1614720, +// ore::id(), +// bs64::encode( +// &[ +// &(Treasury::discriminator() as u64).to_le_bytes(), +// Treasury { +// bump: treasury_pda.1 as u64, +// admin: admin_address, +// difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), +// last_reset_at: 100, +// reward_rate: INITIAL_REWARD_RATE, +// total_claimed_rewards: 0, +// } +// .to_bytes(), +// ] +// .concat(), +// ) +// .as_str(), +// ); - // Mint - let mut mint_src: [u8; Mint::LEN] = [0; Mint::LEN]; - Mint { - mint_authority: COption::Some(TREASURY_ADDRESS), - supply: 2_000_000_000, - decimals: TOKEN_DECIMALS, - is_initialized: true, - freeze_authority: COption::None, - } - .pack_into_slice(&mut mint_src); - program_test.add_account_with_base64_data( - MINT_ADDRESS, - 1461600, - spl_token::id(), - bs64::encode(&mint_src).as_str(), - ); +// // Mint +// let mut mint_src: [u8; Mint::LEN] = [0; Mint::LEN]; +// Mint { +// mint_authority: COption::Some(TREASURY_ADDRESS), +// supply: 2_000_000_000, +// decimals: TOKEN_DECIMALS, +// is_initialized: true, +// freeze_authority: COption::None, +// } +// .pack_into_slice(&mut mint_src); +// program_test.add_account_with_base64_data( +// MINT_ADDRESS, +// 1461600, +// spl_token::id(), +// bs64::encode(&mint_src).as_str(), +// ); - // Treasury tokens - let tokens_address = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - let mut tokens_src: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; - spl_token::state::Account { - mint: MINT_ADDRESS, - owner: TREASURY_ADDRESS, - amount: 2_000_000_000, - delegate: COption::None, - state: AccountState::Initialized, - is_native: COption::None, - delegated_amount: 0, - close_authority: COption::None, - } - .pack_into_slice(&mut tokens_src); - program_test.add_account_with_base64_data( - tokens_address, - 2039280, - spl_token::id(), - bs64::encode(&tokens_src).as_str(), - ); +// // Treasury tokens +// let tokens_address = spl_associated_token_account::get_associated_token_address( +// &TREASURY_ADDRESS, +// &MINT_ADDRESS, +// ); +// let mut tokens_src: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; +// spl_token::state::Account { +// mint: MINT_ADDRESS, +// owner: TREASURY_ADDRESS, +// amount: 2_000_000_000, +// delegate: COption::None, +// state: AccountState::Initialized, +// is_native: COption::None, +// delegated_amount: 0, +// close_authority: COption::None, +// } +// .pack_into_slice(&mut tokens_src); +// program_test.add_account_with_base64_data( +// tokens_address, +// 2039280, +// spl_token::id(), +// bs64::encode(&tokens_src).as_str(), +// ); - // Set sysvar - program_test.add_sysvar_account( - sysvar::clock::id(), - &Clock { - slot: 0, - epoch_start_timestamp: 0, - epoch: 0, - leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, - unix_timestamp: 0, - }, - ); +// // Set sysvar +// program_test.add_sysvar_account( +// sysvar::clock::id(), +// &Clock { +// slot: 0, +// epoch_start_timestamp: 0, +// epoch: 0, +// leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, +// unix_timestamp: 0, +// }, +// ); - program_test.start().await -} +// program_test.start().await +// } diff --git a/tests/test_reset.rs b/tests/test_reset.rs index e0355c2..3f0a774 100644 --- a/tests/test_reset.rs +++ b/tests/test_reset.rs @@ -1,440 +1,440 @@ -use std::str::FromStr; +// use std::str::FromStr; -use ore::{ - instruction::OreInstruction, - state::{Bus, Treasury}, - utils::{AccountDeserialize, Discriminator}, - BUS, BUS_ADDRESSES, BUS_COUNT, BUS_EPOCH_REWARDS, INITIAL_DIFFICULTY, INITIAL_REWARD_RATE, - MAX_EPOCH_REWARDS, MINT_ADDRESS, START_AT, TOKEN_DECIMALS, TREASURY, TREASURY_ADDRESS, -}; -use rand::seq::SliceRandom; -use solana_program::{ - clock::Clock, - epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, - hash::Hash, - instruction::{AccountMeta, Instruction}, - native_token::LAMPORTS_PER_SOL, - program_option::COption, - program_pack::Pack, - pubkey::Pubkey, - system_program, sysvar, -}; -use solana_program_test::{processor, BanksClient, ProgramTest}; -use solana_sdk::{ - account::Account, - signature::{Keypair, Signer}, - transaction::Transaction, -}; -use spl_token::state::{AccountState, Mint}; +// use ore::{ +// instruction::OreInstruction, +// state::{Bus, Treasury}, +// utils::{AccountDeserialize, Discriminator}, +// BUS, BUS_ADDRESSES, BUS_COUNT, BUS_EPOCH_REWARDS, INITIAL_DIFFICULTY, INITIAL_REWARD_RATE, +// MAX_EPOCH_REWARDS, MINT_ADDRESS, START_AT, TOKEN_DECIMALS, TREASURY, TREASURY_ADDRESS, +// }; +// use rand::seq::SliceRandom; +// use solana_program::{ +// clock::Clock, +// epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, +// hash::Hash, +// instruction::{AccountMeta, Instruction}, +// native_token::LAMPORTS_PER_SOL, +// program_option::COption, +// program_pack::Pack, +// pubkey::Pubkey, +// system_program, sysvar, +// }; +// use solana_program_test::{processor, BanksClient, ProgramTest}; +// use solana_sdk::{ +// account::Account, +// signature::{Keypair, Signer}, +// transaction::Transaction, +// }; +// use spl_token::state::{AccountState, Mint}; -#[tokio::test] -async fn test_reset() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; +// #[tokio::test] +// async fn test_reset() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - // Pdas - let bus_pdas = vec![ - Pubkey::find_program_address(&[BUS, &[0]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[1]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[2]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[3]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[4]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[5]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[6]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[7]], &ore::id()), - ]; - // let mint_pda = Pubkey::find_program_address(&[MINT], &ore::id()); - let treasury_tokens_address = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); +// // Pdas +// let bus_pdas = vec![ +// Pubkey::find_program_address(&[BUS, &[0]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[1]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[2]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[3]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[4]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[5]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[6]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[7]], &ore::id()), +// ]; +// // let mint_pda = Pubkey::find_program_address(&[MINT], &ore::id()); +// let treasury_tokens_address = spl_associated_token_account::get_associated_token_address( +// &TREASURY_ADDRESS, +// &MINT_ADDRESS, +// ); - // Submit tx - let ix = ore::instruction::reset(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit tx +// let ix = ore::instruction::reset(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Test bus state - for i in 0..BUS_COUNT { - let bus_account = banks.get_account(bus_pdas[i].0).await.unwrap().unwrap(); - assert_eq!(bus_account.owner, ore::id()); - let bus = Bus::try_from_bytes(&bus_account.data).unwrap(); - assert_eq!(bus.id as u8, i as u8); - assert_eq!(bus.rewards, BUS_EPOCH_REWARDS); - } +// // Test bus state +// for i in 0..BUS_COUNT { +// let bus_account = banks.get_account(bus_pdas[i].0).await.unwrap().unwrap(); +// assert_eq!(bus_account.owner, ore::id()); +// let bus = Bus::try_from_bytes(&bus_account.data).unwrap(); +// assert_eq!(bus.id as u8, i as u8); +// assert_eq!(bus.rewards, BUS_EPOCH_REWARDS); +// } - // Test treasury state - let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); - assert_eq!(treasury_account.owner, ore::id()); - let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - assert_eq!( - treasury.admin, - Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap() - ); - assert_eq!(treasury.difficulty, INITIAL_DIFFICULTY.into()); - assert_eq!(treasury.last_reset_at, START_AT + 1); - assert_eq!(treasury.reward_rate, INITIAL_REWARD_RATE.saturating_div(2)); - assert_eq!(treasury.total_claimed_rewards as u8, 0); +// // Test treasury state +// let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); +// assert_eq!(treasury_account.owner, ore::id()); +// let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); +// assert_eq!( +// treasury.admin, +// Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap() +// ); +// assert_eq!(treasury.difficulty, INITIAL_DIFFICULTY.into()); +// assert_eq!(treasury.last_reset_at, START_AT + 1); +// assert_eq!(treasury.reward_rate, INITIAL_REWARD_RATE.saturating_div(2)); +// assert_eq!(treasury.total_claimed_rewards as u8, 0); - // Test mint state - let mint_account = banks.get_account(MINT_ADDRESS).await.unwrap().unwrap(); - assert_eq!(mint_account.owner, spl_token::id()); - let mint = Mint::unpack(&mint_account.data).unwrap(); - assert_eq!(mint.mint_authority, COption::Some(TREASURY_ADDRESS)); - assert_eq!(mint.supply, MAX_EPOCH_REWARDS); - assert_eq!(mint.decimals, ore::TOKEN_DECIMALS); - assert_eq!(mint.is_initialized, true); - assert_eq!(mint.freeze_authority, COption::None); +// // Test mint state +// let mint_account = banks.get_account(MINT_ADDRESS).await.unwrap().unwrap(); +// assert_eq!(mint_account.owner, spl_token::id()); +// let mint = Mint::unpack(&mint_account.data).unwrap(); +// assert_eq!(mint.mint_authority, COption::Some(TREASURY_ADDRESS)); +// assert_eq!(mint.supply, MAX_EPOCH_REWARDS); +// assert_eq!(mint.decimals, ore::TOKEN_DECIMALS); +// assert_eq!(mint.is_initialized, true); +// assert_eq!(mint.freeze_authority, COption::None); - // Test treasury token state - let treasury_tokens_account = banks - .get_account(treasury_tokens_address) - .await - .unwrap() - .unwrap(); - assert_eq!(treasury_tokens_account.owner, spl_token::id()); - let treasury_tokens = spl_token::state::Account::unpack(&treasury_tokens_account.data).unwrap(); - assert_eq!(treasury_tokens.mint, MINT_ADDRESS); - assert_eq!(treasury_tokens.owner, TREASURY_ADDRESS); - assert_eq!(treasury_tokens.amount, MAX_EPOCH_REWARDS); - assert_eq!(treasury_tokens.delegate, COption::None); - assert_eq!(treasury_tokens.state, AccountState::Initialized); - assert_eq!(treasury_tokens.is_native, COption::None); - assert_eq!(treasury_tokens.delegated_amount, 0); - assert_eq!(treasury_tokens.close_authority, COption::None); -} +// // Test treasury token state +// let treasury_tokens_account = banks +// .get_account(treasury_tokens_address) +// .await +// .unwrap() +// .unwrap(); +// assert_eq!(treasury_tokens_account.owner, spl_token::id()); +// let treasury_tokens = spl_token::state::Account::unpack(&treasury_tokens_account.data).unwrap(); +// assert_eq!(treasury_tokens.mint, MINT_ADDRESS); +// assert_eq!(treasury_tokens.owner, TREASURY_ADDRESS); +// assert_eq!(treasury_tokens.amount, MAX_EPOCH_REWARDS); +// assert_eq!(treasury_tokens.delegate, COption::None); +// assert_eq!(treasury_tokens.state, AccountState::Initialized); +// assert_eq!(treasury_tokens.is_native, COption::None); +// assert_eq!(treasury_tokens.delegated_amount, 0); +// assert_eq!(treasury_tokens.close_authority, COption::None); +// } -#[tokio::test] -async fn test_reset_bad_key() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; +// #[tokio::test] +// async fn test_reset_bad_key() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - // Bad addresses - let bad_pda = Pubkey::find_program_address(&[b"t"], &ore::id()); - for i in 1..13 { - let mut ix = ore::instruction::reset(payer.pubkey()); - ix.accounts[i].pubkey = bad_pda.0; - let tx = - Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); - } -} +// // Bad addresses +// let bad_pda = Pubkey::find_program_address(&[b"t"], &ore::id()); +// for i in 1..13 { +// let mut ix = ore::instruction::reset(payer.pubkey()); +// ix.accounts[i].pubkey = bad_pda.0; +// let tx = +// Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } +// } -#[tokio::test] -async fn test_reset_busses_out_of_order_fail() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; +// #[tokio::test] +// async fn test_reset_busses_out_of_order_fail() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - // Pdas - let signer = payer.pubkey(); - let bus_pdas = vec![ - Pubkey::find_program_address(&[BUS, &[5]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[0]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[6]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[2]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[3]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[7]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[1]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[4]], &ore::id()), - ]; - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); +// // Pdas +// let signer = payer.pubkey(); +// let bus_pdas = vec![ +// Pubkey::find_program_address(&[BUS, &[5]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[0]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[6]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[2]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[3]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[7]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[1]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[4]], &ore::id()), +// ]; +// let treasury_tokens = spl_associated_token_account::get_associated_token_address( +// &TREASURY_ADDRESS, +// &MINT_ADDRESS, +// ); - // Submit tx - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus_pdas[0].0, false), - AccountMeta::new(bus_pdas[1].0, false), - AccountMeta::new(bus_pdas[2].0, false), - AccountMeta::new(bus_pdas[3].0, false), - AccountMeta::new(bus_pdas[4].0, false), - AccountMeta::new(bus_pdas[5].0, false), - AccountMeta::new(bus_pdas[6].0, false), - AccountMeta::new(bus_pdas[7].0, false), - AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: OreInstruction::Reset.to_vec(), - }; - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit tx +// let ix = Instruction { +// program_id: ore::id(), +// accounts: vec![ +// AccountMeta::new(signer, true), +// AccountMeta::new(bus_pdas[0].0, false), +// AccountMeta::new(bus_pdas[1].0, false), +// AccountMeta::new(bus_pdas[2].0, false), +// AccountMeta::new(bus_pdas[3].0, false), +// AccountMeta::new(bus_pdas[4].0, false), +// AccountMeta::new(bus_pdas[5].0, false), +// AccountMeta::new(bus_pdas[6].0, false), +// AccountMeta::new(bus_pdas[7].0, false), +// AccountMeta::new(MINT_ADDRESS, false), +// AccountMeta::new(TREASURY_ADDRESS, false), +// AccountMeta::new(treasury_tokens, false), +// AccountMeta::new_readonly(spl_token::id(), false), +// ], +// data: OreInstruction::Reset.to_vec(), +// }; +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_reset_race() { - // Setup - let (mut banks, payer, payer_alt, blockhash) = setup_program_test_env(ClockState::Normal).await; +// #[tokio::test] +// async fn test_reset_race() { +// // Setup +// let (mut banks, payer, payer_alt, blockhash) = setup_program_test_env(ClockState::Normal).await; - // Reset one passes - let ix = ore::instruction::reset(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Reset one passes +// let ix = ore::instruction::reset(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Reset two fails - let ix = ore::instruction::reset(payer_alt.pubkey()); - let tx = Transaction::new_signed_with_payer( - &[ix], - Some(&payer_alt.pubkey()), - &[&payer_alt], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Reset two fails +// let ix = ore::instruction::reset(payer_alt.pubkey()); +// let tx = Transaction::new_signed_with_payer( +// &[ix], +// Some(&payer_alt.pubkey()), +// &[&payer_alt], +// blockhash, +// ); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_reset_too_early() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::TooEarly).await; +// #[tokio::test] +// async fn test_reset_too_early() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::TooEarly).await; - // Reset one passes - let ix = ore::instruction::reset(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Reset one passes +// let ix = ore::instruction::reset(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_reset_not_enough_keys() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; +// #[tokio::test] +// async fn test_reset_not_enough_keys() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - // Reset with missing account - let mut ix = ore::instruction::reset(payer.pubkey()); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Reset with missing account +// let mut ix = ore::instruction::reset(payer.pubkey()); +// ix.accounts.remove(1); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_reset_busses_duplicate_fail() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; +// #[tokio::test] +// async fn test_reset_busses_duplicate_fail() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - // Pdas - let signer = payer.pubkey(); - let bus_pda = Pubkey::find_program_address(&[BUS, &[0]], &ore::id()); - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); +// // Pdas +// let signer = payer.pubkey(); +// let bus_pda = Pubkey::find_program_address(&[BUS, &[0]], &ore::id()); +// let treasury_tokens = spl_associated_token_account::get_associated_token_address( +// &TREASURY_ADDRESS, +// &MINT_ADDRESS, +// ); - // Submit tx - let ix = Instruction { - program_id: ore::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(bus_pda.0, false), - AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: OreInstruction::Reset.to_vec(), - }; - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit tx +// let ix = Instruction { +// program_id: ore::id(), +// accounts: vec![ +// AccountMeta::new(signer, true), +// AccountMeta::new(bus_pda.0, false), +// AccountMeta::new(bus_pda.0, false), +// AccountMeta::new(bus_pda.0, false), +// AccountMeta::new(bus_pda.0, false), +// AccountMeta::new(bus_pda.0, false), +// AccountMeta::new(bus_pda.0, false), +// AccountMeta::new(bus_pda.0, false), +// AccountMeta::new(bus_pda.0, false), +// AccountMeta::new(MINT_ADDRESS, false), +// AccountMeta::new(TREASURY_ADDRESS, false), +// AccountMeta::new(treasury_tokens, false), +// AccountMeta::new_readonly(spl_token::id(), false), +// ], +// data: OreInstruction::Reset.to_vec(), +// }; +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_reset_shuffle_error() { - // Setup - const FUZZ: u64 = 100; - let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; +// #[tokio::test] +// async fn test_reset_shuffle_error() { +// // Setup +// const FUZZ: u64 = 100; +// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - // Pdas - let signer = payer.pubkey(); - let bus_pdas = vec![ - Pubkey::find_program_address(&[BUS, &[5]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[0]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[6]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[2]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[3]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[7]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[1]], &ore::id()), - Pubkey::find_program_address(&[BUS, &[4]], &ore::id()), - ]; - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); +// // Pdas +// let signer = payer.pubkey(); +// let bus_pdas = vec![ +// Pubkey::find_program_address(&[BUS, &[5]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[0]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[6]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[2]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[3]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[7]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[1]], &ore::id()), +// Pubkey::find_program_address(&[BUS, &[4]], &ore::id()), +// ]; +// let treasury_tokens = spl_associated_token_account::get_associated_token_address( +// &TREASURY_ADDRESS, +// &MINT_ADDRESS, +// ); - // Fuzz test shuffled accounts. - // Note some shuffles may still be valid if signer and non-bus accounts are all in correct positions. - let mut rng = rand::thread_rng(); - for _ in 0..FUZZ { - let mut accounts = vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus_pdas[0].0, false), - AccountMeta::new(bus_pdas[1].0, false), - AccountMeta::new(bus_pdas[2].0, false), - AccountMeta::new(bus_pdas[3].0, false), - AccountMeta::new(bus_pdas[4].0, false), - AccountMeta::new(bus_pdas[5].0, false), - AccountMeta::new(bus_pdas[6].0, false), - AccountMeta::new(bus_pdas[7].0, false), - AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ]; - accounts.shuffle(&mut rng); - let ix = Instruction { - program_id: ore::id(), - accounts, - data: OreInstruction::Reset.to_vec(), - }; - let tx = - Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); - } -} +// // Fuzz test shuffled accounts. +// // Note some shuffles may still be valid if signer and non-bus accounts are all in correct positions. +// let mut rng = rand::thread_rng(); +// for _ in 0..FUZZ { +// let mut accounts = vec![ +// AccountMeta::new(signer, true), +// AccountMeta::new(bus_pdas[0].0, false), +// AccountMeta::new(bus_pdas[1].0, false), +// AccountMeta::new(bus_pdas[2].0, false), +// AccountMeta::new(bus_pdas[3].0, false), +// AccountMeta::new(bus_pdas[4].0, false), +// AccountMeta::new(bus_pdas[5].0, false), +// AccountMeta::new(bus_pdas[6].0, false), +// AccountMeta::new(bus_pdas[7].0, false), +// AccountMeta::new(MINT_ADDRESS, false), +// AccountMeta::new(TREASURY_ADDRESS, false), +// AccountMeta::new(treasury_tokens, false), +// AccountMeta::new_readonly(spl_token::id(), false), +// ]; +// accounts.shuffle(&mut rng); +// let ix = Instruction { +// program_id: ore::id(), +// accounts, +// data: OreInstruction::Reset.to_vec(), +// }; +// let tx = +// Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } +// } -enum ClockState { - Normal, - TooEarly, -} +// enum ClockState { +// Normal, +// TooEarly, +// } -async fn setup_program_test_env(clock_state: ClockState) -> (BanksClient, Keypair, Keypair, Hash) { - let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); - program_test.prefer_bpf(true); +// async fn setup_program_test_env(clock_state: ClockState) -> (BanksClient, Keypair, Keypair, Hash) { +// let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); +// program_test.prefer_bpf(true); - // Busses - for i in 0..BUS_COUNT { - program_test.add_account_with_base64_data( - BUS_ADDRESSES[i], - 1057920, - ore::id(), - bs64::encode( - &[ - &(Bus::discriminator() as u64).to_le_bytes(), - Bus { - id: i as u64, - rewards: 0, - } - .to_bytes(), - ] - .concat(), - ) - .as_str(), - ); - } +// // Busses +// for i in 0..BUS_COUNT { +// program_test.add_account_with_base64_data( +// BUS_ADDRESSES[i], +// 1057920, +// ore::id(), +// bs64::encode( +// &[ +// &(Bus::discriminator() as u64).to_le_bytes(), +// Bus { +// id: i as u64, +// rewards: 0, +// } +// .to_bytes(), +// ] +// .concat(), +// ) +// .as_str(), +// ); +// } - // Treasury - let admin_address = Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap(); - let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); - program_test.add_account_with_base64_data( - treasury_pda.0, - 1614720, - ore::id(), - bs64::encode( - &[ - &(Treasury::discriminator() as u64).to_le_bytes(), - Treasury { - bump: treasury_pda.1 as u64, - admin: admin_address, - difficulty: INITIAL_DIFFICULTY.into(), - last_reset_at: 0, - reward_rate: INITIAL_REWARD_RATE, - total_claimed_rewards: 0, - } - .to_bytes(), - ] - .concat(), - ) - .as_str(), - ); +// // Treasury +// let admin_address = Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap(); +// let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); +// program_test.add_account_with_base64_data( +// treasury_pda.0, +// 1614720, +// ore::id(), +// bs64::encode( +// &[ +// &(Treasury::discriminator() as u64).to_le_bytes(), +// Treasury { +// bump: treasury_pda.1 as u64, +// admin: admin_address, +// difficulty: INITIAL_DIFFICULTY.into(), +// last_reset_at: 0, +// reward_rate: INITIAL_REWARD_RATE, +// total_claimed_rewards: 0, +// } +// .to_bytes(), +// ] +// .concat(), +// ) +// .as_str(), +// ); - // Mint - let mut mint_src: [u8; Mint::LEN] = [0; Mint::LEN]; - Mint { - mint_authority: COption::Some(TREASURY_ADDRESS), - supply: 0, - decimals: TOKEN_DECIMALS, - is_initialized: true, - freeze_authority: COption::None, - } - .pack_into_slice(&mut mint_src); - program_test.add_account_with_base64_data( - MINT_ADDRESS, - 1461600, - spl_token::id(), - bs64::encode(&mint_src).as_str(), - ); +// // Mint +// let mut mint_src: [u8; Mint::LEN] = [0; Mint::LEN]; +// Mint { +// mint_authority: COption::Some(TREASURY_ADDRESS), +// supply: 0, +// decimals: TOKEN_DECIMALS, +// is_initialized: true, +// freeze_authority: COption::None, +// } +// .pack_into_slice(&mut mint_src); +// program_test.add_account_with_base64_data( +// MINT_ADDRESS, +// 1461600, +// spl_token::id(), +// bs64::encode(&mint_src).as_str(), +// ); - // Treasury tokens - let tokens_address = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - let mut tokens_src: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; - spl_token::state::Account { - mint: MINT_ADDRESS, - owner: TREASURY_ADDRESS, - amount: 0, - delegate: COption::None, - state: AccountState::Initialized, - is_native: COption::None, - delegated_amount: 0, - close_authority: COption::None, - } - .pack_into_slice(&mut tokens_src); - program_test.add_account_with_base64_data( - tokens_address, - 2039280, - spl_token::id(), - bs64::encode(&tokens_src).as_str(), - ); +// // Treasury tokens +// let tokens_address = spl_associated_token_account::get_associated_token_address( +// &TREASURY_ADDRESS, +// &MINT_ADDRESS, +// ); +// let mut tokens_src: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; +// spl_token::state::Account { +// mint: MINT_ADDRESS, +// owner: TREASURY_ADDRESS, +// amount: 0, +// delegate: COption::None, +// state: AccountState::Initialized, +// is_native: COption::None, +// delegated_amount: 0, +// close_authority: COption::None, +// } +// .pack_into_slice(&mut tokens_src); +// program_test.add_account_with_base64_data( +// tokens_address, +// 2039280, +// spl_token::id(), +// bs64::encode(&tokens_src).as_str(), +// ); - // Set sysvar - let ts = match clock_state { - ClockState::Normal => START_AT + 1, - ClockState::TooEarly => START_AT - 1, - }; - program_test.add_sysvar_account( - sysvar::clock::id(), - &Clock { - slot: 0, - epoch_start_timestamp: 0, - epoch: 0, - leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, - unix_timestamp: ts, - }, - ); +// // Set sysvar +// let ts = match clock_state { +// ClockState::Normal => START_AT + 1, +// ClockState::TooEarly => START_AT - 1, +// }; +// program_test.add_sysvar_account( +// sysvar::clock::id(), +// &Clock { +// slot: 0, +// epoch_start_timestamp: 0, +// epoch: 0, +// leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, +// unix_timestamp: ts, +// }, +// ); - // Setup alt payer - let payer_alt = Keypair::new(); - program_test.add_account( - payer_alt.pubkey(), - Account { - lamports: LAMPORTS_PER_SOL, - data: vec![], - owner: system_program::id(), - executable: false, - rent_epoch: 0, - }, - ); +// // Setup alt payer +// let payer_alt = Keypair::new(); +// program_test.add_account( +// payer_alt.pubkey(), +// Account { +// lamports: LAMPORTS_PER_SOL, +// data: vec![], +// owner: system_program::id(), +// executable: false, +// rent_epoch: 0, +// }, +// ); - let (banks, payer, blockhash) = program_test.start().await; - (banks, payer, payer_alt, blockhash) -} +// let (banks, payer, blockhash) = program_test.start().await; +// (banks, payer, payer_alt, blockhash) +// } diff --git a/tests/test_update_admin.rs b/tests/test_update_admin.rs index b3bd4a7..cd00ccd 100644 --- a/tests/test_update_admin.rs +++ b/tests/test_update_admin.rs @@ -1,128 +1,128 @@ -use ore::{state::Treasury, utils::AccountDeserialize, TREASURY_ADDRESS}; -use solana_program::{ - hash::Hash, native_token::LAMPORTS_PER_SOL, pubkey::Pubkey, rent::Rent, system_program, -}; -use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; -use solana_sdk::{ - account::Account, - signature::{Keypair, Signer}, - transaction::Transaction, -}; +// use ore::{state::Treasury, utils::AccountDeserialize, TREASURY_ADDRESS}; +// use solana_program::{ +// hash::Hash, native_token::LAMPORTS_PER_SOL, pubkey::Pubkey, rent::Rent, system_program, +// }; +// use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; +// use solana_sdk::{ +// account::Account, +// signature::{Keypair, Signer}, +// transaction::Transaction, +// }; -#[tokio::test] -async fn test_update_admin() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env().await; +// #[tokio::test] +// async fn test_update_admin() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env().await; - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit tx +// let ix = ore::instruction::initialize(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Get treasury account - let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); - let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); +// // Get treasury account +// let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); +// let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - // Submit update admin ix - let new_admin = Pubkey::new_unique(); - let ix = ore::instruction::update_admin(payer.pubkey(), new_admin); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit update admin ix +// let new_admin = Pubkey::new_unique(); +// let ix = ore::instruction::update_admin(payer.pubkey(), new_admin); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Assert treasury state - let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); - let treasury_ = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - assert_eq!(treasury_.bump, treasury.bump); - assert_eq!(treasury_.admin, new_admin); - assert_eq!(treasury_.difficulty, treasury.difficulty); - assert_eq!(treasury_.last_reset_at, treasury.last_reset_at); - assert_eq!(treasury_.reward_rate, treasury.reward_rate); - assert_eq!( - treasury_.total_claimed_rewards, - treasury.total_claimed_rewards - ); +// // Assert treasury state +// let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); +// let treasury_ = Treasury::try_from_bytes(&treasury_account.data).unwrap(); +// assert_eq!(treasury_.bump, treasury.bump); +// assert_eq!(treasury_.admin, new_admin); +// assert_eq!(treasury_.difficulty, treasury.difficulty); +// assert_eq!(treasury_.last_reset_at, treasury.last_reset_at); +// assert_eq!(treasury_.reward_rate, treasury.reward_rate); +// assert_eq!( +// treasury_.total_claimed_rewards, +// treasury.total_claimed_rewards +// ); - // Submit another update admin ix - let ix = ore::instruction::update_admin(payer.pubkey(), payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit another update admin ix +// let ix = ore::instruction::update_admin(payer.pubkey(), payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_update_admin_bad_signer() { - // Setup - let (mut banks, payer, alt_payer, blockhash) = setup_program_test_env().await; +// #[tokio::test] +// async fn test_update_admin_bad_signer() { +// // Setup +// let (mut banks, payer, alt_payer, blockhash) = setup_program_test_env().await; - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit tx +// let ix = ore::instruction::initialize(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Submit ix - let ix = ore::instruction::update_admin(alt_payer.pubkey(), Pubkey::new_unique()); - let tx = Transaction::new_signed_with_payer( - &[ix], - Some(&alt_payer.pubkey()), - &[&alt_payer], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit ix +// let ix = ore::instruction::update_admin(alt_payer.pubkey(), Pubkey::new_unique()); +// let tx = Transaction::new_signed_with_payer( +// &[ix], +// Some(&alt_payer.pubkey()), +// &[&alt_payer], +// blockhash, +// ); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_update_admin_not_enough_accounts() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env().await; +// #[tokio::test] +// async fn test_update_admin_not_enough_accounts() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env().await; - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit tx +// let ix = ore::instruction::initialize(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Submit ix without enough accounts - let mut ix = ore::instruction::update_admin(payer.pubkey(), Pubkey::new_unique()); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit ix without enough accounts +// let mut ix = ore::instruction::update_admin(payer.pubkey(), Pubkey::new_unique()); +// ix.accounts.remove(1); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { - let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); - program_test.prefer_bpf(true); +// async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { +// let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); +// program_test.prefer_bpf(true); - // Setup metadata program - let data = read_file(&"tests/buffers/metadata_program.bpf"); - program_test.add_account( - mpl_token_metadata::ID, - Account { - lamports: Rent::default().minimum_balance(data.len()).max(1), - data, - owner: solana_sdk::bpf_loader::id(), - executable: true, - rent_epoch: 0, - }, - ); +// // Setup metadata program +// let data = read_file(&"tests/buffers/metadata_program.bpf"); +// program_test.add_account( +// mpl_token_metadata::ID, +// Account { +// lamports: Rent::default().minimum_balance(data.len()).max(1), +// data, +// owner: solana_sdk::bpf_loader::id(), +// executable: true, +// rent_epoch: 0, +// }, +// ); - // Setup alt payer - let payer_alt = Keypair::new(); - program_test.add_account( - payer_alt.pubkey(), - Account { - lamports: LAMPORTS_PER_SOL, - data: vec![], - owner: system_program::id(), - executable: false, - rent_epoch: 0, - }, - ); +// // Setup alt payer +// let payer_alt = Keypair::new(); +// program_test.add_account( +// payer_alt.pubkey(), +// Account { +// lamports: LAMPORTS_PER_SOL, +// data: vec![], +// owner: system_program::id(), +// executable: false, +// rent_epoch: 0, +// }, +// ); - let (banks, payer, blockhash) = program_test.start().await; - (banks, payer, payer_alt, blockhash) -} +// let (banks, payer, blockhash) = program_test.start().await; +// (banks, payer, payer_alt, blockhash) +// } diff --git a/tests/test_update_difficulty.rs b/tests/test_update_difficulty.rs index 3c7d335..4666a8a 100644 --- a/tests/test_update_difficulty.rs +++ b/tests/test_update_difficulty.rs @@ -1,125 +1,125 @@ -use ore::{state::Treasury, utils::AccountDeserialize, TREASURY_ADDRESS}; -use solana_program::{ - hash::Hash, keccak::Hash as KeccakHash, native_token::LAMPORTS_PER_SOL, rent::Rent, - system_program, -}; -use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; -use solana_sdk::{ - account::Account, - signature::{Keypair, Signer}, - transaction::Transaction, -}; +// use ore::{state::Treasury, utils::AccountDeserialize, TREASURY_ADDRESS}; +// use solana_program::{ +// hash::Hash, keccak::Hash as KeccakHash, native_token::LAMPORTS_PER_SOL, rent::Rent, +// system_program, +// }; +// use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; +// use solana_sdk::{ +// account::Account, +// signature::{Keypair, Signer}, +// transaction::Transaction, +// }; -#[tokio::test] -async fn test_update_difficulty() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env().await; +// #[tokio::test] +// async fn test_update_difficulty() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env().await; - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit tx +// let ix = ore::instruction::initialize(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Get treasury account - let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); - let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); +// // Get treasury account +// let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); +// let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - // Submit update difficulty ix - let new_difficulty = KeccakHash::new_unique(); - let ix = ore::instruction::update_difficulty(payer.pubkey(), new_difficulty.into()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit update difficulty ix +// let new_difficulty = KeccakHash::new_unique(); +// let ix = ore::instruction::update_difficulty(payer.pubkey(), new_difficulty.into()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Assert treasury state - let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); - let treasury_ = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - assert_eq!(treasury_.bump, treasury.bump); - assert_eq!(treasury_.admin, treasury.admin); - assert_eq!(treasury_.difficulty, new_difficulty.into()); - assert_eq!(treasury_.last_reset_at, treasury.last_reset_at); - assert_eq!(treasury_.reward_rate, treasury.reward_rate); - assert_eq!( - treasury_.total_claimed_rewards, - treasury.total_claimed_rewards - ); -} +// // Assert treasury state +// let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); +// let treasury_ = Treasury::try_from_bytes(&treasury_account.data).unwrap(); +// assert_eq!(treasury_.bump, treasury.bump); +// assert_eq!(treasury_.admin, treasury.admin); +// assert_eq!(treasury_.difficulty, new_difficulty.into()); +// assert_eq!(treasury_.last_reset_at, treasury.last_reset_at); +// assert_eq!(treasury_.reward_rate, treasury.reward_rate); +// assert_eq!( +// treasury_.total_claimed_rewards, +// treasury.total_claimed_rewards +// ); +// } -#[tokio::test] -async fn test_update_difficulty_bad_signer() { - // Setup - let (mut banks, payer, alt_payer, blockhash) = setup_program_test_env().await; +// #[tokio::test] +// async fn test_update_difficulty_bad_signer() { +// // Setup +// let (mut banks, payer, alt_payer, blockhash) = setup_program_test_env().await; - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit tx +// let ix = ore::instruction::initialize(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Submit update difficulty ix - let new_difficulty = KeccakHash::new_unique(); - let ix = ore::instruction::update_difficulty(alt_payer.pubkey(), new_difficulty.into()); - let tx = Transaction::new_signed_with_payer( - &[ix], - Some(&alt_payer.pubkey()), - &[&alt_payer], - blockhash, - ); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit update difficulty ix +// let new_difficulty = KeccakHash::new_unique(); +// let ix = ore::instruction::update_difficulty(alt_payer.pubkey(), new_difficulty.into()); +// let tx = Transaction::new_signed_with_payer( +// &[ix], +// Some(&alt_payer.pubkey()), +// &[&alt_payer], +// blockhash, +// ); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -#[tokio::test] -async fn test_update_difficulty_not_enough_accounts() { - // Setup - let (mut banks, payer, _, blockhash) = setup_program_test_env().await; +// #[tokio::test] +// async fn test_update_difficulty_not_enough_accounts() { +// // Setup +// let (mut banks, payer, _, blockhash) = setup_program_test_env().await; - // Submit tx - let ix = ore::instruction::initialize(payer.pubkey()); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_ok()); +// // Submit tx +// let ix = ore::instruction::initialize(payer.pubkey()); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_ok()); - // Submit ix without enough accounts - let new_difficulty = KeccakHash::new_unique(); - let mut ix = ore::instruction::update_difficulty(payer.pubkey(), new_difficulty.into()); - ix.accounts.remove(1); - let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); - let res = banks.process_transaction(tx).await; - assert!(res.is_err()); -} +// // Submit ix without enough accounts +// let new_difficulty = KeccakHash::new_unique(); +// let mut ix = ore::instruction::update_difficulty(payer.pubkey(), new_difficulty.into()); +// ix.accounts.remove(1); +// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); +// let res = banks.process_transaction(tx).await; +// assert!(res.is_err()); +// } -async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { - let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); - program_test.prefer_bpf(true); +// async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { +// let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); +// program_test.prefer_bpf(true); - // Setup metadata program - let data = read_file(&"tests/buffers/metadata_program.bpf"); - program_test.add_account( - mpl_token_metadata::ID, - Account { - lamports: Rent::default().minimum_balance(data.len()).max(1), - data, - owner: solana_sdk::bpf_loader::id(), - executable: true, - rent_epoch: 0, - }, - ); +// // Setup metadata program +// let data = read_file(&"tests/buffers/metadata_program.bpf"); +// program_test.add_account( +// mpl_token_metadata::ID, +// Account { +// lamports: Rent::default().minimum_balance(data.len()).max(1), +// data, +// owner: solana_sdk::bpf_loader::id(), +// executable: true, +// rent_epoch: 0, +// }, +// ); - // Setup alt payer - let payer_alt = Keypair::new(); - program_test.add_account( - payer_alt.pubkey(), - Account { - lamports: LAMPORTS_PER_SOL, - data: vec![], - owner: system_program::id(), - executable: false, - rent_epoch: 0, - }, - ); +// // Setup alt payer +// let payer_alt = Keypair::new(); +// program_test.add_account( +// payer_alt.pubkey(), +// Account { +// lamports: LAMPORTS_PER_SOL, +// data: vec![], +// owner: system_program::id(), +// executable: false, +// rent_epoch: 0, +// }, +// ); - let (banks, payer, blockhash) = program_test.start().await; - (banks, payer, payer_alt, blockhash) -} +// let (banks, payer, blockhash) = program_test.start().await; +// (banks, payer, payer_alt, blockhash) +// } From f4d36f9b9f7419e40fa28eb201a864d209dcbc5e Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 27 Apr 2024 16:30:45 +0000 Subject: [PATCH 004/111] mine v2 --- Cargo.lock | 1252 +++++++++++++++++++++-------------- Cargo.toml | 1 + src/consts.rs | 9 +- src/error.rs | 10 +- src/instruction.rs | 19 +- src/lib.rs | 6 + src/loaders.rs | 28 +- src/processor/initialize.rs | 10 +- src/processor/mine.rs | 180 ++--- src/processor/register.rs | 7 +- src/processor/reset.rs | 160 +++-- src/processor/stake.rs | 1 + src/state/bus.rs | 6 - src/state/config.rs | 13 +- src/state/hash.rs | 14 + src/state/mod.rs | 4 +- src/state/proof.rs | 14 +- src/state/treasury.rs | 6 - 18 files changed, 1019 insertions(+), 721 deletions(-) create mode 100644 src/processor/stake.rs diff --git a/Cargo.lock b/Cargo.lock index 60533b3..425dd90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,23 +65,23 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.14", "once_cell", "version_check", ] [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom 0.2.11", + "getrandom 0.2.14", "once_cell", "version_check", "zerocopy", @@ -89,9 +89,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -143,9 +143,23 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.78" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca87830a3e3fb156dc96cfbd31cb620265dd053be734723f22b760d6cc3c3051" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" + +[[package]] +name = "aquamarine" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da02abba9f9063d786eab1509833ebb2fac0f966862ca59439c76b9c566760" +dependencies = [ + "include_dir", + "itertools", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] name = "ark-bn254" @@ -340,9 +354,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.5" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5" +checksum = "4e9eabd7a98fe442131a17c316bd9349c43695e49e730c3c8e12cfb5f4da2693" dependencies = [ "brotli", "flate2", @@ -363,13 +377,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.76" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -385,15 +399,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -445,9 +459,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] @@ -463,9 +477,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" dependencies = [ "arrayref", "arrayvec", @@ -520,6 +534,16 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "borsh" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0901fc8eb0aca4c83be0106d6f2db17d86a08dfc2c25f0e84464bf381158add6" +dependencies = [ + "borsh-derive 1.4.0", + "cfg_aliases", +] + [[package]] name = "borsh-derive" version = "0.9.3" @@ -546,6 +570,20 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "borsh-derive" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51670c3aa053938b0ee3bd67c3817e471e626151131b934038e83c5bf8de48f5" +dependencies = [ + "once_cell", + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.60", + "syn_derive", +] + [[package]] name = "borsh-derive-internal" version = "0.9.3" @@ -592,9 +630,9 @@ dependencies = [ [[package]] name = "brotli" -version = "3.4.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +checksum = "19483b140a7ac7174d34b5a581b406c64f84da5409d3e09cf4fff604f9270e67" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -603,9 +641,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "2.5.1" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +checksum = "e6221fe77a248b9117d431ad93761222e1cf8ff282d9d1d5d9f53d6299a1cf76" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -619,9 +657,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ "tinyvec", ] @@ -637,9 +675,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bv" @@ -653,22 +691,22 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -679,9 +717,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "bzip2" @@ -716,12 +754,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -731,10 +770,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.31" +name = "cfg_aliases" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -742,7 +787,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -791,7 +836,7 @@ dependencies = [ "once_cell", "strsim 0.10.0", "termcolor", - "textwrap 0.16.0", + "textwrap 0.16.1", ] [[package]] @@ -888,61 +933,55 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.17" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.18" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crunchy" @@ -995,9 +1034,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" dependencies = [ "darling_core", "darling_macro", @@ -1005,37 +1044,40 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] name = "darling_macro" -version = "0.20.3" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] name = "dashmap" -version = "4.0.2" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "num_cpus", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core", "rayon", ] @@ -1106,6 +1148,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.9.0" @@ -1143,7 +1191,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -1166,7 +1214,25 @@ checksum = "a6cbae11b3de8fce2a456e8ea3dada226b35fe791f0dc1d360c0941f0bb681f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", +] + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "drillx" +version = "0.1.0" +source = "git+https://github.com/hardhatchad/drillx?branch=master#9fa59d964a23618b04f6f4f96ceb58f6638f9257" +dependencies = [ + "enum_dispatch", + "num-traits", + "num_enum 0.5.11", + "solana-program", + "strum 0.26.2", ] [[package]] @@ -1224,9 +1290,9 @@ dependencies = [ [[package]] name = "either" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "encode_unicode" @@ -1236,31 +1302,31 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] [[package]] name = "enum-iterator" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7add3873b5dd076766ee79c8e406ad1a472c385476b9e38849f8eec24f1be689" +checksum = "9fd242f399be1da0a5354aa462d57b4ab2b4ee0683cc552f7c007d2d12d36e94" dependencies = [ "enum-iterator-derive", ] [[package]] name = "enum-iterator-derive" -version = "1.2.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" +checksum = "c19cbb53d33b57ac4df1f0af6b92c38c107cded663c4aea9fae1189dcfc17cf5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -1273,7 +1339,19 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", +] + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.60", ] [[package]] @@ -1313,9 +1391,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "feature-probe" @@ -1331,20 +1409,29 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "windows-sys 0.52.0", ] [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "4556222738635b7a3417ae6130d8f52201e45a0c4d1a907f0826383adb5f85e7" dependencies = [ "crc32fast", "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1361,13 +1448,10 @@ dependencies = [ ] [[package]] -name = "fs-err" -version = "2.11.0" +name = "fragile" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" -dependencies = [ - "autocfg", -] +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" [[package]] name = "futures" @@ -1425,7 +1509,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -1494,9 +1578,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" dependencies = [ "cfg-if", "js-sys", @@ -1524,9 +1608,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.23" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b553656127a00601c8ae5590fcfdc118e4083a7924b6cf4ffc1ea4b99dc429d7" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -1534,7 +1618,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.1.0", + "indexmap 2.2.6", "slab", "tokio", "tokio-util 0.7.10", @@ -1556,7 +1640,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash 0.7.7", + "ahash 0.7.8", ] [[package]] @@ -1565,7 +1649,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.7", + "ahash 0.7.8", ] [[package]] @@ -1574,7 +1658,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", ] [[package]] @@ -1600,9 +1684,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "histogram" @@ -1642,9 +1726,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -1720,9 +1804,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.59" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1773,6 +1857,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "index_list" version = "0.2.11" @@ -1791,9 +1894,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1801,9 +1904,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" dependencies = [ "console", "instant", @@ -1838,24 +1941,24 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -1877,9 +1980,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] @@ -1892,9 +1995,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libsecp256k1" @@ -1958,15 +2061,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1974,9 +2077,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "lru" @@ -2009,9 +2112,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memmap2" @@ -2033,9 +2136,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -2066,24 +2169,51 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.48.0", ] +[[package]] +name = "mockall" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "modular-bitfield" version = "0.11.2" @@ -2141,6 +2271,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num" version = "0.2.1" @@ -2187,6 +2323,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" version = "0.3.3" @@ -2200,30 +2342,29 @@ dependencies = [ [[package]] name = "num-derive" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" dependencies = [ "autocfg", "num-integer", @@ -2244,9 +2385,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -2257,10 +2398,19 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.3", + "hermit-abi 0.3.9", "libc", ] +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive 0.5.11", +] + [[package]] name = "num_enum" version = "0.6.1" @@ -2279,6 +2429,18 @@ dependencies = [ "num_enum_derive 0.7.2", ] +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num_enum_derive" version = "0.6.1" @@ -2288,7 +2450,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -2297,10 +2459,10 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 2.0.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -2335,9 +2497,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl-probe" @@ -2368,9 +2530,10 @@ dependencies = [ name = "ore-program" version = "1.2.1" dependencies = [ - "bs58 0.5.0", + "bs58 0.5.1", "bs64", "bytemuck", + "drillx", "mpl-token-metadata", "num_enum 0.7.2", "rand 0.8.5", @@ -2416,9 +2579,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -2426,15 +2589,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -2487,29 +2650,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -2530,9 +2693,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plain" @@ -2570,6 +2733,36 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "predicates" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" +dependencies = [ + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -2591,12 +2784,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_datetime", - "toml_edit 0.20.2", + "toml_edit 0.21.1", ] [[package]] @@ -2625,9 +2817,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ "unicode-ident", ] @@ -2649,7 +2841,7 @@ checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -2702,9 +2894,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -2768,7 +2960,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.11", + "getrandom 0.2.14", ] [[package]] @@ -2791,9 +2983,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -2801,9 +2993,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -2831,10 +3023,19 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.10.2" +name = "redox_syscall" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -2844,9 +3045,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -2855,15 +3056,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "async-compression", "base64 0.21.7", @@ -2888,6 +3089,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-rustls", @@ -2897,7 +3099,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.25.3", + "webpki-roots 0.25.4", "winreg", ] @@ -2918,16 +3120,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", - "getrandom 0.2.11", + "cfg-if", + "getrandom 0.2.14", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2983,11 +3186,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -2996,12 +3199,12 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" dependencies = [ "log", - "ring 0.17.7", + "ring 0.17.8", "rustls-webpki", "sct", ] @@ -3033,21 +3236,21 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -3090,7 +3293,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -3099,15 +3302,15 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] [[package]] name = "security-framework" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -3118,9 +3321,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" dependencies = [ "core-foundation-sys", "libc", @@ -3128,44 +3331,53 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "seqlock" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5c67b6f14ecc5b86c66fa63d76b5092352678545a8a3cdae80aef5128371910" +dependencies = [ + "parking_lot", +] [[package]] name = "serde" -version = "1.0.193" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb1879ea93538b78549031e2d54da3e901fd7e75f2e4dc758d760937b123d10" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -3203,7 +3415,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -3326,9 +3538,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -3366,25 +3578,25 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "solana-account-decoder" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21ed570fba6f909f69c888b48b39c7e61b454e3594e448d0dad9d973f27f5668" +checksum = "142161f13c328e7807fe98fb8f6eaaa5045a8eaf4492414aa81254870c4fc8a0" dependencies = [ "Inflector", "base64 0.21.7", @@ -3407,9 +3619,9 @@ dependencies = [ [[package]] name = "solana-accounts-db" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c457b335c3b13b0df99ffee59cf8b3d92e861abbddd0de93993367f449a76c" +checksum = "5e8b4b15e353d5f0e0ddd77966c6f01b296bd83569af455da5fd9329356ff642" dependencies = [ "arrayref", "bincode", @@ -3422,7 +3634,6 @@ dependencies = [ "dashmap", "flate2", "fnv", - "fs-err", "im", "index_list", "itertools", @@ -3431,10 +3642,10 @@ dependencies = [ "lz4", "memmap2", "modular-bitfield", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "num_cpus", - "num_enum 0.6.1", + "num_enum 0.7.2", "ouroboros", "percentage", "qualifier_attr", @@ -3442,14 +3653,17 @@ dependencies = [ "rayon", "regex", "rustc_version", + "seqlock", "serde", "serde_derive", + "smallvec", "solana-bucket-map", "solana-config-program", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-measure", "solana-metrics", + "solana-nohash-hasher", "solana-program-runtime", "solana-rayon-threadlimit", "solana-sdk", @@ -3457,8 +3671,8 @@ dependencies = [ "solana-system-program", "solana-vote-program", "static_assertions", - "strum", - "strum_macros", + "strum 0.24.1", + "strum_macros 0.24.3", "tar", "tempfile", "thiserror", @@ -3466,14 +3680,14 @@ dependencies = [ [[package]] name = "solana-address-lookup-table-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba35ca5c434b2479a2a55b831461bd8cfdf2c389ee3ae4a0fc51918fbe17d88" +checksum = "4c4eef9fc8aa3ff804dbf17766ab2d2fe38561adc8b521705faa782c18a108d8" dependencies = [ "bincode", "bytemuck", "log", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "rustc_version", "serde", @@ -3487,11 +3701,11 @@ dependencies = [ [[package]] name = "solana-banks-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f22c7a0b5d1a81193875dded16ed189666fab0b753d46faec311c2798e0af34" +checksum = "13a4cbe27e78987b706caf90cbd16da9da3955c09a660b8107a96c2cb32f1124" dependencies = [ - "borsh 0.10.3", + "borsh 1.4.0", "futures", "solana-banks-interface", "solana-program", @@ -3504,9 +3718,9 @@ dependencies = [ [[package]] name = "solana-banks-interface" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2652008dcd55d163e08e4d94c2b7592cb8562b33e168ac86462ddac4dca143" +checksum = "741279a09bf5ea1a3d17e591db7b189e163722e5c46423308c6a6165bea5e74d" dependencies = [ "serde", "solana-sdk", @@ -3515,9 +3729,9 @@ dependencies = [ [[package]] name = "solana-banks-server" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a01440c39a08f90f8016013918491f8ffe16d066efe0508f9cb12e2863f6eaf" +checksum = "f66768544951feb91c3470e255d4613295b5cc5a58a9cc6a4207ab9a0178cfe9" dependencies = [ "bincode", "crossbeam-channel", @@ -3535,9 +3749,9 @@ dependencies = [ [[package]] name = "solana-bpf-loader-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571e8ef9d82bec9d32dd2d54b00e1572a85c967ed996cf737f3a52946d760623" +checksum = "60e9dd5e42193260cca0794bf4ab9e248f44b3d9710041f241b130d26ed682bc" dependencies = [ "bincode", "byteorder", @@ -3554,16 +3768,16 @@ dependencies = [ [[package]] name = "solana-bucket-map" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109fdb52669846283bc6ef2ed87c832295af6f7e3c4e7888127b3d506054d651" +checksum = "1a7b34296d69867253671a71a2231b8d5b4a810bd7a5c1c603e7b542832d5980" dependencies = [ "bv", "bytemuck", "log", "memmap2", "modular-bitfield", - "num_enum 0.6.1", + "num_enum 0.7.2", "rand 0.8.5", "solana-measure", "solana-sdk", @@ -3572,9 +3786,9 @@ dependencies = [ [[package]] name = "solana-clap-utils" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4729fec3c2ac37b7daaf24c1ef879bbedbff3495b1ac728d9b627282d878753" +checksum = "e8e9f61034a61db538a41700b6df0b4b9f0392038adaf780150481923ff94356" dependencies = [ "chrono", "clap 2.34.0", @@ -3589,16 +3803,16 @@ dependencies = [ [[package]] name = "solana-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da13019a833940af2edebda969db4337ab11c6fb220eb0d4c02d79c83ae8034" +checksum = "13f2bd5a986d7cac1b4ffb4344413b70b6f21fd7ffa92a985911756b4ac7682a" dependencies = [ "async-trait", "bincode", "dashmap", "futures", "futures-util", - "indexmap 2.1.0", + "indexmap 2.2.6", "indicatif", "log", "quinn", @@ -3622,9 +3836,9 @@ dependencies = [ [[package]] name = "solana-compute-budget-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba64641b22efb6332088dc5892369a2f2049f83e66459ea300a4fc74a7a9f84" +checksum = "ca100b2bdd7e455f5f0b9791bc204dacd684a0373ad1032697dbad43f34e527f" dependencies = [ "solana-program-runtime", "solana-sdk", @@ -3632,9 +3846,9 @@ dependencies = [ [[package]] name = "solana-config-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04b91ca968a63946e7513a1de20188e6e917f09136339ee3bec247aa0e985d36" +checksum = "970d28779e92a11e32a89ee453edc7d89394d3a68d8c4b75ef0ffb833944c588" dependencies = [ "bincode", "chrono", @@ -3646,15 +3860,15 @@ dependencies = [ [[package]] name = "solana-connection-cache" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a850c0122f094efb83df00ab080ab6ace0dcd8dbf91240f91832157ee6d460" +checksum = "dd7d0022ded19dca32ced5528c6a050596877fc8b9a89322d876960a89466e1b" dependencies = [ "async-trait", "bincode", "crossbeam-channel", "futures-util", - "indexmap 2.1.0", + "indexmap 2.2.6", "log", "rand 0.8.5", "rayon", @@ -3668,9 +3882,9 @@ dependencies = [ [[package]] name = "solana-cost-model" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c6e08e5be41ab19c7906a6b6adf58172fd49ee042f8511a6c4e0155daa1b7c" +checksum = "dd3c63699df1680535daee8e486bd496e2ec849c427de4b6a42d4f1b27430949" dependencies = [ "lazy_static", "log", @@ -3692,17 +3906,13 @@ dependencies = [ [[package]] name = "solana-frozen-abi" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2c5e5dde22cac045d29675b3fefa84817e1f63b0b911d094c599e80c0c07d9" +checksum = "35a0b24cc4d0ebd5fd45d6bd47bed3790f8a75ade67af8ff24a3d719a8bc93bc" dependencies = [ - "ahash 0.8.6", - "blake3", "block-buffer 0.10.4", "bs58 0.4.0", "bv", - "byteorder", - "cc", "either", "generic-array", "im", @@ -3713,7 +3923,6 @@ dependencies = [ "serde", "serde_bytes", "serde_derive", - "serde_json", "sha2 0.10.8", "solana-frozen-abi-macro", "subtle", @@ -3722,21 +3931,21 @@ dependencies = [ [[package]] name = "solana-frozen-abi-macro" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "296e4cf0e2479e4c21afe4d17e32526f71f1bcd93b1c7c660900bc3e4233447a" +checksum = "51600f4066d3663ab2981fd24e77a8c2e65f5d20ea71b550b853ca9ae40eee7f" dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] name = "solana-loader-v4-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a902445f0bdf610e7eec94dab7f4a8e756d001c17651647730f1efcb9d7af6fe" +checksum = "9c566ebf0da216efc70054bf2d6d06c16fe44b63402c6f3bb04f6a88d8571d9b" dependencies = [ "log", "solana-measure", @@ -3747,9 +3956,9 @@ dependencies = [ [[package]] name = "solana-logger" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37a1b1a383a01039afbc6447a1712fb2a1a73a5ba8916762e693e8e492fabf3" +checksum = "dd79ef26804612173c95be8da84df3128d648173cf1f746de8f183ec8dbedd92" dependencies = [ "env_logger", "lazy_static", @@ -3758,9 +3967,9 @@ dependencies = [ [[package]] name = "solana-measure" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19831a93d760205f5c3e20d05a37b0e533caa1889e48041648ad0859e68ec336" +checksum = "300f716a5f1c2f4b562fb008a0cc7d7c0d889cff802a7f8177fdf28772ae1ed9" dependencies = [ "log", "solana-sdk", @@ -3768,9 +3977,9 @@ dependencies = [ [[package]] name = "solana-metrics" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63c23a8db755b2903262ad473e32cbf0093e2d3a0a7b8183d797a182c08326a" +checksum = "abf1705d52e4f123856725e1b3842cd4928b954ff62391a95af142a5adc58ac6" dependencies = [ "crossbeam-channel", "gethostname", @@ -3783,9 +3992,9 @@ dependencies = [ [[package]] name = "solana-net-utils" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ac1afc7feb590b45fd72bee0ca4c4f24b2386184d7e00d9f0d17913655bb4a" +checksum = "b1f2634fd50743e2ca075e663e07b0bd5c2f94db0ac320ce5bc2022e0002d82d" dependencies = [ "bincode", "clap 3.2.25", @@ -3804,12 +4013,18 @@ dependencies = [ ] [[package]] -name = "solana-perf" -version = "1.17.14" +name = "solana-nohash-hasher" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdf5a429e018e8ba693f4c43f833192db421fe97b88dfaf97041aa258e4b191" +checksum = "8b8a731ed60e89177c8a7ab05fe0f1511cedd3e70e773f288f9de33a9cfdc21e" + +[[package]] +name = "solana-perf" +version = "1.18.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0264d7093d44c239d9eb41beb6877b7b1eea5ad8809c93c1d9ab0c840ba390" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "bincode", "bv", "caps", @@ -3834,9 +4049,9 @@ dependencies = [ [[package]] name = "solana-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3a3b9623f09e2c480b4e129c92d7a036f8614fd0fc7519791bd44e64061ce8" +checksum = "2a5513a02d622ba89e76baf4b49d25ae20c2c2c623fced12b0d6dd7b8f23e006" dependencies = [ "ark-bn254", "ark-ec", @@ -3844,10 +4059,11 @@ dependencies = [ "ark-serialize", "base64 0.21.7", "bincode", - "bitflags 2.4.1", + "bitflags 2.5.0", "blake3", "borsh 0.10.3", "borsh 0.9.3", + "borsh 1.4.0", "bs58 0.4.0", "bv", "bytemuck", @@ -3855,7 +4071,7 @@ dependencies = [ "console_error_panic_hook", "console_log", "curve25519-dalek", - "getrandom 0.2.11", + "getrandom 0.2.14", "itertools", "js-sys", "lazy_static", @@ -3863,9 +4079,9 @@ dependencies = [ "libsecp256k1", "light-poseidon", "log", - "memoffset 0.9.0", + "memoffset 0.9.1", "num-bigint 0.4.4", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "parking_lot", "rand 0.8.5", @@ -3888,9 +4104,9 @@ dependencies = [ [[package]] name = "solana-program-runtime" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5dbb56d36cc15b4cf5a71c0ce6262a263212f7a312b0dbc41b226654329c37" +checksum = "64dc9f666a8e4f93166ce58eea9dfbf275e5cad461b2f1bbfa06538718dc3212" dependencies = [ "base64 0.21.7", "bincode", @@ -3899,7 +4115,7 @@ dependencies = [ "itertools", "libc", "log", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "percentage", "rand 0.8.5", @@ -3916,9 +4132,9 @@ dependencies = [ [[package]] name = "solana-program-test" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61bbf119c35d4393702953e586b72053c3b80a92c781931cd412d53d2036475e" +checksum = "2760112327ffce892f6a21030f7c9e4b6da3ded8c8eadf1dbfffcb5a754c61db" dependencies = [ "assert_matches", "async-trait", @@ -3946,9 +4162,9 @@ dependencies = [ [[package]] name = "solana-pubsub-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c22290c0d296a6a250a8d5b680797f12138a81af9c403a6ce62bd3ddad307e6" +checksum = "5ffdcbdad685b87475a91909fdb442d2edfabc2870110580c7f0cf7eb7883f97" dependencies = [ "crossbeam-channel", "futures-util", @@ -3971,9 +4187,9 @@ dependencies = [ [[package]] name = "solana-quic-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f924d8722f9e910d790678a79c2a0bfed786dffe1aefa5d769f8548679794263" +checksum = "056e909037b05097d2ff0181cb7e3d26876d8dff6d50701463a61e990cf84afd" dependencies = [ "async-mutex", "async-trait", @@ -3998,9 +4214,9 @@ dependencies = [ [[package]] name = "solana-rayon-threadlimit" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0a2e484e5b272690ac1431a6821f2b5180149d67c56934d9e007224ced15d0" +checksum = "e93a5e1ef891dca2cca907f7196b6a5d3b80af4183f2be0f981906b16711ff5d" dependencies = [ "lazy_static", "num_cpus", @@ -4008,14 +4224,14 @@ dependencies = [ [[package]] name = "solana-remote-wallet" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb9a96d1c001d07a0abb08e05b92ff6528b2d9239d03c57f99f738527839eb12" +checksum = "52c06eaf47d9a98ba22e890e68868f5d48c91e01268c541a53b5960288b617d6" dependencies = [ "console", "dialoguer", "log", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "parking_lot", "qstring", @@ -4027,9 +4243,9 @@ dependencies = [ [[package]] name = "solana-rpc-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91503edfdb2ba9c5e0127048e7795f22e050cf2bcee1259361af113d533b4b26" +checksum = "ed1d4b6f1f4e3dab7509401e85edc1c1ac208c61819de90178e01cf162c9c051" dependencies = [ "async-trait", "base64 0.21.7", @@ -4053,9 +4269,9 @@ dependencies = [ [[package]] name = "solana-rpc-client-api" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131662e5eea4fa5fc88b01f07d9e430315c0976be848ba3994244249c5fb033a" +checksum = "a31feddef24d3e0aab189571adea7f109639ef6179fcd3cd34ffc8c73d3409f1" dependencies = [ "base64 0.21.7", "bs58 0.4.0", @@ -4075,9 +4291,9 @@ dependencies = [ [[package]] name = "solana-rpc-client-nonce-utils" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67cdff955b9994ae240f6f287420c6727a581120c02ccc4f2fa535886732a1d" +checksum = "1837728262063723c659e4b8c0acf0baa99cd38cb333511456465d2c9e654474" dependencies = [ "clap 2.34.0", "solana-clap-utils", @@ -4088,10 +4304,11 @@ dependencies = [ [[package]] name = "solana-runtime" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf63159e669f29065c9ff280c09f5b96139b00258502ee401338150fce78fed7" +checksum = "9a3480088ad0ffb701ada496f19754b4ff737e516c6b5f1231508e50ae2e0ea3" dependencies = [ + "aquamarine", "arrayref", "base64 0.21.7", "bincode", @@ -4105,7 +4322,6 @@ dependencies = [ "dir-diff", "flate2", "fnv", - "fs-err", "im", "index_list", "itertools", @@ -4114,11 +4330,12 @@ dependencies = [ "lru", "lz4", "memmap2", + "mockall", "modular-bitfield", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "num_cpus", - "num_enum 0.6.1", + "num_enum 0.7.2", "ouroboros", "percentage", "qualifier_attr", @@ -4129,7 +4346,6 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "siphasher", "solana-accounts-db", "solana-address-lookup-table-program", "solana-bpf-loader-program", @@ -4154,8 +4370,8 @@ dependencies = [ "solana-zk-token-proof-program", "solana-zk-token-sdk", "static_assertions", - "strum", - "strum_macros", + "strum 0.24.1", + "strum_macros 0.24.3", "symlink", "tar", "tempfile", @@ -4165,15 +4381,15 @@ dependencies = [ [[package]] name = "solana-sdk" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb34583922c5e79004ad8d8d69f333d274d21b614f0e1a575f325fc29a104ec2" +checksum = "8f50cac89269a01235f6b421bc580132191f4df388f4265513e78fd00cf864dd" dependencies = [ "assert_matches", "base64 0.21.7", "bincode", - "bitflags 2.4.1", - "borsh 0.10.3", + "bitflags 2.5.0", + "borsh 1.4.0", "bs58 0.4.0", "bytemuck", "byteorder", @@ -4190,9 +4406,9 @@ dependencies = [ "libsecp256k1", "log", "memmap2", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", - "num_enum 0.6.1", + "num_enum 0.7.2", "pbkdf2 0.11.0", "qstring", "qualifier_attr", @@ -4207,6 +4423,7 @@ dependencies = [ "serde_with", "sha2 0.10.8", "sha3 0.10.8", + "siphasher", "solana-frozen-abi", "solana-frozen-abi-macro", "solana-logger", @@ -4219,15 +4436,15 @@ dependencies = [ [[package]] name = "solana-sdk-macro" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f58786e949f43b8c9b826fdfa5ad8586634b077ab04f989fb8e30535786712" +checksum = "5cb099b2f9c0a65a6f23ced791325141cd68c27b04d11c04fef838a00f613861" dependencies = [ "bs58 0.4.0", "proc-macro2", "quote", "rustversion", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -4238,9 +4455,9 @@ checksum = "468aa43b7edb1f9b7b7b686d5c3aeb6630dc1708e86e31343499dd5c4d775183" [[package]] name = "solana-send-transaction-service" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9987eccfe96b38785d95840277da4238de4f01166d0c32ec9bbfc5a319f4a530" +checksum = "0deed4fe8bb31ff178d8b7e8295bc81e6e1d704fc0e2c5565f58d9eb8feec89d" dependencies = [ "crossbeam-channel", "log", @@ -4254,9 +4471,9 @@ dependencies = [ [[package]] name = "solana-stake-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab247c866dab350bf610df8e1fab97ae0a0519cb81914348d382eac9e80940d" +checksum = "4ea02d44b82ed0eb271871cf8a1b8179a0ab50f4f995e7d8ae691c1971bd0a0e" dependencies = [ "bincode", "log", @@ -4269,16 +4486,16 @@ dependencies = [ [[package]] name = "solana-streamer" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efe4c33e0f68ea7a3701650badf6753b85fef2100cac6bc187c8e443e61c53da" +checksum = "f8a20843e8370adb3c04f47caa79ffdc92ae1bf078ad26530be1bca5d7bdd5d2" dependencies = [ "async-channel", "bytes", "crossbeam-channel", "futures-util", "histogram", - "indexmap 2.1.0", + "indexmap 2.2.6", "itertools", "libc", "log", @@ -4291,6 +4508,7 @@ dependencies = [ "rand 0.8.5", "rcgen", "rustls", + "smallvec", "solana-metrics", "solana-perf", "solana-sdk", @@ -4301,9 +4519,9 @@ dependencies = [ [[package]] name = "solana-system-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24147d17f13bef6548d15a7fc63eb8a3271523f7ffc91f13032944b0dc34f974" +checksum = "01294e45b407b7d4c8ff546af6f60344efd6591cf162c88e231ee3ba2c544672" dependencies = [ "bincode", "log", @@ -4315,9 +4533,9 @@ dependencies = [ [[package]] name = "solana-thin-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e782aabf9443a36d65e74d70ce732cc844707a5fec5a498bcbd81d3de7598c" +checksum = "c74da8f36b89b28c47e5ba3bad5279ff3dfea5829154882845d4821fc76ff497" dependencies = [ "bincode", "log", @@ -4330,14 +4548,14 @@ dependencies = [ [[package]] name = "solana-tpu-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980bee30cbfe3c51f973da7fdcccb9df2c2d9b9175c06066b293499e02108fd4" +checksum = "d0f2fd4b4aeffa14b9c5be9913072ea8e72ca261254a65a999f3d2fd70e7a660" dependencies = [ "async-trait", "bincode", "futures-util", - "indexmap 2.1.0", + "indexmap 2.2.6", "indicatif", "log", "rayon", @@ -4354,9 +4572,9 @@ dependencies = [ [[package]] name = "solana-transaction-status" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c180013e406418d593ce7b51da7007a638ace18261de14901b090e53a1d7025" +checksum = "3efa0d30f78dbc74e795638b053dd6ec7230739301e7f0e06b586f7731fd25c8" dependencies = [ "Inflector", "base64 0.21.7", @@ -4379,9 +4597,9 @@ dependencies = [ [[package]] name = "solana-udp-client" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab995970a424c89b7966a01aec90cdf1685c49aacf38a5f463200fc273a7d86b" +checksum = "32af58cadd37be19d04e0f3877104b8640bccc4be8ca1dbf431549b399b784c2" dependencies = [ "async-trait", "solana-connection-cache", @@ -4394,9 +4612,9 @@ dependencies = [ [[package]] name = "solana-version" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32cc394aa7132ab7f270801b98bf47fa585ab93f1038e5be27e480d7b5b2dca" +checksum = "42c7cef8aa9f1c633bf09dd91b8e635b6b30c40236652031b1800b245dc1bd02" dependencies = [ "log", "rustc_version", @@ -4410,9 +4628,9 @@ dependencies = [ [[package]] name = "solana-vote" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6092058284f0e02274177c45a22032eb7288aa4f6f8003ed469b1a562cac3bd" +checksum = "12945ee508c751ffdce58f976be6e58a05529ce0032c1f7db76eed6a8d76b33c" dependencies = [ "crossbeam-channel", "itertools", @@ -4429,13 +4647,13 @@ dependencies = [ [[package]] name = "solana-vote-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589cad4dccb4392e23f5ae4ccdd1f0aaa10f2823b264b27c4feb6382f40f4fd4" +checksum = "725a39044d455c08fe83fca758e94e5ddfaa25f6e2e2cfd5c31d7afdcad8de38" dependencies = [ "bincode", "log", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "rustc_version", "serde", @@ -4451,12 +4669,12 @@ dependencies = [ [[package]] name = "solana-zk-token-proof-program" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6b02ddeb2ab414b513b523aa678fac81109214f08d5c080165c15483a22cce" +checksum = "39263f3e47a160b9b67896f2225d56e6872905c066152cbe61f5fd201c52a6d2" dependencies = [ "bytemuck", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "solana-program-runtime", "solana-sdk", @@ -4465,9 +4683,9 @@ dependencies = [ [[package]] name = "solana-zk-token-sdk" -version = "1.17.14" +version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d932d7b13a223a6c1068d7061df7e9d2de14bfc0a874350eef19d59086b04a" +checksum = "630dc0b5f6250cf6a4c8b2bd3895283738915e83eba5453db20bb02b2527f302" dependencies = [ "aes-gcm-siv", "base64 0.21.7", @@ -4479,7 +4697,7 @@ dependencies = [ "itertools", "lazy_static", "merlin", - "num-derive 0.3.3", + "num-derive 0.4.2", "num-traits", "rand 0.7.3", "serde", @@ -4541,7 +4759,7 @@ checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" dependencies = [ "assert_matches", "borsh 0.10.3", - "num-derive 0.4.1", + "num-derive 0.4.2", "num-traits", "solana-program", "spl-token", @@ -4551,9 +4769,9 @@ dependencies = [ [[package]] name = "spl-discriminator" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce5d563b58ef1bb2cdbbfe0dfb9ffdc24903b10ae6a4df2d8f425ece375033f" +checksum = "daa600f2fe56f32e923261719bae640d873edadbc5237681a39b8e37bfd4d263" dependencies = [ "bytemuck", "solana-program", @@ -4562,25 +4780,25 @@ dependencies = [ [[package]] name = "spl-discriminator-derive" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadbefec4f3c678215ca72bd71862697bb06b41fd77c0088902dd3203354387b" +checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" dependencies = [ "quote", "spl-discriminator-syn", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] name = "spl-discriminator-syn" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e5f2044ca42c8938d54d1255ce599c79a1ffd86b677dfab695caa20f9ffc3f2" +checksum = "18fea7be851bd98d10721782ea958097c03a0c2a07d8d4997041d0ece6319a63" dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.48", + "syn 2.0.60", "thiserror", ] @@ -4595,9 +4813,9 @@ dependencies = [ [[package]] name = "spl-pod" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079" +checksum = "85a5db7e4efb1107b0b8e52a13f035437cdcb36ef99c58f6d467f089d9b2915a" dependencies = [ "borsh 0.10.3", "bytemuck", @@ -4608,11 +4826,11 @@ dependencies = [ [[package]] name = "spl-program-error" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "249e0318493b6bcf27ae9902600566c689b7dfba9f1bdff5893e92253374e78c" +checksum = "7e0657b6490196971d9e729520ba934911ff41fbb2cb9004463dbe23cf8b4b4f" dependencies = [ - "num-derive 0.4.1", + "num-derive 0.4.2", "num-traits", "solana-program", "spl-program-error-derive", @@ -4621,21 +4839,21 @@ dependencies = [ [[package]] name = "spl-program-error-derive" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5269c8e868da17b6552ef35a51355a017bd8e0eae269c201fef830d35fa52c" +checksum = "1845dfe71fd68f70382232742e758557afe973ae19e6c06807b2c30f5d5cb474" dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] name = "spl-tlv-account-resolution" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f7020347c07892c08560d230fbb8a980316c9e198e22b198b7b9d951ff96047" +checksum = "56f335787add7fa711819f9e7c573f8145a5358a709446fe2d24bf2a88117c90" dependencies = [ "bytemuck", "solana-program", @@ -4668,7 +4886,7 @@ checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059" dependencies = [ "arrayref", "bytemuck", - "num-derive 0.4.1", + "num-derive 0.4.2", "num-traits", "num_enum 0.7.2", "solana-program", @@ -4729,9 +4947,9 @@ dependencies = [ [[package]] name = "spl-type-length-value" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a468e6f6371f9c69aae760186ea9f1a01c2908351b06a5e0026d21cfc4d7ecac" +checksum = "8f9ebd75d29c5f48de5f6a9c114e08531030b75b8ac2c557600ac7da0b73b1e8" dependencies = [ "bytemuck", "solana-program", @@ -4764,7 +4982,16 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ - "strum_macros", + "strum_macros 0.24.3", +] + +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros 0.26.2", ] [[package]] @@ -4780,6 +5007,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "strum_macros" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.60", +] + [[package]] name = "subtle" version = "2.4.1" @@ -4805,15 +5045,33 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "synstructure" version = "0.12.6" @@ -4895,26 +5153,31 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", "windows-sys 0.52.0", ] [[package]] name = "termcolor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "test-case" version = "3.3.1" @@ -4933,7 +5196,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -4944,7 +5207,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", "test-case-core", ] @@ -4959,35 +5222,35 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -4995,12 +5258,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -5015,10 +5279,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -5058,9 +5323,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -5083,7 +5348,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -5114,9 +5379,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -5135,7 +5400,7 @@ dependencies = [ "tokio", "tokio-rustls", "tungstenite", - "webpki-roots 0.25.3", + "webpki-roots 0.25.4", ] [[package]] @@ -5178,9 +5443,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" [[package]] name = "toml_edit" @@ -5188,18 +5453,18 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.6", "toml_datetime", "winnow", ] [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.1.0", + "indexmap 2.2.6", "toml_datetime", "winnow", ] @@ -5230,7 +5495,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -5302,9 +5567,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -5314,9 +5579,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -5417,9 +5682,9 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -5448,9 +5713,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -5458,24 +5723,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -5485,9 +5750,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5495,28 +5760,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -5533,9 +5798,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.3" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "winapi" @@ -5555,11 +5820,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -5574,7 +5839,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.5", ] [[package]] @@ -5592,7 +5857,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.5", ] [[package]] @@ -5612,17 +5877,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -5633,9 +5899,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -5645,9 +5911,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -5657,9 +5923,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -5669,9 +5941,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -5681,9 +5953,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -5693,9 +5965,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -5705,15 +5977,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.5.31" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] @@ -5748,9 +6020,9 @@ dependencies = [ [[package]] name = "xattr" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914566e6413e7fa959cc394fb30e563ba80f3541fbd40816d4c05a0fc3f2a0f1" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", "linux-raw-sys", @@ -5783,7 +6055,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -5803,7 +6075,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.60", ] [[package]] @@ -5827,9 +6099,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index c74fce2..3590b23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ default = [] [dependencies] bs58 = "0.5.0" bytemuck = "1.14.3" +drillx = { git = "https://github.com/hardhatchad/drillx", branch = "master" } mpl-token-metadata = "4.1.2" num_enum = "0.7.2" shank = "0.3.0" diff --git a/src/consts.rs b/src/consts.rs index 1a2916b..7950180 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,8 +1,5 @@ use solana_program::{keccak::Hash, pubkey, pubkey::Pubkey}; -/// The unix timestamp after which mining can begin. -pub const START_AT: i64 = 1712070600; - /// The reward rate to intialize the program with. pub const INITIAL_REWARD_RATE: u64 = 10u64.pow(3u32); @@ -40,6 +37,9 @@ pub const BUS_COUNT: usize = 8; /// than a factor of this constant from one epoch to the next. pub const SMOOTHING_FACTOR: u64 = 2; +/// The range of difficulties miners can target above the minimum. +pub const DIFFICULTY_RANGE: usize = 32; + // Assert MAX_EPOCH_REWARDS is evenly divisible by BUS_COUNT. static_assertions::const_assert!( (MAX_EPOCH_REWARDS / BUS_COUNT as u64) * BUS_COUNT as u64 == MAX_EPOCH_REWARDS @@ -96,6 +96,9 @@ pub const CONFIG_ADDRESS: Pubkey = pubkey!("FTap9fv2GPpWGqrLj3o4c9nHH7p36ih7NbSW /// The address of the mint metadata account. pub const METADATA_ADDRESS: Pubkey = pubkey!("2nXZSxfjELuRatcoY64yHdFLZFi3mtesxobHmsoU3Dag"); +/// The address of the mint metadata account. +pub const NOISE_ADDRESS: Pubkey = pubkey!("2nXZSxfjELuRatcoY64yHdFLZFi3mtesxobHmsoU3Dag"); + /// The address of the mint account. pub const MINT_ADDRESS: Pubkey = pubkey!("oreoN2tQbHXVaZsr3pf66A48miqcBXCDJozganhEJgz"); diff --git a/src/error.rs b/src/error.rs index 63a657d..7bf36f4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,18 +5,20 @@ use thiserror::Error; #[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)] #[repr(u32)] pub enum OreError { - #[error("The starting time has not passed yet")] - NotStarted = 0, + #[error("Mining is paused")] + IsPaused = 0, #[error("The epoch has ended and needs reset")] NeedsReset = 1, #[error("The epoch is active and cannot be reset at this time")] ResetTooEarly = 2, - #[error("The provided hash was invalid")] - HashInvalid = 3, + #[error("The provided hash did not satisfy the minimum required difficulty")] + DifficultyInsufficient = 3, #[error("The bus does not have enough rewards to issue at this time")] BusRewardsInsufficient = 4, #[error("The claim amount cannot be greater than the claimable rewards")] ClaimTooLarge = 5, + #[error("The clock time is invalid")] + ClockInvalid = 6, } impl From for ProgramError { diff --git a/src/instruction.rs b/src/instruction.rs index 041e254..e2555ba 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -8,19 +8,10 @@ use solana_program::{ }; use crate::{ - impl_instruction_from_bytes, impl_to_bytes, state::Hash, BUS, CONFIG, CONFIG_ADDRESS, METADATA, - MINT, MINT_ADDRESS, MINT_NOISE, PROOF, TREASURY, TREASURY_ADDRESS, + impl_instruction_from_bytes, impl_to_bytes, BUS, CONFIG, CONFIG_ADDRESS, METADATA, MINT, + MINT_ADDRESS, MINT_NOISE, PROOF, TREASURY, TREASURY_ADDRESS, }; -// TODO Stake -// TODO Unstake - -// TODO Upgrade (v1 to v2 token) -// TODO Downgrade (v2 to v1 token) - -// TODO Personalized difficulty -// TODO Payout rewards based on multiplier and difficulty setting - #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, ShankInstruction, TryFromPrimitive)] #[rustfmt::skip] @@ -138,12 +129,6 @@ pub struct UpdateAdminArgs { pub new_admin: Pubkey, } -#[repr(C)] -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct UpdateDifficultyArgs { - pub new_difficulty: Hash, -} - impl_to_bytes!(InitializeArgs); impl_to_bytes!(RegisterArgs); impl_to_bytes!(MineArgs); diff --git a/src/lib.rs b/src/lib.rs index dca6482..80cd215 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,12 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; +// TODO Stake +// TODO Claim + +// TODO Upgrade (v1 to v2 token) +// TODO Downgrade (v2 to v1 token) + declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); #[cfg(not(feature = "no-entrypoint"))] diff --git a/src/loaders.rs b/src/loaders.rs index aa7b20d..cbf034e 100644 --- a/src/loaders.rs +++ b/src/loaders.rs @@ -7,9 +7,11 @@ use spl_token::state::Mint; use crate::{ state::{Bus, Config, Proof, Treasury}, utils::AccountDeserialize, - BUS_ADDRESSES, BUS_COUNT, CONFIG_ADDRESS, MINT_ADDRESS, TREASURY_ADDRESS, + BUS_ADDRESSES, BUS_COUNT, CONFIG_ADDRESS, MINT_ADDRESS, NOISE_ADDRESS, TREASURY_ADDRESS, }; +// TODO Account checks don't need to deserialize the whole byte array. They can just check the type byte + /// Errors if: /// - Account is not a signer. pub fn load_signer<'a, 'info>(info: &'a AccountInfo<'info>) -> Result<(), ProgramError> { @@ -127,6 +129,30 @@ pub fn load_config<'a, 'info>( Ok(()) } +/// asdf +pub fn load_noise<'a, 'info>( + info: &'a AccountInfo<'info>, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&crate::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.key.ne(&NOISE_ADDRESS) { + return Err(ProgramError::InvalidSeeds); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + /// Errors if: /// - Owner is not Ore program. /// - Data is empty. diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index dfcb3ca..09908b3 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -17,8 +17,8 @@ use crate::{ utils::create_pda, utils::AccountDeserialize, utils::Discriminator, - BUS, BUS_COUNT, CONFIG, INITIAL_DIFFICULTY, INITIAL_REWARD_RATE, METADATA, METADATA_NAME, - METADATA_SYMBOL, METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, + BUS, BUS_COUNT, CONFIG, METADATA, METADATA_NAME, METADATA_SYMBOL, METADATA_URI, MINT, + MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, }; /// Initialize sets up the Ore program. Its responsibilities include: @@ -131,7 +131,7 @@ pub fn process_initialize<'a, 'info>( config_data[0] = Config::discriminator() as u8; let config = Config::try_from_bytes_mut(&mut config_data)?; config.admin = *signer.key; - config.difficulty = INITIAL_DIFFICULTY.into(); + // config.difficulty = INITIAL_DIFFICULTY.into(); // Initialize treasury create_pda( @@ -146,8 +146,8 @@ pub fn process_initialize<'a, 'info>( treasury_data[0] = Treasury::discriminator() as u8; let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; treasury.bump = args.treasury_bump as u64; - treasury.last_reset_at = 0; - treasury.reward_rate = INITIAL_REWARD_RATE; + // treasury.last_reset_at = 0; + // treasury.reward_rate = INITIAL_REWARD_RATE; treasury.total_claimed_rewards = 0; drop(treasury_data); diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 6a15a68..0fb38ab 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -4,8 +4,7 @@ use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, - keccak::{hashv, Hash as KeccakHash}, - program::set_return_data, + keccak::hashv, program_error::ProgramError, pubkey::Pubkey, slot_hashes::SlotHash, @@ -16,24 +15,26 @@ use crate::{ error::OreError, instruction::MineArgs, loaders::*, - state::{Bus, Proof, Treasury}, + state::{Bus, Config, Proof}, utils::AccountDeserialize, - EPOCH_DURATION, START_AT, + DIFFICULTY_RANGE, EPOCH_DURATION, }; +// TODO Look into tx introspection to require 1 hash per tx + /// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: -/// 1. Verify the provided hash is valid. -/// 2. Increment the user's claimable rewards counter. +/// 1. Calculate the hash from the provided nonce. +/// 2. Payout rewards based on difficulty, staking multiplier, and liveness penalty. /// 3. Generate a new challenge for the miner. /// 4. Update the miner's lifetime stats. /// /// Safety requirements: /// - Mine is a permissionless instruction and can be called by any signer. -/// - Can only succeed if START_AT has passed. +/// - Can only succeed if mining is not paused. /// - Can only succeed if the last reset was less than 60 seconds ago. -/// - Can only succeed if the provided SHA3 hash and nonce are valid and satisfy the difficulty. +/// - Can only succeed if the provided hash satisfies the minimum difficulty requirement. /// - The the provided proof account must be associated with the signer. -/// - The provided bus, treasury, and slot hash sysvar must be valid. +/// - The provided bus, config, noise, stake, and slot hash sysvar must be valid. pub fn process_mine<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], @@ -43,115 +44,118 @@ pub fn process_mine<'a, 'info>( let args = MineArgs::try_from_bytes(data)?; // Load accounts - let [signer, bus_info, proof_info, treasury_info, slot_hashes_info] = accounts else { + let [signer, bus_info, config_info, noise_info, proof_info, slot_hashes_info] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; load_any_bus(bus_info, true)?; + load_config(config_info, false)?; + load_noise(noise_info, false)?; load_proof(proof_info, signer.key, true)?; - load_treasury(treasury_info, false)?; load_sysvar(slot_hashes_info, sysvar::slot_hashes::id())?; - // Validate mining has starting + // Validate mining is allowed + let config_data = config_info.data.borrow(); + let config = Config::try_from_bytes(&config_data)?; + if config.paused.ne(&0) { + return Err(OreError::IsPaused.into()); + } + + // Validate the clock state let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; - if clock.unix_timestamp.lt(&START_AT) { - return Err(OreError::NotStarted.into()); + let mut proof_data = proof_info.data.borrow_mut(); + let proof = Proof::try_from_bytes_mut(&mut proof_data)?; + if clock.unix_timestamp.lt(&proof.last_hash_at) { + return Err(OreError::ClockInvalid.into()); } // Validate epoch is active - let treasury_data = treasury_info.data.borrow(); - let treasury = Treasury::try_from_bytes(&treasury_data)?; - let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION); - if clock.unix_timestamp.ge(&threshold) { - return Err(OreError::NeedsReset.into()); + // let treasury_data = treasury_info.data.borrow(); + // let treasury = Treasury::try_from_bytes(&treasury_data)?; + // let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION); + // if clock.unix_timestamp.ge(&threshold) { + // return Err(OreError::NeedsReset.into()); + // } + + // Calculate the hash from the provided nonce + let noise_data = noise_info.data.borrow(); + let hx = drillx::hash(&proof.hash, &args.nonce, &noise_data); + drop(noise_data); + + // Validate hash satisfies the minimnum difficulty + let difficulty = drillx::difficulty(hx); + if difficulty.le(&config.min_difficulty) { + return Err(OreError::DifficultyInsufficient.into()); } - // Validate provided hash - let mut proof_data = proof_info.data.borrow_mut(); - let proof = Proof::try_from_bytes_mut(&mut proof_data)?; - // let hash = validate_hash( - // proof.hash.into(), - // *signer.key, - // u64::from_le_bytes(args.nonce), - // proof.difficulty.into(), - // )?; + // Calculate base reward rate + let difficulty = difficulty + .saturating_sub(config.min_difficulty) + .min(DIFFICULTY_RANGE as u32); + let mut reward = config + .base_reward_rate + .saturating_mul(2u64.saturating_pow(difficulty)); - // TODO Calculate reward based on difficulty - // TODO Calculate rewards multiplier - // TODO Calculate reward payout amount + // Apply staking multiplier + if clock.slot.gt(&proof.last_deposit_slot) { + // Only apply if last deposit was at least 1 block ago to prevent flash loan attacks. + // TODO Cleanup math with a const here (unnecessary cus) + // TODO Maybe move const into config!? + let max_stake = reward + .saturating_mul(60) // min/hour + .saturating_mul(24) // hour/day + .saturating_mul(365) // day/year + .saturating_mul(2); // year + let staking_reward = proof + .balance + .min(max_stake) + .saturating_mul(reward) + .saturating_div(max_stake); + reward = reward.saturating_add(staking_reward); + } - // Update claimable rewards + // Apply liveness penalty + // TODO Should penalty be symmetric? + // TODO Or should the curve be steeper on the <1 min side? + // TODO Eg anything more frequent than 40 seconds should get 0 + // TODO Anything longer than 2 minutes should be 0 + let tolerance = 5i64; // TODO Get from config + let target_time = proof.last_hash_at.saturating_add(EPOCH_DURATION); + if clock + .unix_timestamp + .saturating_sub(target_time) + .abs() + .gt(&tolerance) + { + // TODO Apply + } + + // Update balances let mut bus_data = bus_info.data.borrow_mut(); let bus = Bus::try_from_bytes_mut(&mut bus_data)?; bus.rewards = bus .rewards - .checked_sub(treasury.reward_rate) + .checked_sub(reward) .ok_or(OreError::BusRewardsInsufficient)?; - proof.balance = proof.balance.saturating_add(treasury.reward_rate); + proof.balance = proof.balance.saturating_add(reward); // Hash recent slot hash into the next challenge to prevent pre-mining attacks proof.hash = hashv(&[ - // TODO - // hash.as_ref(), + hx.as_slice(), &slot_hashes_info.data.borrow()[0..size_of::()], ]) - .into(); + .0; + + // Update time trackers + proof.last_deposit_slot = clock.slot; + proof.last_hash_at = clock.unix_timestamp; // Update lifetime stats proof.total_hashes = proof.total_hashes.saturating_add(1); - proof.total_rewards = proof.total_rewards.saturating_add(treasury.reward_rate); + proof.total_rewards = proof.total_rewards.saturating_add(reward); // Log the mined rewards - set_return_data(treasury.reward_rate.to_le_bytes().as_slice()); + // set_return_data(reward.to_le_bytes().as_slice()); Ok(()) } - -/// Validates the provided hash, ensursing it is equal to SHA3(current_hash, singer, nonce). -/// Fails if the provided hash is valid but does not satisfy the required difficulty. -pub(crate) fn validate_hash( - current_hash: KeccakHash, - signer: Pubkey, - nonce: u64, - difficulty: KeccakHash, -) -> Result { - let hash = hashv(&[ - nonce.to_le_bytes().as_slice(), - current_hash.as_ref(), - signer.as_ref(), - ]); - if hash.gt(&difficulty) { - return Err(OreError::HashInvalid.into()); - } - Ok(hash) -} - -#[cfg(test)] -mod tests { - use solana_program::{ - keccak::{Hash, HASH_BYTES}, - pubkey::Pubkey, - }; - - use crate::validate_hash; - - #[test] - fn test_validate_hash_pass() { - let h1 = Hash::new_from_array([1; HASH_BYTES]); - let signer = Pubkey::new_unique(); - let nonce = 10u64; - let difficulty = Hash::new_from_array([255; HASH_BYTES]); - let res = validate_hash(h1, signer, nonce, difficulty); - assert!(res.is_ok()); - } - - #[test] - fn test_validate_hash_fail() { - let h1 = Hash::new_from_array([1; HASH_BYTES]); - let signer = Pubkey::new_unique(); - let nonce = 10u64; - let difficulty = Hash::new_from_array([0; HASH_BYTES]); - let res = validate_hash(h1, signer, nonce, difficulty); - assert!(res.is_err()); - } -} diff --git a/src/processor/register.rs b/src/processor/register.rs index 2c1d810..9890eaa 100644 --- a/src/processor/register.rs +++ b/src/processor/register.rs @@ -14,7 +14,9 @@ use crate::{ PROOF, }; -// TODO Create a stake account +// TODO Create a stake account (token account) +// TODO Need to keep claimable balance (from treasury) separate from staking balance (from stake account) +// TODO Multiplier calculations should account for both /// Register generates a new hash chain for a prospective miner. Its responsibilities include: /// 1. Initialize a new proof account. @@ -65,9 +67,8 @@ pub fn process_register<'a, 'info>( signer.key.as_ref(), &slot_hashes_info.data.borrow()[0..size_of::()], ]) - .into(); + .0; proof.last_hash_at = 0; - proof.multiplier = 1; // TODO proof.total_hashes = 0; proof.total_rewards = 0; diff --git a/src/processor/reset.rs b/src/processor/reset.rs index 68b961c..26db329 100644 --- a/src/processor/reset.rs +++ b/src/processor/reset.rs @@ -1,16 +1,6 @@ -use solana_program::{ - account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, - program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, -}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; -use crate::{ - error::OreError, - loaders::*, - state::{Bus, Treasury}, - utils::AccountDeserialize, - BUS_COUNT, BUS_EPOCH_REWARDS, EPOCH_DURATION, MAX_EPOCH_REWARDS, SMOOTHING_FACTOR, START_AT, - TARGET_EPOCH_REWARDS, TREASURY, -}; +use crate::{BUS_EPOCH_REWARDS, SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS}; /// Reset sets up the Ore program for the next epoch. Its responsibilities include: /// 1. Reset bus account rewards counters. @@ -33,85 +23,85 @@ pub fn process_reset<'a, 'info>( accounts: &'a [AccountInfo<'info>], _data: &[u8], ) -> ProgramResult { - // Load accounts - let [signer, bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, bus_7_info, mint_info, treasury_info, treasury_tokens_info, token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - load_signer(signer)?; - load_bus(bus_0_info, 0, true)?; - load_bus(bus_1_info, 1, true)?; - load_bus(bus_2_info, 2, true)?; - load_bus(bus_3_info, 3, true)?; - load_bus(bus_4_info, 4, true)?; - load_bus(bus_5_info, 5, true)?; - load_bus(bus_6_info, 6, true)?; - load_bus(bus_7_info, 7, true)?; - load_mint(mint_info, true)?; - load_treasury(treasury_info, true)?; - load_token_account( - treasury_tokens_info, - Some(treasury_info.key), - mint_info.key, - true, - )?; - load_program(token_program, spl_token::id())?; - let busses: [&AccountInfo; BUS_COUNT] = [ - bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, - bus_7_info, - ]; + // // Load accounts + // let [signer, bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, bus_7_info, mint_info, treasury_info, treasury_tokens_info, token_program] = + // accounts + // else { + // return Err(ProgramError::NotEnoughAccountKeys); + // }; + // load_signer(signer)?; + // load_bus(bus_0_info, 0, true)?; + // load_bus(bus_1_info, 1, true)?; + // load_bus(bus_2_info, 2, true)?; + // load_bus(bus_3_info, 3, true)?; + // load_bus(bus_4_info, 4, true)?; + // load_bus(bus_5_info, 5, true)?; + // load_bus(bus_6_info, 6, true)?; + // load_bus(bus_7_info, 7, true)?; + // load_mint(mint_info, true)?; + // load_treasury(treasury_info, true)?; + // load_token_account( + // treasury_tokens_info, + // Some(treasury_info.key), + // mint_info.key, + // true, + // )?; + // load_program(token_program, spl_token::id())?; + // let busses: [&AccountInfo; BUS_COUNT] = [ + // bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, + // bus_7_info, + // ]; - // Validate mining has starting - let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; - if clock.unix_timestamp.lt(&START_AT) { - return Err(OreError::NotStarted.into()); - } + // // Validate mining has starting + // let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; + // if clock.unix_timestamp.lt(&START_AT) { + // return Err(OreError::NotStarted.into()); + // } - // Validate at least 60 seconds have passed since last reset - let mut treasury_data = treasury_info.data.borrow_mut(); - let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; - let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION); - if clock.unix_timestamp.lt(&threshold) { - return Err(OreError::ResetTooEarly.into()); - } + // // Validate at least 60 seconds have passed since last reset + // let mut treasury_data = treasury_info.data.borrow_mut(); + // let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; + // let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION); + // if clock.unix_timestamp.lt(&threshold) { + // return Err(OreError::ResetTooEarly.into()); + // } - // Record current timestamp - treasury.last_reset_at = clock.unix_timestamp; + // // Record current timestamp + // treasury.last_reset_at = clock.unix_timestamp; - // Reset bus accounts and calculate actual rewards mined since last reset - let mut total_remaining_rewards = 0u64; - for i in 0..BUS_COUNT { - let mut bus_data = busses[i].data.borrow_mut(); - let bus = Bus::try_from_bytes_mut(&mut bus_data)?; - total_remaining_rewards = total_remaining_rewards.saturating_add(bus.rewards); - bus.rewards = BUS_EPOCH_REWARDS; - } - let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_remaining_rewards); + // // Reset bus accounts and calculate actual rewards mined since last reset + // let mut total_remaining_rewards = 0u64; + // for i in 0..BUS_COUNT { + // let mut bus_data = busses[i].data.borrow_mut(); + // let bus = Bus::try_from_bytes_mut(&mut bus_data)?; + // total_remaining_rewards = total_remaining_rewards.saturating_add(bus.rewards); + // bus.rewards = BUS_EPOCH_REWARDS; + // } + // let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_remaining_rewards); - // Update reward rate for next epoch - treasury.reward_rate = calculate_new_reward_rate(treasury.reward_rate, total_epoch_rewards); + // // Update reward rate for next epoch + // treasury.reward_rate = calculate_new_reward_rate(treasury.reward_rate, total_epoch_rewards); - // Fund treasury token account - let treasury_bump = treasury.bump as u8; - drop(treasury_data); - solana_program::program::invoke_signed( - &spl_token::instruction::mint_to( - &spl_token::id(), - mint_info.key, - treasury_tokens_info.key, - treasury_info.key, - &[treasury_info.key], - total_epoch_rewards, - )?, - &[ - token_program.clone(), - mint_info.clone(), - treasury_tokens_info.clone(), - treasury_info.clone(), - ], - &[&[TREASURY, &[treasury_bump]]], - )?; + // // Fund treasury token account + // let treasury_bump = treasury.bump as u8; + // drop(treasury_data); + // solana_program::program::invoke_signed( + // &spl_token::instruction::mint_to( + // &spl_token::id(), + // mint_info.key, + // treasury_tokens_info.key, + // treasury_info.key, + // &[treasury_info.key], + // total_epoch_rewards, + // )?, + // &[ + // token_program.clone(), + // mint_info.clone(), + // treasury_tokens_info.clone(), + // treasury_info.clone(), + // ], + // &[&[TREASURY, &[treasury_bump]]], + // )?; Ok(()) } diff --git a/src/processor/stake.rs b/src/processor/stake.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/processor/stake.rs @@ -0,0 +1 @@ + diff --git a/src/state/bus.rs b/src/state/bus.rs index b98bd51..849fe36 100644 --- a/src/state/bus.rs +++ b/src/state/bus.rs @@ -17,12 +17,6 @@ pub struct Bus { /// The quantity of rewards this bus can issue in the current epoch epoch. pub rewards: u64, - - /// Histogram of hash count per difficulty - pub hash_hist: [u64; 32], - - /// Cumulative sum of applied multipliers per difficulty - pub multiplier_hist: [u64; 32], } impl Discriminator for Bus { diff --git a/src/state/config.rs b/src/state/config.rs index c992c12..2020ac3 100644 --- a/src/state/config.rs +++ b/src/state/config.rs @@ -2,9 +2,10 @@ use bytemuck::{Pod, Zeroable}; use shank::ShankAccount; use solana_program::pubkey::Pubkey; +// TODO next_min_difficulty: Option, update on reset + use crate::{ impl_account_from_bytes, impl_to_bytes, - state::Hash, utils::{AccountDiscriminator, Discriminator}, }; @@ -15,8 +16,14 @@ pub struct Config { /// The admin authority with permission to update the difficulty. pub admin: Pubkey, - /// The hash difficulty. - pub difficulty: Hash, + /// The base reward rate paid out for a hash of minimum difficulty. + pub base_reward_rate: u64, + + /// The minimum accepted difficulty. + pub min_difficulty: u32, + + /// Is mining paused. + pub paused: u32, } impl Discriminator for Config { diff --git a/src/state/hash.rs b/src/state/hash.rs index 4c4f89c..dd19e38 100644 --- a/src/state/hash.rs +++ b/src/state/hash.rs @@ -30,4 +30,18 @@ impl fmt::Display for Hash { } } +impl Hash { + pub fn difficulty(&self) -> u32 { + let mut count = 0; + for &byte in &self.0 { + let lz = byte.leading_zeros(); + count += lz; + if lz < 8 { + break; + } + } + count + } +} + impl_to_bytes!(Hash); diff --git a/src/state/mod.rs b/src/state/mod.rs index b828008..6b681f5 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -1,11 +1,11 @@ mod bus; mod config; -mod hash; +// mod hash; mod proof; mod treasury; pub use bus::*; pub use config::*; -pub use hash::*; +// pub use hash::*; pub use proof::*; pub use treasury::*; diff --git a/src/state/proof.rs b/src/state/proof.rs index 8d3699c..24ea148 100644 --- a/src/state/proof.rs +++ b/src/state/proof.rs @@ -4,7 +4,6 @@ use solana_program::pubkey::Pubkey; use crate::{ impl_account_from_bytes, impl_to_bytes, - state::Hash, utils::{AccountDiscriminator, Discriminator}, }; @@ -16,18 +15,17 @@ pub struct Proof { /// The signer authorized to use this proof. pub authority: Pubkey, - /// The quantity of tokens this miner may claim from the treasury. + /// The quantity of tokens this miner has staked or earned. pub balance: u64, /// The proof's current hash. - pub hash: Hash, + pub hash: [u8; 32], + + /// The last slot ore was deposited into this account. + pub last_deposit_slot: u64, /// The last time this account provided a hash. - pub last_hash_at: u64, - - // TODO Figure out multiplier representation - /// The rewards multiplier for this account. - pub multiplier: u64, + pub last_hash_at: i64, /// The total lifetime hashes provided by this miner. pub total_hashes: u64, diff --git a/src/state/treasury.rs b/src/state/treasury.rs index 26ae195..61da711 100644 --- a/src/state/treasury.rs +++ b/src/state/treasury.rs @@ -14,12 +14,6 @@ pub struct Treasury { /// The bump of the treasury account PDA, for signing CPIs. pub bump: u64, - /// The timestamp of the reset invocation. - pub last_reset_at: i64, - - /// The reward rate to payout to miners for submiting valid hashes. - pub reward_rate: u64, - /// The total lifetime claimed rewards of the program. pub total_claimed_rewards: u64, } From b30cee7eb29c6cb13f0ead2229056ba37450fb6c Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 27 Apr 2024 18:06:36 +0000 Subject: [PATCH 005/111] stake --- src/error.rs | 4 ++- src/instruction.rs | 16 +++++++++ src/lib.rs | 4 +-- src/processor/claim.rs | 5 +-- src/processor/initialize.rs | 2 +- src/processor/mod.rs | 2 ++ src/processor/stake.rs | 66 +++++++++++++++++++++++++++++++++++++ src/state/treasury.rs | 3 -- 8 files changed, 90 insertions(+), 12 deletions(-) diff --git a/src/error.rs b/src/error.rs index 7bf36f4..423d18c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,8 +17,10 @@ pub enum OreError { BusRewardsInsufficient = 4, #[error("The claim amount cannot be greater than the claimable rewards")] ClaimTooLarge = 5, + #[error("The stake amount cannot exceed u64 size")] + StakeTooLarge = 6, #[error("The clock time is invalid")] - ClockInvalid = 6, + ClockInvalid = 7, } impl From for ProgramError { diff --git a/src/instruction.rs b/src/instruction.rs index e2555ba..c7d9bc8 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -55,6 +55,14 @@ pub enum OreInstruction { #[account(6, name = "token_program", desc = "SPL token program")] Claim = 3, + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "sender", desc = "Signer token account", writable)] + #[account(3, name = "proof", desc = "Ore proof account", writable)] + #[account(4, name = "treasury_tokens", desc = "Ore treasury token account", writable)] + #[account(5, name = "token_program", desc = "SPL token program")] + Stake = 4, + #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] #[account(2, name = "bus_0", desc = "Ore bus account 0", writable)] @@ -123,6 +131,12 @@ pub struct ClaimArgs { pub amount: [u8; 8], } +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct StakeArgs { + pub amount: [u8; 8], +} + #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct UpdateAdminArgs { @@ -133,12 +147,14 @@ impl_to_bytes!(InitializeArgs); impl_to_bytes!(RegisterArgs); impl_to_bytes!(MineArgs); impl_to_bytes!(ClaimArgs); +impl_to_bytes!(StakeArgs); impl_to_bytes!(UpdateAdminArgs); impl_instruction_from_bytes!(InitializeArgs); impl_instruction_from_bytes!(RegisterArgs); impl_instruction_from_bytes!(MineArgs); impl_instruction_from_bytes!(ClaimArgs); +impl_instruction_from_bytes!(StakeArgs); impl_instruction_from_bytes!(UpdateAdminArgs); /// Builds a reset instruction. diff --git a/src/lib.rs b/src/lib.rs index 80cd215..aced536 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,9 +14,6 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; -// TODO Stake -// TODO Claim - // TODO Upgrade (v1 to v2 token) // TODO Downgrade (v2 to v1 token) @@ -43,6 +40,7 @@ pub fn process_instruction( OreInstruction::Register => process_register(program_id, accounts, data)?, OreInstruction::Mine => process_mine(program_id, accounts, data)?, OreInstruction::Claim => process_claim(program_id, accounts, data)?, + OreInstruction::Stake => process_stake(program_id, accounts, data)?, OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, OreInstruction::UpdateAdmin => process_update_admin(program_id, accounts, data)?, } diff --git a/src/processor/claim.rs b/src/processor/claim.rs index 9ba5b29..9287b12 100644 --- a/src/processor/claim.rs +++ b/src/processor/claim.rs @@ -56,12 +56,9 @@ pub fn process_claim<'a, 'info>( .checked_sub(amount) .ok_or(OreError::ClaimTooLarge)?; - // Update lifetime status + // Distribute tokens from treasury to beneficiary let mut treasury_data = treasury_info.data.borrow_mut(); let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; - treasury.total_claimed_rewards = treasury.total_claimed_rewards.saturating_add(amount); - - // Distribute tokens from treasury to beneficiary let treasury_bump = treasury.bump; drop(treasury_data); solana_program::program::invoke_signed( diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 09908b3..2e6b23f 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -148,7 +148,7 @@ pub fn process_initialize<'a, 'info>( treasury.bump = args.treasury_bump as u64; // treasury.last_reset_at = 0; // treasury.reward_rate = INITIAL_REWARD_RATE; - treasury.total_claimed_rewards = 0; + // treasury.total_claimed_rewards = 0; drop(treasury_data); // Initialize mint diff --git a/src/processor/mod.rs b/src/processor/mod.rs index e5730a0..2410f76 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -3,6 +3,7 @@ mod initialize; mod mine; mod register; mod reset; +mod stake; mod update_admin; pub use claim::*; @@ -10,4 +11,5 @@ pub use initialize::*; pub use mine::*; pub use register::*; pub use reset::*; +pub use stake::*; pub use update_admin::*; diff --git a/src/processor/stake.rs b/src/processor/stake.rs index 8b13789..e3390a3 100644 --- a/src/processor/stake.rs +++ b/src/processor/stake.rs @@ -1 +1,67 @@ +use solana_program::{ + account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, +}; +use crate::{ + error::OreError, instruction::StakeArgs, loaders::*, state::Proof, utils::AccountDeserialize, + MINT_ADDRESS, TREASURY_ADDRESS, +}; + +pub fn process_stake<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + data: &[u8], +) -> ProgramResult { + // Parse args + let args = StakeArgs::try_from_bytes(data)?; + let amount = u64::from_le_bytes(args.amount); + + // Load accounts + let [signer_info, sender_info, proof_info, treasury_tokens_info, token_program] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer_info)?; + load_token_account(sender_info, Some(signer_info.key), &MINT_ADDRESS, true)?; + load_proof(proof_info, signer_info.key, true)?; + load_token_account( + treasury_tokens_info, + Some(&TREASURY_ADDRESS), + &MINT_ADDRESS, + true, + )?; + load_program(token_program, spl_token::id())?; + + // Update proof balance + let mut proof_data = proof_info.data.borrow_mut(); + let proof = Proof::try_from_bytes_mut(&mut proof_data)?; + proof.balance = proof + .balance + .checked_add(amount) + .ok_or(OreError::StakeTooLarge)?; + + // Update deposit timestamp + let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; + proof.last_deposit_slot = clock.slot; + + // Distribute tokens from signer to treasury + solana_program::program::invoke( + &spl_token::instruction::transfer( + &spl_token::id(), + sender_info.key, + treasury_tokens_info.key, + signer_info.key, + &[signer_info.key], + amount, + )?, + &[ + token_program.clone(), + sender_info.clone(), + treasury_tokens_info.clone(), + signer_info.clone(), + ], + )?; + + Ok(()) +} diff --git a/src/state/treasury.rs b/src/state/treasury.rs index 61da711..3ae7bc2 100644 --- a/src/state/treasury.rs +++ b/src/state/treasury.rs @@ -13,9 +13,6 @@ use crate::{ pub struct Treasury { /// The bump of the treasury account PDA, for signing CPIs. pub bump: u64, - - /// The total lifetime claimed rewards of the program. - pub total_claimed_rewards: u64, } impl Discriminator for Treasury { From c80e3ac5b3e9aeefc3a4602bf29b3f2920ec1313 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 27 Apr 2024 18:27:28 +0000 Subject: [PATCH 006/111] scaffold upgrade --- src/consts.rs | 3 ++ src/instruction.rs | 18 +++++++++ src/lib.rs | 1 + src/loaders.rs | 5 ++- src/processor/mod.rs | 2 + src/processor/upgrade.rs | 81 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 src/processor/upgrade.rs diff --git a/src/consts.rs b/src/consts.rs index 7950180..6680cb3 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -102,5 +102,8 @@ pub const NOISE_ADDRESS: Pubkey = pubkey!("2nXZSxfjELuRatcoY64yHdFLZFi3mtesxobHm /// The address of the mint account. pub const MINT_ADDRESS: Pubkey = pubkey!("oreoN2tQbHXVaZsr3pf66A48miqcBXCDJozganhEJgz"); +/// The address of the mint account. +pub const MINT_V1_ADDRESS: Pubkey = pubkey!("oreoN2tQbHXVaZsr3pf66A48miqcBXCDJozganhEJgz"); + /// The address of the treasury account. pub const TREASURY_ADDRESS: Pubkey = pubkey!("FTap9fv2GPpWGqrLj3o4c9nHH7p36ih7NbSWHnrkQYqa"); diff --git a/src/instruction.rs b/src/instruction.rs index c7d9bc8..5e49e49 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -63,6 +63,16 @@ pub enum OreInstruction { #[account(5, name = "token_program", desc = "SPL token program")] Stake = 4, + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "beneficiary", desc = "Beneficiary token account", writable)] + #[account(3, name = "sender", desc = "Signer token account", writable)] + #[account(4, name = "treasury", desc = "Ore treasury account", writable)] + #[account(5, name = "mint", desc = "Ore token mint account", writable)] + #[account(6, name = "mint_v1", desc = "Ore v1 token mint account", writable)] + #[account(7, name = "token_program", desc = "SPL token program")] + Upgrade = 5, + #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] #[account(2, name = "bus_0", desc = "Ore bus account 0", writable)] @@ -137,6 +147,12 @@ pub struct StakeArgs { pub amount: [u8; 8], } +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct UpgradeArgs { + pub amount: [u8; 8], +} + #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct UpdateAdminArgs { @@ -148,6 +164,7 @@ impl_to_bytes!(RegisterArgs); impl_to_bytes!(MineArgs); impl_to_bytes!(ClaimArgs); impl_to_bytes!(StakeArgs); +impl_to_bytes!(UpgradeArgs); impl_to_bytes!(UpdateAdminArgs); impl_instruction_from_bytes!(InitializeArgs); @@ -155,6 +172,7 @@ impl_instruction_from_bytes!(RegisterArgs); impl_instruction_from_bytes!(MineArgs); impl_instruction_from_bytes!(ClaimArgs); impl_instruction_from_bytes!(StakeArgs); +impl_instruction_from_bytes!(UpgradeArgs); impl_instruction_from_bytes!(UpdateAdminArgs); /// Builds a reset instruction. diff --git a/src/lib.rs b/src/lib.rs index aced536..c31df80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,6 +41,7 @@ pub fn process_instruction( OreInstruction::Mine => process_mine(program_id, accounts, data)?, OreInstruction::Claim => process_claim(program_id, accounts, data)?, OreInstruction::Stake => process_stake(program_id, accounts, data)?, + OreInstruction::Upgrade => process_upgrade(program_id, accounts, data)?, OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, OreInstruction::UpdateAdmin => process_update_admin(program_id, accounts, data)?, } diff --git a/src/loaders.rs b/src/loaders.rs index cbf034e..71c7ba3 100644 --- a/src/loaders.rs +++ b/src/loaders.rs @@ -7,7 +7,7 @@ use spl_token::state::Mint; use crate::{ state::{Bus, Config, Proof, Treasury}, utils::AccountDeserialize, - BUS_ADDRESSES, BUS_COUNT, CONFIG_ADDRESS, MINT_ADDRESS, NOISE_ADDRESS, TREASURY_ADDRESS, + BUS_ADDRESSES, BUS_COUNT, CONFIG_ADDRESS, NOISE_ADDRESS, TREASURY_ADDRESS, }; // TODO Account checks don't need to deserialize the whole byte array. They can just check the type byte @@ -226,13 +226,14 @@ pub fn load_treasury<'a, 'info>( /// - Expected to be writable, but is not. pub fn load_mint<'a, 'info>( info: &'a AccountInfo<'info>, + address: Pubkey, is_writable: bool, ) -> Result<(), ProgramError> { if info.owner.ne(&spl_token::id()) { return Err(ProgramError::InvalidAccountOwner); } - if info.key.ne(&MINT_ADDRESS) { + if info.key.ne(&address) { return Err(ProgramError::InvalidSeeds); } diff --git a/src/processor/mod.rs b/src/processor/mod.rs index 2410f76..23535f5 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -5,6 +5,7 @@ mod register; mod reset; mod stake; mod update_admin; +mod upgrade; pub use claim::*; pub use initialize::*; @@ -13,3 +14,4 @@ pub use register::*; pub use reset::*; pub use stake::*; pub use update_admin::*; +pub use upgrade::*; diff --git a/src/processor/upgrade.rs b/src/processor/upgrade.rs new file mode 100644 index 0000000..4788c17 --- /dev/null +++ b/src/processor/upgrade.rs @@ -0,0 +1,81 @@ +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; + +use crate::{ + instruction::StakeArgs, loaders::*, state::Treasury, utils::AccountDeserialize, MINT_ADDRESS, + MINT_V1_ADDRESS, TREASURY, +}; + +pub fn process_upgrade<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + data: &[u8], +) -> ProgramResult { + // Parse args + let args = StakeArgs::try_from_bytes(data)?; + let amount = u64::from_le_bytes(args.amount); + + // Load accounts + let [signer_info, beneficiary_info, mint_info, mint_v1_info, sender_info, treasury_info, token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer_info)?; + load_token_account( + beneficiary_info, + Some(&signer_info.key), + &MINT_ADDRESS, + true, + )?; + load_mint(mint_info, MINT_ADDRESS, true)?; + load_mint(mint_v1_info, MINT_V1_ADDRESS, true)?; + load_token_account(sender_info, Some(signer_info.key), &MINT_V1_ADDRESS, true)?; + load_program(token_program, spl_token::id())?; + + // Burn v1 tokens + solana_program::program::invoke( + &spl_token::instruction::burn( + &spl_token::id(), + sender_info.key, + mint_v1_info.key, + signer_info.key, + &[signer_info.key], + amount, + )?, + &[ + token_program.clone(), + sender_info.clone(), + mint_v1_info.clone(), + signer_info.clone(), + ], + )?; + + // Mint to beneficiary account + // TODO Account for decimals! + let treasury_data = treasury_info.data.borrow(); + let treasury = Treasury::try_from_bytes(&treasury_data)?; + let treasury_bump = treasury.bump as u8; + drop(treasury_data); + solana_program::program::invoke_signed( + &spl_token::instruction::mint_to( + &spl_token::id(), + mint_info.key, + beneficiary_info.key, + treasury_info.key, + &[treasury_info.key], + amount, + )?, + &[ + token_program.clone(), + mint_info.clone(), + beneficiary_info.clone(), + treasury_info.clone(), + ], + &[&[TREASURY, &[treasury_bump]]], + )?; + + Ok(()) +} From 566bbeb5bfdd8185cd6ca1274c2728fbac81a944 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 28 Apr 2024 16:32:23 +0000 Subject: [PATCH 007/111] const pda --- Cargo.lock | 23 +++++++++++++++ Cargo.toml | 7 +++-- src/consts.rs | 65 +++++++++++++++++++++++++++---------------- src/instruction.rs | 28 +++++++------------ src/lib.rs | 3 +- src/processor/mine.rs | 7 ++--- src/state/hash.rs | 26 ++++++++--------- 7 files changed, 95 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 425dd90..4ee9711 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -903,6 +903,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "const-crypto" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c63acd992239f1877a7d6387038ca886a2029e41f3d91087fd454f9995f22c" +dependencies = [ + "keccak-const", + "sha2-const-stable", +] + [[package]] name = "const-oid" version = "0.7.1" @@ -1987,6 +1997,12 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d8d8ce877200136358e0bbff3a77965875db3af755a11e1fa6b1b3e2df13ea" + [[package]] name = "lazy_static" version = "1.4.0" @@ -2533,6 +2549,7 @@ dependencies = [ "bs58 0.5.1", "bs64", "bytemuck", + "const-crypto", "drillx", "mpl-token-metadata", "num_enum 0.7.2", @@ -3453,6 +3470,12 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + [[package]] name = "sha3" version = "0.9.1" diff --git a/Cargo.toml b/Cargo.toml index 3590b23..9b8ab90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,11 +21,12 @@ default = [] [dependencies] bs58 = "0.5.0" bytemuck = "1.14.3" +const-crypto = "0.1.0" drillx = { git = "https://github.com/hardhatchad/drillx", branch = "master" } mpl-token-metadata = "4.1.2" num_enum = "0.7.2" shank = "0.3.0" -solana-program = "^1.16" +solana-program = "1.18" spl-token = { version = "^4", features = ["no-entrypoint"] } spl-associated-token-account = { version = "^2.2", features = [ "no-entrypoint" ] } static_assertions = "1.1.0" @@ -34,6 +35,6 @@ thiserror = "1.0.57" [dev-dependencies] bs64 = "0.1.2" rand = "0.8.5" -solana-program-test = "^1.16" -solana-sdk = "^1.16" +solana-program-test = "^1.18" +solana-sdk = "^1.18" tokio = { version = "1.35", features = ["full"] } diff --git a/src/consts.rs b/src/consts.rs index 6680cb3..304ff1f 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,3 +1,4 @@ +use const_crypto::ed25519; use solana_program::{keccak::Hash, pubkey, pubkey::Pubkey}; /// The reward rate to intialize the program with. @@ -37,9 +38,6 @@ pub const BUS_COUNT: usize = 8; /// than a factor of this constant from one epoch to the next. pub const SMOOTHING_FACTOR: u64 = 2; -/// The range of difficulties miners can target above the minimum. -pub const DIFFICULTY_RANGE: usize = 32; - // Assert MAX_EPOCH_REWARDS is evenly divisible by BUS_COUNT. static_assertions::const_assert!( (MAX_EPOCH_REWARDS / BUS_COUNT as u64) * BUS_COUNT as u64 == MAX_EPOCH_REWARDS @@ -57,12 +55,20 @@ pub const METADATA: &[u8] = b"metadata"; /// The seed of the mint account PDA. pub const MINT: &[u8] = b"mint"; +/// The seed of the noise account PDA. +pub const NOISE: &[u8] = b"noise"; + /// The seed of proof account PDAs. pub const PROOF: &[u8] = b"proof"; /// The seed of the treasury account PDA. pub const TREASURY: &[u8] = b"treasury"; +/// Noise for deriving the mint pda +pub const MINT_NOISE: [u8; 16] = [ + 166, 199, 85, 221, 225, 119, 21, 185, 160, 82, 242, 237, 194, 84, 250, 252, +]; + /// The name for token metadata. pub const METADATA_NAME: &str = "Ore"; @@ -72,38 +78,49 @@ pub const METADATA_SYMBOL: &str = "ORE"; /// The uri for token metdata. pub const METADATA_URI: &str = "https://ore.supply/metadata.json"; -/// Noise for deriving the mint PDA. -pub const MINT_NOISE: [u8; 16] = [ - 166, 199, 85, 221, 225, 119, 21, 185, 160, 82, 242, 237, 194, 84, 250, 252, -]; +/// Program id for const pda derivations +const PROGRAM_ID: [u8; 32] = unsafe { *(&crate::id() as *const Pubkey as *const [u8; 32]) }; /// The addresses of the bus accounts. pub const BUS_ADDRESSES: [Pubkey; BUS_COUNT] = [ - pubkey!("9ShaCzHhQNvH8PLfGyrJbB8MeKHrDnuPMLnUDLJ2yMvz"), - pubkey!("4Cq8685h9GwsaD5ppPsrtfcsk3fum8f9UP4SPpKSbj2B"), - pubkey!("8L1vdGdvU3cPj9tsjJrKVUoBeXYvAzJYhExjTYHZT7h7"), - pubkey!("JBdVURCrUiHp4kr7srYtXbB7B4CwurUt1Bfxrxw6EoRY"), - pubkey!("DkmVBWJ4CLKb3pPHoSwYC2wRZXKKXLD2Ued5cGNpkWmr"), - pubkey!("9uLpj2ZCMqN6Yo1vV6yTkP6dDiTTXmeM5K3915q5CHyh"), - pubkey!("EpcfjBs8eQ4unSMdowxyTE8K3vVJ3XUnEr5BEWvSX7RB"), - pubkey!("Ay5N9vKS2Tyo2M9u9TFt59N1XbxdW93C7UrFZW3h8sMC"), + Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[0]], &PROGRAM_ID).0), + Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[1]], &PROGRAM_ID).0), + Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[2]], &PROGRAM_ID).0), + Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[3]], &PROGRAM_ID).0), + Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[4]], &PROGRAM_ID).0), + Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[5]], &PROGRAM_ID).0), + Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[6]], &PROGRAM_ID).0), + Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[7]], &PROGRAM_ID).0), ]; -// TODO /// The address of the config account. -pub const CONFIG_ADDRESS: Pubkey = pubkey!("FTap9fv2GPpWGqrLj3o4c9nHH7p36ih7NbSWHnrkQYqa"); +pub const CONFIG_ADDRESS: Pubkey = + Pubkey::new_from_array(ed25519::derive_program_address(&[CONFIG], &PROGRAM_ID).0); /// The address of the mint metadata account. -pub const METADATA_ADDRESS: Pubkey = pubkey!("2nXZSxfjELuRatcoY64yHdFLZFi3mtesxobHmsoU3Dag"); - -/// The address of the mint metadata account. -pub const NOISE_ADDRESS: Pubkey = pubkey!("2nXZSxfjELuRatcoY64yHdFLZFi3mtesxobHmsoU3Dag"); +pub const METADATA_ADDRESS: Pubkey = Pubkey::new_from_array( + ed25519::derive_program_address( + &[ + METADATA, + unsafe { &*(&mpl_token_metadata::ID as *const Pubkey as *const [u8; 32]) }, + unsafe { &*(&MINT_ADDRESS as *const Pubkey as *const [u8; 32]) }, + ], + unsafe { &*(&mpl_token_metadata::ID as *const Pubkey as *const [u8; 32]) }, + ) + .0, +); /// The address of the mint account. -pub const MINT_ADDRESS: Pubkey = pubkey!("oreoN2tQbHXVaZsr3pf66A48miqcBXCDJozganhEJgz"); +pub const MINT_ADDRESS: Pubkey = + Pubkey::new_from_array(ed25519::derive_program_address(&[MINT, &MINT_NOISE], &PROGRAM_ID).0); -/// The address of the mint account. +/// The address of the v1 mint account. pub const MINT_V1_ADDRESS: Pubkey = pubkey!("oreoN2tQbHXVaZsr3pf66A48miqcBXCDJozganhEJgz"); +/// The address of the mint metadata account. +pub const NOISE_ADDRESS: Pubkey = + Pubkey::new_from_array(ed25519::derive_program_address(&[NOISE], &PROGRAM_ID).0); + /// The address of the treasury account. -pub const TREASURY_ADDRESS: Pubkey = pubkey!("FTap9fv2GPpWGqrLj3o4c9nHH7p36ih7NbSWHnrkQYqa"); +pub const TREASURY_ADDRESS: Pubkey = + Pubkey::new_from_array(ed25519::derive_program_address(&[TREASURY], &PROGRAM_ID).0); diff --git a/src/instruction.rs b/src/instruction.rs index 5e49e49..349b4a3 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -8,8 +8,8 @@ use solana_program::{ }; use crate::{ - impl_instruction_from_bytes, impl_to_bytes, BUS, CONFIG, CONFIG_ADDRESS, METADATA, MINT, - MINT_ADDRESS, MINT_NOISE, PROOF, TREASURY, TREASURY_ADDRESS, + impl_instruction_from_bytes, impl_to_bytes, BUS, BUS_ADDRESSES, CONFIG, CONFIG_ADDRESS, + METADATA, MINT, MINT_ADDRESS, MINT_NOISE, PROOF, TREASURY, TREASURY_ADDRESS, }; #[repr(u8)] @@ -177,14 +177,6 @@ impl_instruction_from_bytes!(UpdateAdminArgs); /// Builds a reset instruction. pub fn reset(signer: Pubkey) -> Instruction { - let bus_0 = Pubkey::find_program_address(&[BUS, &[0]], &crate::id()).0; - let bus_1 = Pubkey::find_program_address(&[BUS, &[1]], &crate::id()).0; - let bus_2 = Pubkey::find_program_address(&[BUS, &[2]], &crate::id()).0; - let bus_3 = Pubkey::find_program_address(&[BUS, &[3]], &crate::id()).0; - let bus_4 = Pubkey::find_program_address(&[BUS, &[4]], &crate::id()).0; - let bus_5 = Pubkey::find_program_address(&[BUS, &[5]], &crate::id()).0; - let bus_6 = Pubkey::find_program_address(&[BUS, &[6]], &crate::id()).0; - let bus_7 = Pubkey::find_program_address(&[BUS, &[7]], &crate::id()).0; let treasury_tokens = spl_associated_token_account::get_associated_token_address( &TREASURY_ADDRESS, &MINT_ADDRESS, @@ -193,14 +185,14 @@ pub fn reset(signer: Pubkey) -> Instruction { program_id: crate::id(), accounts: vec![ AccountMeta::new(signer, true), - AccountMeta::new(bus_0, false), - AccountMeta::new(bus_1, false), - AccountMeta::new(bus_2, false), - AccountMeta::new(bus_3, false), - AccountMeta::new(bus_4, false), - AccountMeta::new(bus_5, false), - AccountMeta::new(bus_6, false), - AccountMeta::new(bus_7, false), + AccountMeta::new(BUS_ADDRESSES[0], false), + AccountMeta::new(BUS_ADDRESSES[1], false), + AccountMeta::new(BUS_ADDRESSES[2], false), + AccountMeta::new(BUS_ADDRESSES[3], false), + AccountMeta::new(BUS_ADDRESSES[4], false), + AccountMeta::new(BUS_ADDRESSES[5], false), + AccountMeta::new(BUS_ADDRESSES[6], false), + AccountMeta::new(BUS_ADDRESSES[7], false), AccountMeta::new(MINT_ADDRESS, false), AccountMeta::new(TREASURY_ADDRESS, false), AccountMeta::new(treasury_tokens, false), diff --git a/src/lib.rs b/src/lib.rs index c31df80..dc85594 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,8 +14,7 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; -// TODO Upgrade (v1 to v2 token) -// TODO Downgrade (v2 to v1 token) +// TODO Is downgrade necessary or no? declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 0fb38ab..daf02fa 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -17,7 +17,7 @@ use crate::{ loaders::*, state::{Bus, Config, Proof}, utils::AccountDeserialize, - DIFFICULTY_RANGE, EPOCH_DURATION, + EPOCH_DURATION, }; // TODO Look into tx introspection to require 1 hash per tx @@ -89,9 +89,7 @@ pub fn process_mine<'a, 'info>( } // Calculate base reward rate - let difficulty = difficulty - .saturating_sub(config.min_difficulty) - .min(DIFFICULTY_RANGE as u32); + let difficulty = difficulty.saturating_sub(config.min_difficulty); let mut reward = config .base_reward_rate .saturating_mul(2u64.saturating_pow(difficulty)); @@ -131,6 +129,7 @@ pub fn process_mine<'a, 'info>( } // Update balances + // TODO Handle case where reward is higher than bus can payout let mut bus_data = bus_info.data.borrow_mut(); let bus = Bus::try_from_bytes_mut(&mut bus_data)?; bus.rewards = bus diff --git a/src/state/hash.rs b/src/state/hash.rs index dd19e38..55084de 100644 --- a/src/state/hash.rs +++ b/src/state/hash.rs @@ -30,18 +30,18 @@ impl fmt::Display for Hash { } } -impl Hash { - pub fn difficulty(&self) -> u32 { - let mut count = 0; - for &byte in &self.0 { - let lz = byte.leading_zeros(); - count += lz; - if lz < 8 { - break; - } - } - count - } -} +// impl Hash { +// pub fn difficulty(&self) -> u32 { +// let mut count = 0; +// for &byte in &self.0 { +// let lz = byte.leading_zeros(); +// count += lz; +// if lz < 8 { +// break; +// } +// } +// count +// } +// } impl_to_bytes!(Hash); From 71dcac553a1c947e28942464fe19af20343270b9 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 28 Apr 2024 17:06:36 +0000 Subject: [PATCH 008/111] reset --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/consts.rs | 31 +++---- src/lib.rs | 1 + src/processor/initialize.rs | 4 +- src/processor/mine.rs | 2 +- src/processor/reset.rs | 163 +++++++++++++++++++----------------- src/processor/upgrade.rs | 4 +- src/state/config.rs | 3 + src/state/treasury.rs | 1 + 10 files changed, 119 insertions(+), 98 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ee9711..79e32c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,6 +278,12 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "array-const-fn-init" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bcb85e548c05d407fa6faff46b750ba287714ef32afc0f5e15b4641ffd6affb" + [[package]] name = "arrayref" version = "0.3.7" @@ -2546,6 +2552,7 @@ dependencies = [ name = "ore-program" version = "1.2.1" dependencies = [ + "array-const-fn-init", "bs58 0.5.1", "bs64", "bytemuck", diff --git a/Cargo.toml b/Cargo.toml index 9b8ab90..5de42fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ no-entrypoint = [] default = [] [dependencies] +array-const-fn-init = "0.1.1" bs58 = "0.5.0" bytemuck = "1.14.3" const-crypto = "0.1.0" diff --git a/src/consts.rs b/src/consts.rs index 304ff1f..9ca222d 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,15 +1,11 @@ +use array_const_fn_init::array_const_fn_init; use const_crypto::ed25519; -use solana_program::{keccak::Hash, pubkey, pubkey::Pubkey}; +use solana_program::{pubkey, pubkey::Pubkey}; /// The reward rate to intialize the program with. -pub const INITIAL_REWARD_RATE: u64 = 10u64.pow(3u32); - -/// The mining difficulty to initialize the program with. -pub const INITIAL_DIFFICULTY: Hash = Hash::new_from_array([ - 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, -]); +pub const INITIAL_BASE_REWARD_RATE: u64 = 10u64.pow(3u32); +// TODO Migrate to 11 decimals? /// The decimal precision of the ORE token. /// Using SI prefixes, the smallest indivisible unit of ORE is a nanoORE. /// 1 nanoORE = 0.000000001 ORE = one billionth of an ORE @@ -82,16 +78,12 @@ pub const METADATA_URI: &str = "https://ore.supply/metadata.json"; const PROGRAM_ID: [u8; 32] = unsafe { *(&crate::id() as *const Pubkey as *const [u8; 32]) }; /// The addresses of the bus accounts. -pub const BUS_ADDRESSES: [Pubkey; BUS_COUNT] = [ - Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[0]], &PROGRAM_ID).0), - Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[1]], &PROGRAM_ID).0), - Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[2]], &PROGRAM_ID).0), - Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[3]], &PROGRAM_ID).0), - Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[4]], &PROGRAM_ID).0), - Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[5]], &PROGRAM_ID).0), - Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[6]], &PROGRAM_ID).0), - Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[7]], &PROGRAM_ID).0), -]; +pub const BUS_ADDRESSES: [Pubkey; BUS_COUNT] = array_const_fn_init![const_bus_address; 8]; + +/// Function to derive const bus addresses. +const fn const_bus_address(i: usize) -> Pubkey { + Pubkey::new_from_array(ed25519::derive_program_address(&[BUS, &[i as u8]], &PROGRAM_ID).0) +} /// The address of the config account. pub const CONFIG_ADDRESS: Pubkey = @@ -124,3 +116,6 @@ pub const NOISE_ADDRESS: Pubkey = /// The address of the treasury account. pub const TREASURY_ADDRESS: Pubkey = Pubkey::new_from_array(ed25519::derive_program_address(&[TREASURY], &PROGRAM_ID).0); + +/// The bump of the treasury account, for cpis. +pub const TREASURY_BUMP: u8 = ed25519::derive_program_address(&[TREASURY], &PROGRAM_ID).1; diff --git a/src/lib.rs b/src/lib.rs index dc85594..e20901d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; +// TODO u128 for internal rewards representation? // TODO Is downgrade necessary or no? declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 2e6b23f..2c90ea3 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -118,7 +118,7 @@ pub fn process_initialize<'a, 'info>( bus.rewards = 0; } - // Initialize config + // TODO Initialize config create_pda( config_info, &crate::id(), @@ -133,7 +133,7 @@ pub fn process_initialize<'a, 'info>( config.admin = *signer.key; // config.difficulty = INITIAL_DIFFICULTY.into(); - // Initialize treasury + // TODO Initialize treasury create_pda( treasury_info, &crate::id(), diff --git a/src/processor/mine.rs b/src/processor/mine.rs index daf02fa..e941040 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -54,7 +54,7 @@ pub fn process_mine<'a, 'info>( load_proof(proof_info, signer.key, true)?; load_sysvar(slot_hashes_info, sysvar::slot_hashes::id())?; - // Validate mining is allowed + // Validate mining is not paused let config_data = config_info.data.borrow(); let config = Config::try_from_bytes(&config_data)?; if config.paused.ne(&0) { diff --git a/src/processor/reset.rs b/src/processor/reset.rs index 26db329..b4dc799 100644 --- a/src/processor/reset.rs +++ b/src/processor/reset.rs @@ -1,6 +1,19 @@ -use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey}; +use solana_program::{ + account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, +}; -use crate::{BUS_EPOCH_REWARDS, SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS}; +use crate::{ + error::OreError, + loaders::{ + load_bus, load_config, load_mint, load_program, load_signer, load_token_account, + load_treasury, + }, + state::{Bus, Config}, + utils::AccountDeserialize, + BUS_COUNT, BUS_EPOCH_REWARDS, EPOCH_DURATION, MAX_EPOCH_REWARDS, MINT_ADDRESS, + SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS, TREASURY, TREASURY_BUMP, +}; /// Reset sets up the Ore program for the next epoch. Its responsibilities include: /// 1. Reset bus account rewards counters. @@ -23,85 +36,85 @@ pub fn process_reset<'a, 'info>( accounts: &'a [AccountInfo<'info>], _data: &[u8], ) -> ProgramResult { - // // Load accounts - // let [signer, bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, bus_7_info, mint_info, treasury_info, treasury_tokens_info, token_program] = - // accounts - // else { - // return Err(ProgramError::NotEnoughAccountKeys); - // }; - // load_signer(signer)?; - // load_bus(bus_0_info, 0, true)?; - // load_bus(bus_1_info, 1, true)?; - // load_bus(bus_2_info, 2, true)?; - // load_bus(bus_3_info, 3, true)?; - // load_bus(bus_4_info, 4, true)?; - // load_bus(bus_5_info, 5, true)?; - // load_bus(bus_6_info, 6, true)?; - // load_bus(bus_7_info, 7, true)?; - // load_mint(mint_info, true)?; - // load_treasury(treasury_info, true)?; - // load_token_account( - // treasury_tokens_info, - // Some(treasury_info.key), - // mint_info.key, - // true, - // )?; - // load_program(token_program, spl_token::id())?; - // let busses: [&AccountInfo; BUS_COUNT] = [ - // bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, - // bus_7_info, - // ]; + // Load accounts + let [signer, 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] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer)?; + load_bus(bus_0_info, 0, true)?; + load_bus(bus_1_info, 1, true)?; + load_bus(bus_2_info, 2, true)?; + load_bus(bus_3_info, 3, true)?; + load_bus(bus_4_info, 4, true)?; + load_bus(bus_5_info, 5, true)?; + load_bus(bus_6_info, 6, true)?; + load_bus(bus_7_info, 7, true)?; + load_config(config_info, true)?; + load_mint(mint_info, MINT_ADDRESS, true)?; + load_treasury(treasury_info, true)?; + load_token_account( + treasury_tokens_info, + Some(treasury_info.key), + mint_info.key, + true, + )?; + load_program(token_program, spl_token::id())?; + let busses: [&AccountInfo; BUS_COUNT] = [ + bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, + bus_7_info, + ]; - // // Validate mining has starting - // let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; - // if clock.unix_timestamp.lt(&START_AT) { - // return Err(OreError::NotStarted.into()); - // } + // Validate mining is not paused + let mut config_data = config_info.data.borrow_mut(); + let config = Config::try_from_bytes_mut(&mut config_data)?; + if config.paused.ne(&0) { + return Err(OreError::IsPaused.into()); + } - // // Validate at least 60 seconds have passed since last reset - // let mut treasury_data = treasury_info.data.borrow_mut(); - // let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; - // let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION); - // if clock.unix_timestamp.lt(&threshold) { - // return Err(OreError::ResetTooEarly.into()); - // } + // Validate enough time has passed since last reset + let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; + let threshold = config.last_reset_at.saturating_add(EPOCH_DURATION); + if clock.unix_timestamp.lt(&threshold) { + return Err(OreError::ResetTooEarly.into()); + } - // // Record current timestamp - // treasury.last_reset_at = clock.unix_timestamp; + // Update reset timestamp + config.last_reset_at = clock.unix_timestamp; - // // Reset bus accounts and calculate actual rewards mined since last reset - // let mut total_remaining_rewards = 0u64; - // for i in 0..BUS_COUNT { - // let mut bus_data = busses[i].data.borrow_mut(); - // let bus = Bus::try_from_bytes_mut(&mut bus_data)?; - // total_remaining_rewards = total_remaining_rewards.saturating_add(bus.rewards); - // bus.rewards = BUS_EPOCH_REWARDS; - // } - // let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_remaining_rewards); + // Reset bus accounts and calculate actual rewards mined since last reset + let mut total_remaining_rewards = 0u64; + for i in 0..BUS_COUNT { + let mut bus_data = busses[i].data.borrow_mut(); + let bus = Bus::try_from_bytes_mut(&mut bus_data)?; + total_remaining_rewards = total_remaining_rewards.saturating_add(bus.rewards); + bus.rewards = BUS_EPOCH_REWARDS; + } + let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_remaining_rewards); - // // Update reward rate for next epoch - // treasury.reward_rate = calculate_new_reward_rate(treasury.reward_rate, total_epoch_rewards); + // Update base reward rate for next epoch + config.base_reward_rate = + calculate_new_reward_rate(config.base_reward_rate, total_epoch_rewards); - // // Fund treasury token account - // let treasury_bump = treasury.bump as u8; - // drop(treasury_data); - // solana_program::program::invoke_signed( - // &spl_token::instruction::mint_to( - // &spl_token::id(), - // mint_info.key, - // treasury_tokens_info.key, - // treasury_info.key, - // &[treasury_info.key], - // total_epoch_rewards, - // )?, - // &[ - // token_program.clone(), - // mint_info.clone(), - // treasury_tokens_info.clone(), - // treasury_info.clone(), - // ], - // &[&[TREASURY, &[treasury_bump]]], - // )?; + // Fund treasury token account + solana_program::program::invoke_signed( + &spl_token::instruction::mint_to( + &spl_token::id(), + mint_info.key, + treasury_tokens_info.key, + treasury_info.key, + &[treasury_info.key], + total_epoch_rewards, + )?, + &[ + token_program.clone(), + mint_info.clone(), + treasury_tokens_info.clone(), + treasury_info.clone(), + ], + &[&[TREASURY, &[TREASURY_BUMP]]], + )?; Ok(()) } diff --git a/src/processor/upgrade.rs b/src/processor/upgrade.rs index 4788c17..fa4aeef 100644 --- a/src/processor/upgrade.rs +++ b/src/processor/upgrade.rs @@ -53,8 +53,8 @@ pub fn process_upgrade<'a, 'info>( ], )?; - // Mint to beneficiary account - // TODO Account for decimals! + // Mint to the beneficiary account + // TODO Account for decimals change! let treasury_data = treasury_info.data.borrow(); let treasury = Treasury::try_from_bytes(&treasury_data)?; let treasury_bump = treasury.bump as u8; diff --git a/src/state/config.rs b/src/state/config.rs index 2020ac3..b796b69 100644 --- a/src/state/config.rs +++ b/src/state/config.rs @@ -19,6 +19,9 @@ pub struct Config { /// 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: u32, diff --git a/src/state/treasury.rs b/src/state/treasury.rs index 3ae7bc2..0975de6 100644 --- a/src/state/treasury.rs +++ b/src/state/treasury.rs @@ -12,6 +12,7 @@ use crate::{ #[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] pub struct Treasury { /// The bump of the treasury account PDA, for signing CPIs. + // TODO Is this needed if bump is const? pub bump: u64, } From e65aa9c38555eb5fb1f01da0a9ffee587d51fc87 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 28 Apr 2024 17:13:50 +0000 Subject: [PATCH 009/111] pause --- rust-toolchain.toml | 2 +- src/instruction.rs | 39 ++++++++++++++++++++++++++++++++++++--- src/lib.rs | 4 ++++ src/processor/mod.rs | 2 ++ src/processor/pause.rs | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 src/processor/pause.rs diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 833a265..fa4b8db 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.75.0" +channel = "1.76.0" components = [ "rustfmt", "rust-analyzer" ] targets = [ "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-apple-darwin"] profile = "minimal" diff --git a/src/instruction.rs b/src/instruction.rs index 349b4a3..955ca1c 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -96,8 +96,13 @@ pub enum OreInstruction { #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] - #[account(2, name = "treasury", desc = "Ore treasury account")] + #[account(2, name = "config", desc = "Ore config account")] UpdateAdmin = 101, + + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Admin signer", signer)] + #[account(2, name = "config", desc = "Ore config account")] + Pause = 102, } impl OreInstruction { @@ -159,6 +164,12 @@ pub struct UpdateAdminArgs { pub new_admin: Pubkey, } +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct PauseArgs { + pub paused: u8, +} + impl_to_bytes!(InitializeArgs); impl_to_bytes!(RegisterArgs); impl_to_bytes!(MineArgs); @@ -166,6 +177,7 @@ impl_to_bytes!(ClaimArgs); impl_to_bytes!(StakeArgs); impl_to_bytes!(UpgradeArgs); impl_to_bytes!(UpdateAdminArgs); +impl_to_bytes!(PauseArgs); impl_instruction_from_bytes!(InitializeArgs); impl_instruction_from_bytes!(RegisterArgs); @@ -174,6 +186,7 @@ impl_instruction_from_bytes!(ClaimArgs); impl_instruction_from_bytes!(StakeArgs); impl_instruction_from_bytes!(UpgradeArgs); impl_instruction_from_bytes!(UpdateAdminArgs); +impl_instruction_from_bytes!(PauseArgs); /// Builds a reset instruction. pub fn reset(signer: Pubkey) -> Instruction { @@ -345,13 +358,13 @@ pub fn initialize(signer: Pubkey) -> Instruction { } } -/// Builds an update_admin instruction. +/// Build an update_admin instruction. pub fn update_admin(signer: Pubkey, new_admin: Pubkey) -> Instruction { Instruction { program_id: crate::id(), accounts: vec![ AccountMeta::new(signer, true), - AccountMeta::new(TREASURY_ADDRESS, false), + AccountMeta::new(CONFIG_ADDRESS, false), ], data: [ OreInstruction::UpdateAdmin.to_vec(), @@ -360,3 +373,23 @@ pub fn update_admin(signer: Pubkey, new_admin: Pubkey) -> Instruction { .concat(), } } + +/// Build a pause instruction. +pub fn pause(signer: Pubkey, paused: bool) -> Instruction { + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(CONFIG_ADDRESS, false), + ], + data: [ + OreInstruction::UpdateAdmin.to_vec(), + PauseArgs { + paused: paused as u8, + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} diff --git a/src/lib.rs b/src/lib.rs index e20901d..a8316e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,10 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; +// TODO Admin fn for pause // TODO u128 for internal rewards representation? +// TODO Admin fn for min difficulty? What if this were set automatically by u128 base reward rate? +// TODO Increase bus count? // TODO Is downgrade necessary or no? declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); @@ -44,6 +47,7 @@ pub fn process_instruction( OreInstruction::Upgrade => process_upgrade(program_id, accounts, data)?, OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, OreInstruction::UpdateAdmin => process_update_admin(program_id, accounts, data)?, + OreInstruction::Pause => process_pause(program_id, accounts, data)?, } Ok(()) diff --git a/src/processor/mod.rs b/src/processor/mod.rs index 23535f5..66b20b4 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -1,6 +1,7 @@ mod claim; mod initialize; mod mine; +mod pause; mod register; mod reset; mod stake; @@ -10,6 +11,7 @@ mod upgrade; pub use claim::*; pub use initialize::*; pub use mine::*; +pub use pause::*; pub use register::*; pub use reset::*; pub use stake::*; diff --git a/src/processor/pause.rs b/src/processor/pause.rs new file mode 100644 index 0000000..be2cbb9 --- /dev/null +++ b/src/processor/pause.rs @@ -0,0 +1,34 @@ +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; + +use crate::{instruction::PauseArgs, loaders::*, state::Config, utils::AccountDeserialize}; + +pub fn process_pause<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + data: &[u8], +) -> ProgramResult { + // Parse args + let args = PauseArgs::try_from_bytes(data)?; + + // Load accounts + let [signer, config_info] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer)?; + load_config(config_info, true)?; + + // Validate signer is admin + let mut config_data = config_info.data.borrow_mut(); + let config = Config::try_from_bytes_mut(&mut config_data)?; + if config.admin.ne(&signer.key) { + return Err(ProgramError::MissingRequiredSignature); + } + + // Update paused + config.paused = args.paused as u32; + + Ok(()) +} From 96bc317c9694ccd1f0996e3393d071225527b3c5 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 28 Apr 2024 17:20:14 +0000 Subject: [PATCH 010/111] ix builders --- src/instruction.rs | 34 +++++++++++++++++++++++++++++++--- src/lib.rs | 1 - src/processor/claim.rs | 21 ++++++--------------- src/processor/stake.rs | 4 ++-- 4 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/instruction.rs b/src/instruction.rs index 955ca1c..6674f6c 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -57,8 +57,8 @@ pub enum OreInstruction { #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] - #[account(2, name = "sender", desc = "Signer token account", writable)] - #[account(3, name = "proof", desc = "Ore proof account", writable)] + #[account(2, name = "proof", desc = "Ore proof account", writable)] + #[account(3, name = "sender", desc = "Signer token account", writable)] #[account(4, name = "treasury_tokens", desc = "Ore treasury token account", writable)] #[account(5, name = "token_program", desc = "SPL token program")] Stake = 4, @@ -271,7 +271,7 @@ pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { AccountMeta::new(signer, true), AccountMeta::new(beneficiary, false), AccountMeta::new(proof, false), - AccountMeta::new(TREASURY_ADDRESS, false), + AccountMeta::new_readonly(TREASURY_ADDRESS, false), AccountMeta::new(treasury_tokens, false), AccountMeta::new_readonly(spl_token::id(), false), ], @@ -287,6 +287,34 @@ pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { } } +/// Build a stake instruction. +pub fn stake(signer: Pubkey, sender: Pubkey, amount: u64) -> Instruction { + let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; + let treasury_tokens = spl_associated_token_account::get_associated_token_address( + &TREASURY_ADDRESS, + &MINT_ADDRESS, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(proof, false), + AccountMeta::new(sender, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: [ + OreInstruction::Stake.to_vec(), + StakeArgs { + amount: amount.to_le_bytes(), + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + /// Builds an initialize instruction. pub fn initialize(signer: Pubkey) -> Instruction { let bus_pdas = [ diff --git a/src/lib.rs b/src/lib.rs index a8316e4..724c880 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,6 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; -// TODO Admin fn for pause // TODO u128 for internal rewards representation? // TODO Admin fn for min difficulty? What if this were set automatically by u128 base reward rate? // TODO Increase bus count? diff --git a/src/processor/claim.rs b/src/processor/claim.rs index 9287b12..69ae1fe 100644 --- a/src/processor/claim.rs +++ b/src/processor/claim.rs @@ -4,18 +4,13 @@ use solana_program::{ }; use crate::{ - error::OreError, - instruction::ClaimArgs, - loaders::*, - state::{Proof, Treasury}, - utils::AccountDeserialize, - MINT_ADDRESS, TREASURY, + error::OreError, instruction::ClaimArgs, loaders::*, state::Proof, utils::AccountDeserialize, + MINT_ADDRESS, TREASURY, TREASURY_BUMP, }; /// Claim distributes owed token rewards from the treasury to the miner. Its responsibilies include: -/// 1. Transfer tokens from the treasury to the miner. -/// 2. Decrement the miner's claimable rewards counter by an appropriate amount. -/// 3. Update the program's lifetime stats. +/// 1. Decrement the miner's claimable rewards counter. +/// 2. Transfer tokens from the treasury to the miner. /// /// Safety requirements: /// - Claim is a permissionless instruction and can be called by any miner. @@ -39,7 +34,7 @@ pub fn process_claim<'a, 'info>( load_signer(signer)?; load_token_account(beneficiary_info, None, &MINT_ADDRESS, true)?; load_proof(proof_info, signer.key, true)?; - load_treasury(treasury_info, true)?; + load_treasury(treasury_info, false)?; load_token_account( treasury_tokens_info, Some(treasury_info.key), @@ -57,10 +52,6 @@ pub fn process_claim<'a, 'info>( .ok_or(OreError::ClaimTooLarge)?; // Distribute tokens from treasury to beneficiary - let mut treasury_data = treasury_info.data.borrow_mut(); - let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; - let treasury_bump = treasury.bump; - drop(treasury_data); solana_program::program::invoke_signed( &spl_token::instruction::transfer( &spl_token::id(), @@ -76,7 +67,7 @@ pub fn process_claim<'a, 'info>( beneficiary_info.clone(), treasury_info.clone(), ], - &[&[TREASURY, &[treasury_bump as u8]]], + &[&[TREASURY, &[TREASURY_BUMP]]], )?; Ok(()) diff --git a/src/processor/stake.rs b/src/processor/stake.rs index e3390a3..68405ed 100644 --- a/src/processor/stake.rs +++ b/src/processor/stake.rs @@ -18,13 +18,13 @@ pub fn process_stake<'a, 'info>( let amount = u64::from_le_bytes(args.amount); // Load accounts - let [signer_info, sender_info, proof_info, treasury_tokens_info, token_program] = accounts + let [signer_info, proof_info, sender_info, treasury_tokens_info, token_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer_info)?; - load_token_account(sender_info, Some(signer_info.key), &MINT_ADDRESS, true)?; load_proof(proof_info, signer_info.key, true)?; + load_token_account(sender_info, Some(signer_info.key), &MINT_ADDRESS, true)?; load_token_account( treasury_tokens_info, Some(&TREASURY_ADDRESS), From 0677500cb40a9e83bdc3ed635e7b65bb6453f096 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 28 Apr 2024 17:24:47 +0000 Subject: [PATCH 011/111] optimize loaders --- src/loaders.rs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/loaders.rs b/src/loaders.rs index 71c7ba3..81f0a6d 100644 --- a/src/loaders.rs +++ b/src/loaders.rs @@ -6,12 +6,10 @@ use spl_token::state::Mint; use crate::{ state::{Bus, Config, Proof, Treasury}, - utils::AccountDeserialize, - BUS_ADDRESSES, BUS_COUNT, CONFIG_ADDRESS, NOISE_ADDRESS, TREASURY_ADDRESS, + utils::{AccountDeserialize, Discriminator}, + BUS_ADDRESSES, CONFIG_ADDRESS, NOISE_ADDRESS, TREASURY_ADDRESS, }; -// TODO Account checks don't need to deserialize the whole byte array. They can just check the type byte - /// Errors if: /// - Account is not a signer. pub fn load_signer<'a, 'info>(info: &'a AccountInfo<'info>) -> Result<(), ProgramError> { @@ -79,14 +77,11 @@ pub fn load_any_bus<'a, 'info>( return Err(ProgramError::UninitializedAccount); } - let bus_data = info.data.borrow(); - let bus = Bus::try_from_bytes(&bus_data)?; - - if bus.id.ge(&(BUS_COUNT as u64)) { - return Err(ProgramError::InvalidAccountData); + if info.data.borrow()[0].ne(&(Bus::discriminator() as u8)) { + return Err(solana_program::program_error::ProgramError::InvalidAccountData); } - if info.key.ne(&BUS_ADDRESSES[bus.id as usize]) { + if !BUS_ADDRESSES.contains(info.key) { return Err(ProgramError::InvalidSeeds); } @@ -119,8 +114,9 @@ pub fn load_config<'a, 'info>( return Err(ProgramError::UninitializedAccount); } - let config_data = info.data.borrow(); - let _ = Config::try_from_bytes(&config_data)?; + if info.data.borrow()[0].ne(&(Config::discriminator() as u8)) { + return Err(solana_program::program_error::ProgramError::InvalidAccountData); + } if is_writable && !info.is_writable { return Err(ProgramError::InvalidAccountData); @@ -208,8 +204,9 @@ pub fn load_treasury<'a, 'info>( return Err(ProgramError::UninitializedAccount); } - let treasury_data = info.data.borrow(); - let _ = Treasury::try_from_bytes(&treasury_data)?; + if info.data.borrow()[0].ne(&(Treasury::discriminator() as u8)) { + return Err(solana_program::program_error::ProgramError::InvalidAccountData); + } if is_writable && !info.is_writable { return Err(ProgramError::InvalidAccountData); @@ -241,8 +238,7 @@ pub fn load_mint<'a, 'info>( return Err(ProgramError::UninitializedAccount); } - let mint_data = info.data.borrow(); - if Mint::unpack_unchecked(&mint_data).is_err() { + if Mint::unpack_unchecked(&info.data.borrow()).is_err() { return Err(ProgramError::InvalidAccountData); } From 3ecf215151128c269b36093f187690b24a3bb5cd Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 28 Apr 2024 20:53:22 +0000 Subject: [PATCH 012/111] silent return on reset too early --- src/error.rs | 12 +++++------- src/processor/register.rs | 4 ---- src/processor/reset.rs | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/error.rs b/src/error.rs index 423d18c..2dc6684 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,18 +9,16 @@ pub enum OreError { IsPaused = 0, #[error("The epoch has ended and needs reset")] NeedsReset = 1, - #[error("The epoch is active and cannot be reset at this time")] - ResetTooEarly = 2, #[error("The provided hash did not satisfy the minimum required difficulty")] - DifficultyInsufficient = 3, + DifficultyInsufficient = 2, #[error("The bus does not have enough rewards to issue at this time")] - BusRewardsInsufficient = 4, + BusRewardsInsufficient = 3, #[error("The claim amount cannot be greater than the claimable rewards")] - ClaimTooLarge = 5, + ClaimTooLarge = 4, #[error("The stake amount cannot exceed u64 size")] - StakeTooLarge = 6, + StakeTooLarge = 5, #[error("The clock time is invalid")] - ClockInvalid = 7, + ClockInvalid = 6, } impl From for ProgramError { diff --git a/src/processor/register.rs b/src/processor/register.rs index 9890eaa..acaf52d 100644 --- a/src/processor/register.rs +++ b/src/processor/register.rs @@ -14,10 +14,6 @@ use crate::{ PROOF, }; -// TODO Create a stake account (token account) -// TODO Need to keep claimable balance (from treasury) separate from staking balance (from stake account) -// TODO Multiplier calculations should account for both - /// Register generates a new hash chain for a prospective miner. Its responsibilities include: /// 1. Initialize a new proof account. /// 2. Generate an initial hash from the signer's key. diff --git a/src/processor/reset.rs b/src/processor/reset.rs index b4dc799..96b66b8 100644 --- a/src/processor/reset.rs +++ b/src/processor/reset.rs @@ -77,7 +77,7 @@ pub fn process_reset<'a, 'info>( let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; let threshold = config.last_reset_at.saturating_add(EPOCH_DURATION); if clock.unix_timestamp.lt(&threshold) { - return Err(OreError::ResetTooEarly.into()); + return Ok(()); } // Update reset timestamp From 6d89872752a20b680da461f45dd86e69ef52a498 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 28 Apr 2024 21:21:37 +0000 Subject: [PATCH 013/111] challenge --- src/processor/mine.rs | 4 ++-- src/processor/register.rs | 2 +- src/state/proof.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index e941040..efc036f 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -79,7 +79,7 @@ pub fn process_mine<'a, 'info>( // Calculate the hash from the provided nonce let noise_data = noise_info.data.borrow(); - let hx = drillx::hash(&proof.hash, &args.nonce, &noise_data); + let hx = drillx::hash(&proof.challenge, &args.nonce, &noise_data); drop(noise_data); // Validate hash satisfies the minimnum difficulty @@ -139,7 +139,7 @@ pub fn process_mine<'a, 'info>( proof.balance = proof.balance.saturating_add(reward); // Hash recent slot hash into the next challenge to prevent pre-mining attacks - proof.hash = hashv(&[ + proof.challenge = hashv(&[ hx.as_slice(), &slot_hashes_info.data.borrow()[0..size_of::()], ]) diff --git a/src/processor/register.rs b/src/processor/register.rs index acaf52d..4ad7c15 100644 --- a/src/processor/register.rs +++ b/src/processor/register.rs @@ -59,7 +59,7 @@ pub fn process_register<'a, 'info>( let proof = Proof::try_from_bytes_mut(&mut proof_data)?; proof.authority = *signer.key; proof.balance = 0; - proof.hash = hashv(&[ + proof.challenge = hashv(&[ signer.key.as_ref(), &slot_hashes_info.data.borrow()[0..size_of::()], ]) diff --git a/src/state/proof.rs b/src/state/proof.rs index 24ea148..e2913d1 100644 --- a/src/state/proof.rs +++ b/src/state/proof.rs @@ -18,8 +18,8 @@ pub struct Proof { /// The quantity of tokens this miner has staked or earned. pub balance: u64, - /// The proof's current hash. - pub hash: [u8; 32], + /// The current mining challenge. + pub challenge: [u8; 32], /// The last slot ore was deposited into this account. pub last_deposit_slot: u64, From 2fafa6358763f3fbeb8d513be68369f51273820b Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Mon, 29 Apr 2024 20:19:30 +0000 Subject: [PATCH 014/111] const noise --- Cargo.lock | 1 - Cargo.toml | 2 +- src/consts.rs | 7 ---- src/error.rs | 2 ++ src/instruction.rs | 38 +++++++++++--------- src/loaders.rs | 26 +------------- src/processor/initialize.rs | 69 +++++++++++++++++++------------------ src/processor/mine.rs | 7 ++-- 8 files changed, 62 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79e32c0..d3b15f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1242,7 +1242,6 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "drillx" version = "0.1.0" -source = "git+https://github.com/hardhatchad/drillx?branch=master#9fa59d964a23618b04f6f4f96ceb58f6638f9257" dependencies = [ "enum_dispatch", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index 5de42fb..be2a6ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ array-const-fn-init = "0.1.1" bs58 = "0.5.0" bytemuck = "1.14.3" const-crypto = "0.1.0" -drillx = { git = "https://github.com/hardhatchad/drillx", branch = "master" } +drillx = { path = "../drillx/drillx" } mpl-token-metadata = "4.1.2" num_enum = "0.7.2" shank = "0.3.0" diff --git a/src/consts.rs b/src/consts.rs index 9ca222d..48448a7 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -51,9 +51,6 @@ pub const METADATA: &[u8] = b"metadata"; /// The seed of the mint account PDA. pub const MINT: &[u8] = b"mint"; -/// The seed of the noise account PDA. -pub const NOISE: &[u8] = b"noise"; - /// The seed of proof account PDAs. pub const PROOF: &[u8] = b"proof"; @@ -109,10 +106,6 @@ pub const MINT_ADDRESS: Pubkey = /// The address of the v1 mint account. pub const MINT_V1_ADDRESS: Pubkey = pubkey!("oreoN2tQbHXVaZsr3pf66A48miqcBXCDJozganhEJgz"); -/// The address of the mint metadata account. -pub const NOISE_ADDRESS: Pubkey = - Pubkey::new_from_array(ed25519::derive_program_address(&[NOISE], &PROGRAM_ID).0); - /// The address of the treasury account. pub const TREASURY_ADDRESS: Pubkey = Pubkey::new_from_array(ed25519::derive_program_address(&[TREASURY], &PROGRAM_ID).0); diff --git a/src/error.rs b/src/error.rs index 2dc6684..8880788 100644 --- a/src/error.rs +++ b/src/error.rs @@ -19,6 +19,8 @@ pub enum OreError { StakeTooLarge = 5, #[error("The clock time is invalid")] ClockInvalid = 6, + #[error("The noise account is as large as it can get")] + NoiseSizeExceeded = 7, } impl From for ProgramError { diff --git a/src/instruction.rs b/src/instruction.rs index 6674f6c..ce4465c 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -26,10 +26,11 @@ pub enum OreInstruction { #[account(7, name = "bus_5", desc = "Ore bus account 5", writable)] #[account(8, name = "bus_6", desc = "Ore bus account 6", writable)] #[account(9, name = "bus_7", desc = "Ore bus account 7", writable)] - #[account(10, name = "mint", desc = "Ore token mint account", writable)] - #[account(11, name = "treasury", desc = "Ore treasury account", writable)] - #[account(12, name = "treasury_tokens", desc = "Ore treasury token account", writable)] - #[account(13, name = "token_program", desc = "SPL token program")] + #[account(10, name = "config", desc = "Ore config account")] + #[account(11, name = "mint", desc = "Ore token mint account", writable)] + #[account(12, name = "treasury", desc = "Ore treasury account", writable)] + #[account(13, name = "treasury_tokens", desc = "Ore treasury token account", writable)] + #[account(14, name = "token_program", desc = "SPL token program")] Reset = 0, #[account(0, name = "ore_program", desc = "Ore program")] @@ -41,9 +42,10 @@ pub enum OreInstruction { #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "bus", desc = "Ore bus account", writable)] - #[account(3, name = "proof", desc = "Ore proof account", writable)] - #[account(4, name = "treasury", desc = "Ore treasury account")] - #[account(5, name = "slot_hashes", desc = "Solana slot hashes sysvar")] + #[account(3, name = "config", desc = "Ore config account")] + #[account(4, name = "noise", desc = "Ore noise account")] + #[account(5, name = "proof", desc = "Ore proof account", writable)] + #[account(6, name = "slot_hashes", desc = "Solana slot hashes sysvar")] Mine = 2, #[account(0, name = "ore_program", desc = "Ore program")] @@ -85,23 +87,24 @@ pub enum OreInstruction { #[account(9, name = "bus_7", desc = "Ore bus account 7", writable)] #[account(10, name = "metadata", desc = "Ore mint metadata account", writable)] #[account(11, name = "mint", desc = "Ore mint account", writable)] - #[account(12, name = "treasury", desc = "Ore treasury account", writable)] - #[account(13, name = "treasury_tokens", desc = "Ore treasury token account", writable)] - #[account(14, name = "system_program", desc = "Solana system program")] - #[account(15, name = "token_program", desc = "SPL token program")] - #[account(16, name = "associated_token_program", desc = "SPL associated token program")] - #[account(17, name = "mpl_metadata_program", desc = "Metaplex metadata program")] - #[account(18, name = "rent", desc = "Solana rent sysvar")] + #[account(12, name = "noise", desc = "Ore noise account", writable)] + #[account(13, name = "treasury", desc = "Ore treasury account", writable)] + #[account(14, name = "treasury_tokens", desc = "Ore treasury token account", writable)] + #[account(15, name = "system_program", desc = "Solana system program")] + #[account(16, name = "token_program", desc = "SPL token program")] + #[account(17, name = "associated_token_program", desc = "SPL associated token program")] + #[account(18, name = "mpl_metadata_program", desc = "Metaplex metadata program")] + #[account(19, name = "rent", desc = "Solana rent sysvar")] Initialize = 100, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] - #[account(2, name = "config", desc = "Ore config account")] + #[account(2, name = "config", desc = "Ore config account", writable)] UpdateAdmin = 101, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] - #[account(2, name = "config", desc = "Ore config account")] + #[account(2, name = "config", desc = "Ore config account", writable)] Pause = 102, } @@ -206,6 +209,7 @@ pub fn reset(signer: Pubkey) -> Instruction { AccountMeta::new(BUS_ADDRESSES[5], false), AccountMeta::new(BUS_ADDRESSES[6], false), AccountMeta::new(BUS_ADDRESSES[7], false), + AccountMeta::new(CONFIG_ADDRESS, false), AccountMeta::new(MINT_ADDRESS, false), AccountMeta::new(TREASURY_ADDRESS, false), AccountMeta::new(treasury_tokens, false), @@ -360,7 +364,7 @@ pub fn initialize(signer: Pubkey) -> Instruction { AccountMeta::new_readonly(system_program::id(), false), AccountMeta::new_readonly(spl_token::id(), false), AccountMeta::new_readonly(spl_associated_token_account::id(), false), - AccountMeta::new_readonly(mpl_token_metadata::ID, false), + // AccountMeta::new_readonly(mpl_token_metadata::ID, false), AccountMeta::new_readonly(sysvar::rent::id(), false), ], data: [ diff --git a/src/loaders.rs b/src/loaders.rs index 81f0a6d..eaddb54 100644 --- a/src/loaders.rs +++ b/src/loaders.rs @@ -7,7 +7,7 @@ use spl_token::state::Mint; use crate::{ state::{Bus, Config, Proof, Treasury}, utils::{AccountDeserialize, Discriminator}, - BUS_ADDRESSES, CONFIG_ADDRESS, NOISE_ADDRESS, TREASURY_ADDRESS, + BUS_ADDRESSES, CONFIG_ADDRESS, TREASURY_ADDRESS, }; /// Errors if: @@ -125,30 +125,6 @@ pub fn load_config<'a, 'info>( Ok(()) } -/// asdf -pub fn load_noise<'a, 'info>( - info: &'a AccountInfo<'info>, - is_writable: bool, -) -> Result<(), ProgramError> { - if info.owner.ne(&crate::id()) { - return Err(ProgramError::InvalidAccountOwner); - } - - if info.key.ne(&NOISE_ADDRESS) { - return Err(ProgramError::InvalidSeeds); - } - - if info.data_is_empty() { - return Err(ProgramError::UninitializedAccount); - } - - if is_writable && !info.is_writable { - return Err(ProgramError::InvalidAccountData); - } - - Ok(()) -} - /// Errors if: /// - Owner is not Ore program. /// - Data is empty. diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 2c90ea3..be483fe 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -17,8 +17,8 @@ use crate::{ utils::create_pda, utils::AccountDeserialize, utils::Discriminator, - BUS, BUS_COUNT, CONFIG, METADATA, METADATA_NAME, METADATA_SYMBOL, METADATA_URI, MINT, - MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, + BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, METADATA, METADATA_NAME, METADATA_SYMBOL, + METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, }; /// Initialize sets up the Ore program. Its responsibilities include: @@ -48,7 +48,8 @@ pub fn process_initialize<'a, 'info>( let args = InitializeArgs::try_from_bytes(data)?; // Load accounts - let [signer, 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, metadata_info, mint_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program, metadata_program, rent_sysvar] = + // let [signer, 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, metadata_info, mint_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program, metadata_program, rent_sysvar] = + let [signer, 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, metadata_info, mint_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program, rent_sysvar] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -84,7 +85,7 @@ pub fn process_initialize<'a, 'info>( load_program(system_program, system_program::id())?; load_program(token_program, spl_token::id())?; load_program(associated_token_program, spl_associated_token_account::id())?; - load_program(metadata_program, mpl_token_metadata::ID)?; + // load_program(metadata_program, mpl_token_metadata::ID)?; load_sysvar(rent_sysvar, sysvar::rent::id())?; // Initialize bus accounts @@ -118,7 +119,7 @@ pub fn process_initialize<'a, 'info>( bus.rewards = 0; } - // TODO Initialize config + // Initialize config create_pda( config_info, &crate::id(), @@ -131,9 +132,12 @@ pub fn process_initialize<'a, 'info>( config_data[0] = Config::discriminator() as u8; let config = Config::try_from_bytes_mut(&mut config_data)?; config.admin = *signer.key; - // config.difficulty = INITIAL_DIFFICULTY.into(); + config.base_reward_rate = INITIAL_BASE_REWARD_RATE; + config.last_reset_at = 0; + config.min_difficulty = 8; + config.paused = 0; - // TODO Initialize treasury + // Initialize treasury create_pda( treasury_info, &crate::id(), @@ -146,9 +150,6 @@ pub fn process_initialize<'a, 'info>( treasury_data[0] = Treasury::discriminator() as u8; let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; treasury.bump = args.treasury_bump as u64; - // treasury.last_reset_at = 0; - // treasury.reward_rate = INITIAL_REWARD_RATE; - // treasury.total_claimed_rewards = 0; drop(treasury_data); // Initialize mint @@ -178,30 +179,30 @@ pub fn process_initialize<'a, 'info>( )?; // Initialize mint metadata - mpl_token_metadata::instructions::CreateMetadataAccountV3Cpi { - __program: metadata_program, - metadata: metadata_info, - mint: mint_info, - mint_authority: treasury_info, - payer: signer, - update_authority: (signer, true), - system_program, - rent: Some(rent_sysvar), - __args: mpl_token_metadata::instructions::CreateMetadataAccountV3InstructionArgs { - data: mpl_token_metadata::types::DataV2 { - name: METADATA_NAME.to_string(), - symbol: METADATA_SYMBOL.to_string(), - uri: METADATA_URI.to_string(), - seller_fee_basis_points: 0, - creators: None, - collection: None, - uses: None, - }, - is_mutable: true, - collection_details: None, - }, - } - .invoke_signed(&[&[TREASURY, &[args.treasury_bump]]])?; + // mpl_token_metadata::instructions::CreateMetadataAccountV3Cpi { + // __program: metadata_program, + // metadata: metadata_info, + // mint: mint_info, + // mint_authority: treasury_info, + // payer: signer, + // update_authority: (signer, true), + // system_program, + // rent: Some(rent_sysvar), + // __args: mpl_token_metadata::instructions::CreateMetadataAccountV3InstructionArgs { + // data: mpl_token_metadata::types::DataV2 { + // name: METADATA_NAME.to_string(), + // symbol: METADATA_SYMBOL.to_string(), + // uri: METADATA_URI.to_string(), + // seller_fee_basis_points: 0, + // creators: None, + // collection: None, + // uses: None, + // }, + // is_mutable: true, + // collection_details: None, + // }, + // } + // .invoke_signed(&[&[TREASURY, &[args.treasury_bump]]])?; // Initialize treasury token account solana_program::program::invoke( diff --git a/src/processor/mine.rs b/src/processor/mine.rs index efc036f..031baff 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -44,13 +44,12 @@ pub fn process_mine<'a, 'info>( let args = MineArgs::try_from_bytes(data)?; // Load accounts - let [signer, bus_info, config_info, noise_info, proof_info, slot_hashes_info] = accounts else { + let [signer, bus_info, config_info, proof_info, slot_hashes_info] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; load_any_bus(bus_info, true)?; load_config(config_info, false)?; - load_noise(noise_info, false)?; load_proof(proof_info, signer.key, true)?; load_sysvar(slot_hashes_info, sysvar::slot_hashes::id())?; @@ -78,9 +77,7 @@ pub fn process_mine<'a, 'info>( // } // Calculate the hash from the provided nonce - let noise_data = noise_info.data.borrow(); - let hx = drillx::hash(&proof.challenge, &args.nonce, &noise_data); - drop(noise_data); + let hx = drillx::hash(&proof.challenge, &args.nonce); // Validate hash satisfies the minimnum difficulty let difficulty = drillx::difficulty(hx); From 43a01003a37adfc42f3280a7184bc0e41d8a6a5a Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 14:02:15 +0000 Subject: [PATCH 015/111] liveness/spam penalty --- src/processor/mine.rs | 55 +++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 031baff..68cdaa0 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -5,6 +5,7 @@ use solana_program::{ clock::Clock, entrypoint::ProgramResult, keccak::hashv, + log::sol_log, program_error::ProgramError, pubkey::Pubkey, slot_hashes::SlotHash, @@ -17,7 +18,7 @@ use crate::{ loaders::*, state::{Bus, Config, Proof}, utils::AccountDeserialize, - EPOCH_DURATION, + BUS_EPOCH_REWARDS, EPOCH_DURATION, }; // TODO Look into tx introspection to require 1 hash per tx @@ -81,6 +82,7 @@ pub fn process_mine<'a, 'info>( // Validate hash satisfies the minimnum difficulty let difficulty = drillx::difficulty(hx); + sol_log(&format!("Diff {}", difficulty)); if difficulty.le(&config.min_difficulty) { return Err(OreError::DifficultyInsufficient.into()); } @@ -90,12 +92,13 @@ pub fn process_mine<'a, 'info>( let mut reward = config .base_reward_rate .saturating_mul(2u64.saturating_pow(difficulty)); + sol_log(&format!("Base {}", reward)); // Apply staking multiplier if clock.slot.gt(&proof.last_deposit_slot) { // Only apply if last deposit was at least 1 block ago to prevent flash loan attacks. // TODO Cleanup math with a const here (unnecessary cus) - // TODO Maybe move const into config!? + // TODO Move const into config let max_stake = reward .saturating_mul(60) // min/hour .saturating_mul(24) // hour/day @@ -106,29 +109,45 @@ pub fn process_mine<'a, 'info>( .min(max_stake) .saturating_mul(reward) .saturating_div(max_stake); + sol_log(&format!("Staking {}", staking_reward)); reward = reward.saturating_add(staking_reward); } - // Apply liveness penalty - // TODO Should penalty be symmetric? - // TODO Or should the curve be steeper on the <1 min side? - // TODO Eg anything more frequent than 40 seconds should get 0 - // TODO Anything longer than 2 minutes should be 0 - let tolerance = 5i64; // TODO Get from config - let target_time = proof.last_hash_at.saturating_add(EPOCH_DURATION); - if clock - .unix_timestamp - .saturating_sub(target_time) - .abs() - .gt(&tolerance) - { - // TODO Apply + // Apply spam/liveness penalty + let tolerance_early = 5i64; // TODO Get from config + let tolerance_late = 5i64; // TODO Get from config + let t = clock.unix_timestamp; + let t_target = proof.last_hash_at.saturating_add(EPOCH_DURATION); + let t_early = t_target.saturating_sub(tolerance_early); + let t_late = t_target.saturating_add(tolerance_late); + if t.lt(&t_early) { + reward = 0; + sol_log("Spam penalty"); + } else if t.gt(&t_late) { + reward = reward.saturating_sub( + reward + .saturating_mul(t.saturating_sub(t_late) as u64) + .saturating_div( + t_target + .saturating_add(EPOCH_DURATION) + .saturating_sub(t_late) as u64, + ), + ); + sol_log(&format!( + "Liveness penalty ({} sec) {}", + t.saturating_sub(t_late), + reward, + )); } - // Update balances - // TODO Handle case where reward is higher than bus can payout + // Set upper bound to whatever is left in the bus let mut bus_data = bus_info.data.borrow_mut(); let bus = Bus::try_from_bytes_mut(&mut bus_data)?; + reward = reward.min(bus.rewards); + + // Update balances + sol_log(&format!("Total {}", reward)); + sol_log(&format!("Bus {}", bus.rewards)); bus.rewards = bus .rewards .checked_sub(reward) From 8ab3c274925b749b8ed789204ee7cdc1626338ac Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 17:43:04 +0000 Subject: [PATCH 016/111] 11 decimals --- src/consts.rs | 16 ++++++++-------- src/error.rs | 12 ++++-------- src/lib.rs | 6 ++---- src/processor/initialize.rs | 7 ++++--- src/processor/mine.rs | 15 ++++++++------- src/processor/reset.rs | 21 ++++++++++++++------- src/processor/upgrade.rs | 7 +++++-- src/state/bus.rs | 4 ++++ 8 files changed, 49 insertions(+), 39 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 48448a7..9b3906f 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -5,23 +5,23 @@ use solana_program::{pubkey, pubkey::Pubkey}; /// The reward rate to intialize the program with. pub const INITIAL_BASE_REWARD_RATE: u64 = 10u64.pow(3u32); -// TODO Migrate to 11 decimals? -/// The decimal precision of the ORE token. -/// Using SI prefixes, the smallest indivisible unit of ORE is a nanoORE. -/// 1 nanoORE = 0.000000001 ORE = one billionth of an ORE -pub const TOKEN_DECIMALS: u8 = 9; +/// The minimum difficulty to initialize the program with. +pub const INITIAL_MIN_DIFFICULTY: u32 = 12; -/// One ORE token, denominated in units of nanoORE. +/// The decimal precision of the ORE token. +pub const TOKEN_DECIMALS: u8 = 11; + +/// One ORE token, denominated in indivisible units. pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); /// The duration of an epoch, in units of seconds. pub const EPOCH_DURATION: i64 = 60; -/// The target quantity of ORE to be mined per epoch, in units of nanoORE. +/// The target quantity of ORE to be mined per epoch. /// Inflation rate ≈ 1 ORE / epoch (min 0, max 2) pub const TARGET_EPOCH_REWARDS: u64 = ONE_ORE; -/// The maximum quantity of ORE that can be mined per epoch, in units of nanoORE. +/// The maximum quantity of ORE that can be mined per epoch. pub const MAX_EPOCH_REWARDS: u64 = ONE_ORE.saturating_mul(2); /// The quantity of ORE each bus is allowed to issue per epoch. diff --git a/src/error.rs b/src/error.rs index 8880788..5fdaf61 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,17 +10,13 @@ pub enum OreError { #[error("The epoch has ended and needs reset")] NeedsReset = 1, #[error("The provided hash did not satisfy the minimum required difficulty")] - DifficultyInsufficient = 2, - #[error("The bus does not have enough rewards to issue at this time")] - BusRewardsInsufficient = 3, + HashTooEasy = 2, #[error("The claim amount cannot be greater than the claimable rewards")] - ClaimTooLarge = 4, + ClaimTooLarge = 3, #[error("The stake amount cannot exceed u64 size")] - StakeTooLarge = 5, + StakeTooLarge = 4, #[error("The clock time is invalid")] - ClockInvalid = 6, - #[error("The noise account is as large as it can get")] - NoiseSizeExceeded = 7, + ClockInvalid = 5, } impl From for ProgramError { diff --git a/src/lib.rs b/src/lib.rs index 724c880..c15417e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,10 +14,8 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; -// TODO u128 for internal rewards representation? -// TODO Admin fn for min difficulty? What if this were set automatically by u128 base reward rate? -// TODO Increase bus count? -// TODO Is downgrade necessary or no? +// TODO Admin fn for min difficulty? +// TODO Is downgrade necessary? declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index be483fe..3e9a083 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -17,8 +17,9 @@ use crate::{ utils::create_pda, utils::AccountDeserialize, utils::Discriminator, - BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, METADATA, METADATA_NAME, METADATA_SYMBOL, - METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, + BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, INITIAL_MIN_DIFFICULTY, METADATA, + METADATA_NAME, METADATA_SYMBOL, METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, + TREASURY, }; /// Initialize sets up the Ore program. Its responsibilities include: @@ -134,7 +135,7 @@ pub fn process_initialize<'a, 'info>( config.admin = *signer.key; config.base_reward_rate = INITIAL_BASE_REWARD_RATE; config.last_reset_at = 0; - config.min_difficulty = 8; + config.min_difficulty = INITIAL_MIN_DIFFICULTY; config.paused = 0; // Initialize treasury diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 68cdaa0..16bcddf 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -18,7 +18,7 @@ use crate::{ loaders::*, state::{Bus, Config, Proof}, utils::AccountDeserialize, - BUS_EPOCH_REWARDS, EPOCH_DURATION, + EPOCH_DURATION, }; // TODO Look into tx introspection to require 1 hash per tx @@ -69,7 +69,7 @@ pub fn process_mine<'a, 'info>( return Err(OreError::ClockInvalid.into()); } - // Validate epoch is active + // TODO Validate epoch is active // let treasury_data = treasury_info.data.borrow(); // let treasury = Treasury::try_from_bytes(&treasury_data)?; // let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION); @@ -84,7 +84,7 @@ pub fn process_mine<'a, 'info>( let difficulty = drillx::difficulty(hx); sol_log(&format!("Diff {}", difficulty)); if difficulty.le(&config.min_difficulty) { - return Err(OreError::DifficultyInsufficient.into()); + return Err(OreError::HashTooEasy.into()); } // Calculate base reward rate @@ -143,16 +143,17 @@ pub fn process_mine<'a, 'info>( // Set upper bound to whatever is left in the bus let mut bus_data = bus_info.data.borrow_mut(); let bus = Bus::try_from_bytes_mut(&mut bus_data)?; - reward = reward.min(bus.rewards); + let actual_reward = reward.min(bus.rewards); // Update balances sol_log(&format!("Total {}", reward)); sol_log(&format!("Bus {}", bus.rewards)); + bus.theoretical_rewards = bus.theoretical_rewards.saturating_add(reward); bus.rewards = bus .rewards - .checked_sub(reward) - .ok_or(OreError::BusRewardsInsufficient)?; - proof.balance = proof.balance.saturating_add(reward); + .checked_sub(actual_reward) + .expect("This should not happen"); + proof.balance = proof.balance.saturating_add(actual_reward); // Hash recent slot hash into the next challenge to prevent pre-mining attacks proof.challenge = hashv(&[ diff --git a/src/processor/reset.rs b/src/processor/reset.rs index 96b66b8..2b27b6f 100644 --- a/src/processor/reset.rs +++ b/src/processor/reset.rs @@ -18,7 +18,7 @@ use crate::{ /// Reset sets up the Ore program for the next epoch. Its responsibilities include: /// 1. Reset bus account rewards counters. /// 2. Adjust the reward rate to stabilize inflation. -/// 3. Top up the treasury token account to backup claims. +/// 3. Top up the treasury token account to fund claims. /// /// Safety requirements: /// - Reset is a permissionless instruction and can be invoked by any signer. @@ -29,8 +29,11 @@ use crate::{ /// Discussion: /// - It is important that `reset` can only be invoked once per 60 second period to ensure the supply growth rate /// stays within the guaranteed bounds of 0 ≤ R ≤ 2 ORE/min. -/// - The reward rate is dynamically adjusted based on last epoch's actual reward rate (proxy for hashpower) to -/// target an average supply growth rate of 1 ORE/min. +/// - The reward rate is dynamically adjusted based on last epoch's theoretical reward rate to target an average +/// supply growth rate of 1 ORE/min. +/// - The "theoretical" reward rate refers to the amount that would have been paid out if rewards were not capped by +/// the bus limits. It's necessary to use this value to ensure the reward rate update calculation accurately +/// accounts for the difficulty of submitted hashes. pub fn process_reset<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], @@ -85,17 +88,21 @@ pub fn process_reset<'a, 'info>( // Reset bus accounts and calculate actual rewards mined since last reset let mut total_remaining_rewards = 0u64; + let mut total_theoretical_rewards = 0u64; for i in 0..BUS_COUNT { let mut bus_data = busses[i].data.borrow_mut(); let bus = Bus::try_from_bytes_mut(&mut bus_data)?; total_remaining_rewards = total_remaining_rewards.saturating_add(bus.rewards); + total_theoretical_rewards = + total_theoretical_rewards.saturating_add(bus.theoretical_rewards); bus.rewards = BUS_EPOCH_REWARDS; + bus.theoretical_rewards = 0; } let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_remaining_rewards); // Update base reward rate for next epoch config.base_reward_rate = - calculate_new_reward_rate(config.base_reward_rate, total_epoch_rewards); + calculate_new_reward_rate(config.base_reward_rate, total_theoretical_rewards); // Fund treasury token account solana_program::program::invoke_signed( @@ -135,9 +142,9 @@ pub(crate) fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) - } // Calculate new reward rate. - let new_rate = (current_rate) - .saturating_mul(TARGET_EPOCH_REWARDS) - .saturating_div(epoch_rewards) as u64; + let new_rate = (current_rate as u128) + .saturating_mul(TARGET_EPOCH_REWARDS as u128) + .saturating_div(epoch_rewards as u128) as u64; // Smooth reward rate so it cannot change by more than a constant factor from one epoch to the next. let new_rate_min = current_rate.saturating_div(SMOOTHING_FACTOR); diff --git a/src/processor/upgrade.rs b/src/processor/upgrade.rs index fa4aeef..4c149f8 100644 --- a/src/processor/upgrade.rs +++ b/src/processor/upgrade.rs @@ -53,8 +53,11 @@ pub fn process_upgrade<'a, 'info>( ], )?; + // Account for decimals change. + // v1 token has 9 decimals. v2 token has 11. + let amount_to_mint = amount.saturating_mul(100); + // Mint to the beneficiary account - // TODO Account for decimals change! let treasury_data = treasury_info.data.borrow(); let treasury = Treasury::try_from_bytes(&treasury_data)?; let treasury_bump = treasury.bump as u8; @@ -66,7 +69,7 @@ pub fn process_upgrade<'a, 'info>( beneficiary_info.key, treasury_info.key, &[treasury_info.key], - amount, + amount_to_mint, )?, &[ token_program.clone(), diff --git a/src/state/bus.rs b/src/state/bus.rs index 849fe36..a01fd49 100644 --- a/src/state/bus.rs +++ b/src/state/bus.rs @@ -17,6 +17,10 @@ pub struct Bus { /// The quantity of rewards this bus can issue in the current epoch epoch. pub rewards: u64, + + // TODO Come up with better name + /// The rewards that would have been paid out this epoch if the bus had no limit. + pub theoretical_rewards: u64, } impl Discriminator for Bus { From f7572aa2cf9a7d516acc8e1abae1256df8f3268f Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 17:50:26 +0000 Subject: [PATCH 017/111] tolerance to config --- src/consts.rs | 3 +++ src/lib.rs | 1 + src/processor/initialize.rs | 8 +++++--- src/processor/mine.rs | 16 +++++++--------- src/state/bus.rs | 1 + src/state/config.rs | 6 ++++++ 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 9b3906f..8c7e73c 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -8,6 +8,9 @@ pub const INITIAL_BASE_REWARD_RATE: u64 = 10u64.pow(3u32); /// The minimum difficulty to initialize the program with. pub const INITIAL_MIN_DIFFICULTY: u32 = 12; +/// The spam/liveness tolerance to initialize the program with. +pub const INITIAL_TOLERANCE: i64 = 5; + /// The decimal precision of the ORE token. pub const TOKEN_DECIMALS: u8 = 11; diff --git a/src/lib.rs b/src/lib.rs index c15417e..8a26acc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ use solana_program::{ }; // TODO Admin fn for min difficulty? +// TODO Admin fn for spam/liveness tolerances? // TODO Is downgrade necessary? declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 3e9a083..022c04a 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -17,9 +17,9 @@ use crate::{ utils::create_pda, utils::AccountDeserialize, utils::Discriminator, - BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, INITIAL_MIN_DIFFICULTY, METADATA, - METADATA_NAME, METADATA_SYMBOL, METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, - TREASURY, + BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, INITIAL_MIN_DIFFICULTY, INITIAL_TOLERANCE, + METADATA, METADATA_NAME, METADATA_SYMBOL, METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, + TOKEN_DECIMALS, TREASURY, }; /// Initialize sets up the Ore program. Its responsibilities include: @@ -137,6 +137,8 @@ pub fn process_initialize<'a, 'info>( config.last_reset_at = 0; config.min_difficulty = INITIAL_MIN_DIFFICULTY; config.paused = 0; + config.tolerance_liveness = INITIAL_TOLERANCE; + config.tolerance_spam = INITIAL_TOLERANCE; // Initialize treasury create_pda( diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 16bcddf..a7cc6f0 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -114,28 +114,26 @@ pub fn process_mine<'a, 'info>( } // Apply spam/liveness penalty - let tolerance_early = 5i64; // TODO Get from config - let tolerance_late = 5i64; // TODO Get from config let t = clock.unix_timestamp; let t_target = proof.last_hash_at.saturating_add(EPOCH_DURATION); - let t_early = t_target.saturating_sub(tolerance_early); - let t_late = t_target.saturating_add(tolerance_late); - if t.lt(&t_early) { + let t_spam = t_target.saturating_sub(config.tolerance_spam); + let t_liveness = t_target.saturating_add(config.tolerance_liveness); + if t.lt(&t_spam) { reward = 0; sol_log("Spam penalty"); - } else if t.gt(&t_late) { + } else if t.gt(&t_liveness) { reward = reward.saturating_sub( reward - .saturating_mul(t.saturating_sub(t_late) as u64) + .saturating_mul(t.saturating_sub(t_liveness) as u64) .saturating_div( t_target .saturating_add(EPOCH_DURATION) - .saturating_sub(t_late) as u64, + .saturating_sub(t_liveness) as u64, ), ); sol_log(&format!( "Liveness penalty ({} sec) {}", - t.saturating_sub(t_late), + t.saturating_sub(t_liveness), reward, )); } diff --git a/src/state/bus.rs b/src/state/bus.rs index a01fd49..b26b726 100644 --- a/src/state/bus.rs +++ b/src/state/bus.rs @@ -15,6 +15,7 @@ pub struct Bus { /// The ID of the bus account. pub id: u64, + // TODO Update logic to count up rather than down /// The quantity of rewards this bus can issue in the current epoch epoch. pub rewards: u64, diff --git a/src/state/config.rs b/src/state/config.rs index b796b69..725d8c3 100644 --- a/src/state/config.rs +++ b/src/state/config.rs @@ -27,6 +27,12 @@ pub struct Config { /// Is mining paused. pub paused: u32, + + /// Seconds prior to a miner's target time during which their hashes will not be penalized. + pub tolerance_spam: i64, + + /// Seconds after a miner's target time during which their hashes will not be penalized. + pub tolerance_liveness: i64, } impl Discriminator for Config { From 54bd0a025c3ac9df064434038bcf0baf30163717 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 18:17:06 +0000 Subject: [PATCH 018/111] consts --- src/consts.rs | 13 ++++++++----- src/processor/initialize.rs | 6 ++---- src/processor/mine.rs | 24 +++++++++--------------- src/processor/pause.rs | 2 +- src/processor/reset.rs | 6 +++--- src/state/config.rs | 7 +------ 6 files changed, 24 insertions(+), 34 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 8c7e73c..71a12a9 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -5,20 +5,23 @@ use solana_program::{pubkey, pubkey::Pubkey}; /// The reward rate to intialize the program with. pub const INITIAL_BASE_REWARD_RATE: u64 = 10u64.pow(3u32); -/// The minimum difficulty to initialize the program with. -pub const INITIAL_MIN_DIFFICULTY: u32 = 12; - /// The spam/liveness tolerance to initialize the program with. pub const INITIAL_TOLERANCE: i64 = 5; +/// The minimum difficulty required of all submitted hashes. +pub const MIN_DIFFICULTY: u32 = 12; + /// The decimal precision of the ORE token. pub const TOKEN_DECIMALS: u8 = 11; /// One ORE token, denominated in indivisible units. pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); -/// The duration of an epoch, in units of seconds. -pub const EPOCH_DURATION: i64 = 60; +/// The duration of one minute, in seconds. +pub const ONE_MINUTE: i64 = 60; + +/// The duration of two years, in minutes. +pub const TWO_YEARS: u64 = 60 * 24 * 365 * 2; /// The target quantity of ORE to be mined per epoch. /// Inflation rate ≈ 1 ORE / epoch (min 0, max 2) diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 022c04a..85c424a 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -17,9 +17,8 @@ use crate::{ utils::create_pda, utils::AccountDeserialize, utils::Discriminator, - BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, INITIAL_MIN_DIFFICULTY, INITIAL_TOLERANCE, - METADATA, METADATA_NAME, METADATA_SYMBOL, METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, - TOKEN_DECIMALS, TREASURY, + BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, INITIAL_TOLERANCE, METADATA, MINT, + MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, }; /// Initialize sets up the Ore program. Its responsibilities include: @@ -135,7 +134,6 @@ pub fn process_initialize<'a, 'info>( config.admin = *signer.key; config.base_reward_rate = INITIAL_BASE_REWARD_RATE; config.last_reset_at = 0; - config.min_difficulty = INITIAL_MIN_DIFFICULTY; config.paused = 0; config.tolerance_liveness = INITIAL_TOLERANCE; config.tolerance_spam = INITIAL_TOLERANCE; diff --git a/src/processor/mine.rs b/src/processor/mine.rs index a7cc6f0..0ce5c83 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -18,7 +18,7 @@ use crate::{ loaders::*, state::{Bus, Config, Proof}, utils::AccountDeserialize, - EPOCH_DURATION, + MIN_DIFFICULTY, ONE_MINUTE, TWO_YEARS, }; // TODO Look into tx introspection to require 1 hash per tx @@ -72,7 +72,7 @@ pub fn process_mine<'a, 'info>( // TODO Validate epoch is active // let treasury_data = treasury_info.data.borrow(); // let treasury = Treasury::try_from_bytes(&treasury_data)?; - // let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION); + // let threshold = treasury.last_reset_at.saturating_add(ONE_MINUTE); // if clock.unix_timestamp.ge(&threshold) { // return Err(OreError::NeedsReset.into()); // } @@ -83,27 +83,21 @@ pub fn process_mine<'a, 'info>( // Validate hash satisfies the minimnum difficulty let difficulty = drillx::difficulty(hx); sol_log(&format!("Diff {}", difficulty)); - if difficulty.le(&config.min_difficulty) { + if difficulty.lt(&MIN_DIFFICULTY) { return Err(OreError::HashTooEasy.into()); } // Calculate base reward rate - let difficulty = difficulty.saturating_sub(config.min_difficulty); + let difficulty = difficulty.saturating_sub(MIN_DIFFICULTY); let mut reward = config .base_reward_rate .saturating_mul(2u64.saturating_pow(difficulty)); sol_log(&format!("Base {}", reward)); - // Apply staking multiplier + // Apply staking multiplier, only if last deposit was at least 1 block ago to prevent flash loan attacks if clock.slot.gt(&proof.last_deposit_slot) { - // Only apply if last deposit was at least 1 block ago to prevent flash loan attacks. - // TODO Cleanup math with a const here (unnecessary cus) - // TODO Move const into config - let max_stake = reward - .saturating_mul(60) // min/hour - .saturating_mul(24) // hour/day - .saturating_mul(365) // day/year - .saturating_mul(2); // year + // TODO Move staking requirement into config? Admin adjustable? + let max_stake = reward.saturating_mul(TWO_YEARS); let staking_reward = proof .balance .min(max_stake) @@ -115,7 +109,7 @@ pub fn process_mine<'a, 'info>( // Apply spam/liveness penalty let t = clock.unix_timestamp; - let t_target = proof.last_hash_at.saturating_add(EPOCH_DURATION); + let t_target = proof.last_hash_at.saturating_add(ONE_MINUTE); let t_spam = t_target.saturating_sub(config.tolerance_spam); let t_liveness = t_target.saturating_add(config.tolerance_liveness); if t.lt(&t_spam) { @@ -127,7 +121,7 @@ pub fn process_mine<'a, 'info>( .saturating_mul(t.saturating_sub(t_liveness) as u64) .saturating_div( t_target - .saturating_add(EPOCH_DURATION) + .saturating_add(ONE_MINUTE) .saturating_sub(t_liveness) as u64, ), ); diff --git a/src/processor/pause.rs b/src/processor/pause.rs index be2cbb9..4970c6e 100644 --- a/src/processor/pause.rs +++ b/src/processor/pause.rs @@ -28,7 +28,7 @@ pub fn process_pause<'a, 'info>( } // Update paused - config.paused = args.paused as u32; + config.paused = args.paused as u64; Ok(()) } diff --git a/src/processor/reset.rs b/src/processor/reset.rs index 2b27b6f..95627fd 100644 --- a/src/processor/reset.rs +++ b/src/processor/reset.rs @@ -11,8 +11,8 @@ use crate::{ }, state::{Bus, Config}, utils::AccountDeserialize, - BUS_COUNT, BUS_EPOCH_REWARDS, EPOCH_DURATION, MAX_EPOCH_REWARDS, MINT_ADDRESS, - SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS, TREASURY, TREASURY_BUMP, + BUS_COUNT, BUS_EPOCH_REWARDS, MAX_EPOCH_REWARDS, MINT_ADDRESS, ONE_MINUTE, SMOOTHING_FACTOR, + TARGET_EPOCH_REWARDS, TREASURY, TREASURY_BUMP, }; /// Reset sets up the Ore program for the next epoch. Its responsibilities include: @@ -78,7 +78,7 @@ pub fn process_reset<'a, 'info>( // Validate enough time has passed since last reset let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; - let threshold = config.last_reset_at.saturating_add(EPOCH_DURATION); + let threshold = config.last_reset_at.saturating_add(ONE_MINUTE); if clock.unix_timestamp.lt(&threshold) { return Ok(()); } diff --git a/src/state/config.rs b/src/state/config.rs index 725d8c3..ea15408 100644 --- a/src/state/config.rs +++ b/src/state/config.rs @@ -2,8 +2,6 @@ use bytemuck::{Pod, Zeroable}; use shank::ShankAccount; use solana_program::pubkey::Pubkey; -// TODO next_min_difficulty: Option, update on reset - use crate::{ impl_account_from_bytes, impl_to_bytes, utils::{AccountDiscriminator, Discriminator}, @@ -22,11 +20,8 @@ pub struct Config { /// The timestamp of the last reset pub last_reset_at: i64, - /// The minimum accepted difficulty. - pub min_difficulty: u32, - /// Is mining paused. - pub paused: u32, + pub paused: u64, /// Seconds prior to a miner's target time during which their hashes will not be penalized. pub tolerance_spam: i64, From 42b81b1d0f166f7a81c55df2ddcbeb0247042f37 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 18:23:53 +0000 Subject: [PATCH 019/111] comments --- src/lib.rs | 1 + src/processor/mine.rs | 2 +- src/state/bus.rs | 6 ++---- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8a26acc..d6c17cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ use solana_program::{ // TODO Admin fn for min difficulty? // TODO Admin fn for spam/liveness tolerances? +// TODO Alternative to bincode? // TODO Is downgrade necessary? declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 0ce5c83..525b0ea 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -94,7 +94,7 @@ pub fn process_mine<'a, 'info>( .saturating_mul(2u64.saturating_pow(difficulty)); sol_log(&format!("Base {}", reward)); - // Apply staking multiplier, only if last deposit was at least 1 block ago to prevent flash loan attacks + // Apply staking multiplier, only if last deposit was at least 1 block ago to prevent flash loan attacks. if clock.slot.gt(&proof.last_deposit_slot) { // TODO Move staking requirement into config? Admin adjustable? let max_stake = reward.saturating_mul(TWO_YEARS); diff --git a/src/state/bus.rs b/src/state/bus.rs index b26b726..a143fe1 100644 --- a/src/state/bus.rs +++ b/src/state/bus.rs @@ -15,12 +15,10 @@ pub struct Bus { /// The ID of the bus account. pub id: u64, - // TODO Update logic to count up rather than down - /// The quantity of rewards this bus can issue in the current epoch epoch. + /// The quantity of rewards this bus has left to issue in the current epoch epoch. pub rewards: u64, - // TODO Come up with better name - /// The rewards that would have been paid out this epoch if the bus had no limit. + /// The rewards that would have been paid out by this bus in the current epoch if the bus had no limit. pub theoretical_rewards: u64, } From c13f5ce8cc5c2f7ea38c278715b2d76c1f74c79c Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 18:25:19 +0000 Subject: [PATCH 020/111] active epoch --- src/processor/mine.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 525b0ea..29bbce3 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -69,13 +69,13 @@ pub fn process_mine<'a, 'info>( return Err(OreError::ClockInvalid.into()); } - // TODO Validate epoch is active - // let treasury_data = treasury_info.data.borrow(); - // let treasury = Treasury::try_from_bytes(&treasury_data)?; - // let threshold = treasury.last_reset_at.saturating_add(ONE_MINUTE); - // if clock.unix_timestamp.ge(&threshold) { - // return Err(OreError::NeedsReset.into()); - // } + // Validate epoch is active + if clock + .unix_timestamp + .ge(&config.last_reset_at.saturating_add(ONE_MINUTE)) + { + return Err(OreError::NeedsReset.into()); + } // Calculate the hash from the provided nonce let hx = drillx::hash(&proof.challenge, &args.nonce); From 0c4cdb7b6409695686401edb532f5f95b5869304 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 18:31:02 +0000 Subject: [PATCH 021/111] remove todo --- src/processor/initialize.rs | 2 +- src/processor/mine.rs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 85c424a..cdf8d0c 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -18,7 +18,7 @@ use crate::{ utils::AccountDeserialize, utils::Discriminator, BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, INITIAL_TOLERANCE, METADATA, MINT, - MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, + MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, TWO_YEARS, }; /// Initialize sets up the Ore program. Its responsibilities include: diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 29bbce3..87e15b6 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -69,7 +69,7 @@ pub fn process_mine<'a, 'info>( return Err(OreError::ClockInvalid.into()); } - // Validate epoch is active + // TODO Validate epoch is active if clock .unix_timestamp .ge(&config.last_reset_at.saturating_add(ONE_MINUTE)) @@ -96,13 +96,12 @@ pub fn process_mine<'a, 'info>( // Apply staking multiplier, only if last deposit was at least 1 block ago to prevent flash loan attacks. if clock.slot.gt(&proof.last_deposit_slot) { - // TODO Move staking requirement into config? Admin adjustable? - let max_stake = reward.saturating_mul(TWO_YEARS); + let stake_max = reward.saturating_mul(TWO_YEARS); let staking_reward = proof .balance - .min(max_stake) + .min(stake_max) .saturating_mul(reward) - .saturating_div(max_stake); + .saturating_div(stake_max); sol_log(&format!("Staking {}", staking_reward)); reward = reward.saturating_add(staking_reward); } From 5b2463a92fd3d38d72fd2c563e495619a09778d5 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 18:37:19 +0000 Subject: [PATCH 022/111] staking --- src/processor/mine.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 87e15b6..7b6ba8f 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -94,16 +94,19 @@ pub fn process_mine<'a, 'info>( .saturating_mul(2u64.saturating_pow(difficulty)); sol_log(&format!("Base {}", reward)); - // Apply staking multiplier, only if last deposit was at least 1 block ago to prevent flash loan attacks. + // Apply staking multiplier. + // To prevent flash loan attacks, multiplier is only applied if the last deposit was at least 1 block ago. + // The multiplier can range 1x to 2x. To get the maximum multiplier, the stake balance must be + // greater than or equal to two years worth of rewards at the selected difficulty. if clock.slot.gt(&proof.last_deposit_slot) { - let stake_max = reward.saturating_mul(TWO_YEARS); + let upper_bound = reward.saturating_mul(TWO_YEARS); let staking_reward = proof .balance - .min(stake_max) + .min(upper_bound) .saturating_mul(reward) - .saturating_div(stake_max); - sol_log(&format!("Staking {}", staking_reward)); + .saturating_div(upper_bound); reward = reward.saturating_add(staking_reward); + sol_log(&format!("Staking {}", staking_reward)); } // Apply spam/liveness penalty From 4dd09b4a70db0321d82c877e0db3c7ed33f68a80 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 18:37:31 +0000 Subject: [PATCH 023/111] remove todo --- src/processor/mine.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 7b6ba8f..898fa25 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -69,7 +69,7 @@ pub fn process_mine<'a, 'info>( return Err(OreError::ClockInvalid.into()); } - // TODO Validate epoch is active + // Validate epoch is active if clock .unix_timestamp .ge(&config.last_reset_at.saturating_add(ONE_MINUTE)) From 93657d02ccb8ff1a546cc7b5df40800047730196 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 18:47:48 +0000 Subject: [PATCH 024/111] update tolerance --- src/error.rs | 2 ++ src/instruction.rs | 41 ++++++++++++++++++++++++++++- src/lib.rs | 2 +- src/processor/initialize.rs | 2 +- src/processor/mod.rs | 2 ++ src/processor/update_tolerance.rs | 43 +++++++++++++++++++++++++++++++ 6 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 src/processor/update_tolerance.rs diff --git a/src/error.rs b/src/error.rs index 5fdaf61..452e229 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,6 +17,8 @@ pub enum OreError { StakeTooLarge = 4, #[error("The clock time is invalid")] ClockInvalid = 5, + #[error("The tolerance cannot be negative")] + ToleranceNegative = 6, } impl From for ProgramError { diff --git a/src/instruction.rs b/src/instruction.rs index ce4465c..0a32ac1 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -105,7 +105,12 @@ pub enum OreInstruction { #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] #[account(2, name = "config", desc = "Ore config account", writable)] - Pause = 102, + UpdateTolerance = 102, + + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Admin signer", signer)] + #[account(2, name = "config", desc = "Ore config account", writable)] + Pause = 103, } impl OreInstruction { @@ -167,6 +172,13 @@ pub struct UpdateAdminArgs { pub new_admin: Pubkey, } +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct UpdateToleranceArgs { + pub tolerance_liveness: u64, + pub tolerance_spam: u64, +} + #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct PauseArgs { @@ -180,6 +192,7 @@ impl_to_bytes!(ClaimArgs); impl_to_bytes!(StakeArgs); impl_to_bytes!(UpgradeArgs); impl_to_bytes!(UpdateAdminArgs); +impl_to_bytes!(UpdateToleranceArgs); impl_to_bytes!(PauseArgs); impl_instruction_from_bytes!(InitializeArgs); @@ -189,6 +202,7 @@ impl_instruction_from_bytes!(ClaimArgs); impl_instruction_from_bytes!(StakeArgs); impl_instruction_from_bytes!(UpgradeArgs); impl_instruction_from_bytes!(UpdateAdminArgs); +impl_instruction_from_bytes!(UpdateToleranceArgs); impl_instruction_from_bytes!(PauseArgs); /// Builds a reset instruction. @@ -406,6 +420,31 @@ pub fn update_admin(signer: Pubkey, new_admin: Pubkey) -> Instruction { } } +/// Build an update_tolerance instruction. +pub fn update_tolerance( + signer: Pubkey, + new_liveness_tolerance: u64, + new_spam_tolerance: u64, +) -> Instruction { + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(CONFIG_ADDRESS, false), + ], + data: [ + OreInstruction::UpdateTolerance.to_vec(), + UpdateToleranceArgs { + tolerance_liveness: new_liveness_tolerance, + tolerance_spam: new_spam_tolerance, + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + /// Build a pause instruction. pub fn pause(signer: Pubkey, paused: bool) -> Instruction { Instruction { diff --git a/src/lib.rs b/src/lib.rs index d6c17cf..17c0395 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,6 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; -// TODO Admin fn for min difficulty? // TODO Admin fn for spam/liveness tolerances? // TODO Alternative to bincode? // TODO Is downgrade necessary? @@ -46,6 +45,7 @@ pub fn process_instruction( OreInstruction::Upgrade => process_upgrade(program_id, accounts, data)?, OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, OreInstruction::UpdateAdmin => process_update_admin(program_id, accounts, data)?, + OreInstruction::UpdateTolerance => process_update_tolerance(program_id, accounts, data)?, OreInstruction::Pause => process_pause(program_id, accounts, data)?, } diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index cdf8d0c..85c424a 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -18,7 +18,7 @@ use crate::{ utils::AccountDeserialize, utils::Discriminator, BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, INITIAL_TOLERANCE, METADATA, MINT, - MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, TWO_YEARS, + MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, }; /// Initialize sets up the Ore program. Its responsibilities include: diff --git a/src/processor/mod.rs b/src/processor/mod.rs index 66b20b4..f1dd9cc 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -6,6 +6,7 @@ mod register; mod reset; mod stake; mod update_admin; +mod update_tolerance; mod upgrade; pub use claim::*; @@ -16,4 +17,5 @@ pub use register::*; pub use reset::*; pub use stake::*; pub use update_admin::*; +pub use update_tolerance::*; pub use upgrade::*; diff --git a/src/processor/update_tolerance.rs b/src/processor/update_tolerance.rs new file mode 100644 index 0000000..25ea1c9 --- /dev/null +++ b/src/processor/update_tolerance.rs @@ -0,0 +1,43 @@ +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; + +use crate::{ + error::OreError, instruction::UpdateToleranceArgs, loaders::*, state::Config, + utils::AccountDeserialize, +}; + +pub fn process_update_tolerance<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + data: &[u8], +) -> ProgramResult { + // Parse args + let args = UpdateToleranceArgs::try_from_bytes(data)?; + + // Load accounts + let [signer, config_info] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer)?; + load_config(config_info, true)?; + + // Validate signer is admin + let mut config_data = config_info.data.borrow_mut(); + let config = Config::try_from_bytes_mut(&mut config_data)?; + if config.admin.ne(&signer.key) { + return Err(ProgramError::MissingRequiredSignature); + } + + // Update tolerances + config.tolerance_liveness = args.tolerance_liveness as i64; + config.tolerance_spam = args.tolerance_spam as i64; + + // Sanity checks + if config.tolerance_liveness.lt(&0) || config.tolerance_spam.lt(&0) { + return Err(OreError::ToleranceNegative.into()); + } + + Ok(()) +} From aa27fc6f132e89ea765ac1b4a4c09ba17f7ca8ad Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 18:52:20 +0000 Subject: [PATCH 025/111] overflow check --- src/error.rs | 4 ++-- src/lib.rs | 1 - src/processor/update_tolerance.rs | 13 ++++++++----- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/error.rs b/src/error.rs index 452e229..03e9e30 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,8 +17,8 @@ pub enum OreError { StakeTooLarge = 4, #[error("The clock time is invalid")] ClockInvalid = 5, - #[error("The tolerance cannot be negative")] - ToleranceNegative = 6, + #[error("The tolerance cannot exceed i64 max value")] + ToleranceInvalid = 6, } impl From for ProgramError { diff --git a/src/lib.rs b/src/lib.rs index 17c0395..d1a0e83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,6 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; -// TODO Admin fn for spam/liveness tolerances? // TODO Alternative to bincode? // TODO Is downgrade necessary? diff --git a/src/processor/update_tolerance.rs b/src/processor/update_tolerance.rs index 25ea1c9..8a55294 100644 --- a/src/processor/update_tolerance.rs +++ b/src/processor/update_tolerance.rs @@ -30,14 +30,17 @@ pub fn process_update_tolerance<'a, 'info>( return Err(ProgramError::MissingRequiredSignature); } + // Overflow checks + if args.tolerance_liveness.gt(&(i64::MAX as u64)) { + return Err(OreError::ToleranceInvalid.into()); + } + if args.tolerance_spam.gt(&(i64::MAX as u64)) { + return Err(OreError::ToleranceInvalid.into()); + } + // Update tolerances config.tolerance_liveness = args.tolerance_liveness as i64; config.tolerance_spam = args.tolerance_spam as i64; - // Sanity checks - if config.tolerance_liveness.lt(&0) || config.tolerance_spam.lt(&0) { - return Err(OreError::ToleranceNegative.into()); - } - Ok(()) } From 38deb279954ca9bce00c9908a93cf6de8645dad4 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 20:44:00 +0000 Subject: [PATCH 026/111] tx introspection --- Cargo.lock | 669 ++++++++++++++---------------- src/consts.rs | 4 + src/error.rs | 8 +- src/instruction.rs | 1 + src/processor/mine.rs | 103 ++++- src/processor/stake.rs | 9 +- src/processor/update_tolerance.rs | 4 +- 7 files changed, 433 insertions(+), 365 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3b15f5..7e3fab0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,11 +65,11 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.8" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.11", "once_cell", "version_check", ] @@ -81,7 +81,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom 0.2.14", + "getrandom 0.2.11", "once_cell", "version_check", "zerocopy", @@ -89,9 +89,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -143,9 +143,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "ca87830a3e3fb156dc96cfbd31cb620265dd053be734723f22b760d6cc3c3051" [[package]] name = "aquamarine" @@ -360,9 +360,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.9" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9eabd7a98fe442131a17c316bd9349c43695e49e730c3c8e12cfb5f4da2693" +checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5" dependencies = [ "brotli", "flate2", @@ -389,7 +389,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -405,15 +405,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -483,9 +483,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.5.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" dependencies = [ "arrayref", "arrayvec", @@ -542,11 +542,11 @@ dependencies = [ [[package]] name = "borsh" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0901fc8eb0aca4c83be0106d6f2db17d86a08dfc2c25f0e84464bf381158add6" +checksum = "dbe5b10e214954177fb1dc9fbd20a1a2608fe99e6c832033bdc7cea287a20d77" dependencies = [ - "borsh-derive 1.4.0", + "borsh-derive 1.5.0", "cfg_aliases", ] @@ -578,15 +578,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51670c3aa053938b0ee3bd67c3817e471e626151131b934038e83c5bf8de48f5" +checksum = "d7a8646f94ab393e43e8b35a2558b1624bed28b97ee09c5d15456e3c9463f46d" dependencies = [ "once_cell", "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", "syn_derive", ] @@ -636,9 +636,9 @@ dependencies = [ [[package]] name = "brotli" -version = "5.0.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19483b140a7ac7174d34b5a581b406c64f84da5409d3e09cf4fff604f9270e67" +checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -647,9 +647,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6221fe77a248b9117d431ad93761222e1cf8ff282d9d1d5d9f53d6299a1cf76" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -663,9 +663,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bs58" -version = "0.5.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" dependencies = [ "tinyvec", ] @@ -681,9 +681,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bv" @@ -697,22 +697,22 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.6.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" +checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -723,9 +723,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "bzip2" @@ -760,13 +760,12 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.95" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "jobserver", "libc", - "once_cell", ] [[package]] @@ -783,9 +782,9 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", @@ -793,7 +792,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets 0.48.5", ] [[package]] @@ -842,7 +841,7 @@ dependencies = [ "once_cell", "strsim 0.10.0", "termcolor", - "textwrap 0.16.1", + "textwrap 0.16.0", ] [[package]] @@ -949,55 +948,61 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" dependencies = [ + "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" dependencies = [ + "autocfg", + "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" +dependencies = [ + "cfg-if", +] [[package]] name = "crunchy" @@ -1050,9 +1055,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.8" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ "darling_core", "darling_macro", @@ -1060,27 +1065,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.8" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.10.0", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] name = "darling_macro" -version = "0.20.8" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -1207,7 +1212,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -1230,7 +1235,7 @@ checksum = "a6cbae11b3de8fce2a456e8ea3dada226b35fe791f0dc1d360c0941f0bb681f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -1305,9 +1310,9 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encode_unicode" @@ -1317,9 +1322,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -1341,7 +1346,7 @@ checksum = "c19cbb53d33b57ac4df1f0af6b92c38c107cded663c4aea9fae1189dcfc17cf5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -1354,7 +1359,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -1366,7 +1371,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -1406,9 +1411,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" -version = "2.0.2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "feature-probe" @@ -1424,15 +1429,15 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall", "windows-sys 0.52.0", ] [[package]] name = "flate2" -version = "1.0.29" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4556222738635b7a3417ae6130d8f52201e45a0c4d1a907f0826383adb5f85e7" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -1524,7 +1529,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -1593,9 +1598,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "js-sys", @@ -1623,9 +1628,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.26" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "b553656127a00601c8ae5590fcfdc118e4083a7924b6cf4ffc1ea4b99dc429d7" dependencies = [ "bytes", "fnv", @@ -1633,7 +1638,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.6", + "indexmap 2.1.0", "slab", "tokio", "tokio-util 0.7.10", @@ -1655,7 +1660,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" dependencies = [ - "ahash 0.7.8", + "ahash 0.7.7", ] [[package]] @@ -1664,7 +1669,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.8", + "ahash 0.7.7", ] [[package]] @@ -1699,9 +1704,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "histogram" @@ -1741,9 +1746,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.12" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -1819,9 +1824,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1909,9 +1914,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown 0.14.3", @@ -1919,9 +1924,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.8" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" dependencies = [ "console", "instant", @@ -1956,15 +1961,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" dependencies = [ "libc", ] @@ -1995,9 +2000,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.5" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" dependencies = [ "cpufeatures", ] @@ -2016,9 +2021,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libsecp256k1" @@ -2082,15 +2087,15 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -2098,9 +2103,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "lru" @@ -2133,9 +2138,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" @@ -2157,9 +2162,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.9.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] @@ -2190,18 +2195,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.11" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -2344,12 +2349,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-derive" version = "0.3.3" @@ -2363,29 +2362,30 @@ dependencies = [ [[package]] name = "num-derive" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ + "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.44" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ "autocfg", "num-integer", @@ -2419,7 +2419,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi 0.3.3", "libc", ] @@ -2471,7 +2471,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -2480,10 +2480,10 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -2518,9 +2518,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl-probe" @@ -2552,7 +2552,7 @@ name = "ore-program" version = "1.2.1" dependencies = [ "array-const-fn-init", - "bs58 0.5.1", + "bs58 0.5.0", "bs64", "bytemuck", "const-crypto", @@ -2602,9 +2602,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", @@ -2612,15 +2612,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.1", + "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.48.5", ] [[package]] @@ -2673,29 +2673,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -2716,9 +2716,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" [[package]] name = "plain" @@ -2840,9 +2840,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -2864,7 +2864,7 @@ checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -2917,9 +2917,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -2983,7 +2983,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.14", + "getrandom 0.2.11", ] [[package]] @@ -3045,15 +3045,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "redox_syscall" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" -dependencies = [ - "bitflags 2.5.0", -] - [[package]] name = "regex" version = "1.10.4" @@ -3079,15 +3070,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ "async-compression", "base64 0.21.7", @@ -3112,7 +3103,6 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", "system-configuration", "tokio", "tokio-rustls", @@ -3122,7 +3112,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.25.4", + "webpki-roots 0.25.3", "winreg", ] @@ -3143,17 +3133,16 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.8" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" dependencies = [ "cc", - "cfg-if", - "getrandom 0.2.14", + "getrandom 0.2.11", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -3209,9 +3198,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ "bitflags 2.5.0", "errno", @@ -3222,12 +3211,12 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.11" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", + "ring 0.17.7", "rustls-webpki", "sct", ] @@ -3259,21 +3248,21 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", + "ring 0.17.7", "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.15" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "same-file" @@ -3316,7 +3305,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -3325,15 +3314,15 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", + "ring 0.17.7", "untrusted 0.9.0", ] [[package]] name = "security-framework" -version = "2.10.0" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -3344,9 +3333,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -3369,9 +3358,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.198" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" dependencies = [ "serde_derive", ] @@ -3387,13 +3376,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -3438,7 +3427,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -3567,9 +3556,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] @@ -3613,12 +3602,12 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -3671,7 +3660,7 @@ dependencies = [ "lz4", "memmap2", "modular-bitfield", - "num-derive 0.4.2", + "num-derive 0.4.1", "num-traits", "num_cpus", "num_enum 0.7.2", @@ -3716,7 +3705,7 @@ dependencies = [ "bincode", "bytemuck", "log", - "num-derive 0.4.2", + "num-derive 0.4.1", "num-traits", "rustc_version", "serde", @@ -3734,7 +3723,7 @@ version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13a4cbe27e78987b706caf90cbd16da9da3955c09a660b8107a96c2cb32f1124" dependencies = [ - "borsh 1.4.0", + "borsh 1.5.0", "futures", "solana-banks-interface", "solana-program", @@ -3841,7 +3830,7 @@ dependencies = [ "dashmap", "futures", "futures-util", - "indexmap 2.2.6", + "indexmap 2.1.0", "indicatif", "log", "quinn", @@ -3897,7 +3886,7 @@ dependencies = [ "bincode", "crossbeam-channel", "futures-util", - "indexmap 2.2.6", + "indexmap 2.1.0", "log", "rand 0.8.5", "rayon", @@ -3967,7 +3956,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -4092,7 +4081,7 @@ dependencies = [ "blake3", "borsh 0.10.3", "borsh 0.9.3", - "borsh 1.4.0", + "borsh 1.5.0", "bs58 0.4.0", "bv", "bytemuck", @@ -4100,7 +4089,7 @@ dependencies = [ "console_error_panic_hook", "console_log", "curve25519-dalek", - "getrandom 0.2.14", + "getrandom 0.2.11", "itertools", "js-sys", "lazy_static", @@ -4108,9 +4097,9 @@ dependencies = [ "libsecp256k1", "light-poseidon", "log", - "memoffset 0.9.1", + "memoffset 0.9.0", "num-bigint 0.4.4", - "num-derive 0.4.2", + "num-derive 0.4.1", "num-traits", "parking_lot", "rand 0.8.5", @@ -4144,7 +4133,7 @@ dependencies = [ "itertools", "libc", "log", - "num-derive 0.4.2", + "num-derive 0.4.1", "num-traits", "percentage", "rand 0.8.5", @@ -4260,7 +4249,7 @@ dependencies = [ "console", "dialoguer", "log", - "num-derive 0.4.2", + "num-derive 0.4.1", "num-traits", "parking_lot", "qstring", @@ -4361,7 +4350,7 @@ dependencies = [ "memmap2", "mockall", "modular-bitfield", - "num-derive 0.4.2", + "num-derive 0.4.1", "num-traits", "num_cpus", "num_enum 0.7.2", @@ -4418,7 +4407,7 @@ dependencies = [ "base64 0.21.7", "bincode", "bitflags 2.5.0", - "borsh 1.4.0", + "borsh 1.5.0", "bs58 0.4.0", "bytemuck", "byteorder", @@ -4435,7 +4424,7 @@ dependencies = [ "libsecp256k1", "log", "memmap2", - "num-derive 0.4.2", + "num-derive 0.4.1", "num-traits", "num_enum 0.7.2", "pbkdf2 0.11.0", @@ -4473,7 +4462,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -4524,7 +4513,7 @@ dependencies = [ "crossbeam-channel", "futures-util", "histogram", - "indexmap 2.2.6", + "indexmap 2.1.0", "itertools", "libc", "log", @@ -4584,7 +4573,7 @@ dependencies = [ "async-trait", "bincode", "futures-util", - "indexmap 2.2.6", + "indexmap 2.1.0", "indicatif", "log", "rayon", @@ -4682,7 +4671,7 @@ checksum = "725a39044d455c08fe83fca758e94e5ddfaa25f6e2e2cfd5c31d7afdcad8de38" dependencies = [ "bincode", "log", - "num-derive 0.4.2", + "num-derive 0.4.1", "num-traits", "rustc_version", "serde", @@ -4703,7 +4692,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39263f3e47a160b9b67896f2225d56e6872905c066152cbe61f5fd201c52a6d2" dependencies = [ "bytemuck", - "num-derive 0.4.2", + "num-derive 0.4.1", "num-traits", "solana-program-runtime", "solana-sdk", @@ -4726,7 +4715,7 @@ dependencies = [ "itertools", "lazy_static", "merlin", - "num-derive 0.4.2", + "num-derive 0.4.1", "num-traits", "rand 0.7.3", "serde", @@ -4788,7 +4777,7 @@ checksum = "992d9c64c2564cc8f63a4b508bf3ebcdf2254b0429b13cd1d31adb6162432a5f" dependencies = [ "assert_matches", "borsh 0.10.3", - "num-derive 0.4.2", + "num-derive 0.4.1", "num-traits", "solana-program", "spl-token", @@ -4798,9 +4787,9 @@ dependencies = [ [[package]] name = "spl-discriminator" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa600f2fe56f32e923261719bae640d873edadbc5237681a39b8e37bfd4d263" +checksum = "cce5d563b58ef1bb2cdbbfe0dfb9ffdc24903b10ae6a4df2d8f425ece375033f" dependencies = [ "bytemuck", "solana-program", @@ -4809,25 +4798,25 @@ dependencies = [ [[package]] name = "spl-discriminator-derive" -version = "0.1.2" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fd7858fc4ff8fb0e34090e41d7eb06a823e1057945c26d480bfc21d2338a93" +checksum = "fadbefec4f3c678215ca72bd71862697bb06b41fd77c0088902dd3203354387b" dependencies = [ "quote", "spl-discriminator-syn", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] name = "spl-discriminator-syn" -version = "0.1.2" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fea7be851bd98d10721782ea958097c03a0c2a07d8d4997041d0ece6319a63" +checksum = "0e5f2044ca42c8938d54d1255ce599c79a1ffd86b677dfab695caa20f9ffc3f2" dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.60", + "syn 2.0.48", "thiserror", ] @@ -4842,9 +4831,9 @@ dependencies = [ [[package]] name = "spl-pod" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5db7e4efb1107b0b8e52a13f035437cdcb36ef99c58f6d467f089d9b2915a" +checksum = "2881dddfca792737c0706fa0175345ab282b1b0879c7d877bad129645737c079" dependencies = [ "borsh 0.10.3", "bytemuck", @@ -4855,11 +4844,11 @@ dependencies = [ [[package]] name = "spl-program-error" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e0657b6490196971d9e729520ba934911ff41fbb2cb9004463dbe23cf8b4b4f" +checksum = "249e0318493b6bcf27ae9902600566c689b7dfba9f1bdff5893e92253374e78c" dependencies = [ - "num-derive 0.4.2", + "num-derive 0.4.1", "num-traits", "solana-program", "spl-program-error-derive", @@ -4868,21 +4857,21 @@ dependencies = [ [[package]] name = "spl-program-error-derive" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1845dfe71fd68f70382232742e758557afe973ae19e6c06807b2c30f5d5cb474" +checksum = "ab5269c8e868da17b6552ef35a51355a017bd8e0eae269c201fef830d35fa52c" dependencies = [ "proc-macro2", "quote", "sha2 0.10.8", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] name = "spl-tlv-account-resolution" -version = "0.5.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f335787add7fa711819f9e7c573f8145a5358a709446fe2d24bf2a88117c90" +checksum = "3f7020347c07892c08560d230fbb8a980316c9e198e22b198b7b9d951ff96047" dependencies = [ "bytemuck", "solana-program", @@ -4915,7 +4904,7 @@ checksum = "d697fac19fd74ff472dfcc13f0b442dd71403178ce1de7b5d16f83a33561c059" dependencies = [ "arrayref", "bytemuck", - "num-derive 0.4.2", + "num-derive 0.4.1", "num-traits", "num_enum 0.7.2", "solana-program", @@ -4976,9 +4965,9 @@ dependencies = [ [[package]] name = "spl-type-length-value" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9ebd75d29c5f48de5f6a9c114e08531030b75b8ac2c557600ac7da0b73b1e8" +checksum = "a468e6f6371f9c69aae760186ea9f1a01c2908351b06a5e0026d21cfc4d7ecac" dependencies = [ "bytemuck", "solana-program", @@ -5046,7 +5035,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -5074,9 +5063,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.60" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -5092,15 +5081,9 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "synstructure" version = "0.12.6" @@ -5182,21 +5165,22 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", + "redox_syscall", "rustix", "windows-sys 0.52.0", ] [[package]] name = "termcolor" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] @@ -5225,7 +5209,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -5236,7 +5220,7 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", "test-case-core", ] @@ -5251,35 +5235,35 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ "cfg-if", "once_cell", @@ -5287,13 +5271,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", "itoa", - "num-conv", "powerfmt", "serde", "time-core", @@ -5308,11 +5291,10 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" dependencies = [ - "num-conv", "time-core", ] @@ -5352,9 +5334,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -5377,7 +5359,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -5408,9 +5390,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", @@ -5429,7 +5411,7 @@ dependencies = [ "tokio", "tokio-rustls", "tungstenite", - "webpki-roots 0.25.4", + "webpki-roots 0.25.3", ] [[package]] @@ -5482,7 +5464,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.1.0", "toml_datetime", "winnow", ] @@ -5493,7 +5475,7 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.1.0", "toml_datetime", "winnow", ] @@ -5524,7 +5506,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -5596,9 +5578,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" [[package]] name = "unicode-ident" @@ -5608,9 +5590,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] @@ -5711,9 +5693,9 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "walkdir" -version = "2.5.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -5761,15 +5743,15 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -5795,7 +5777,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5808,9 +5790,9 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", @@ -5827,9 +5809,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.4" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "winapi" @@ -5849,11 +5831,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ - "windows-sys 0.52.0", + "winapi", ] [[package]] @@ -5868,7 +5850,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.0", ] [[package]] @@ -5886,7 +5868,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.0", ] [[package]] @@ -5906,18 +5888,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -5928,9 +5909,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" @@ -5940,9 +5921,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" @@ -5952,15 +5933,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" @@ -5970,9 +5945,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" @@ -5982,9 +5957,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" @@ -5994,9 +5969,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" @@ -6006,15 +5981,15 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.40" +version = "0.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" dependencies = [ "memchr", ] @@ -6049,9 +6024,9 @@ dependencies = [ [[package]] name = "xattr" -version = "1.3.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +checksum = "914566e6413e7fa959cc394fb30e563ba80f3541fbd40816d4c05a0fc3f2a0f1" dependencies = [ "libc", "linux-raw-sys", @@ -6084,7 +6059,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -6104,7 +6079,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn 2.0.48", ] [[package]] @@ -6128,9 +6103,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" +version = "2.0.9+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" dependencies = [ "cc", "pkg-config", diff --git a/src/consts.rs b/src/consts.rs index 71a12a9..c95453e 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -77,6 +77,10 @@ pub const METADATA_SYMBOL: &str = "ORE"; /// The uri for token metdata. pub const METADATA_URI: &str = "https://ore.supply/metadata.json"; +/// Program id of the compute budge program. +pub const COMPUTE_BUDGET_PROGRAM_ID: Pubkey = + pubkey!("ComputeBudget111111111111111111111111111111"); + /// Program id for const pda derivations const PROGRAM_ID: [u8; 32] = unsafe { *(&crate::id() as *const Pubkey as *const [u8; 32]) }; diff --git a/src/error.rs b/src/error.rs index 03e9e30..fe711ec 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,12 +13,12 @@ pub enum OreError { HashTooEasy = 2, #[error("The claim amount cannot be greater than the claimable rewards")] ClaimTooLarge = 3, - #[error("The stake amount cannot exceed u64 size")] - StakeTooLarge = 4, #[error("The clock time is invalid")] - ClockInvalid = 5, + ClockInvalid = 4, + #[error("Only one hash may be validated per transaction")] + TransactionInvalid = 5, #[error("The tolerance cannot exceed i64 max value")] - ToleranceInvalid = 6, + ToleranceOverflow = 6, } impl From for ProgramError { diff --git a/src/instruction.rs b/src/instruction.rs index 0a32ac1..b6664e2 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -262,6 +262,7 @@ pub fn mine(signer: Pubkey, bus: Pubkey, nonce: u64) -> Instruction { AccountMeta::new(bus, false), AccountMeta::new_readonly(CONFIG_ADDRESS, false), AccountMeta::new(proof, false), + AccountMeta::new_readonly(sysvar::instructions::id(), false), AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), ], data: [ diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 898fa25..abb4c75 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -1,5 +1,6 @@ use std::mem::size_of; +#[allow(deprecated)] use solana_program::{ account_info::AccountInfo, clock::Clock, @@ -8,17 +9,19 @@ use solana_program::{ log::sol_log, program_error::ProgramError, pubkey::Pubkey, + sanitize::SanitizeError, + serialize_utils::{read_pubkey, read_u16, read_u8}, slot_hashes::SlotHash, - sysvar::{self, Sysvar}, + sysvar::{self, instructions::load_current_index, Sysvar}, }; use crate::{ error::OreError, - instruction::MineArgs, + instruction::{MineArgs, OreInstruction}, loaders::*, state::{Bus, Config, Proof}, utils::AccountDeserialize, - MIN_DIFFICULTY, ONE_MINUTE, TWO_YEARS, + COMPUTE_BUDGET_PROGRAM_ID, MIN_DIFFICULTY, ONE_MINUTE, TWO_YEARS, }; // TODO Look into tx introspection to require 1 hash per tx @@ -45,14 +48,22 @@ pub fn process_mine<'a, 'info>( let args = MineArgs::try_from_bytes(data)?; // Load accounts - let [signer, bus_info, config_info, proof_info, slot_hashes_info] = accounts else { + let [signer, bus_info, config_info, proof_info, instructions_sysvar, slot_hashes_sysvar] = + accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; load_any_bus(bus_info, true)?; load_config(config_info, false)?; load_proof(proof_info, signer.key, true)?; - load_sysvar(slot_hashes_info, sysvar::slot_hashes::id())?; + load_sysvar(instructions_sysvar, sysvar::instructions::id())?; + load_sysvar(slot_hashes_sysvar, sysvar::slot_hashes::id())?; + + // Validate this is the only mine ix in the transaction + if !validate_transaction(&instructions_sysvar.data.borrow()).unwrap_or(false) { + return Err(OreError::TransactionInvalid.into()); + } // Validate mining is not paused let config_data = config_info.data.borrow(); @@ -152,7 +163,7 @@ pub fn process_mine<'a, 'info>( // Hash recent slot hash into the next challenge to prevent pre-mining attacks proof.challenge = hashv(&[ hx.as_slice(), - &slot_hashes_info.data.borrow()[0..size_of::()], + &slot_hashes_sysvar.data.borrow()[0..size_of::()], ]) .0; @@ -169,3 +180,83 @@ pub fn process_mine<'a, 'info>( Ok(()) } + +/// Require that there is only one `mine` instruction per transaction and it is called from the +/// top level of the transaction. +fn validate_transaction(msg: &[u8]) -> Result { + #[allow(deprecated)] + let idx = load_current_index(msg); + let mut c = 0; + let num_instructions = read_u16(&mut c, msg)?; + let pc = c; + for i in 0..num_instructions as usize { + c = pc + i * 2; + c = read_u16(&mut c, msg)? as usize; + let num_accounts = read_u16(&mut c, msg)? as usize; + c += num_accounts * 33; + match read_pubkey(&mut c, msg)? { + crate::ID => { + c += 2; + if let Ok(ix) = OreInstruction::try_from(read_u8(&mut c, msg)?) { + if let OreInstruction::Mine = ix { + if i.ne(&(idx as usize)) { + return Ok(false); + } + } + } else { + return Ok(false); + } + } + COMPUTE_BUDGET_PROGRAM_ID => { + // Noop + } + _ => return Ok(false), + } + } + + Ok(true) +} + +// fn deserialize_instruction(index: usize, data: &[u8]) -> Result { +// const IS_SIGNER_BIT: usize = 0; +// const IS_WRITABLE_BIT: usize = 1; + +// let mut current = 0; +// let num_instructions = read_u16(&mut current, data)?; +// if index >= num_instructions as usize { +// return Err(SanitizeError::IndexOutOfBounds); +// } + +// // index into the instruction byte-offset table. +// current += index * 2; +// let start = read_u16(&mut current, data)?; + +// current = start as usize; +// let num_accounts = read_u16(&mut current, data)?; +// let mut accounts = Vec::with_capacity(num_accounts as usize); +// for _ in 0..num_accounts { +// let meta_byte = read_u8(&mut current, data)?; +// let mut is_signer = false; +// let mut is_writable = false; +// if meta_byte & (1 << IS_SIGNER_BIT) != 0 { +// is_signer = true; +// } +// if meta_byte & (1 << IS_WRITABLE_BIT) != 0 { +// is_writable = true; +// } +// let pubkey = read_pubkey(&mut current, data)?; +// accounts.push(AccountMeta { +// pubkey, +// is_signer, +// is_writable, +// }); +// } +// let program_id = read_pubkey(&mut current, data)?; +// let data_len = read_u16(&mut current, data)?; +// let data = read_slice(&mut current, data, data_len as usize)?; +// Ok(Instruction { +// program_id, +// accounts, +// data, +// }) +// } diff --git a/src/processor/stake.rs b/src/processor/stake.rs index 68405ed..008a815 100644 --- a/src/processor/stake.rs +++ b/src/processor/stake.rs @@ -4,8 +4,8 @@ use solana_program::{ }; use crate::{ - error::OreError, instruction::StakeArgs, loaders::*, state::Proof, utils::AccountDeserialize, - MINT_ADDRESS, TREASURY_ADDRESS, + instruction::StakeArgs, loaders::*, state::Proof, utils::AccountDeserialize, MINT_ADDRESS, + TREASURY_ADDRESS, }; pub fn process_stake<'a, 'info>( @@ -36,10 +36,7 @@ pub fn process_stake<'a, 'info>( // Update proof balance let mut proof_data = proof_info.data.borrow_mut(); let proof = Proof::try_from_bytes_mut(&mut proof_data)?; - proof.balance = proof - .balance - .checked_add(amount) - .ok_or(OreError::StakeTooLarge)?; + proof.balance = proof.balance.saturating_add(amount); // Update deposit timestamp let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; diff --git a/src/processor/update_tolerance.rs b/src/processor/update_tolerance.rs index 8a55294..c869f6f 100644 --- a/src/processor/update_tolerance.rs +++ b/src/processor/update_tolerance.rs @@ -32,10 +32,10 @@ pub fn process_update_tolerance<'a, 'info>( // Overflow checks if args.tolerance_liveness.gt(&(i64::MAX as u64)) { - return Err(OreError::ToleranceInvalid.into()); + return Err(OreError::ToleranceOverflow.into()); } if args.tolerance_spam.gt(&(i64::MAX as u64)) { - return Err(OreError::ToleranceInvalid.into()); + return Err(OreError::ToleranceOverflow.into()); } // Update tolerances From 963c9f7909b080109330f5aa79f41a41eb5ea408 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 20:48:58 +0000 Subject: [PATCH 027/111] comments --- src/lib.rs | 1 - src/processor/mine.rs | 2 -- src/state/bus.rs | 5 +++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d1a0e83..dbf8573 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,6 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; -// TODO Alternative to bincode? // TODO Is downgrade necessary? declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); diff --git a/src/processor/mine.rs b/src/processor/mine.rs index abb4c75..16fda06 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -24,8 +24,6 @@ use crate::{ COMPUTE_BUDGET_PROGRAM_ID, MIN_DIFFICULTY, ONE_MINUTE, TWO_YEARS, }; -// TODO Look into tx introspection to require 1 hash per tx - /// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: /// 1. Calculate the hash from the provided nonce. /// 2. Payout rewards based on difficulty, staking multiplier, and liveness penalty. diff --git a/src/state/bus.rs b/src/state/bus.rs index a143fe1..e3fef7c 100644 --- a/src/state/bus.rs +++ b/src/state/bus.rs @@ -15,10 +15,11 @@ pub struct Bus { /// The ID of the bus account. pub id: u64, - /// The quantity of rewards this bus has left to issue in the current epoch epoch. + /// The remaining rewards this bus has left to payout in the current epoch epoch. pub rewards: u64, - /// The rewards that would have been paid out by this bus in the current epoch if the bus had no limit. + /// The rewards this bus would have paid out in the current epoch if there no limit. + /// Used to calculate the updated reward rate. pub theoretical_rewards: u64, } From cf4d0e1d0593fc3432a94da99c3dd61f187b1cc7 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 30 Apr 2024 20:59:08 +0000 Subject: [PATCH 028/111] comment --- src/processor/mine.rs | 64 ++++++++++--------------------------------- 1 file changed, 14 insertions(+), 50 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 16fda06..69916b8 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -104,8 +104,8 @@ pub fn process_mine<'a, 'info>( sol_log(&format!("Base {}", reward)); // Apply staking multiplier. - // To prevent flash loan attacks, multiplier is only applied if the last deposit was at least 1 block ago. - // The multiplier can range 1x to 2x. To get the maximum multiplier, the stake balance must be + // To prevent flash loan attacks, only apply if the last deposit was at least 1 block ago. + // The multiplier can range 1x to 2x. To receive the maximum multiplier, the stake balance must be // greater than or equal to two years worth of rewards at the selected difficulty. if clock.slot.gt(&proof.last_deposit_slot) { let upper_bound = reward.saturating_mul(TWO_YEARS); @@ -143,10 +143,10 @@ pub fn process_mine<'a, 'info>( )); } - // Set upper bound to whatever is left in the bus + // Limit payout amount to whatever is left in the bus let mut bus_data = bus_info.data.borrow_mut(); let bus = Bus::try_from_bytes_mut(&mut bus_data)?; - let actual_reward = reward.min(bus.rewards); + let reward_actual = reward.min(bus.rewards); // Update balances sol_log(&format!("Total {}", reward)); @@ -154,9 +154,9 @@ pub fn process_mine<'a, 'info>( bus.theoretical_rewards = bus.theoretical_rewards.saturating_add(reward); bus.rewards = bus .rewards - .checked_sub(actual_reward) + .checked_sub(reward_actual) .expect("This should not happen"); - proof.balance = proof.balance.saturating_add(actual_reward); + proof.balance = proof.balance.saturating_add(reward_actual); // Hash recent slot hash into the next challenge to prevent pre-mining attacks proof.challenge = hashv(&[ @@ -181,6 +181,13 @@ pub fn process_mine<'a, 'info>( /// Require that there is only one `mine` instruction per transaction and it is called from the /// top level of the transaction. +/// +/// The intent here is to disincentivize sybil. As long as a user can fit multiple hashes in a single +/// transaction, there is a financial incentive to sybil multiple keypairs and pack as many hashes +/// as possible into each transaction to minimize fee / hash. +/// +/// If each transaction is limited to one hash only, then a user will minimize their fee / hash +/// by allocating all their hashpower to finding the single most difficult hash they can. fn validate_transaction(msg: &[u8]) -> Result { #[allow(deprecated)] let idx = load_current_index(msg); @@ -192,6 +199,7 @@ fn validate_transaction(msg: &[u8]) -> Result { c = read_u16(&mut c, msg)? as usize; let num_accounts = read_u16(&mut c, msg)? as usize; c += num_accounts * 33; + // Only allow instructions to call ore and the compute budget program. match read_pubkey(&mut c, msg)? { crate::ID => { c += 2; @@ -214,47 +222,3 @@ fn validate_transaction(msg: &[u8]) -> Result { Ok(true) } - -// fn deserialize_instruction(index: usize, data: &[u8]) -> Result { -// const IS_SIGNER_BIT: usize = 0; -// const IS_WRITABLE_BIT: usize = 1; - -// let mut current = 0; -// let num_instructions = read_u16(&mut current, data)?; -// if index >= num_instructions as usize { -// return Err(SanitizeError::IndexOutOfBounds); -// } - -// // index into the instruction byte-offset table. -// current += index * 2; -// let start = read_u16(&mut current, data)?; - -// current = start as usize; -// let num_accounts = read_u16(&mut current, data)?; -// let mut accounts = Vec::with_capacity(num_accounts as usize); -// for _ in 0..num_accounts { -// let meta_byte = read_u8(&mut current, data)?; -// let mut is_signer = false; -// let mut is_writable = false; -// if meta_byte & (1 << IS_SIGNER_BIT) != 0 { -// is_signer = true; -// } -// if meta_byte & (1 << IS_WRITABLE_BIT) != 0 { -// is_writable = true; -// } -// let pubkey = read_pubkey(&mut current, data)?; -// accounts.push(AccountMeta { -// pubkey, -// is_signer, -// is_writable, -// }); -// } -// let program_id = read_pubkey(&mut current, data)?; -// let data_len = read_u16(&mut current, data)?; -// let data = read_slice(&mut current, data, data_len as usize)?; -// Ok(Instruction { -// program_id, -// accounts, -// data, -// }) -// } From 5d1b1a56aad3d5c76c7cb3aac5a64baa36e4f4e3 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 1 May 2024 02:26:02 +0000 Subject: [PATCH 029/111] scaffold deregister --- src/instruction.rs | 6 ++++++ src/lib.rs | 2 ++ src/processor/deregister.rs | 36 +++++++++++++++++++++++++++++++ src/processor/mod.rs | 2 ++ src/processor/register.rs | 1 + src/processor/update_tolerance.rs | 8 +++---- 6 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 src/processor/deregister.rs diff --git a/src/instruction.rs b/src/instruction.rs index b6664e2..340bff6 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -74,6 +74,12 @@ pub enum OreInstruction { #[account(6, name = "mint_v1", desc = "Ore v1 token mint account", writable)] #[account(7, name = "token_program", desc = "SPL token program")] Upgrade = 5, + + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "proof", desc = "Ore proof account", writable)] + #[account(3, name = "system_program", desc = "Solana system program")] + Deregister = 6, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] diff --git a/src/lib.rs b/src/lib.rs index dbf8573..fc29e90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; +// TODO Close proof accounts to recover sol // TODO Is downgrade necessary? declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); @@ -41,6 +42,7 @@ pub fn process_instruction( OreInstruction::Claim => process_claim(program_id, accounts, data)?, OreInstruction::Stake => process_stake(program_id, accounts, data)?, OreInstruction::Upgrade => process_upgrade(program_id, accounts, data)?, + OreInstruction::Deregister => process_deregister(program_id, accounts, data)?, OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, OreInstruction::UpdateAdmin => process_update_admin(program_id, accounts, data)?, OreInstruction::UpdateTolerance => process_update_tolerance(program_id, accounts, data)?, diff --git a/src/processor/deregister.rs b/src/processor/deregister.rs new file mode 100644 index 0000000..6d6c881 --- /dev/null +++ b/src/processor/deregister.rs @@ -0,0 +1,36 @@ +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, system_program, +}; + +use crate::{instruction::RegisterArgs, loaders::*}; + +/// Register generates a new hash chain for a prospective miner. Its responsibilities include: +/// 1. Initialize a new proof account. +/// 2. Generate an initial hash from the signer's key. +/// +/// Safety requirements: +/// - Register is a permissionless instruction and can be invoked by any singer. +/// - Can only succeed if the provided proof acount PDA is valid (associated with the signer). +/// - Can only succeed once per signer. +/// - The provided system program must be valid. +pub fn process_deregister<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + data: &[u8], +) -> ProgramResult { + // Load accounts + let [signer, proof_info, system_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer)?; + load_proof(proof_info, signer.key, true)?; + load_program(system_program, system_program::id())?; + + // TODO Ensure proof.balance == 0 + // TODO Send lamports to signer + // TODO Realloc data to 0 + // TODO Reassign back to system program + + Ok(()) +} diff --git a/src/processor/mod.rs b/src/processor/mod.rs index f1dd9cc..6807980 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -1,4 +1,5 @@ mod claim; +mod deregister; mod initialize; mod mine; mod pause; @@ -10,6 +11,7 @@ mod update_tolerance; mod upgrade; pub use claim::*; +pub use deregister::*; pub use initialize::*; pub use mine::*; pub use pause::*; diff --git a/src/processor/register.rs b/src/processor/register.rs index 4ad7c15..6b100ca 100644 --- a/src/processor/register.rs +++ b/src/processor/register.rs @@ -64,6 +64,7 @@ pub fn process_register<'a, 'info>( &slot_hashes_info.data.borrow()[0..size_of::()], ]) .0; + proof.last_deposit_slot = 0; proof.last_hash_at = 0; proof.total_hashes = 0; proof.total_rewards = 0; diff --git a/src/processor/update_tolerance.rs b/src/processor/update_tolerance.rs index c869f6f..6dfbfe9 100644 --- a/src/processor/update_tolerance.rs +++ b/src/processor/update_tolerance.rs @@ -5,7 +5,7 @@ use solana_program::{ use crate::{ error::OreError, instruction::UpdateToleranceArgs, loaders::*, state::Config, - utils::AccountDeserialize, + utils::AccountDeserialize, ONE_MINUTE, }; pub fn process_update_tolerance<'a, 'info>( @@ -30,11 +30,11 @@ pub fn process_update_tolerance<'a, 'info>( return Err(ProgramError::MissingRequiredSignature); } - // Overflow checks - if args.tolerance_liveness.gt(&(i64::MAX as u64)) { + // Sanity checks + if args.tolerance_liveness.ge(&(ONE_MINUTE as u64)) { return Err(OreError::ToleranceOverflow.into()); } - if args.tolerance_spam.gt(&(i64::MAX as u64)) { + if args.tolerance_spam.ge(&(ONE_MINUTE as u64)) { return Err(OreError::ToleranceOverflow.into()); } From ac96b5f5281982e0e1e2eef215f3c32574fd8d46 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 1 May 2024 13:39:40 +0000 Subject: [PATCH 030/111] cleanup --- src/builders.rs | 275 ++++++++++++++++++++++++++++++++++++ src/consts.rs | 4 - src/instruction.rs | 272 +---------------------------------- src/lib.rs | 6 +- src/processor/deregister.rs | 52 ++++--- src/processor/initialize.rs | 1 + src/processor/mine.rs | 23 +-- src/processor/stake.rs | 15 +- src/processor/upgrade.rs | 28 ++-- 9 files changed, 345 insertions(+), 331 deletions(-) create mode 100644 src/builders.rs diff --git a/src/builders.rs b/src/builders.rs new file mode 100644 index 0000000..9e0508a --- /dev/null +++ b/src/builders.rs @@ -0,0 +1,275 @@ +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, sysvar, +}; + +use crate::{ + instruction::{ + ClaimArgs, InitializeArgs, MineArgs, OreInstruction, PauseArgs, RegisterArgs, StakeArgs, + UpdateAdminArgs, UpdateToleranceArgs, + }, + BUS, BUS_ADDRESSES, CONFIG, CONFIG_ADDRESS, METADATA, MINT, MINT_ADDRESS, MINT_NOISE, PROOF, + TREASURY, TREASURY_ADDRESS, +}; + +/// Builds a reset instruction. +pub fn reset(signer: Pubkey) -> Instruction { + let treasury_tokens = spl_associated_token_account::get_associated_token_address( + &TREASURY_ADDRESS, + &MINT_ADDRESS, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(BUS_ADDRESSES[0], false), + AccountMeta::new(BUS_ADDRESSES[1], false), + AccountMeta::new(BUS_ADDRESSES[2], false), + AccountMeta::new(BUS_ADDRESSES[3], false), + AccountMeta::new(BUS_ADDRESSES[4], false), + AccountMeta::new(BUS_ADDRESSES[5], false), + AccountMeta::new(BUS_ADDRESSES[6], false), + AccountMeta::new(BUS_ADDRESSES[7], false), + AccountMeta::new(CONFIG_ADDRESS, false), + AccountMeta::new(MINT_ADDRESS, false), + AccountMeta::new(TREASURY_ADDRESS, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: OreInstruction::Reset.to_vec(), + } +} + +/// Builds a register instruction. +pub fn register(signer: Pubkey) -> Instruction { + let proof_pda = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(proof_pda.0, false), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), + ], + data: [ + OreInstruction::Register.to_vec(), + RegisterArgs { bump: proof_pda.1 }.to_bytes().to_vec(), + ] + .concat(), + } +} + +/// Builds a mine instruction. +pub fn mine(signer: Pubkey, bus: Pubkey, nonce: u64) -> Instruction { + let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(bus, false), + AccountMeta::new_readonly(CONFIG_ADDRESS, false), + AccountMeta::new(proof, false), + AccountMeta::new_readonly(sysvar::instructions::id(), false), + AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), + ], + data: [ + OreInstruction::Mine.to_vec(), + MineArgs { + nonce: nonce.to_le_bytes(), + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + +/// Builds a claim instruction. +pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { + let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; + let treasury_tokens = spl_associated_token_account::get_associated_token_address( + &TREASURY_ADDRESS, + &MINT_ADDRESS, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(beneficiary, false), + AccountMeta::new(proof, false), + AccountMeta::new_readonly(TREASURY_ADDRESS, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: [ + OreInstruction::Claim.to_vec(), + ClaimArgs { + amount: amount.to_le_bytes(), + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + +/// Build a stake instruction. +pub fn stake(signer: Pubkey, sender: Pubkey, amount: u64) -> Instruction { + let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; + let treasury_tokens = spl_associated_token_account::get_associated_token_address( + &TREASURY_ADDRESS, + &MINT_ADDRESS, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(proof, false), + AccountMeta::new(sender, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: [ + OreInstruction::Stake.to_vec(), + StakeArgs { + amount: amount.to_le_bytes(), + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + +/// Builds an initialize instruction. +pub fn initialize(signer: Pubkey) -> Instruction { + let bus_pdas = [ + Pubkey::find_program_address(&[BUS, &[0]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[1]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[2]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[3]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[4]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[5]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[6]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[7]], &crate::id()), + ]; + let config_pda = Pubkey::find_program_address(&[CONFIG], &crate::id()); + let mint_pda = Pubkey::find_program_address(&[MINT, MINT_NOISE.as_slice()], &crate::id()); + let treasury_pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); + let treasury_tokens = + spl_associated_token_account::get_associated_token_address(&treasury_pda.0, &mint_pda.0); + let metadata_pda = Pubkey::find_program_address( + &[ + METADATA, + mpl_token_metadata::ID.as_ref(), + mint_pda.0.as_ref(), + ], + &mpl_token_metadata::ID, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(bus_pdas[0].0, false), + AccountMeta::new(bus_pdas[1].0, false), + AccountMeta::new(bus_pdas[2].0, false), + AccountMeta::new(bus_pdas[3].0, false), + AccountMeta::new(bus_pdas[4].0, false), + AccountMeta::new(bus_pdas[5].0, false), + AccountMeta::new(bus_pdas[6].0, false), + AccountMeta::new(bus_pdas[7].0, false), + AccountMeta::new(config_pda.0, false), + AccountMeta::new(metadata_pda.0, false), + AccountMeta::new(mint_pda.0, false), + AccountMeta::new(treasury_pda.0, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(spl_associated_token_account::id(), false), + // AccountMeta::new_readonly(mpl_token_metadata::ID, false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + ], + data: [ + OreInstruction::Initialize.to_vec(), + InitializeArgs { + bus_0_bump: bus_pdas[0].1, + bus_1_bump: bus_pdas[1].1, + bus_2_bump: bus_pdas[2].1, + bus_3_bump: bus_pdas[3].1, + bus_4_bump: bus_pdas[4].1, + bus_5_bump: bus_pdas[5].1, + bus_6_bump: bus_pdas[6].1, + bus_7_bump: bus_pdas[7].1, + config_bump: config_pda.1, + metadata_bump: metadata_pda.1, + mint_bump: mint_pda.1, + treasury_bump: treasury_pda.1, + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + +/// Build an update_admin instruction. +pub fn update_admin(signer: Pubkey, new_admin: Pubkey) -> Instruction { + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(CONFIG_ADDRESS, false), + ], + data: [ + OreInstruction::UpdateAdmin.to_vec(), + UpdateAdminArgs { new_admin }.to_bytes().to_vec(), + ] + .concat(), + } +} + +/// Build an update_tolerance instruction. +pub fn update_tolerance( + signer: Pubkey, + new_liveness_tolerance: u64, + new_spam_tolerance: u64, +) -> Instruction { + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(CONFIG_ADDRESS, false), + ], + data: [ + OreInstruction::UpdateTolerance.to_vec(), + UpdateToleranceArgs { + tolerance_liveness: new_liveness_tolerance, + tolerance_spam: new_spam_tolerance, + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + +/// Build a pause instruction. +pub fn pause(signer: Pubkey, paused: bool) -> Instruction { + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(CONFIG_ADDRESS, false), + ], + data: [ + OreInstruction::UpdateAdmin.to_vec(), + PauseArgs { + paused: paused as u8, + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} diff --git a/src/consts.rs b/src/consts.rs index c95453e..71a12a9 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -77,10 +77,6 @@ pub const METADATA_SYMBOL: &str = "ORE"; /// The uri for token metdata. pub const METADATA_URI: &str = "https://ore.supply/metadata.json"; -/// Program id of the compute budge program. -pub const COMPUTE_BUDGET_PROGRAM_ID: Pubkey = - pubkey!("ComputeBudget111111111111111111111111111111"); - /// Program id for const pda derivations const PROGRAM_ID: [u8; 32] = unsafe { *(&crate::id() as *const Pubkey as *const [u8; 32]) }; diff --git a/src/instruction.rs b/src/instruction.rs index 340bff6..6c81123 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -1,16 +1,9 @@ use bytemuck::{Pod, Zeroable}; use num_enum::TryFromPrimitive; use shank::ShankInstruction; -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, sysvar, -}; +use solana_program::pubkey::Pubkey; -use crate::{ - impl_instruction_from_bytes, impl_to_bytes, BUS, BUS_ADDRESSES, CONFIG, CONFIG_ADDRESS, - METADATA, MINT, MINT_ADDRESS, MINT_NOISE, PROOF, TREASURY, TREASURY_ADDRESS, -}; +use crate::{impl_instruction_from_bytes, impl_to_bytes}; #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, ShankInstruction, TryFromPrimitive)] @@ -210,264 +203,3 @@ impl_instruction_from_bytes!(UpgradeArgs); impl_instruction_from_bytes!(UpdateAdminArgs); impl_instruction_from_bytes!(UpdateToleranceArgs); impl_instruction_from_bytes!(PauseArgs); - -/// Builds a reset instruction. -pub fn reset(signer: Pubkey) -> Instruction { - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(BUS_ADDRESSES[0], false), - AccountMeta::new(BUS_ADDRESSES[1], false), - AccountMeta::new(BUS_ADDRESSES[2], false), - AccountMeta::new(BUS_ADDRESSES[3], false), - AccountMeta::new(BUS_ADDRESSES[4], false), - AccountMeta::new(BUS_ADDRESSES[5], false), - AccountMeta::new(BUS_ADDRESSES[6], false), - AccountMeta::new(BUS_ADDRESSES[7], false), - AccountMeta::new(CONFIG_ADDRESS, false), - AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: OreInstruction::Reset.to_vec(), - } -} - -/// Builds a register instruction. -pub fn register(signer: Pubkey) -> Instruction { - let proof_pda = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(proof_pda.0, false), - AccountMeta::new_readonly(solana_program::system_program::id(), false), - AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), - ], - data: [ - OreInstruction::Register.to_vec(), - RegisterArgs { bump: proof_pda.1 }.to_bytes().to_vec(), - ] - .concat(), - } -} - -/// Builds a mine instruction. -pub fn mine(signer: Pubkey, bus: Pubkey, nonce: u64) -> Instruction { - let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus, false), - AccountMeta::new_readonly(CONFIG_ADDRESS, false), - AccountMeta::new(proof, false), - AccountMeta::new_readonly(sysvar::instructions::id(), false), - AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), - ], - data: [ - OreInstruction::Mine.to_vec(), - MineArgs { - nonce: nonce.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} - -/// Builds a claim instruction. -pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { - let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(beneficiary, false), - AccountMeta::new(proof, false), - AccountMeta::new_readonly(TREASURY_ADDRESS, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: [ - OreInstruction::Claim.to_vec(), - ClaimArgs { - amount: amount.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} - -/// Build a stake instruction. -pub fn stake(signer: Pubkey, sender: Pubkey, amount: u64) -> Instruction { - let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(proof, false), - AccountMeta::new(sender, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: [ - OreInstruction::Stake.to_vec(), - StakeArgs { - amount: amount.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} - -/// Builds an initialize instruction. -pub fn initialize(signer: Pubkey) -> Instruction { - let bus_pdas = [ - Pubkey::find_program_address(&[BUS, &[0]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[1]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[2]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[3]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[4]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[5]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[6]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[7]], &crate::id()), - ]; - let config_pda = Pubkey::find_program_address(&[CONFIG], &crate::id()); - let mint_pda = Pubkey::find_program_address(&[MINT, MINT_NOISE.as_slice()], &crate::id()); - let treasury_pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); - let treasury_tokens = - spl_associated_token_account::get_associated_token_address(&treasury_pda.0, &mint_pda.0); - let metadata_pda = Pubkey::find_program_address( - &[ - METADATA, - mpl_token_metadata::ID.as_ref(), - mint_pda.0.as_ref(), - ], - &mpl_token_metadata::ID, - ); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus_pdas[0].0, false), - AccountMeta::new(bus_pdas[1].0, false), - AccountMeta::new(bus_pdas[2].0, false), - AccountMeta::new(bus_pdas[3].0, false), - AccountMeta::new(bus_pdas[4].0, false), - AccountMeta::new(bus_pdas[5].0, false), - AccountMeta::new(bus_pdas[6].0, false), - AccountMeta::new(bus_pdas[7].0, false), - AccountMeta::new(config_pda.0, false), - AccountMeta::new(metadata_pda.0, false), - AccountMeta::new(mint_pda.0, false), - AccountMeta::new(treasury_pda.0, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(system_program::id(), false), - AccountMeta::new_readonly(spl_token::id(), false), - AccountMeta::new_readonly(spl_associated_token_account::id(), false), - // AccountMeta::new_readonly(mpl_token_metadata::ID, false), - AccountMeta::new_readonly(sysvar::rent::id(), false), - ], - data: [ - OreInstruction::Initialize.to_vec(), - InitializeArgs { - bus_0_bump: bus_pdas[0].1, - bus_1_bump: bus_pdas[1].1, - bus_2_bump: bus_pdas[2].1, - bus_3_bump: bus_pdas[3].1, - bus_4_bump: bus_pdas[4].1, - bus_5_bump: bus_pdas[5].1, - bus_6_bump: bus_pdas[6].1, - bus_7_bump: bus_pdas[7].1, - config_bump: config_pda.1, - metadata_bump: metadata_pda.1, - mint_bump: mint_pda.1, - treasury_bump: treasury_pda.1, - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} - -/// Build an update_admin instruction. -pub fn update_admin(signer: Pubkey, new_admin: Pubkey) -> Instruction { - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(CONFIG_ADDRESS, false), - ], - data: [ - OreInstruction::UpdateAdmin.to_vec(), - UpdateAdminArgs { new_admin }.to_bytes().to_vec(), - ] - .concat(), - } -} - -/// Build an update_tolerance instruction. -pub fn update_tolerance( - signer: Pubkey, - new_liveness_tolerance: u64, - new_spam_tolerance: u64, -) -> Instruction { - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(CONFIG_ADDRESS, false), - ], - data: [ - OreInstruction::UpdateTolerance.to_vec(), - UpdateToleranceArgs { - tolerance_liveness: new_liveness_tolerance, - tolerance_spam: new_spam_tolerance, - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} - -/// Build a pause instruction. -pub fn pause(signer: Pubkey, paused: bool) -> Instruction { - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(CONFIG_ADDRESS, false), - ], - data: [ - OreInstruction::UpdateAdmin.to_vec(), - PauseArgs { - paused: paused as u8, - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} diff --git a/src/lib.rs b/src/lib.rs index fc29e90..4050f87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +pub mod builders; pub mod consts; pub mod error; pub mod instruction; @@ -14,7 +15,6 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; -// TODO Close proof accounts to recover sol // TODO Is downgrade necessary? declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); @@ -36,13 +36,13 @@ pub fn process_instruction( .ok_or(ProgramError::InvalidInstructionData)?; match OreInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))? { - OreInstruction::Reset => process_reset(program_id, accounts, data)?, OreInstruction::Register => process_register(program_id, accounts, data)?, + OreInstruction::Deregister => process_deregister(program_id, accounts, data)?, + OreInstruction::Reset => process_reset(program_id, accounts, data)?, OreInstruction::Mine => process_mine(program_id, accounts, data)?, OreInstruction::Claim => process_claim(program_id, accounts, data)?, OreInstruction::Stake => process_stake(program_id, accounts, data)?, OreInstruction::Upgrade => process_upgrade(program_id, accounts, data)?, - OreInstruction::Deregister => process_deregister(program_id, accounts, data)?, OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, OreInstruction::UpdateAdmin => process_update_admin(program_id, accounts, data)?, OreInstruction::UpdateTolerance => process_update_tolerance(program_id, accounts, data)?, diff --git a/src/processor/deregister.rs b/src/processor/deregister.rs index 6d6c881..0640e6c 100644 --- a/src/processor/deregister.rs +++ b/src/processor/deregister.rs @@ -1,23 +1,14 @@ use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, system_program, + account_info::AccountInfo, entrypoint::ProgramResult, program::invoke_signed, + program_error::ProgramError, pubkey::Pubkey, system_program, }; -use crate::{instruction::RegisterArgs, loaders::*}; +use crate::{loaders::*, state::Proof, utils::AccountDeserialize, PROOF}; -/// Register generates a new hash chain for a prospective miner. Its responsibilities include: -/// 1. Initialize a new proof account. -/// 2. Generate an initial hash from the signer's key. -/// -/// Safety requirements: -/// - Register is a permissionless instruction and can be invoked by any singer. -/// - Can only succeed if the provided proof acount PDA is valid (associated with the signer). -/// - Can only succeed once per signer. -/// - The provided system program must be valid. pub fn process_deregister<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], - data: &[u8], + _data: &[u8], ) -> ProgramResult { // Load accounts let [signer, proof_info, system_program] = accounts else { @@ -27,10 +18,37 @@ pub fn process_deregister<'a, 'info>( load_proof(proof_info, signer.key, true)?; load_program(system_program, system_program::id())?; - // TODO Ensure proof.balance == 0 - // TODO Send lamports to signer - // TODO Realloc data to 0 - // TODO Reassign back to system program + // Validate balance is zero + let proof_data = proof_info.data.borrow(); + let proof = Proof::try_from_bytes(&proof_data)?; + if proof.balance.gt(&0) { + return Err(ProgramError::InvalidAccountData); + } + + // Generate bump + let bump = Pubkey::find_program_address(&[PROOF, signer.key.as_ref()], &crate::id()).1; + + // Realloc data to zero + drop(proof_data); + proof_info.realloc(0, false)?; + + // Send lamports to signer + invoke_signed( + &solana_program::system_instruction::transfer( + proof_info.key, + signer.key, + proof_info.lamports(), + ), + &[proof_info.clone(), signer.clone(), system_program.clone()], + &[&[PROOF, signer.key.as_ref(), &[bump]]], + )?; + + // Reassign back to system program + solana_program::program::invoke_signed( + &solana_program::system_instruction::assign(proof_info.key, &system_program::id()), + &[proof_info.clone(), system_program.clone()], + &[&[PROOF, signer.key.as_ref(), &[bump]]], + )?; Ok(()) } diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 85c424a..f387eed 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -179,6 +179,7 @@ pub fn process_initialize<'a, 'info>( &[&[MINT, MINT_NOISE.as_slice(), &[args.mint_bump]]], )?; + // TODO Fix metadata initialization // Initialize mint metadata // mpl_token_metadata::instructions::CreateMetadataAccountV3Cpi { // __program: metadata_program, diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 69916b8..a0d168f 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -14,6 +14,7 @@ use solana_program::{ slot_hashes::SlotHash, sysvar::{self, instructions::load_current_index, Sysvar}, }; +use solana_program::{program::set_return_data, pubkey}; use crate::{ error::OreError, @@ -21,7 +22,7 @@ use crate::{ loaders::*, state::{Bus, Config, Proof}, utils::AccountDeserialize, - COMPUTE_BUDGET_PROGRAM_ID, MIN_DIFFICULTY, ONE_MINUTE, TWO_YEARS, + MIN_DIFFICULTY, ONE_MINUTE, TWO_YEARS, }; /// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: @@ -118,23 +119,22 @@ pub fn process_mine<'a, 'info>( sol_log(&format!("Staking {}", staking_reward)); } - // Apply spam/liveness penalty + // Apply spam penalty let t = clock.unix_timestamp; let t_target = proof.last_hash_at.saturating_add(ONE_MINUTE); let t_spam = t_target.saturating_sub(config.tolerance_spam); - let t_liveness = t_target.saturating_add(config.tolerance_liveness); if t.lt(&t_spam) { reward = 0; sol_log("Spam penalty"); - } else if t.gt(&t_liveness) { + } + + // Apply liveness penalty + let t_liveness = t_target.saturating_add(config.tolerance_liveness); + if t.gt(&t_liveness) { reward = reward.saturating_sub( reward .saturating_mul(t.saturating_sub(t_liveness) as u64) - .saturating_div( - t_target - .saturating_add(ONE_MINUTE) - .saturating_sub(t_liveness) as u64, - ), + .saturating_div(ONE_MINUTE as u64), ); sol_log(&format!( "Liveness penalty ({} sec) {}", @@ -174,7 +174,7 @@ pub fn process_mine<'a, 'info>( proof.total_rewards = proof.total_rewards.saturating_add(reward); // Log the mined rewards - // set_return_data(reward.to_le_bytes().as_slice()); + set_return_data(reward_actual.to_le_bytes().as_slice()); Ok(()) } @@ -222,3 +222,6 @@ fn validate_transaction(msg: &[u8]) -> Result { Ok(true) } + +/// Program id of the compute budge program. +const COMPUTE_BUDGET_PROGRAM_ID: Pubkey = pubkey!("ComputeBudget111111111111111111111111111111"); diff --git a/src/processor/stake.rs b/src/processor/stake.rs index 008a815..dc18c61 100644 --- a/src/processor/stake.rs +++ b/src/processor/stake.rs @@ -18,13 +18,12 @@ pub fn process_stake<'a, 'info>( let amount = u64::from_le_bytes(args.amount); // Load accounts - let [signer_info, proof_info, sender_info, treasury_tokens_info, token_program] = accounts - else { + let [signer, proof_info, sender_info, treasury_tokens_info, token_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; - load_signer(signer_info)?; - load_proof(proof_info, signer_info.key, true)?; - load_token_account(sender_info, Some(signer_info.key), &MINT_ADDRESS, true)?; + load_signer(signer)?; + load_proof(proof_info, signer.key, true)?; + load_token_account(sender_info, Some(signer.key), &MINT_ADDRESS, true)?; load_token_account( treasury_tokens_info, Some(&TREASURY_ADDRESS), @@ -48,15 +47,15 @@ pub fn process_stake<'a, 'info>( &spl_token::id(), sender_info.key, treasury_tokens_info.key, - signer_info.key, - &[signer_info.key], + signer.key, + &[signer.key], amount, )?, &[ token_program.clone(), sender_info.clone(), treasury_tokens_info.clone(), - signer_info.clone(), + signer.clone(), ], )?; diff --git a/src/processor/upgrade.rs b/src/processor/upgrade.rs index 4c149f8..b4ee4cf 100644 --- a/src/processor/upgrade.rs +++ b/src/processor/upgrade.rs @@ -4,8 +4,7 @@ use solana_program::{ }; use crate::{ - instruction::StakeArgs, loaders::*, state::Treasury, utils::AccountDeserialize, MINT_ADDRESS, - MINT_V1_ADDRESS, TREASURY, + instruction::StakeArgs, loaders::*, MINT_ADDRESS, MINT_V1_ADDRESS, TREASURY, TREASURY_BUMP, }; pub fn process_upgrade<'a, 'info>( @@ -18,21 +17,16 @@ pub fn process_upgrade<'a, 'info>( let amount = u64::from_le_bytes(args.amount); // Load accounts - let [signer_info, beneficiary_info, mint_info, mint_v1_info, sender_info, treasury_info, token_program] = + let [signer, beneficiary_info, mint_info, mint_v1_info, sender_info, treasury_info, token_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; - load_signer(signer_info)?; - load_token_account( - beneficiary_info, - Some(&signer_info.key), - &MINT_ADDRESS, - true, - )?; + load_signer(signer)?; + load_token_account(beneficiary_info, Some(&signer.key), &MINT_ADDRESS, true)?; load_mint(mint_info, MINT_ADDRESS, true)?; load_mint(mint_v1_info, MINT_V1_ADDRESS, true)?; - load_token_account(sender_info, Some(signer_info.key), &MINT_V1_ADDRESS, true)?; + load_token_account(sender_info, Some(signer.key), &MINT_V1_ADDRESS, true)?; load_program(token_program, spl_token::id())?; // Burn v1 tokens @@ -41,15 +35,15 @@ pub fn process_upgrade<'a, 'info>( &spl_token::id(), sender_info.key, mint_v1_info.key, - signer_info.key, - &[signer_info.key], + signer.key, + &[signer.key], amount, )?, &[ token_program.clone(), sender_info.clone(), mint_v1_info.clone(), - signer_info.clone(), + signer.clone(), ], )?; @@ -58,10 +52,6 @@ pub fn process_upgrade<'a, 'info>( let amount_to_mint = amount.saturating_mul(100); // Mint to the beneficiary account - let treasury_data = treasury_info.data.borrow(); - let treasury = Treasury::try_from_bytes(&treasury_data)?; - let treasury_bump = treasury.bump as u8; - drop(treasury_data); solana_program::program::invoke_signed( &spl_token::instruction::mint_to( &spl_token::id(), @@ -77,7 +67,7 @@ pub fn process_upgrade<'a, 'info>( beneficiary_info.clone(), treasury_info.clone(), ], - &[&[TREASURY, &[treasury_bump]]], + &[&[TREASURY, &[TREASURY_BUMP]]], )?; Ok(()) From 813006cc99f0af519c2cf9f393c16195132ea9b4 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 1 May 2024 19:17:07 +0000 Subject: [PATCH 031/111] move builders back into instruction --- src/builders.rs | 275 --------------------------------------------- src/instruction.rs | 272 +++++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 1 - 3 files changed, 270 insertions(+), 278 deletions(-) delete mode 100644 src/builders.rs diff --git a/src/builders.rs b/src/builders.rs deleted file mode 100644 index 9e0508a..0000000 --- a/src/builders.rs +++ /dev/null @@ -1,275 +0,0 @@ -use solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - system_program, sysvar, -}; - -use crate::{ - instruction::{ - ClaimArgs, InitializeArgs, MineArgs, OreInstruction, PauseArgs, RegisterArgs, StakeArgs, - UpdateAdminArgs, UpdateToleranceArgs, - }, - BUS, BUS_ADDRESSES, CONFIG, CONFIG_ADDRESS, METADATA, MINT, MINT_ADDRESS, MINT_NOISE, PROOF, - TREASURY, TREASURY_ADDRESS, -}; - -/// Builds a reset instruction. -pub fn reset(signer: Pubkey) -> Instruction { - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(BUS_ADDRESSES[0], false), - AccountMeta::new(BUS_ADDRESSES[1], false), - AccountMeta::new(BUS_ADDRESSES[2], false), - AccountMeta::new(BUS_ADDRESSES[3], false), - AccountMeta::new(BUS_ADDRESSES[4], false), - AccountMeta::new(BUS_ADDRESSES[5], false), - AccountMeta::new(BUS_ADDRESSES[6], false), - AccountMeta::new(BUS_ADDRESSES[7], false), - AccountMeta::new(CONFIG_ADDRESS, false), - AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(TREASURY_ADDRESS, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: OreInstruction::Reset.to_vec(), - } -} - -/// Builds a register instruction. -pub fn register(signer: Pubkey) -> Instruction { - let proof_pda = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(proof_pda.0, false), - AccountMeta::new_readonly(solana_program::system_program::id(), false), - AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), - ], - data: [ - OreInstruction::Register.to_vec(), - RegisterArgs { bump: proof_pda.1 }.to_bytes().to_vec(), - ] - .concat(), - } -} - -/// Builds a mine instruction. -pub fn mine(signer: Pubkey, bus: Pubkey, nonce: u64) -> Instruction { - let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus, false), - AccountMeta::new_readonly(CONFIG_ADDRESS, false), - AccountMeta::new(proof, false), - AccountMeta::new_readonly(sysvar::instructions::id(), false), - AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), - ], - data: [ - OreInstruction::Mine.to_vec(), - MineArgs { - nonce: nonce.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} - -/// Builds a claim instruction. -pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { - let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(beneficiary, false), - AccountMeta::new(proof, false), - AccountMeta::new_readonly(TREASURY_ADDRESS, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: [ - OreInstruction::Claim.to_vec(), - ClaimArgs { - amount: amount.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} - -/// Build a stake instruction. -pub fn stake(signer: Pubkey, sender: Pubkey, amount: u64) -> Instruction { - let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; - let treasury_tokens = spl_associated_token_account::get_associated_token_address( - &TREASURY_ADDRESS, - &MINT_ADDRESS, - ); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(proof, false), - AccountMeta::new(sender, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(spl_token::id(), false), - ], - data: [ - OreInstruction::Stake.to_vec(), - StakeArgs { - amount: amount.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} - -/// Builds an initialize instruction. -pub fn initialize(signer: Pubkey) -> Instruction { - let bus_pdas = [ - Pubkey::find_program_address(&[BUS, &[0]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[1]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[2]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[3]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[4]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[5]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[6]], &crate::id()), - Pubkey::find_program_address(&[BUS, &[7]], &crate::id()), - ]; - let config_pda = Pubkey::find_program_address(&[CONFIG], &crate::id()); - let mint_pda = Pubkey::find_program_address(&[MINT, MINT_NOISE.as_slice()], &crate::id()); - let treasury_pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); - let treasury_tokens = - spl_associated_token_account::get_associated_token_address(&treasury_pda.0, &mint_pda.0); - let metadata_pda = Pubkey::find_program_address( - &[ - METADATA, - mpl_token_metadata::ID.as_ref(), - mint_pda.0.as_ref(), - ], - &mpl_token_metadata::ID, - ); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(bus_pdas[0].0, false), - AccountMeta::new(bus_pdas[1].0, false), - AccountMeta::new(bus_pdas[2].0, false), - AccountMeta::new(bus_pdas[3].0, false), - AccountMeta::new(bus_pdas[4].0, false), - AccountMeta::new(bus_pdas[5].0, false), - AccountMeta::new(bus_pdas[6].0, false), - AccountMeta::new(bus_pdas[7].0, false), - AccountMeta::new(config_pda.0, false), - AccountMeta::new(metadata_pda.0, false), - AccountMeta::new(mint_pda.0, false), - AccountMeta::new(treasury_pda.0, false), - AccountMeta::new(treasury_tokens, false), - AccountMeta::new_readonly(system_program::id(), false), - AccountMeta::new_readonly(spl_token::id(), false), - AccountMeta::new_readonly(spl_associated_token_account::id(), false), - // AccountMeta::new_readonly(mpl_token_metadata::ID, false), - AccountMeta::new_readonly(sysvar::rent::id(), false), - ], - data: [ - OreInstruction::Initialize.to_vec(), - InitializeArgs { - bus_0_bump: bus_pdas[0].1, - bus_1_bump: bus_pdas[1].1, - bus_2_bump: bus_pdas[2].1, - bus_3_bump: bus_pdas[3].1, - bus_4_bump: bus_pdas[4].1, - bus_5_bump: bus_pdas[5].1, - bus_6_bump: bus_pdas[6].1, - bus_7_bump: bus_pdas[7].1, - config_bump: config_pda.1, - metadata_bump: metadata_pda.1, - mint_bump: mint_pda.1, - treasury_bump: treasury_pda.1, - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} - -/// Build an update_admin instruction. -pub fn update_admin(signer: Pubkey, new_admin: Pubkey) -> Instruction { - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(CONFIG_ADDRESS, false), - ], - data: [ - OreInstruction::UpdateAdmin.to_vec(), - UpdateAdminArgs { new_admin }.to_bytes().to_vec(), - ] - .concat(), - } -} - -/// Build an update_tolerance instruction. -pub fn update_tolerance( - signer: Pubkey, - new_liveness_tolerance: u64, - new_spam_tolerance: u64, -) -> Instruction { - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(CONFIG_ADDRESS, false), - ], - data: [ - OreInstruction::UpdateTolerance.to_vec(), - UpdateToleranceArgs { - tolerance_liveness: new_liveness_tolerance, - tolerance_spam: new_spam_tolerance, - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} - -/// Build a pause instruction. -pub fn pause(signer: Pubkey, paused: bool) -> Instruction { - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(CONFIG_ADDRESS, false), - ], - data: [ - OreInstruction::UpdateAdmin.to_vec(), - PauseArgs { - paused: paused as u8, - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} diff --git a/src/instruction.rs b/src/instruction.rs index 6c81123..340bff6 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -1,9 +1,16 @@ use bytemuck::{Pod, Zeroable}; use num_enum::TryFromPrimitive; use shank::ShankInstruction; -use solana_program::pubkey::Pubkey; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + system_program, sysvar, +}; -use crate::{impl_instruction_from_bytes, impl_to_bytes}; +use crate::{ + impl_instruction_from_bytes, impl_to_bytes, BUS, BUS_ADDRESSES, CONFIG, CONFIG_ADDRESS, + METADATA, MINT, MINT_ADDRESS, MINT_NOISE, PROOF, TREASURY, TREASURY_ADDRESS, +}; #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, ShankInstruction, TryFromPrimitive)] @@ -203,3 +210,264 @@ impl_instruction_from_bytes!(UpgradeArgs); impl_instruction_from_bytes!(UpdateAdminArgs); impl_instruction_from_bytes!(UpdateToleranceArgs); impl_instruction_from_bytes!(PauseArgs); + +/// Builds a reset instruction. +pub fn reset(signer: Pubkey) -> Instruction { + let treasury_tokens = spl_associated_token_account::get_associated_token_address( + &TREASURY_ADDRESS, + &MINT_ADDRESS, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(BUS_ADDRESSES[0], false), + AccountMeta::new(BUS_ADDRESSES[1], false), + AccountMeta::new(BUS_ADDRESSES[2], false), + AccountMeta::new(BUS_ADDRESSES[3], false), + AccountMeta::new(BUS_ADDRESSES[4], false), + AccountMeta::new(BUS_ADDRESSES[5], false), + AccountMeta::new(BUS_ADDRESSES[6], false), + AccountMeta::new(BUS_ADDRESSES[7], false), + AccountMeta::new(CONFIG_ADDRESS, false), + AccountMeta::new(MINT_ADDRESS, false), + AccountMeta::new(TREASURY_ADDRESS, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: OreInstruction::Reset.to_vec(), + } +} + +/// Builds a register instruction. +pub fn register(signer: Pubkey) -> Instruction { + let proof_pda = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(proof_pda.0, false), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), + ], + data: [ + OreInstruction::Register.to_vec(), + RegisterArgs { bump: proof_pda.1 }.to_bytes().to_vec(), + ] + .concat(), + } +} + +/// Builds a mine instruction. +pub fn mine(signer: Pubkey, bus: Pubkey, nonce: u64) -> Instruction { + let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(bus, false), + AccountMeta::new_readonly(CONFIG_ADDRESS, false), + AccountMeta::new(proof, false), + AccountMeta::new_readonly(sysvar::instructions::id(), false), + AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), + ], + data: [ + OreInstruction::Mine.to_vec(), + MineArgs { + nonce: nonce.to_le_bytes(), + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + +/// Builds a claim instruction. +pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { + let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; + let treasury_tokens = spl_associated_token_account::get_associated_token_address( + &TREASURY_ADDRESS, + &MINT_ADDRESS, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(beneficiary, false), + AccountMeta::new(proof, false), + AccountMeta::new_readonly(TREASURY_ADDRESS, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: [ + OreInstruction::Claim.to_vec(), + ClaimArgs { + amount: amount.to_le_bytes(), + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + +/// Build a stake instruction. +pub fn stake(signer: Pubkey, sender: Pubkey, amount: u64) -> Instruction { + let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; + let treasury_tokens = spl_associated_token_account::get_associated_token_address( + &TREASURY_ADDRESS, + &MINT_ADDRESS, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(proof, false), + AccountMeta::new(sender, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: [ + OreInstruction::Stake.to_vec(), + StakeArgs { + amount: amount.to_le_bytes(), + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + +/// Builds an initialize instruction. +pub fn initialize(signer: Pubkey) -> Instruction { + let bus_pdas = [ + Pubkey::find_program_address(&[BUS, &[0]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[1]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[2]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[3]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[4]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[5]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[6]], &crate::id()), + Pubkey::find_program_address(&[BUS, &[7]], &crate::id()), + ]; + let config_pda = Pubkey::find_program_address(&[CONFIG], &crate::id()); + let mint_pda = Pubkey::find_program_address(&[MINT, MINT_NOISE.as_slice()], &crate::id()); + let treasury_pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); + let treasury_tokens = + spl_associated_token_account::get_associated_token_address(&treasury_pda.0, &mint_pda.0); + let metadata_pda = Pubkey::find_program_address( + &[ + METADATA, + mpl_token_metadata::ID.as_ref(), + mint_pda.0.as_ref(), + ], + &mpl_token_metadata::ID, + ); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(bus_pdas[0].0, false), + AccountMeta::new(bus_pdas[1].0, false), + AccountMeta::new(bus_pdas[2].0, false), + AccountMeta::new(bus_pdas[3].0, false), + AccountMeta::new(bus_pdas[4].0, false), + AccountMeta::new(bus_pdas[5].0, false), + AccountMeta::new(bus_pdas[6].0, false), + AccountMeta::new(bus_pdas[7].0, false), + AccountMeta::new(config_pda.0, false), + AccountMeta::new(metadata_pda.0, false), + AccountMeta::new(mint_pda.0, false), + AccountMeta::new(treasury_pda.0, false), + AccountMeta::new(treasury_tokens, false), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(spl_associated_token_account::id(), false), + // AccountMeta::new_readonly(mpl_token_metadata::ID, false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + ], + data: [ + OreInstruction::Initialize.to_vec(), + InitializeArgs { + bus_0_bump: bus_pdas[0].1, + bus_1_bump: bus_pdas[1].1, + bus_2_bump: bus_pdas[2].1, + bus_3_bump: bus_pdas[3].1, + bus_4_bump: bus_pdas[4].1, + bus_5_bump: bus_pdas[5].1, + bus_6_bump: bus_pdas[6].1, + bus_7_bump: bus_pdas[7].1, + config_bump: config_pda.1, + metadata_bump: metadata_pda.1, + mint_bump: mint_pda.1, + treasury_bump: treasury_pda.1, + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + +/// Build an update_admin instruction. +pub fn update_admin(signer: Pubkey, new_admin: Pubkey) -> Instruction { + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(CONFIG_ADDRESS, false), + ], + data: [ + OreInstruction::UpdateAdmin.to_vec(), + UpdateAdminArgs { new_admin }.to_bytes().to_vec(), + ] + .concat(), + } +} + +/// Build an update_tolerance instruction. +pub fn update_tolerance( + signer: Pubkey, + new_liveness_tolerance: u64, + new_spam_tolerance: u64, +) -> Instruction { + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(CONFIG_ADDRESS, false), + ], + data: [ + OreInstruction::UpdateTolerance.to_vec(), + UpdateToleranceArgs { + tolerance_liveness: new_liveness_tolerance, + tolerance_spam: new_spam_tolerance, + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + +/// Build a pause instruction. +pub fn pause(signer: Pubkey, paused: bool) -> Instruction { + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(CONFIG_ADDRESS, false), + ], + data: [ + OreInstruction::UpdateAdmin.to_vec(), + PauseArgs { + paused: paused as u8, + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} diff --git a/src/lib.rs b/src/lib.rs index 4050f87..ee46608 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -pub mod builders; pub mod consts; pub mod error; pub mod instruction; From dc84a347e7b73cb84feb7407229882c341fb91cc Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 2 May 2024 15:03:56 +0000 Subject: [PATCH 032/111] comments --- src/lib.rs | 2 -- src/processor/claim.rs | 8 ++++---- src/processor/deregister.rs | 9 +++++++++ src/processor/pause.rs | 10 ++++++++++ src/processor/register.rs | 2 +- src/processor/stake.rs | 8 ++++++++ src/processor/update_tolerance.rs | 8 ++++++++ src/processor/upgrade.rs | 7 +++++++ 8 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ee46608..3bcda6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,8 +14,6 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; -// TODO Is downgrade necessary? - declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); #[cfg(not(feature = "no-entrypoint"))] diff --git a/src/processor/claim.rs b/src/processor/claim.rs index 69ae1fe..4875e65 100644 --- a/src/processor/claim.rs +++ b/src/processor/claim.rs @@ -8,14 +8,14 @@ use crate::{ MINT_ADDRESS, TREASURY, TREASURY_BUMP, }; -/// Claim distributes owed token rewards from the treasury to the miner. Its responsibilies include: -/// 1. Decrement the miner's claimable rewards counter. +/// Claim distributes Ore from the treasury to a miner. Its responsibilies include: +/// 1. Decrement the miner's claimable balance. /// 2. Transfer tokens from the treasury to the miner. /// /// Safety requirements: -/// - Claim is a permissionless instruction and can be called by any miner. +/// - Claim is a permissionless instruction and can be called by any user. /// - Can only succeed if the claimed amount is less than or equal to the miner's claimable rewards. -/// - The provided beneficiary token account, treasury, treasury token account, and token program must be valid. +/// - The provided beneficiary, token account, treasury, treasury token account, and token program must be valid. pub fn process_claim<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], diff --git a/src/processor/deregister.rs b/src/processor/deregister.rs index 0640e6c..8ec8d4a 100644 --- a/src/processor/deregister.rs +++ b/src/processor/deregister.rs @@ -5,6 +5,15 @@ use solana_program::{ use crate::{loaders::*, state::Proof, utils::AccountDeserialize, PROOF}; +/// Deregister closes a proof account and returns the rent to the owner. Its responsibilities include: +/// 1. Realloc proof account size to 0. +/// 2. Transfer lamports to the owner. +/// 3. Reassign the account owner back to the system program. +/// +/// Safety requirements: +/// - Deregister is a permissionless instruction and can be invoked by any singer. +/// - Can only succeed if the provided proof acount PDA is valid (associated with the signer). +/// - The provided system program must be valid. pub fn process_deregister<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], diff --git a/src/processor/pause.rs b/src/processor/pause.rs index 4970c6e..29e4d17 100644 --- a/src/processor/pause.rs +++ b/src/processor/pause.rs @@ -5,6 +5,16 @@ use solana_program::{ use crate::{instruction::PauseArgs, loaders::*, state::Config, utils::AccountDeserialize}; +/// Pause updates the program's pause flag. Its responsibilities include: +/// 1. Update the pause flag. +/// +/// Safety requirements: +/// - Can only succeed if the signer is the program admin. +/// - Can only succeed if the provided config is valid. +/// +/// Discussion: +/// - This should only be used to address critical contract risks and force migration to a new +/// verison (hardfork). pub fn process_pause<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], diff --git a/src/processor/register.rs b/src/processor/register.rs index 6b100ca..550ee24 100644 --- a/src/processor/register.rs +++ b/src/processor/register.rs @@ -21,7 +21,7 @@ use crate::{ /// Safety requirements: /// - Register is a permissionless instruction and can be invoked by any singer. /// - Can only succeed if the provided proof acount PDA is valid (associated with the signer). -/// - Can only succeed once per signer. +/// - Can only succeed if the user does not already have a proof account. /// - The provided system program must be valid. pub fn process_register<'a, 'info>( _program_id: &Pubkey, diff --git a/src/processor/stake.rs b/src/processor/stake.rs index dc18c61..bb3275b 100644 --- a/src/processor/stake.rs +++ b/src/processor/stake.rs @@ -8,6 +8,14 @@ use crate::{ TREASURY_ADDRESS, }; +/// Stake deposits Ore into a miner's proof account to earn multiplier. Its responsibilies include: +/// 1. Transfer tokens from the miner to the treasury account. +/// 2. Increment the miner's claimable balance. +/// +/// Safety requirements: +/// - Stake is a permissionless instruction and can be called by any user. +/// - Can only succeed if the amount is less than or equal to the miner's transferable tokens. +/// - The provided beneficiary, proof, sender, treasury token account, and token program must be valid. pub fn process_stake<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], diff --git a/src/processor/update_tolerance.rs b/src/processor/update_tolerance.rs index 6dfbfe9..b7bd86b 100644 --- a/src/processor/update_tolerance.rs +++ b/src/processor/update_tolerance.rs @@ -8,6 +8,14 @@ use crate::{ utils::AccountDeserialize, ONE_MINUTE, }; +/// UpdateTolerance updates the program's tolerance settings. Its responsibilities include: +/// 1. Update the liveness tolerance. +/// 2. Update the spam tolerance. +/// +/// Safety requirements: +/// - Can only succeed if the signer is the program admin. +/// - Can only succeed if the provided config is valid. +/// - Can only succeed if the tolerances pass sanity tests. pub fn process_update_tolerance<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], diff --git a/src/processor/upgrade.rs b/src/processor/upgrade.rs index b4ee4cf..f1f2bf5 100644 --- a/src/processor/upgrade.rs +++ b/src/processor/upgrade.rs @@ -7,6 +7,13 @@ use crate::{ instruction::StakeArgs, loaders::*, MINT_ADDRESS, MINT_V1_ADDRESS, TREASURY, TREASURY_BUMP, }; +/// Upgrade allows a user to migrate a v1 token to a v2 token one-for-one. Its responsibilies include: +/// 1. Burns the v1 tokens. +/// 2. Mints an equivalent number of v2 tokens to the user. +/// +/// Safety requirements: +/// - Upgrade is a permissionless instruction and can be called by any user. +/// - The provided beneficiary, mint, mint v1, sender, and token program must be valid. pub fn process_upgrade<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], From d1c903ec48d509b19dabaf23fa38934d740de711 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 3 May 2024 02:59:34 +0000 Subject: [PATCH 033/111] add max supply --- src/consts.rs | 7 +++++-- src/error.rs | 2 ++ src/processor/reset.rs | 18 ++++++++++++++---- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 71a12a9..3aa7b38 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -11,10 +11,10 @@ pub const INITIAL_TOLERANCE: i64 = 5; /// The minimum difficulty required of all submitted hashes. pub const MIN_DIFFICULTY: u32 = 12; -/// The decimal precision of the ORE token. +/// The decimal precision of the Ore token (100 billion indivisible units per Ore). pub const TOKEN_DECIMALS: u8 = 11; -/// One ORE token, denominated in indivisible units. +/// One Ore token, denominated in indivisible units. pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); /// The duration of one minute, in seconds. @@ -23,6 +23,9 @@ pub const ONE_MINUTE: i64 = 60; /// The duration of two years, in minutes. pub const TWO_YEARS: u64 = 60 * 24 * 365 * 2; +/// The maximum token supply (100 million). +pub const MAX_SUPPLY: u64 = ONE_ORE.saturating_mul(100_000_000); + /// The target quantity of ORE to be mined per epoch. /// Inflation rate ≈ 1 ORE / epoch (min 0, max 2) pub const TARGET_EPOCH_REWARDS: u64 = ONE_ORE; diff --git a/src/error.rs b/src/error.rs index fe711ec..0956ad3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -19,6 +19,8 @@ pub enum OreError { TransactionInvalid = 5, #[error("The tolerance cannot exceed i64 max value")] ToleranceOverflow = 6, + #[error("The maximum supply has been reached")] + MaxSupply = 7, } impl From for ProgramError { diff --git a/src/processor/reset.rs b/src/processor/reset.rs index 95627fd..f204504 100644 --- a/src/processor/reset.rs +++ b/src/processor/reset.rs @@ -1,7 +1,8 @@ use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, - program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, + program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, sysvar::Sysvar, }; +use spl_token::state::Mint; use crate::{ error::OreError, @@ -11,8 +12,8 @@ use crate::{ }, state::{Bus, Config}, utils::AccountDeserialize, - BUS_COUNT, BUS_EPOCH_REWARDS, MAX_EPOCH_REWARDS, MINT_ADDRESS, ONE_MINUTE, SMOOTHING_FACTOR, - TARGET_EPOCH_REWARDS, TREASURY, TREASURY_BUMP, + BUS_COUNT, BUS_EPOCH_REWARDS, MAX_EPOCH_REWARDS, MAX_SUPPLY, MINT_ADDRESS, ONE_MINUTE, + SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS, TREASURY, TREASURY_BUMP, }; /// Reset sets up the Ore program for the next epoch. Its responsibilities include: @@ -104,6 +105,15 @@ pub fn process_reset<'a, 'info>( config.base_reward_rate = calculate_new_reward_rate(config.base_reward_rate, total_theoretical_rewards); + // Load mint + let mint = Mint::unpack(&mint_info.data.borrow()).expect("Failed to parse mint"); + let amount = MAX_SUPPLY + .saturating_sub(mint.supply) + .min(total_epoch_rewards); + if amount.eq(&0) { + return Err(OreError::MaxSupply.into()); + } + // Fund treasury token account solana_program::program::invoke_signed( &spl_token::instruction::mint_to( @@ -112,7 +122,7 @@ pub fn process_reset<'a, 'info>( treasury_tokens_info.key, treasury_info.key, &[treasury_info.key], - total_epoch_rewards, + amount, )?, &[ token_program.clone(), From 41be4300ff21130920260f5906a903a6b99fb250 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 3 May 2024 02:59:47 +0000 Subject: [PATCH 034/111] return data --- src/processor/mine.rs | 29 +++++++++++++---------------- src/utils.rs | 11 +++++++++++ 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index a0d168f..1bc4ba7 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -6,7 +6,6 @@ use solana_program::{ clock::Clock, entrypoint::ProgramResult, keccak::hashv, - log::sol_log, program_error::ProgramError, pubkey::Pubkey, sanitize::SanitizeError, @@ -21,7 +20,7 @@ use crate::{ instruction::{MineArgs, OreInstruction}, loaders::*, state::{Bus, Config, Proof}, - utils::AccountDeserialize, + utils::{AccountDeserialize, MineEvent}, MIN_DIFFICULTY, ONE_MINUTE, TWO_YEARS, }; @@ -92,7 +91,6 @@ pub fn process_mine<'a, 'info>( // Validate hash satisfies the minimnum difficulty let difficulty = drillx::difficulty(hx); - sol_log(&format!("Diff {}", difficulty)); if difficulty.lt(&MIN_DIFFICULTY) { return Err(OreError::HashTooEasy.into()); } @@ -102,12 +100,12 @@ pub fn process_mine<'a, 'info>( let mut reward = config .base_reward_rate .saturating_mul(2u64.saturating_pow(difficulty)); - sol_log(&format!("Base {}", reward)); // Apply staking multiplier. // To prevent flash loan attacks, only apply if the last deposit was at least 1 block ago. // The multiplier can range 1x to 2x. To receive the maximum multiplier, the stake balance must be // greater than or equal to two years worth of rewards at the selected difficulty. + let mut multiplier = 1f32; if clock.slot.gt(&proof.last_deposit_slot) { let upper_bound = reward.saturating_mul(TWO_YEARS); let staking_reward = proof @@ -115,9 +113,10 @@ pub fn process_mine<'a, 'info>( .min(upper_bound) .saturating_mul(reward) .saturating_div(upper_bound); - reward = reward.saturating_add(staking_reward); - sol_log(&format!("Staking {}", staking_reward)); - } + let multiplied_reward = reward.saturating_add(staking_reward); + multiplier = ((multiplied_reward as f64) / (reward as f64)) as f32; + reward = multiplied_reward; + }; // Apply spam penalty let t = clock.unix_timestamp; @@ -125,7 +124,6 @@ pub fn process_mine<'a, 'info>( let t_spam = t_target.saturating_sub(config.tolerance_spam); if t.lt(&t_spam) { reward = 0; - sol_log("Spam penalty"); } // Apply liveness penalty @@ -136,11 +134,6 @@ pub fn process_mine<'a, 'info>( .saturating_mul(t.saturating_sub(t_liveness) as u64) .saturating_div(ONE_MINUTE as u64), ); - sol_log(&format!( - "Liveness penalty ({} sec) {}", - t.saturating_sub(t_liveness), - reward, - )); } // Limit payout amount to whatever is left in the bus @@ -149,8 +142,6 @@ pub fn process_mine<'a, 'info>( let reward_actual = reward.min(bus.rewards); // Update balances - sol_log(&format!("Total {}", reward)); - sol_log(&format!("Bus {}", bus.rewards)); bus.theoretical_rewards = bus.theoretical_rewards.saturating_add(reward); bus.rewards = bus .rewards @@ -174,7 +165,13 @@ pub fn process_mine<'a, 'info>( proof.total_rewards = proof.total_rewards.saturating_add(reward); // Log the mined rewards - set_return_data(reward_actual.to_le_bytes().as_slice()); + set_return_data(bytemuck::bytes_of(&MineEvent { + difficulty, + multiplier, + reward, + reward_actual, + timing: t.saturating_sub(t_target), + })); Ok(()) } diff --git a/src/utils.rs b/src/utils.rs index df3eac4..c4d80bf 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,4 @@ +use bytemuck::{Pod, Zeroable}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, @@ -78,6 +79,16 @@ pub(crate) fn create_pda<'a, 'info>( Ok(()) } +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct MineEvent { + pub difficulty: u32, + pub multiplier: f32, + pub reward: u64, + pub reward_actual: u64, + pub timing: i64, +} + #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] pub enum AccountDiscriminator { From bfa422a11d5c4878238addec2ffb8342d0317b5c Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 3 May 2024 03:46:05 +0000 Subject: [PATCH 035/111] fix max supply logic --- src/processor/mine.rs | 32 +++++++++++++++++++------------- src/processor/reset.rs | 10 +++++----- src/utils.rs | 4 +--- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 1bc4ba7..e994e8e 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -13,14 +13,14 @@ use solana_program::{ slot_hashes::SlotHash, sysvar::{self, instructions::load_current_index, Sysvar}, }; -use solana_program::{program::set_return_data, pubkey}; +use solana_program::{log::sol_log, pubkey}; use crate::{ error::OreError, instruction::{MineArgs, OreInstruction}, loaders::*, state::{Bus, Config, Proof}, - utils::{AccountDeserialize, MineEvent}, + utils::AccountDeserialize, MIN_DIFFICULTY, ONE_MINUTE, TWO_YEARS, }; @@ -97,15 +97,16 @@ pub fn process_mine<'a, 'info>( // Calculate base reward rate let difficulty = difficulty.saturating_sub(MIN_DIFFICULTY); + sol_log(&format!("Diff {}", difficulty)); let mut reward = config .base_reward_rate .saturating_mul(2u64.saturating_pow(difficulty)); + sol_log(&format!("Base {}", reward)); // Apply staking multiplier. // To prevent flash loan attacks, only apply if the last deposit was at least 1 block ago. // The multiplier can range 1x to 2x. To receive the maximum multiplier, the stake balance must be // greater than or equal to two years worth of rewards at the selected difficulty. - let mut multiplier = 1f32; if clock.slot.gt(&proof.last_deposit_slot) { let upper_bound = reward.saturating_mul(TWO_YEARS); let staking_reward = proof @@ -113,9 +114,8 @@ pub fn process_mine<'a, 'info>( .min(upper_bound) .saturating_mul(reward) .saturating_div(upper_bound); - let multiplied_reward = reward.saturating_add(staking_reward); - multiplier = ((multiplied_reward as f64) / (reward as f64)) as f32; - reward = multiplied_reward; + reward = reward.saturating_add(staking_reward); + sol_log(&format!("Staking {}", staking_reward)); }; // Apply spam penalty @@ -124,6 +124,7 @@ pub fn process_mine<'a, 'info>( let t_spam = t_target.saturating_sub(config.tolerance_spam); if t.lt(&t_spam) { reward = 0; + sol_log("Spam penalty"); } // Apply liveness penalty @@ -134,6 +135,11 @@ pub fn process_mine<'a, 'info>( .saturating_mul(t.saturating_sub(t_liveness) as u64) .saturating_div(ONE_MINUTE as u64), ); + sol_log(&format!( + "Liveness penalty ({} sec) {}", + t.saturating_sub(t_liveness), + reward, + )); } // Limit payout amount to whatever is left in the bus @@ -142,6 +148,8 @@ pub fn process_mine<'a, 'info>( let reward_actual = reward.min(bus.rewards); // Update balances + sol_log(&format!("Total {}", reward)); + sol_log(&format!("Bus {}", bus.rewards)); bus.theoretical_rewards = bus.theoretical_rewards.saturating_add(reward); bus.rewards = bus .rewards @@ -165,13 +173,11 @@ pub fn process_mine<'a, 'info>( proof.total_rewards = proof.total_rewards.saturating_add(reward); // Log the mined rewards - set_return_data(bytemuck::bytes_of(&MineEvent { - difficulty, - multiplier, - reward, - reward_actual, - timing: t.saturating_sub(t_target), - })); + // set_return_data(bytemuck::bytes_of(&MineEvent { + // difficulty: difficulty as u64, + // reward, + // timing: t.saturating_sub(t_target), + // })); Ok(()) } diff --git a/src/processor/reset.rs b/src/processor/reset.rs index f204504..a2fe824 100644 --- a/src/processor/reset.rs +++ b/src/processor/reset.rs @@ -105,16 +105,16 @@ pub fn process_reset<'a, 'info>( config.base_reward_rate = calculate_new_reward_rate(config.base_reward_rate, total_theoretical_rewards); - // Load mint + // Max supply check let mint = Mint::unpack(&mint_info.data.borrow()).expect("Failed to parse mint"); - let amount = MAX_SUPPLY - .saturating_sub(mint.supply) - .min(total_epoch_rewards); - if amount.eq(&0) { + if mint.supply.ge(&MAX_SUPPLY) { return Err(OreError::MaxSupply.into()); } // Fund treasury token account + let amount = MAX_SUPPLY + .saturating_sub(mint.supply) + .min(total_epoch_rewards); solana_program::program::invoke_signed( &spl_token::instruction::mint_to( &spl_token::id(), diff --git a/src/utils.rs b/src/utils.rs index c4d80bf..ed4398e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -82,10 +82,8 @@ pub(crate) fn create_pda<'a, 'info>( #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] pub struct MineEvent { - pub difficulty: u32, - pub multiplier: f32, + pub difficulty: u64, pub reward: u64, - pub reward_actual: u64, pub timing: i64, } From 6bc3979b35af0d18ad34f6829f747ef802a1ffe3 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 3 May 2024 13:24:18 +0000 Subject: [PATCH 036/111] todo --- src/processor/claim.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/processor/claim.rs b/src/processor/claim.rs index 4875e65..593bdf1 100644 --- a/src/processor/claim.rs +++ b/src/processor/claim.rs @@ -33,7 +33,6 @@ pub fn process_claim<'a, 'info>( }; load_signer(signer)?; load_token_account(beneficiary_info, None, &MINT_ADDRESS, true)?; - load_proof(proof_info, signer.key, true)?; load_treasury(treasury_info, false)?; load_token_account( treasury_tokens_info, From 6f1454a895eb5fcc68035a29c274fede7fc3592e Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 3 May 2024 16:49:28 +0000 Subject: [PATCH 037/111] one minute stake requirement --- src/processor/mine.rs | 13 ++++++++----- src/processor/register.rs | 2 +- src/state/proof.rs | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index e994e8e..6a75d42 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -91,23 +91,27 @@ pub fn process_mine<'a, 'info>( // Validate hash satisfies the minimnum difficulty let difficulty = drillx::difficulty(hx); + sol_log(&format!("Diff {}", difficulty)); if difficulty.lt(&MIN_DIFFICULTY) { return Err(OreError::HashTooEasy.into()); } // Calculate base reward rate let difficulty = difficulty.saturating_sub(MIN_DIFFICULTY); - sol_log(&format!("Diff {}", difficulty)); let mut reward = config .base_reward_rate .saturating_mul(2u64.saturating_pow(difficulty)); sol_log(&format!("Base {}", reward)); // Apply staking multiplier. - // To prevent flash loan attacks, only apply if the last deposit was at least 1 block ago. // The multiplier can range 1x to 2x. To receive the maximum multiplier, the stake balance must be - // greater than or equal to two years worth of rewards at the selected difficulty. - if clock.slot.gt(&proof.last_deposit_slot) { + // greater than or equal to two years worth of rewards at the selected difficulty. Miners are only + // eligable for a multipler if their last stake deposit was more than one minute ago. + if proof + .last_stake_at + .saturating_add(ONE_MINUTE) + .le(&clock.unix_timestamp) + { let upper_bound = reward.saturating_mul(TWO_YEARS); let staking_reward = proof .balance @@ -165,7 +169,6 @@ pub fn process_mine<'a, 'info>( .0; // Update time trackers - proof.last_deposit_slot = clock.slot; proof.last_hash_at = clock.unix_timestamp; // Update lifetime stats diff --git a/src/processor/register.rs b/src/processor/register.rs index 550ee24..cfed8ed 100644 --- a/src/processor/register.rs +++ b/src/processor/register.rs @@ -64,8 +64,8 @@ pub fn process_register<'a, 'info>( &slot_hashes_info.data.borrow()[0..size_of::()], ]) .0; - proof.last_deposit_slot = 0; proof.last_hash_at = 0; + proof.last_stake_at = 0; proof.total_hashes = 0; proof.total_rewards = 0; diff --git a/src/state/proof.rs b/src/state/proof.rs index e2913d1..902fc14 100644 --- a/src/state/proof.rs +++ b/src/state/proof.rs @@ -21,12 +21,12 @@ pub struct Proof { /// The current mining challenge. pub challenge: [u8; 32], - /// The last slot ore was deposited into this account. - pub last_deposit_slot: u64, - /// The last time this account provided a hash. pub last_hash_at: i64, + /// The last time stake was deposited into this account. + pub last_stake_at: i64, + /// The total lifetime hashes provided by this miner. pub total_hashes: u64, From 06779be532129ffcc436fceb96a8f1d850f9f4b3 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 3 May 2024 16:50:40 +0000 Subject: [PATCH 038/111] last stake at --- src/processor/stake.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/processor/stake.rs b/src/processor/stake.rs index bb3275b..79be3dd 100644 --- a/src/processor/stake.rs +++ b/src/processor/stake.rs @@ -47,7 +47,7 @@ pub fn process_stake<'a, 'info>( // Update deposit timestamp let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; - proof.last_deposit_slot = clock.slot; + proof.last_stake_at = clock.unix_timestamp; // Distribute tokens from signer to treasury solana_program::program::invoke( From 28e24322dd88f42b93dfcca07281870facce4dcb Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 4 May 2024 13:59:14 +0000 Subject: [PATCH 039/111] long epochs --- src/consts.rs | 3 +++ src/processor/mine.rs | 10 ++++++---- src/processor/reset.rs | 9 ++++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 3aa7b38..bddd95a 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -20,6 +20,9 @@ pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); /// The duration of one minute, in seconds. pub const ONE_MINUTE: i64 = 60; +/// The duration of an Ore epoch, in seconds. +pub const EPOCH_DURATION: i64 = ONE_MINUTE.saturating_mul(3); + /// The duration of two years, in minutes. pub const TWO_YEARS: u64 = 60 * 24 * 365 * 2; diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 6a75d42..a55132e 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -21,7 +21,7 @@ use crate::{ loaders::*, state::{Bus, Config, Proof}, utils::AccountDeserialize, - MIN_DIFFICULTY, ONE_MINUTE, TWO_YEARS, + EPOCH_DURATION, MIN_DIFFICULTY, ONE_MINUTE, TWO_YEARS, }; /// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: @@ -70,6 +70,7 @@ pub fn process_mine<'a, 'info>( return Err(OreError::IsPaused.into()); } + // TODO Is this really needed? // Validate the clock state let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; let mut proof_data = proof_info.data.borrow_mut(); @@ -79,9 +80,10 @@ pub fn process_mine<'a, 'info>( } // Validate epoch is active - if clock - .unix_timestamp - .ge(&config.last_reset_at.saturating_add(ONE_MINUTE)) + if config + .last_reset_at + .saturating_add(EPOCH_DURATION) + .le(&clock.unix_timestamp) { return Err(OreError::NeedsReset.into()); } diff --git a/src/processor/reset.rs b/src/processor/reset.rs index a2fe824..ed385fb 100644 --- a/src/processor/reset.rs +++ b/src/processor/reset.rs @@ -12,7 +12,7 @@ use crate::{ }, state::{Bus, Config}, utils::AccountDeserialize, - BUS_COUNT, BUS_EPOCH_REWARDS, MAX_EPOCH_REWARDS, MAX_SUPPLY, MINT_ADDRESS, ONE_MINUTE, + BUS_COUNT, BUS_EPOCH_REWARDS, EPOCH_DURATION, MAX_EPOCH_REWARDS, MAX_SUPPLY, MINT_ADDRESS, SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS, TREASURY, TREASURY_BUMP, }; @@ -79,8 +79,11 @@ pub fn process_reset<'a, 'info>( // Validate enough time has passed since last reset let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; - let threshold = config.last_reset_at.saturating_add(ONE_MINUTE); - if clock.unix_timestamp.lt(&threshold) { + if config + .last_reset_at + .saturating_add(EPOCH_DURATION) + .gt(&clock.unix_timestamp) + { return Ok(()); } From fdadc1dcfc1e982d0385513c524b8a386a09004a Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Mon, 6 May 2024 17:24:59 +0000 Subject: [PATCH 040/111] fine tuning --- src/consts.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index bddd95a..fa395c4 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -21,13 +21,13 @@ pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); pub const ONE_MINUTE: i64 = 60; /// The duration of an Ore epoch, in seconds. -pub const EPOCH_DURATION: i64 = ONE_MINUTE.saturating_mul(3); +pub const EPOCH_DURATION: i64 = ONE_MINUTE.saturating_mul(5); /// The duration of two years, in minutes. pub const TWO_YEARS: u64 = 60 * 24 * 365 * 2; -/// The maximum token supply (100 million). -pub const MAX_SUPPLY: u64 = ONE_ORE.saturating_mul(100_000_000); +/// The maximum token supply (42 million). +pub const MAX_SUPPLY: u64 = ONE_ORE.saturating_mul(42_000_000); /// The target quantity of ORE to be mined per epoch. /// Inflation rate ≈ 1 ORE / epoch (min 0, max 2) From be78043d4be507eccbfa95af39f2a384692e727e Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 8 May 2024 13:45:46 +0000 Subject: [PATCH 041/111] fix metadata testing issue --- src/instruction.rs | 2 +- src/processor/initialize.rs | 58 ++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/instruction.rs b/src/instruction.rs index 340bff6..29d7ec4 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -385,7 +385,7 @@ pub fn initialize(signer: Pubkey) -> Instruction { AccountMeta::new_readonly(system_program::id(), false), AccountMeta::new_readonly(spl_token::id(), false), AccountMeta::new_readonly(spl_associated_token_account::id(), false), - // AccountMeta::new_readonly(mpl_token_metadata::ID, false), + AccountMeta::new_readonly(mpl_token_metadata::ID, false), AccountMeta::new_readonly(sysvar::rent::id(), false), ], data: [ diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index f387eed..bc8512a 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -17,8 +17,8 @@ use crate::{ utils::create_pda, utils::AccountDeserialize, utils::Discriminator, - BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, INITIAL_TOLERANCE, METADATA, MINT, - MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, + BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, INITIAL_TOLERANCE, METADATA, METADATA_NAME, + METADATA_SYMBOL, METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, }; /// Initialize sets up the Ore program. Its responsibilities include: @@ -48,8 +48,7 @@ pub fn process_initialize<'a, 'info>( let args = InitializeArgs::try_from_bytes(data)?; // Load accounts - // let [signer, 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, metadata_info, mint_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program, metadata_program, rent_sysvar] = - let [signer, 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, metadata_info, mint_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program, rent_sysvar] = + let [signer, 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, metadata_info, mint_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program, metadata_program, rent_sysvar] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); @@ -85,7 +84,7 @@ pub fn process_initialize<'a, 'info>( load_program(system_program, system_program::id())?; load_program(token_program, spl_token::id())?; load_program(associated_token_program, spl_associated_token_account::id())?; - // load_program(metadata_program, mpl_token_metadata::ID)?; + load_program(metadata_program, mpl_token_metadata::ID)?; load_sysvar(rent_sysvar, sysvar::rent::id())?; // Initialize bus accounts @@ -179,32 +178,31 @@ pub fn process_initialize<'a, 'info>( &[&[MINT, MINT_NOISE.as_slice(), &[args.mint_bump]]], )?; - // TODO Fix metadata initialization // Initialize mint metadata - // mpl_token_metadata::instructions::CreateMetadataAccountV3Cpi { - // __program: metadata_program, - // metadata: metadata_info, - // mint: mint_info, - // mint_authority: treasury_info, - // payer: signer, - // update_authority: (signer, true), - // system_program, - // rent: Some(rent_sysvar), - // __args: mpl_token_metadata::instructions::CreateMetadataAccountV3InstructionArgs { - // data: mpl_token_metadata::types::DataV2 { - // name: METADATA_NAME.to_string(), - // symbol: METADATA_SYMBOL.to_string(), - // uri: METADATA_URI.to_string(), - // seller_fee_basis_points: 0, - // creators: None, - // collection: None, - // uses: None, - // }, - // is_mutable: true, - // collection_details: None, - // }, - // } - // .invoke_signed(&[&[TREASURY, &[args.treasury_bump]]])?; + mpl_token_metadata::instructions::CreateMetadataAccountV3Cpi { + __program: metadata_program, + metadata: metadata_info, + mint: mint_info, + mint_authority: treasury_info, + payer: signer, + update_authority: (signer, true), + system_program, + rent: Some(rent_sysvar), + __args: mpl_token_metadata::instructions::CreateMetadataAccountV3InstructionArgs { + data: mpl_token_metadata::types::DataV2 { + name: METADATA_NAME.to_string(), + symbol: METADATA_SYMBOL.to_string(), + uri: METADATA_URI.to_string(), + seller_fee_basis_points: 0, + creators: None, + collection: None, + uses: None, + }, + is_mutable: true, + collection_details: None, + }, + } + .invoke_signed(&[&[TREASURY, &[args.treasury_bump]]])?; // Initialize treasury token account solana_program::program::invoke( From 31e1d85bacf23bcfae0a796a5e1f6d27d68a39d2 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 8 May 2024 16:00:51 +0000 Subject: [PATCH 042/111] update program id --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e3fab0..97fc16a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2549,7 +2549,7 @@ dependencies = [ [[package]] name = "ore-program" -version = "1.2.1" +version = "2.0.0" dependencies = [ "array-const-fn-init", "bs58 0.5.0", diff --git a/Cargo.toml b/Cargo.toml index be2a6ac..9e6d084 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ore-program" -version = "1.2.1" +version = "2.0.0" description = "Ore is a digital currency you can mine from anywhere, at home or on your phone." edition = "2021" license = "Apache-2.0" diff --git a/src/lib.rs b/src/lib.rs index 3bcda6e..8f054b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; -declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); +declare_id!("mineS8xKxv3GurPq4RAssdZa6kNSXuuxJCEVtQwPZX4"); #[cfg(not(feature = "no-entrypoint"))] solana_program::entrypoint!(process_instruction); From 8b064d313bdb885b7ee56a2d6fd4f1b36080e103 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 8 May 2024 16:13:45 +0000 Subject: [PATCH 043/111] todos --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 8f054b3..3fce010 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,9 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; +// TODO Initialize with mining paused +// TODO Require hardcoded admin key for initialization + declare_id!("mineS8xKxv3GurPq4RAssdZa6kNSXuuxJCEVtQwPZX4"); #[cfg(not(feature = "no-entrypoint"))] From 6b5dba464c62a949f9e47e83b3069f386376386f Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 9 May 2024 13:19:16 +0000 Subject: [PATCH 044/111] correct bus balances --- src/consts.rs | 21 +++++++++++++-------- src/processor/reset.rs | 2 ++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index fa395c4..0b687ba 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -11,7 +11,8 @@ pub const INITIAL_TOLERANCE: i64 = 5; /// The minimum difficulty required of all submitted hashes. pub const MIN_DIFFICULTY: u32 = 12; -/// The decimal precision of the Ore token (100 billion indivisible units per Ore). +/// The decimal precision of the Ore token. +/// There are 100 billion indivisible units per Ore (called "grains"). pub const TOKEN_DECIMALS: u8 = 11; /// One Ore token, denominated in indivisible units. @@ -20,21 +21,21 @@ pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); /// The duration of one minute, in seconds. pub const ONE_MINUTE: i64 = 60; -/// The duration of an Ore epoch, in seconds. -pub const EPOCH_DURATION: i64 = ONE_MINUTE.saturating_mul(5); +/// The number of minutes in an Ore epoch. +pub const EPOCH_MINUTES: i64 = 5; -/// The duration of two years, in minutes. -pub const TWO_YEARS: u64 = 60 * 24 * 365 * 2; +/// The duration of an Ore epoch, in seconds. +pub const EPOCH_DURATION: i64 = ONE_MINUTE.saturating_mul(EPOCH_MINUTES); /// The maximum token supply (42 million). pub const MAX_SUPPLY: u64 = ONE_ORE.saturating_mul(42_000_000); /// The target quantity of ORE to be mined per epoch. -/// Inflation rate ≈ 1 ORE / epoch (min 0, max 2) -pub const TARGET_EPOCH_REWARDS: u64 = ONE_ORE; +/// Inflation rate ≈ 1 ORE / min (min 0, max 2) +pub const TARGET_EPOCH_REWARDS: u64 = ONE_ORE.saturating_mul(EPOCH_MINUTES as u64); /// The maximum quantity of ORE that can be mined per epoch. -pub const MAX_EPOCH_REWARDS: u64 = ONE_ORE.saturating_mul(2); +pub const MAX_EPOCH_REWARDS: u64 = TARGET_EPOCH_REWARDS.saturating_mul(2); /// The quantity of ORE each bus is allowed to issue per epoch. pub const BUS_EPOCH_REWARDS: u64 = MAX_EPOCH_REWARDS.saturating_div(BUS_COUNT as u64); @@ -51,6 +52,10 @@ static_assertions::const_assert!( (MAX_EPOCH_REWARDS / BUS_COUNT as u64) * BUS_COUNT as u64 == MAX_EPOCH_REWARDS ); +/// The duration of two years, in minutes. +/// Used to calculate the staking reward multiplier. +pub const TWO_YEARS: u64 = 60 * 24 * 365 * 2; + /// The seed of the bus account PDA. pub const BUS: &[u8] = b"bus"; diff --git a/src/processor/reset.rs b/src/processor/reset.rs index ed385fb..54ed945 100644 --- a/src/processor/reset.rs +++ b/src/processor/reset.rs @@ -16,6 +16,8 @@ use crate::{ SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS, TREASURY, TREASURY_BUMP, }; +// TODO Update comments to account for 5 minute epoch + /// Reset sets up the Ore program for the next epoch. Its responsibilities include: /// 1. Reset bus account rewards counters. /// 2. Adjust the reward rate to stabilize inflation. From b264a5a5b15bcea857422efa7b26a9f262710cc3 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 10 May 2024 15:18:13 +0000 Subject: [PATCH 045/111] max epoch rewards --- src/consts.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/consts.rs b/src/consts.rs index 0b687ba..53e55a8 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -35,7 +35,7 @@ pub const MAX_SUPPLY: u64 = ONE_ORE.saturating_mul(42_000_000); pub const TARGET_EPOCH_REWARDS: u64 = ONE_ORE.saturating_mul(EPOCH_MINUTES as u64); /// The maximum quantity of ORE that can be mined per epoch. -pub const MAX_EPOCH_REWARDS: u64 = TARGET_EPOCH_REWARDS.saturating_mul(2); +pub const MAX_EPOCH_REWARDS: u64 = TARGET_EPOCH_REWARDS.saturating_mul(3); /// The quantity of ORE each bus is allowed to issue per epoch. pub const BUS_EPOCH_REWARDS: u64 = MAX_EPOCH_REWARDS.saturating_div(BUS_COUNT as u64); From ba6d746c503f17b263dbfc04b3cfa6546fcc89ab Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 12 May 2024 19:28:43 +0000 Subject: [PATCH 046/111] claim rate limiter --- src/consts.rs | 3 +++ src/instruction.rs | 1 + src/processor/claim.rs | 49 ++++++++++++++++++++++++++++++++++----- src/processor/register.rs | 17 ++++++++++---- src/state/proof.rs | 3 +++ 5 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 53e55a8..cbe8987 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -21,6 +21,9 @@ pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); /// The duration of one minute, in seconds. pub const ONE_MINUTE: i64 = 60; +/// The duration of one day, in seconds. +pub const ONE_DAY: i64 = 86400; + /// The number of minutes in an Ore epoch. pub const EPOCH_MINUTES: i64 = 5; diff --git a/src/instruction.rs b/src/instruction.rs index 29d7ec4..949e87a 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -295,6 +295,7 @@ pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { accounts: vec![ AccountMeta::new(signer, true), AccountMeta::new(beneficiary, false), + AccountMeta::new(MINT_ADDRESS, false), AccountMeta::new(proof, false), AccountMeta::new_readonly(TREASURY_ADDRESS, false), AccountMeta::new(treasury_tokens, false), diff --git a/src/processor/claim.rs b/src/processor/claim.rs index 593bdf1..67456f0 100644 --- a/src/processor/claim.rs +++ b/src/processor/claim.rs @@ -1,11 +1,11 @@ use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, + account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, }; use crate::{ error::OreError, instruction::ClaimArgs, loaders::*, state::Proof, utils::AccountDeserialize, - MINT_ADDRESS, TREASURY, TREASURY_BUMP, + MINT_ADDRESS, ONE_DAY, TREASURY, TREASURY_BUMP, }; /// Claim distributes Ore from the treasury to a miner. Its responsibilies include: @@ -26,13 +26,14 @@ pub fn process_claim<'a, 'info>( let amount = u64::from_le_bytes(args.amount); // Load accounts - let [signer, beneficiary_info, proof_info, treasury_info, treasury_tokens_info, token_program] = + let [signer, beneficiary_info, mint_info, proof_info, treasury_info, treasury_tokens_info, token_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; load_token_account(beneficiary_info, None, &MINT_ADDRESS, true)?; + load_mint(mint_info, MINT_ADDRESS, true)?; load_treasury(treasury_info, false)?; load_token_account( treasury_tokens_info, @@ -42,14 +43,50 @@ pub fn process_claim<'a, 'info>( )?; load_program(token_program, spl_token::id())?; - // Update miner balance + // If last claim was less than 1 day ago, burn some of the claim amount + let mut claim_amount = amount; let mut proof_data = proof_info.data.borrow_mut(); let proof = Proof::try_from_bytes_mut(&mut proof_data)?; + let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; + let t = proof.last_claim_at.saturating_add(ONE_DAY); + if clock.unix_timestamp.lt(&t) { + // Calculate burn amount + let burn_amount = amount + .saturating_mul(t.saturating_sub(clock.unix_timestamp) as u64) + .saturating_div(ONE_DAY as u64); + + // Burn tokens from treasury + solana_program::program::invoke_signed( + &spl_token::instruction::burn( + &spl_token::id(), + treasury_tokens_info.key, + mint_info.key, + treasury_info.key, + &[treasury_info.key], + burn_amount, + )?, + &[ + token_program.clone(), + treasury_tokens_info.clone(), + mint_info.clone(), + treasury_info.clone(), + ], + &[&[TREASURY, &[TREASURY_BUMP]]], + )?; + + // Update claim amount + claim_amount = amount.saturating_sub(burn_amount); + } + + // Update miner balance proof.balance = proof .balance .checked_sub(amount) .ok_or(OreError::ClaimTooLarge)?; + // Update timestamp + proof.last_claim_at = clock.unix_timestamp; + // Distribute tokens from treasury to beneficiary solana_program::program::invoke_signed( &spl_token::instruction::transfer( @@ -58,7 +95,7 @@ pub fn process_claim<'a, 'info>( beneficiary_info.key, treasury_info.key, &[treasury_info.key], - amount, + claim_amount, )?, &[ token_program.clone(), diff --git a/src/processor/register.rs b/src/processor/register.rs index cfed8ed..8dbb760 100644 --- a/src/processor/register.rs +++ b/src/processor/register.rs @@ -1,8 +1,15 @@ use std::mem::size_of; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, keccak::hashv, - program_error::ProgramError, pubkey::Pubkey, slot_hashes::SlotHash, system_program, sysvar, + account_info::AccountInfo, + clock::Clock, + entrypoint::ProgramResult, + keccak::hashv, + program_error::ProgramError, + pubkey::Pubkey, + slot_hashes::SlotHash, + system_program, + sysvar::{self, Sysvar}, }; use crate::{ @@ -54,6 +61,7 @@ pub fn process_register<'a, 'info>( system_program, signer, )?; + let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; let mut proof_data = proof_info.data.borrow_mut(); proof_data[0] = Proof::discriminator() as u8; let proof = Proof::try_from_bytes_mut(&mut proof_data)?; @@ -64,8 +72,9 @@ pub fn process_register<'a, 'info>( &slot_hashes_info.data.borrow()[0..size_of::()], ]) .0; - proof.last_hash_at = 0; - proof.last_stake_at = 0; + proof.last_claim_at = clock.unix_timestamp; + proof.last_hash_at = clock.unix_timestamp; + proof.last_stake_at = clock.unix_timestamp; proof.total_hashes = 0; proof.total_rewards = 0; diff --git a/src/state/proof.rs b/src/state/proof.rs index 902fc14..78c819c 100644 --- a/src/state/proof.rs +++ b/src/state/proof.rs @@ -21,6 +21,9 @@ pub struct Proof { /// The current mining challenge. pub challenge: [u8; 32], + /// The last time this account claimed rewards. + pub last_claim_at: i64, + /// The last time this account provided a hash. pub last_hash_at: i64, From e2868cc8617a431ce4218ee3a1ba2843072a742b Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 12 May 2024 19:29:56 +0000 Subject: [PATCH 047/111] epoch tuning --- src/consts.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index cbe8987..982faf9 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -25,7 +25,7 @@ pub const ONE_MINUTE: i64 = 60; pub const ONE_DAY: i64 = 86400; /// The number of minutes in an Ore epoch. -pub const EPOCH_MINUTES: i64 = 5; +pub const EPOCH_MINUTES: i64 = 1; /// The duration of an Ore epoch, in seconds. pub const EPOCH_DURATION: i64 = ONE_MINUTE.saturating_mul(EPOCH_MINUTES); @@ -34,11 +34,11 @@ pub const EPOCH_DURATION: i64 = ONE_MINUTE.saturating_mul(EPOCH_MINUTES); pub const MAX_SUPPLY: u64 = ONE_ORE.saturating_mul(42_000_000); /// The target quantity of ORE to be mined per epoch. -/// Inflation rate ≈ 1 ORE / min (min 0, max 2) pub const TARGET_EPOCH_REWARDS: u64 = ONE_ORE.saturating_mul(EPOCH_MINUTES as u64); /// The maximum quantity of ORE that can be mined per epoch. -pub const MAX_EPOCH_REWARDS: u64 = TARGET_EPOCH_REWARDS.saturating_mul(3); +/// Inflation rate ≈ 1 ORE / min (min 0, max 5) +pub const MAX_EPOCH_REWARDS: u64 = TARGET_EPOCH_REWARDS.saturating_mul(5); /// The quantity of ORE each bus is allowed to issue per epoch. pub const BUS_EPOCH_REWARDS: u64 = MAX_EPOCH_REWARDS.saturating_div(BUS_COUNT as u64); From 56e20bf78e5673eb94d0bfe395cd5c5c56affb2f Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sun, 12 May 2024 20:41:39 +0000 Subject: [PATCH 048/111] update address --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 3fce010..2a0fe43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ use solana_program::{ // TODO Initialize with mining paused // TODO Require hardcoded admin key for initialization -declare_id!("mineS8xKxv3GurPq4RAssdZa6kNSXuuxJCEVtQwPZX4"); +declare_id!("mineQW6HcBby3YyZMTaRRtuFWPaGEg8AjmCAWs4nBU8"); #[cfg(not(feature = "no-entrypoint"))] solana_program::entrypoint!(process_instruction); From a71024cfa1e62ad251daaee3f96ab90090316702 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Mon, 13 May 2024 15:06:14 +0000 Subject: [PATCH 049/111] deregister fixes --- src/instruction.rs | 14 ++++++++++++++ src/processor/deregister.rs | 32 +++++++------------------------- src/utils.rs | 16 +++++----------- 3 files changed, 26 insertions(+), 36 deletions(-) diff --git a/src/instruction.rs b/src/instruction.rs index 949e87a..1d94c13 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -258,6 +258,20 @@ pub fn register(signer: Pubkey) -> Instruction { } } +/// Builds a deregister instruction. +pub fn deregister(signer: Pubkey) -> Instruction { + let proof_pda = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(proof_pda.0, false), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + ], + data: OreInstruction::Deregister.to_vec(), + } +} + /// Builds a mine instruction. pub fn mine(signer: Pubkey, bus: Pubkey, nonce: u64) -> Instruction { let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; diff --git a/src/processor/deregister.rs b/src/processor/deregister.rs index 8ec8d4a..ea69397 100644 --- a/src/processor/deregister.rs +++ b/src/processor/deregister.rs @@ -1,14 +1,13 @@ use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program::invoke_signed, - program_error::ProgramError, pubkey::Pubkey, system_program, + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, system_program, }; -use crate::{loaders::*, state::Proof, utils::AccountDeserialize, PROOF}; +use crate::{loaders::*, state::Proof, utils::AccountDeserialize}; /// Deregister closes a proof account and returns the rent to the owner. Its responsibilities include: /// 1. Realloc proof account size to 0. /// 2. Transfer lamports to the owner. -/// 3. Reassign the account owner back to the system program. /// /// Safety requirements: /// - Deregister is a permissionless instruction and can be invoked by any singer. @@ -33,31 +32,14 @@ pub fn process_deregister<'a, 'info>( if proof.balance.gt(&0) { return Err(ProgramError::InvalidAccountData); } - - // Generate bump - let bump = Pubkey::find_program_address(&[PROOF, signer.key.as_ref()], &crate::id()).1; + drop(proof_data); // Realloc data to zero - drop(proof_data); - proof_info.realloc(0, false)?; + proof_info.realloc(0, true)?; // Send lamports to signer - invoke_signed( - &solana_program::system_instruction::transfer( - proof_info.key, - signer.key, - proof_info.lamports(), - ), - &[proof_info.clone(), signer.clone(), system_program.clone()], - &[&[PROOF, signer.key.as_ref(), &[bump]]], - )?; - - // Reassign back to system program - solana_program::program::invoke_signed( - &solana_program::system_instruction::assign(proof_info.key, &system_program::id()), - &[proof_info.clone(), system_program.clone()], - &[&[PROOF, signer.key.as_ref(), &[bump]]], - )?; + **signer.lamports.borrow_mut() = signer.lamports().saturating_add(proof_info.lamports()); + **proof_info.lamports.borrow_mut() = 0; Ok(()) } diff --git a/src/utils.rs b/src/utils.rs index ed4398e..cdb9779 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -48,9 +48,9 @@ pub(crate) fn create_pda<'a, 'info>( rent_exempt_balance, ), &[ - payer.as_ref().clone(), - target_account.as_ref().clone(), - system_program.as_ref().clone(), + payer.clone(), + target_account.clone(), + system_program.clone(), ], )?; } @@ -58,20 +58,14 @@ pub(crate) fn create_pda<'a, 'info>( // 2) allocate space for the account solana_program::program::invoke_signed( &solana_program::system_instruction::allocate(target_account.key, space as u64), - &[ - target_account.as_ref().clone(), - system_program.as_ref().clone(), - ], + &[target_account.clone(), system_program.clone()], &[pda_seeds], )?; // 3) assign our program as the owner solana_program::program::invoke_signed( &solana_program::system_instruction::assign(target_account.key, owner), - &[ - target_account.as_ref().clone(), - system_program.as_ref().clone(), - ], + &[target_account.clone(), system_program.clone()], &[pda_seeds], )?; } From 3cfffdffc067cd8e30559be7b4b7ea0d1ee7685b Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 14 May 2024 03:11:47 +0000 Subject: [PATCH 050/111] max multiplier --- src/consts.rs | 2 +- src/processor/deregister.rs | 2 +- src/processor/mine.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index 982faf9..bba86e8 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -57,7 +57,7 @@ static_assertions::const_assert!( /// The duration of two years, in minutes. /// Used to calculate the staking reward multiplier. -pub const TWO_YEARS: u64 = 60 * 24 * 365 * 2; +pub const ONE_YEAR: u64 = 60 * 24 * 365; /// The seed of the bus account PDA. pub const BUS: &[u8] = b"bus"; diff --git a/src/processor/deregister.rs b/src/processor/deregister.rs index ea69397..00ba60e 100644 --- a/src/processor/deregister.rs +++ b/src/processor/deregister.rs @@ -38,7 +38,7 @@ pub fn process_deregister<'a, 'info>( proof_info.realloc(0, true)?; // Send lamports to signer - **signer.lamports.borrow_mut() = signer.lamports().saturating_add(proof_info.lamports()); + **signer.lamports.borrow_mut() += proof_info.lamports(); **proof_info.lamports.borrow_mut() = 0; Ok(()) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index a55132e..a422792 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -21,7 +21,7 @@ use crate::{ loaders::*, state::{Bus, Config, Proof}, utils::AccountDeserialize, - EPOCH_DURATION, MIN_DIFFICULTY, ONE_MINUTE, TWO_YEARS, + EPOCH_DURATION, MIN_DIFFICULTY, ONE_MINUTE, ONE_YEAR, }; /// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: @@ -114,7 +114,7 @@ pub fn process_mine<'a, 'info>( .saturating_add(ONE_MINUTE) .le(&clock.unix_timestamp) { - let upper_bound = reward.saturating_mul(TWO_YEARS); + let upper_bound = reward.saturating_mul(ONE_YEAR); let staking_reward = proof .balance .min(upper_bound) From 96548235dc8b67fe8e23cc96ee66be4b4fbf1419 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 15 May 2024 14:39:28 +0000 Subject: [PATCH 051/111] integrate equix based drillx --- Cargo.lock | 90 ++++++++++++++++++++++++++++++++++++++++++- src/error.rs | 14 ++++--- src/instruction.rs | 4 +- src/processor/mine.rs | 13 +++++-- 4 files changed, 109 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 97fc16a..58308ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,6 +481,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "blake3" version = "1.5.0" @@ -1249,12 +1258,40 @@ name = "drillx" version = "0.1.0" dependencies = [ "enum_dispatch", + "equix", "num-traits", "num_enum 0.5.11", "solana-program", "strum 0.26.2", ] +[[package]] +name = "dynasm" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33dc03612f42465a8ed7f5e354bc2b79ba54cedefa81d5bd3a064f1835adaba8" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dynasmrt" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7dccc31a678058996aef614f6bd418ced384da70f284e83e2b7bf29b27b6a28" +dependencies = [ + "byteorder", + "dynasm", + "fnv", + "memmap2", +] + [[package]] name = "eager" version = "0.1.0" @@ -1393,6 +1430,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "equix" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e25ed202554ac3bf3c8e5fab352947cc9b5c592e219185351d50fedf2b4213a" +dependencies = [ + "arrayvec", + "hashx", + "num-traits", + "thiserror", + "visibility", +] + [[package]] name = "errno" version = "0.3.8" @@ -1433,6 +1483,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fixed-capacity-vec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b31a14f5ee08ed1a40e1252b35af18bed062e3f39b69aab34decde36bc43e40" + [[package]] name = "flate2" version = "1.0.28" @@ -1687,6 +1743,21 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +[[package]] +name = "hashx" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49d1afec2e9689360a16d9790ac9704c038da4fd04f17890a9ff1c56e536e20a" +dependencies = [ + "arrayvec", + "blake2", + "dynasmrt", + "fixed-capacity-vec", + "hex", + "rand_core 0.6.4", + "thiserror", +] + [[package]] name = "heck" version = "0.4.1" @@ -1708,6 +1779,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "histogram" version = "0.6.9" @@ -2480,7 +2557,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 2.0.48", @@ -5685,6 +5762,17 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "visibility" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3fd98999db9227cf28e59d83e1f120f42bc233d4b152e8fab9bc87d5bb1e0f8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "void" version = "1.0.2" diff --git a/src/error.rs b/src/error.rs index 0956ad3..a0bf815 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,18 +9,20 @@ pub enum OreError { IsPaused = 0, #[error("The epoch has ended and needs reset")] NeedsReset = 1, + #[error("The provided hash is invalid")] + HashInvalid = 2, #[error("The provided hash did not satisfy the minimum required difficulty")] - HashTooEasy = 2, + HashTooEasy = 3, #[error("The claim amount cannot be greater than the claimable rewards")] - ClaimTooLarge = 3, + ClaimTooLarge = 4, #[error("The clock time is invalid")] - ClockInvalid = 4, + ClockInvalid = 5, #[error("Only one hash may be validated per transaction")] - TransactionInvalid = 5, + TransactionInvalid = 6, #[error("The tolerance cannot exceed i64 max value")] - ToleranceOverflow = 6, + ToleranceOverflow = 7, #[error("The maximum supply has been reached")] - MaxSupply = 7, + MaxSupply = 8, } impl From for ProgramError { diff --git a/src/instruction.rs b/src/instruction.rs index 1d94c13..ed105c7 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -151,6 +151,7 @@ pub struct RegisterArgs { #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct MineArgs { + pub digest: [u8; 16], pub nonce: [u8; 8], } @@ -273,7 +274,7 @@ pub fn deregister(signer: Pubkey) -> Instruction { } /// Builds a mine instruction. -pub fn mine(signer: Pubkey, bus: Pubkey, nonce: u64) -> Instruction { +pub fn mine(signer: Pubkey, bus: Pubkey, digest: [u8; 16], nonce: u64) -> Instruction { let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; Instruction { program_id: crate::id(), @@ -288,6 +289,7 @@ pub fn mine(signer: Pubkey, bus: Pubkey, nonce: u64) -> Instruction { data: [ OreInstruction::Mine.to_vec(), MineArgs { + digest, nonce: nonce.to_le_bytes(), } .to_bytes() diff --git a/src/processor/mine.rs b/src/processor/mine.rs index a422792..36b606b 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -1,5 +1,6 @@ use std::mem::size_of; +use drillx::Solution; #[allow(deprecated)] use solana_program::{ account_info::AccountInfo, @@ -88,11 +89,15 @@ pub fn process_mine<'a, 'info>( return Err(OreError::NeedsReset.into()); } - // Calculate the hash from the provided nonce - let hx = drillx::hash(&proof.challenge, &args.nonce); + // Validate the digest + let solution = Solution::new(args.digest, args.nonce); + if !solution.is_valid(&proof.challenge) { + return Err(OreError::HashInvalid.into()); + } // Validate hash satisfies the minimnum difficulty - let difficulty = drillx::difficulty(hx); + let hash = solution.to_hash(); + let difficulty = hash.difficulty(); sol_log(&format!("Diff {}", difficulty)); if difficulty.lt(&MIN_DIFFICULTY) { return Err(OreError::HashTooEasy.into()); @@ -165,7 +170,7 @@ pub fn process_mine<'a, 'info>( // Hash recent slot hash into the next challenge to prevent pre-mining attacks proof.challenge = hashv(&[ - hx.as_slice(), + hash.h.as_slice(), &slot_hashes_sysvar.data.borrow()[0..size_of::()], ]) .0; From 7c4ccfd7af77a92af79fd7e47d3934c5fe683156 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 15 May 2024 16:57:51 +0000 Subject: [PATCH 052/111] mine ix --- src/instruction.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/instruction.rs b/src/instruction.rs index ed105c7..da5d501 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -1,4 +1,5 @@ use bytemuck::{Pod, Zeroable}; +use drillx::Solution; use num_enum::TryFromPrimitive; use shank::ShankInstruction; use solana_program::{ @@ -274,7 +275,7 @@ pub fn deregister(signer: Pubkey) -> Instruction { } /// Builds a mine instruction. -pub fn mine(signer: Pubkey, bus: Pubkey, digest: [u8; 16], nonce: u64) -> Instruction { +pub fn mine(signer: Pubkey, bus: Pubkey, solution: Solution) -> Instruction { let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; Instruction { program_id: crate::id(), @@ -289,8 +290,8 @@ pub fn mine(signer: Pubkey, bus: Pubkey, digest: [u8; 16], nonce: u64) -> Instru data: [ OreInstruction::Mine.to_vec(), MineArgs { - digest, - nonce: nonce.to_le_bytes(), + digest: solution.d, + nonce: solution.n, } .to_bytes() .to_vec(), From 7db852f1c8d89ce92335f55946ac67732f2fc8a3 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 15 May 2024 17:24:23 +0000 Subject: [PATCH 053/111] min difficulty --- src/consts.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index bba86e8..dbd6c0b 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -9,7 +9,7 @@ pub const INITIAL_BASE_REWARD_RATE: u64 = 10u64.pow(3u32); pub const INITIAL_TOLERANCE: i64 = 5; /// The minimum difficulty required of all submitted hashes. -pub const MIN_DIFFICULTY: u32 = 12; +pub const MIN_DIFFICULTY: u32 = 8; // 12; /// The decimal precision of the Ore token. /// There are 100 billion indivisible units per Ore (called "grains"). @@ -24,6 +24,9 @@ pub const ONE_MINUTE: i64 = 60; /// The duration of one day, in seconds. pub const ONE_DAY: i64 = 86400; +/// The duration of one year, in minutes. +pub const ONE_YEAR: u64 = 525600; + /// The number of minutes in an Ore epoch. pub const EPOCH_MINUTES: i64 = 1; @@ -55,10 +58,6 @@ static_assertions::const_assert!( (MAX_EPOCH_REWARDS / BUS_COUNT as u64) * BUS_COUNT as u64 == MAX_EPOCH_REWARDS ); -/// The duration of two years, in minutes. -/// Used to calculate the staking reward multiplier. -pub const ONE_YEAR: u64 = 60 * 24 * 365; - /// The seed of the bus account PDA. pub const BUS: &[u8] = b"bus"; From 84ff192b2d66b6731acfc2cf10864eb51316d848 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 15 May 2024 19:38:39 +0000 Subject: [PATCH 054/111] blake3 --- src/processor/mine.rs | 2 +- src/processor/register.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 36b606b..87ce4c0 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -4,9 +4,9 @@ use drillx::Solution; #[allow(deprecated)] use solana_program::{ account_info::AccountInfo, + blake3::hashv, clock::Clock, entrypoint::ProgramResult, - keccak::hashv, program_error::ProgramError, pubkey::Pubkey, sanitize::SanitizeError, diff --git a/src/processor/register.rs b/src/processor/register.rs index 8dbb760..e556f84 100644 --- a/src/processor/register.rs +++ b/src/processor/register.rs @@ -2,9 +2,9 @@ use std::mem::size_of; use solana_program::{ account_info::AccountInfo, + blake3::hashv, clock::Clock, entrypoint::ProgramResult, - keccak::hashv, program_error::ProgramError, pubkey::Pubkey, slot_hashes::SlotHash, From 94525812f2885e663781bff605171ec2a4d26d62 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 15 May 2024 21:15:16 +0000 Subject: [PATCH 055/111] drillx dep features --- Cargo.lock | 37 +------------------------------------ Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 58308ed..0e6ace1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1257,10 +1257,8 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" name = "drillx" version = "0.1.0" dependencies = [ - "enum_dispatch", + "blake3", "equix", - "num-traits", - "num_enum 0.5.11", "solana-program", "strum 0.26.2", ] @@ -1399,18 +1397,6 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "enum_dispatch" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" -dependencies = [ - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.48", -] - [[package]] name = "env_logger" version = "0.9.3" @@ -2500,15 +2486,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive 0.5.11", -] - [[package]] name = "num_enum" version = "0.6.1" @@ -2527,18 +2504,6 @@ dependencies = [ "num_enum_derive 0.7.2", ] -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "num_enum_derive" version = "0.6.1" diff --git a/Cargo.toml b/Cargo.toml index 9e6d084..6291f36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ array-const-fn-init = "0.1.1" bs58 = "0.5.0" bytemuck = "1.14.3" const-crypto = "0.1.0" -drillx = { path = "../drillx/drillx" } +drillx = { path = "../drillx/drillx", features = ["solana"] } mpl-token-metadata = "4.1.2" num_enum = "0.7.2" shank = "0.3.0" From 1fe1649c4916087cc5ab5c6cf72ed73255d32b3b Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 17 May 2024 14:39:27 +0000 Subject: [PATCH 056/111] todo --- Cargo.lock | 2 +- src/processor/claim.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 0e6ace1..edd864e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1255,7 +1255,7 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "drillx" -version = "0.1.0" +version = "0.2.0" dependencies = [ "blake3", "equix", diff --git a/src/processor/claim.rs b/src/processor/claim.rs index 67456f0..0afaaa1 100644 --- a/src/processor/claim.rs +++ b/src/processor/claim.rs @@ -8,6 +8,8 @@ use crate::{ MINT_ADDRESS, ONE_DAY, TREASURY, TREASURY_BUMP, }; +// TODO Change rate limitter to be based on 1440 non spam txs rather than time based + /// Claim distributes Ore from the treasury to a miner. Its responsibilies include: /// 1. Decrement the miner's claimable balance. /// 2. Transfer tokens from the treasury to the miner. From e13a5bd889892ff46308d8787beef205a96c7f24 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 18 May 2024 05:30:55 +0000 Subject: [PATCH 057/111] remove claim rate limiter --- src/processor/claim.rs | 36 ++---------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/src/processor/claim.rs b/src/processor/claim.rs index 0afaaa1..f4f080f 100644 --- a/src/processor/claim.rs +++ b/src/processor/claim.rs @@ -5,11 +5,9 @@ use solana_program::{ use crate::{ error::OreError, instruction::ClaimArgs, loaders::*, state::Proof, utils::AccountDeserialize, - MINT_ADDRESS, ONE_DAY, TREASURY, TREASURY_BUMP, + MINT_ADDRESS, TREASURY, TREASURY_BUMP, }; -// TODO Change rate limitter to be based on 1440 non spam txs rather than time based - /// Claim distributes Ore from the treasury to a miner. Its responsibilies include: /// 1. Decrement the miner's claimable balance. /// 2. Transfer tokens from the treasury to the miner. @@ -46,39 +44,9 @@ pub fn process_claim<'a, 'info>( load_program(token_program, spl_token::id())?; // If last claim was less than 1 day ago, burn some of the claim amount - let mut claim_amount = amount; let mut proof_data = proof_info.data.borrow_mut(); let proof = Proof::try_from_bytes_mut(&mut proof_data)?; let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; - let t = proof.last_claim_at.saturating_add(ONE_DAY); - if clock.unix_timestamp.lt(&t) { - // Calculate burn amount - let burn_amount = amount - .saturating_mul(t.saturating_sub(clock.unix_timestamp) as u64) - .saturating_div(ONE_DAY as u64); - - // Burn tokens from treasury - solana_program::program::invoke_signed( - &spl_token::instruction::burn( - &spl_token::id(), - treasury_tokens_info.key, - mint_info.key, - treasury_info.key, - &[treasury_info.key], - burn_amount, - )?, - &[ - token_program.clone(), - treasury_tokens_info.clone(), - mint_info.clone(), - treasury_info.clone(), - ], - &[&[TREASURY, &[TREASURY_BUMP]]], - )?; - - // Update claim amount - claim_amount = amount.saturating_sub(burn_amount); - } // Update miner balance proof.balance = proof @@ -97,7 +65,7 @@ pub fn process_claim<'a, 'info>( beneficiary_info.key, treasury_info.key, &[treasury_info.key], - claim_amount, + amount, )?, &[ token_program.clone(), From 465643c2c9a3557c6655793f37a4fc67d1a62913 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 23 May 2024 03:41:30 +0000 Subject: [PATCH 058/111] last hash --- src/processor/claim.rs | 3 --- src/processor/mine.rs | 1 + src/processor/register.rs | 2 +- src/state/proof.rs | 4 ++-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/processor/claim.rs b/src/processor/claim.rs index f4f080f..0115276 100644 --- a/src/processor/claim.rs +++ b/src/processor/claim.rs @@ -54,9 +54,6 @@ pub fn process_claim<'a, 'info>( .checked_sub(amount) .ok_or(OreError::ClaimTooLarge)?; - // Update timestamp - proof.last_claim_at = clock.unix_timestamp; - // Distribute tokens from treasury to beneficiary solana_program::program::invoke_signed( &spl_token::instruction::transfer( diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 87ce4c0..1c45280 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -169,6 +169,7 @@ pub fn process_mine<'a, 'info>( proof.balance = proof.balance.saturating_add(reward_actual); // Hash recent slot hash into the next challenge to prevent pre-mining attacks + proof.last_hash = hash.h; proof.challenge = hashv(&[ hash.h.as_slice(), &slot_hashes_sysvar.data.borrow()[0..size_of::()], diff --git a/src/processor/register.rs b/src/processor/register.rs index e556f84..74fa0d7 100644 --- a/src/processor/register.rs +++ b/src/processor/register.rs @@ -72,7 +72,7 @@ pub fn process_register<'a, 'info>( &slot_hashes_info.data.borrow()[0..size_of::()], ]) .0; - proof.last_claim_at = clock.unix_timestamp; + proof.last_hash = [0; 32]; proof.last_hash_at = clock.unix_timestamp; proof.last_stake_at = clock.unix_timestamp; proof.total_hashes = 0; diff --git a/src/state/proof.rs b/src/state/proof.rs index 78c819c..5cc2f9c 100644 --- a/src/state/proof.rs +++ b/src/state/proof.rs @@ -21,8 +21,8 @@ pub struct Proof { /// The current mining challenge. pub challenge: [u8; 32], - /// The last time this account claimed rewards. - pub last_claim_at: i64, + /// The last hash the miner provided. + pub last_hash: [u8; 32], /// The last time this account provided a hash. pub last_hash_at: i64, From 61c3f366c5603ccafde861a729d7a6741100dd3c Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 23 May 2024 04:10:49 +0000 Subject: [PATCH 059/111] cleanup --- src/processor/claim.rs | 5 +---- src/processor/reset.rs | 2 -- src/state/hash.rs | 47 ------------------------------------------ src/state/mod.rs | 2 -- 4 files changed, 1 insertion(+), 55 deletions(-) delete mode 100644 src/state/hash.rs diff --git a/src/processor/claim.rs b/src/processor/claim.rs index 0115276..2f7ee5c 100644 --- a/src/processor/claim.rs +++ b/src/processor/claim.rs @@ -43,12 +43,9 @@ pub fn process_claim<'a, 'info>( )?; load_program(token_program, spl_token::id())?; - // If last claim was less than 1 day ago, burn some of the claim amount + // Update miner balance let mut proof_data = proof_info.data.borrow_mut(); let proof = Proof::try_from_bytes_mut(&mut proof_data)?; - let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; - - // Update miner balance proof.balance = proof .balance .checked_sub(amount) diff --git a/src/processor/reset.rs b/src/processor/reset.rs index 54ed945..ed385fb 100644 --- a/src/processor/reset.rs +++ b/src/processor/reset.rs @@ -16,8 +16,6 @@ use crate::{ SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS, TREASURY, TREASURY_BUMP, }; -// TODO Update comments to account for 5 minute epoch - /// Reset sets up the Ore program for the next epoch. Its responsibilities include: /// 1. Reset bus account rewards counters. /// 2. Adjust the reward rate to stabilize inflation. diff --git a/src/state/hash.rs b/src/state/hash.rs deleted file mode 100644 index 55084de..0000000 --- a/src/state/hash.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::{fmt, mem::transmute}; - -use bytemuck::{Pod, Zeroable}; -use solana_program::keccak::{Hash as KeccakHash, HASH_BYTES}; - -use crate::impl_to_bytes; - -/// Hash is an equivalent type to solana_program::keccak::Hash which supports bytemuck serialization. -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] -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) } - } -} - -impl fmt::Display for Hash { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", bs58::encode(self.0).into_string()) - } -} - -// impl Hash { -// pub fn difficulty(&self) -> u32 { -// let mut count = 0; -// for &byte in &self.0 { -// let lz = byte.leading_zeros(); -// count += lz; -// if lz < 8 { -// break; -// } -// } -// count -// } -// } - -impl_to_bytes!(Hash); diff --git a/src/state/mod.rs b/src/state/mod.rs index 6b681f5..7a3b009 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -1,11 +1,9 @@ mod bus; mod config; -// mod hash; mod proof; mod treasury; pub use bus::*; pub use config::*; -// pub use hash::*; pub use proof::*; pub use treasury::*; From 588d40b0aa4f08272734fb3708c9b3ce403a9857 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 23 May 2024 04:11:23 +0000 Subject: [PATCH 060/111] cleanup --- src/processor/claim.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/processor/claim.rs b/src/processor/claim.rs index 2f7ee5c..4770b94 100644 --- a/src/processor/claim.rs +++ b/src/processor/claim.rs @@ -1,6 +1,6 @@ use solana_program::{ - account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, - program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, }; use crate::{ From da4a81c30b2dd207cc1fcba72ddcdf943e1fa109 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 24 May 2024 21:54:38 +0000 Subject: [PATCH 061/111] cleanup --- src/error.rs | 8 +++++--- src/lib.rs | 5 +---- src/processor/initialize.rs | 11 ++++++++++- src/processor/mine.rs | 5 +++-- src/state/bus.rs | 2 +- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/error.rs b/src/error.rs index a0bf815..4a1dfd3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,12 +17,14 @@ pub enum OreError { ClaimTooLarge = 4, #[error("The clock time is invalid")] ClockInvalid = 5, + #[error("You are trying to submit too soon")] + Spam = 6, #[error("Only one hash may be validated per transaction")] - TransactionInvalid = 6, + TransactionInvalid = 7, #[error("The tolerance cannot exceed i64 max value")] - ToleranceOverflow = 7, + ToleranceOverflow = 8, #[error("The maximum supply has been reached")] - MaxSupply = 8, + MaxSupply = 9, } impl From for ProgramError { diff --git a/src/lib.rs b/src/lib.rs index 2a0fe43..c1ed6c5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ pub mod consts; pub mod error; pub mod instruction; -mod loaders; +pub mod loaders; mod processor; pub mod state; pub mod utils; @@ -14,9 +14,6 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; -// TODO Initialize with mining paused -// TODO Require hardcoded admin key for initialization - declare_id!("mineQW6HcBby3YyZMTaRRtuFWPaGEg8AjmCAWs4nBU8"); #[cfg(not(feature = "no-entrypoint"))] diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index bc8512a..c594a8e 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -5,6 +5,7 @@ use solana_program::{ entrypoint::ProgramResult, program_error::ProgramError, program_pack::Pack, + pubkey, pubkey::Pubkey, system_program, {self, sysvar}, }; @@ -21,6 +22,9 @@ use crate::{ METADATA_SYMBOL, METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, }; +/// The address to allow for initialization. +const AUTHORIZED_INITIALIZER: Pubkey = pubkey!("HBUh9g46wk2X89CvaNN15UmsznP59rh6od1h8JwYAopk"); + /// Initialize sets up the Ore program. Its responsibilities include: /// 1. Initialize the 8 bus accounts. /// 2. Initialize the treasury account. @@ -87,6 +91,11 @@ pub fn process_initialize<'a, 'info>( load_program(metadata_program, mpl_token_metadata::ID)?; load_sysvar(rent_sysvar, sysvar::rent::id())?; + // Check signer + if signer.key.ne(&AUTHORIZED_INITIALIZER) { + return Err(ProgramError::MissingRequiredSignature); + } + // Initialize bus accounts let bus_infos = [ bus_0_info, bus_1_info, bus_2_info, bus_3_info, bus_4_info, bus_5_info, bus_6_info, @@ -133,7 +142,7 @@ pub fn process_initialize<'a, 'info>( config.admin = *signer.key; config.base_reward_rate = INITIAL_BASE_REWARD_RATE; config.last_reset_at = 0; - config.paused = 0; + config.paused = 1; config.tolerance_liveness = INITIAL_TOLERANCE; config.tolerance_spam = INITIAL_TOLERANCE; diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 1c45280..7548caa 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -7,14 +7,15 @@ use solana_program::{ blake3::hashv, clock::Clock, entrypoint::ProgramResult, + log::sol_log, program_error::ProgramError, + pubkey, pubkey::Pubkey, sanitize::SanitizeError, serialize_utils::{read_pubkey, read_u16, read_u8}, slot_hashes::SlotHash, sysvar::{self, instructions::load_current_index, Sysvar}, }; -use solana_program::{log::sol_log, pubkey}; use crate::{ error::OreError, @@ -134,8 +135,8 @@ pub fn process_mine<'a, 'info>( let t_target = proof.last_hash_at.saturating_add(ONE_MINUTE); let t_spam = t_target.saturating_sub(config.tolerance_spam); if t.lt(&t_spam) { - reward = 0; sol_log("Spam penalty"); + return Err(OreError::Spam.into()); } // Apply liveness penalty diff --git a/src/state/bus.rs b/src/state/bus.rs index e3fef7c..36b3479 100644 --- a/src/state/bus.rs +++ b/src/state/bus.rs @@ -15,7 +15,7 @@ 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 epoch. + /// 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. From bbc51abfe3b6d41896ffe83703b4526f274b93ad Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 24 May 2024 21:55:47 +0000 Subject: [PATCH 062/111] todos --- src/loaders.rs | 2 +- src/state/treasury.rs | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/loaders.rs b/src/loaders.rs index eaddb54..73d7c50 100644 --- a/src/loaders.rs +++ b/src/loaders.rs @@ -757,7 +757,7 @@ pub fn load_program<'a, 'info>( // hash: KeccakHash::new_from_array([u8::MAX; 32]).into(), // total_hashes: 0, // total_rewards: 0, -// multiplier: 1, // TODO +// multiplier: 1, // last_hash_at: 0, // } // .to_bytes(), diff --git a/src/state/treasury.rs b/src/state/treasury.rs index 0975de6..e699ae2 100644 --- a/src/state/treasury.rs +++ b/src/state/treasury.rs @@ -10,11 +10,7 @@ use crate::{ /// It is the mint authority for the Ore token and also the authority of the program-owned token account. #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] -pub struct Treasury { - /// The bump of the treasury account PDA, for signing CPIs. - // TODO Is this needed if bump is const? - pub bump: u64, -} +pub struct Treasury {} impl Discriminator for Treasury { fn discriminator() -> AccountDiscriminator { From 8b8f77ee1f38db95857b377edc1f434995323a45 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 24 May 2024 21:56:47 +0000 Subject: [PATCH 063/111] clock state --- src/processor/mine.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 7548caa..bb3b67c 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -72,16 +72,8 @@ pub fn process_mine<'a, 'info>( return Err(OreError::IsPaused.into()); } - // TODO Is this really needed? - // Validate the clock state - let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; - let mut proof_data = proof_info.data.borrow_mut(); - let proof = Proof::try_from_bytes_mut(&mut proof_data)?; - if clock.unix_timestamp.lt(&proof.last_hash_at) { - return Err(OreError::ClockInvalid.into()); - } - // Validate epoch is active + let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; if config .last_reset_at .saturating_add(EPOCH_DURATION) @@ -91,6 +83,8 @@ pub fn process_mine<'a, 'info>( } // Validate the digest + let mut proof_data = proof_info.data.borrow_mut(); + let proof = Proof::try_from_bytes_mut(&mut proof_data)?; let solution = Solution::new(args.digest, args.nonce); if !solution.is_valid(&proof.challenge) { return Err(OreError::HashInvalid.into()); From b47c80979048655545460da0379848ea561dff3e Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 24 May 2024 21:58:09 +0000 Subject: [PATCH 064/111] sub --- src/processor/mine.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index bb3b67c..8038eb2 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -157,10 +157,7 @@ pub fn process_mine<'a, 'info>( sol_log(&format!("Total {}", reward)); sol_log(&format!("Bus {}", bus.rewards)); bus.theoretical_rewards = bus.theoretical_rewards.saturating_add(reward); - bus.rewards = bus - .rewards - .checked_sub(reward_actual) - .expect("This should not happen"); + bus.rewards = bus.rewards.saturating_sub(reward_actual); proof.balance = proof.balance.saturating_add(reward_actual); // Hash recent slot hash into the next challenge to prevent pre-mining attacks From f9f4e578bd9922366898994084553eea50488235 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Mon, 27 May 2024 12:06:09 -0500 Subject: [PATCH 065/111] treasury bump --- src/processor/initialize.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index c594a8e..0651a2d 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -157,8 +157,6 @@ pub fn process_initialize<'a, 'info>( )?; let mut treasury_data = treasury_info.data.borrow_mut(); treasury_data[0] = Treasury::discriminator() as u8; - let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?; - treasury.bump = args.treasury_bump as u64; drop(treasury_data); // Initialize mint From 9d46f7f3da4bed9db75ac2843f82a876e4f98a3f Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 28 May 2024 16:08:24 +0000 Subject: [PATCH 066/111] claim --- src/processor/claim.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/processor/claim.rs b/src/processor/claim.rs index 4770b94..f41cf0d 100644 --- a/src/processor/claim.rs +++ b/src/processor/claim.rs @@ -34,6 +34,7 @@ pub fn process_claim<'a, 'info>( load_signer(signer)?; load_token_account(beneficiary_info, None, &MINT_ADDRESS, true)?; load_mint(mint_info, MINT_ADDRESS, true)?; + load_proof(proof_info, signer.key, true)?; load_treasury(treasury_info, false)?; load_token_account( treasury_tokens_info, From 9bccf82edf3eb2a4056ab28024758cf824788bd7 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 28 May 2024 19:00:07 +0000 Subject: [PATCH 067/111] program id --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c1ed6c5..8114be6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ use solana_program::{ program_error::ProgramError, pubkey::Pubkey, }; -declare_id!("mineQW6HcBby3YyZMTaRRtuFWPaGEg8AjmCAWs4nBU8"); +declare_id!("oreFHcE6FvJTrsfaYca4mVeZn7J7T6oZS9FAvW9eg4q"); #[cfg(not(feature = "no-entrypoint"))] solana_program::entrypoint!(process_instruction); From fffba72723b19cba80f2c6f29f624aede5b349f0 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Mon, 3 Jun 2024 16:46:17 +0000 Subject: [PATCH 068/111] cap max supply on upgrade --- src/processor/upgrade.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/processor/upgrade.rs b/src/processor/upgrade.rs index f1f2bf5..53930d1 100644 --- a/src/processor/upgrade.rs +++ b/src/processor/upgrade.rs @@ -1,10 +1,12 @@ use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, + program_pack::Pack, pubkey::Pubkey, }; +use spl_token::state::Mint; use crate::{ - instruction::StakeArgs, loaders::*, MINT_ADDRESS, MINT_V1_ADDRESS, TREASURY, TREASURY_BUMP, + error::OreError, instruction::StakeArgs, loaders::*, MAX_SUPPLY, MINT_ADDRESS, MINT_V1_ADDRESS, + TREASURY, TREASURY_BUMP, }; /// Upgrade allows a user to migrate a v1 token to a v2 token one-for-one. Its responsibilies include: @@ -58,6 +60,13 @@ pub fn process_upgrade<'a, 'info>( // v1 token has 9 decimals. v2 token has 11. let amount_to_mint = amount.saturating_mul(100); + // Cap at max supply. + let mint_data = mint_info.data.borrow(); + let mint = Mint::unpack(&mint_data)?; + if mint.supply.saturating_add(amount_to_mint).gt(&MAX_SUPPLY) { + return Err(OreError::MaxSupply.into()); + } + // Mint to the beneficiary account solana_program::program::invoke_signed( &spl_token::instruction::mint_to( From 32f10563ba257ac1014a5eba000347899fe83d57 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 13 Jun 2024 14:06:19 +0000 Subject: [PATCH 069/111] drillx dep --- Cargo.lock | 1 + Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index edd864e..412b136 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1256,6 +1256,7 @@ checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" [[package]] name = "drillx" version = "0.2.0" +source = "git+https://github.com/regolith-labs/drillx?branch=master#ce0330957a2153c9191bdbcbcf4acce031ccd6bf" dependencies = [ "blake3", "equix", diff --git a/Cargo.toml b/Cargo.toml index 6291f36..fa1ecd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ array-const-fn-init = "0.1.1" bs58 = "0.5.0" bytemuck = "1.14.3" const-crypto = "0.1.0" -drillx = { path = "../drillx/drillx", features = ["solana"] } +drillx = { git = "https://github.com/regolith-labs/drillx", branch = "master", features = ["solana"] } mpl-token-metadata = "4.1.2" num_enum = "0.7.2" shank = "0.3.0" From 891565dff603891e540603d8399c233071f036af Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 13 Jun 2024 15:30:13 +0000 Subject: [PATCH 070/111] return data --- src/processor/mine.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 8038eb2..76f0667 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -1,6 +1,7 @@ use std::mem::size_of; use drillx::Solution; +use solana_program::program::set_return_data; #[allow(deprecated)] use solana_program::{ account_info::AccountInfo, @@ -22,7 +23,7 @@ use crate::{ instruction::{MineArgs, OreInstruction}, loaders::*, state::{Bus, Config, Proof}, - utils::AccountDeserialize, + utils::{AccountDeserialize, MineEvent}, EPOCH_DURATION, MIN_DIFFICULTY, ONE_MINUTE, ONE_YEAR, }; @@ -176,11 +177,11 @@ pub fn process_mine<'a, 'info>( proof.total_rewards = proof.total_rewards.saturating_add(reward); // Log the mined rewards - // set_return_data(bytemuck::bytes_of(&MineEvent { - // difficulty: difficulty as u64, - // reward, - // timing: t.saturating_sub(t_target), - // })); + set_return_data(bytemuck::bytes_of(&MineEvent { + difficulty: difficulty as u64, + reward: reward_actual, + timing: t.saturating_sub(t_liveness), + })); Ok(()) } From 1d20ed7772216a900ccb41845b1166aaf64482a5 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 18 Jun 2024 22:06:22 -0700 Subject: [PATCH 071/111] upgrade instruction builder --- src/instruction.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/instruction.rs b/src/instruction.rs index da5d501..a185ce0 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -10,7 +10,7 @@ use solana_program::{ use crate::{ impl_instruction_from_bytes, impl_to_bytes, BUS, BUS_ADDRESSES, CONFIG, CONFIG_ADDRESS, - METADATA, MINT, MINT_ADDRESS, MINT_NOISE, PROOF, TREASURY, TREASURY_ADDRESS, + METADATA, MINT, MINT_ADDRESS, MINT_NOISE, MINT_V1_ADDRESS, PROOF, TREASURY, TREASURY_ADDRESS, }; #[repr(u8)] @@ -358,6 +358,31 @@ pub fn stake(signer: Pubkey, sender: Pubkey, amount: u64) -> Instruction { } } +// build an upgrade instruction. +pub fn upgrade(signer: Pubkey, beneficiary: Pubkey, sender: Pubkey, amount: u64) -> Instruction { + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(beneficiary, false), + AccountMeta::new(MINT_ADDRESS, false), + AccountMeta::new(MINT_V1_ADDRESS, false), + AccountMeta::new(sender, false), + AccountMeta::new(TREASURY_ADDRESS, false), + AccountMeta::new_readonly(spl_token::id(), false), + ], + data: [ + OreInstruction::Upgrade.to_vec(), + UpgradeArgs { + amount: amount.to_le_bytes(), + } + .to_bytes() + .to_vec(), + ] + .concat(), + } +} + /// Builds an initialize instruction. pub fn initialize(signer: Pubkey) -> Instruction { let bus_pdas = [ From 583461e8d1d315d134eb61e10014888e1a0c5896 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 18 Jun 2024 22:08:13 -0700 Subject: [PATCH 072/111] instruction tries to borrow reference for an account which is already borrowed --- src/processor/upgrade.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/processor/upgrade.rs b/src/processor/upgrade.rs index 53930d1..e6aff30 100644 --- a/src/processor/upgrade.rs +++ b/src/processor/upgrade.rs @@ -61,8 +61,8 @@ pub fn process_upgrade<'a, 'info>( let amount_to_mint = amount.saturating_mul(100); // Cap at max supply. - let mint_data = mint_info.data.borrow(); - let mint = Mint::unpack(&mint_data)?; + let mint_data = mint_info.data.clone(); + let mint = Mint::unpack(&mint_data.borrow())?; if mint.supply.saturating_add(amount_to_mint).gt(&MAX_SUPPLY) { return Err(OreError::MaxSupply.into()); } From 0b57e5bba48d9f8a1f52700127e1418d6d55307e Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 19 Jun 2024 16:25:12 +0000 Subject: [PATCH 073/111] drop mint data --- src/processor/upgrade.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/processor/upgrade.rs b/src/processor/upgrade.rs index 53930d1..acfe18d 100644 --- a/src/processor/upgrade.rs +++ b/src/processor/upgrade.rs @@ -66,6 +66,7 @@ pub fn process_upgrade<'a, 'info>( if mint.supply.saturating_add(amount_to_mint).gt(&MAX_SUPPLY) { return Err(OreError::MaxSupply.into()); } + drop(mint_data); // Mint to the beneficiary account solana_program::program::invoke_signed( From 039c0a615029abf765606cda2dfaf5b399ad1778 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 19 Jun 2024 15:01:38 -0700 Subject: [PATCH 074/111] drop not borrow --- src/processor/upgrade.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/processor/upgrade.rs b/src/processor/upgrade.rs index 50fa0c3..acfe18d 100644 --- a/src/processor/upgrade.rs +++ b/src/processor/upgrade.rs @@ -61,8 +61,8 @@ pub fn process_upgrade<'a, 'info>( let amount_to_mint = amount.saturating_mul(100); // Cap at max supply. - let mint_data = mint_info.data.clone(); - let mint = Mint::unpack(&mint_data.borrow())?; + let mint_data = mint_info.data.borrow(); + let mint = Mint::unpack(&mint_data)?; if mint.supply.saturating_add(amount_to_mint).gt(&MAX_SUPPLY) { return Err(OreError::MaxSupply.into()); } From 8d560d255b3fb42e895324d821121dc44e96024b Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 19 Jun 2024 16:44:10 -0700 Subject: [PATCH 075/111] v1 token decimals --- src/consts.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/consts.rs b/src/consts.rs index dbd6c0b..dfca525 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -15,6 +15,9 @@ pub const MIN_DIFFICULTY: u32 = 8; // 12; /// There are 100 billion indivisible units per Ore (called "grains"). pub const TOKEN_DECIMALS: u8 = 11; +/// The decimal precision of the Ore v1 token. +pub const TOKEN_DECIMALS_V1: u8 = 9; + /// One Ore token, denominated in indivisible units. pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); From 1464ebf6ba3a691d65a5c294493f1a8a775ca8e6 Mon Sep 17 00:00:00 2001 From: Hardhat Chad <155858888+HardhatChad@users.noreply.github.com> Date: Sun, 23 Jun 2024 21:40:41 -0500 Subject: [PATCH 076/111] Update README.md --- README.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 337a941..0235961 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,6 @@ -# Ore +# ORE -**Ore is a digital currency you can mine from anywhere, at home or on your phone.** It uses a novel proof-of-work algorithm to guarantee no miner can ever be starved out from earning rewards. - - -## How it works - -The primary innovation of Ore is to offer non-exclusive mining rewards. This means one miner finding a valid solution does not prevent another miner from finding one as well. Rather than setting up every miner in a winner-take-all competition against one another, Ore gives each miner a personalized computational challenge. As long as a miner provides a valid solution to their own individual challenge, the protocol guarantees they will earn a piece of the supply. Since no miner can be censored from the network and valid solutions are non-exclusive, starvation is avoided. - - -## Supply - -Ore is designed to protect holders from runaway supply inflation. Regardless of how many miners are active in the world, supply growth is strictly bounded to a rate of `0 ≤ R ≤ 2 ORE/min`. In other words, linear. The mining reward rate – amount paid out to miners per valid solution – is dynamically adjusted every 60 seconds to maintain an average supply growth of `1 ORE/min`. This level was chosen for its straightforward simplicity, scale agnosticism, and for striking a balance between the extremes of exponential inflation on one hand and stagnant deflation on the other. +**ORE is a fair-launch, proof-of-work, cross-border digital currency everyone can mine.** ## Program From c2b38908fd04cd29988101eb1445a62320618331 Mon Sep 17 00:00:00 2001 From: Hardhat Chad <155858888+HardhatChad@users.noreply.github.com> Date: Sun, 23 Jun 2024 21:40:59 -0500 Subject: [PATCH 077/111] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0235961..bfbf28a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ORE -**ORE is a fair-launch, proof-of-work, cross-border digital currency everyone can mine.** +**ORE is a fair-launch, proof-of-work, cross-border digital currency anyone can mine.** ## Program From 3ef1623565412f94c473f09a7ce04fab324b7ba5 Mon Sep 17 00:00:00 2001 From: Hardhat Chad <155858888+HardhatChad@users.noreply.github.com> Date: Sun, 23 Jun 2024 21:47:20 -0500 Subject: [PATCH 078/111] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index bfbf28a..0d95802 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ **ORE is a fair-launch, proof-of-work, cross-border digital currency anyone can mine.** +## Install + +```sh +cargo install ore-cli +``` + ## Program - [`Consts`](src/consts.rs) – Program constants. From dfc24e5c46275600a8c16f4ce5b133eb0b437f50 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Mon, 24 Jun 2024 16:55:23 +0000 Subject: [PATCH 079/111] remove admin stuff --- src/consts.rs | 7 ++-- src/instruction.rs | 39 ---------------------- src/lib.rs | 1 - src/processor/initialize.rs | 10 ++---- src/processor/mine.rs | 6 ++-- src/processor/mod.rs | 2 -- src/processor/update_tolerance.rs | 54 ------------------------------- src/state/config.rs | 6 ---- 8 files changed, 10 insertions(+), 115 deletions(-) delete mode 100644 src/processor/update_tolerance.rs diff --git a/src/consts.rs b/src/consts.rs index dbd6c0b..fdc8e35 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -5,8 +5,11 @@ use solana_program::{pubkey, pubkey::Pubkey}; /// The reward rate to intialize the program with. pub const INITIAL_BASE_REWARD_RATE: u64 = 10u64.pow(3u32); -/// The spam/liveness tolerance to initialize the program with. -pub const INITIAL_TOLERANCE: i64 = 5; +/// The admin allowed to initialize the program. +pub const INITIAL_ADMIN: Pubkey = pubkey!("HBUh9g46wk2X89CvaNN15UmsznP59rh6od1h8JwYAopk"); + +/// The spam/liveness tolerance in seconds. +pub const TOLERANCE: i64 = 5; /// The minimum difficulty required of all submitted hashes. pub const MIN_DIFFICULTY: u32 = 8; // 12; diff --git a/src/instruction.rs b/src/instruction.rs index da5d501..038c7c5 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -108,11 +108,6 @@ pub enum OreInstruction { #[account(1, name = "signer", desc = "Admin signer", signer)] #[account(2, name = "config", desc = "Ore config account", writable)] UpdateAdmin = 101, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "signer", desc = "Admin signer", signer)] - #[account(2, name = "config", desc = "Ore config account", writable)] - UpdateTolerance = 102, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] @@ -180,13 +175,6 @@ pub struct UpdateAdminArgs { pub new_admin: Pubkey, } -#[repr(C)] -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct UpdateToleranceArgs { - pub tolerance_liveness: u64, - pub tolerance_spam: u64, -} - #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct PauseArgs { @@ -200,7 +188,6 @@ impl_to_bytes!(ClaimArgs); impl_to_bytes!(StakeArgs); impl_to_bytes!(UpgradeArgs); impl_to_bytes!(UpdateAdminArgs); -impl_to_bytes!(UpdateToleranceArgs); impl_to_bytes!(PauseArgs); impl_instruction_from_bytes!(InitializeArgs); @@ -210,7 +197,6 @@ impl_instruction_from_bytes!(ClaimArgs); impl_instruction_from_bytes!(StakeArgs); impl_instruction_from_bytes!(UpgradeArgs); impl_instruction_from_bytes!(UpdateAdminArgs); -impl_instruction_from_bytes!(UpdateToleranceArgs); impl_instruction_from_bytes!(PauseArgs); /// Builds a reset instruction. @@ -445,31 +431,6 @@ pub fn update_admin(signer: Pubkey, new_admin: Pubkey) -> Instruction { } } -/// Build an update_tolerance instruction. -pub fn update_tolerance( - signer: Pubkey, - new_liveness_tolerance: u64, - new_spam_tolerance: u64, -) -> Instruction { - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(CONFIG_ADDRESS, false), - ], - data: [ - OreInstruction::UpdateTolerance.to_vec(), - UpdateToleranceArgs { - tolerance_liveness: new_liveness_tolerance, - tolerance_spam: new_spam_tolerance, - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} - /// Build a pause instruction. pub fn pause(signer: Pubkey, paused: bool) -> Instruction { Instruction { diff --git a/src/lib.rs b/src/lib.rs index 8114be6..09a335b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,6 @@ pub fn process_instruction( OreInstruction::Upgrade => process_upgrade(program_id, accounts, data)?, OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, OreInstruction::UpdateAdmin => process_update_admin(program_id, accounts, data)?, - OreInstruction::UpdateTolerance => process_update_tolerance(program_id, accounts, data)?, OreInstruction::Pause => process_pause(program_id, accounts, data)?, } diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 0651a2d..6bbd628 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -5,7 +5,6 @@ use solana_program::{ entrypoint::ProgramResult, program_error::ProgramError, program_pack::Pack, - pubkey, pubkey::Pubkey, system_program, {self, sysvar}, }; @@ -18,13 +17,10 @@ use crate::{ utils::create_pda, utils::AccountDeserialize, utils::Discriminator, - BUS, BUS_COUNT, CONFIG, INITIAL_BASE_REWARD_RATE, INITIAL_TOLERANCE, METADATA, METADATA_NAME, + BUS, BUS_COUNT, CONFIG, INITIAL_ADMIN, INITIAL_BASE_REWARD_RATE, METADATA, METADATA_NAME, METADATA_SYMBOL, METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, }; -/// The address to allow for initialization. -const AUTHORIZED_INITIALIZER: Pubkey = pubkey!("HBUh9g46wk2X89CvaNN15UmsznP59rh6od1h8JwYAopk"); - /// Initialize sets up the Ore program. Its responsibilities include: /// 1. Initialize the 8 bus accounts. /// 2. Initialize the treasury account. @@ -92,7 +88,7 @@ pub fn process_initialize<'a, 'info>( load_sysvar(rent_sysvar, sysvar::rent::id())?; // Check signer - if signer.key.ne(&AUTHORIZED_INITIALIZER) { + if signer.key.ne(&INITIAL_ADMIN) { return Err(ProgramError::MissingRequiredSignature); } @@ -143,8 +139,6 @@ pub fn process_initialize<'a, 'info>( config.base_reward_rate = INITIAL_BASE_REWARD_RATE; config.last_reset_at = 0; config.paused = 1; - config.tolerance_liveness = INITIAL_TOLERANCE; - config.tolerance_spam = INITIAL_TOLERANCE; // Initialize treasury create_pda( diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 76f0667..c075bd7 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -24,7 +24,7 @@ use crate::{ loaders::*, state::{Bus, Config, Proof}, utils::{AccountDeserialize, MineEvent}, - EPOCH_DURATION, MIN_DIFFICULTY, ONE_MINUTE, ONE_YEAR, + EPOCH_DURATION, MIN_DIFFICULTY, ONE_MINUTE, ONE_YEAR, TOLERANCE, }; /// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: @@ -128,14 +128,14 @@ pub fn process_mine<'a, 'info>( // Apply spam penalty let t = clock.unix_timestamp; let t_target = proof.last_hash_at.saturating_add(ONE_MINUTE); - let t_spam = t_target.saturating_sub(config.tolerance_spam); + let t_spam = t_target.saturating_sub(TOLERANCE); if t.lt(&t_spam) { sol_log("Spam penalty"); return Err(OreError::Spam.into()); } // Apply liveness penalty - let t_liveness = t_target.saturating_add(config.tolerance_liveness); + let t_liveness = t_target.saturating_add(TOLERANCE); if t.gt(&t_liveness) { reward = reward.saturating_sub( reward diff --git a/src/processor/mod.rs b/src/processor/mod.rs index 6807980..79f21c9 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -7,7 +7,6 @@ mod register; mod reset; mod stake; mod update_admin; -mod update_tolerance; mod upgrade; pub use claim::*; @@ -19,5 +18,4 @@ pub use register::*; pub use reset::*; pub use stake::*; pub use update_admin::*; -pub use update_tolerance::*; pub use upgrade::*; diff --git a/src/processor/update_tolerance.rs b/src/processor/update_tolerance.rs deleted file mode 100644 index b7bd86b..0000000 --- a/src/processor/update_tolerance.rs +++ /dev/null @@ -1,54 +0,0 @@ -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, -}; - -use crate::{ - error::OreError, instruction::UpdateToleranceArgs, loaders::*, state::Config, - utils::AccountDeserialize, ONE_MINUTE, -}; - -/// UpdateTolerance updates the program's tolerance settings. Its responsibilities include: -/// 1. Update the liveness tolerance. -/// 2. Update the spam tolerance. -/// -/// Safety requirements: -/// - Can only succeed if the signer is the program admin. -/// - Can only succeed if the provided config is valid. -/// - Can only succeed if the tolerances pass sanity tests. -pub fn process_update_tolerance<'a, 'info>( - _program_id: &Pubkey, - accounts: &'a [AccountInfo<'info>], - data: &[u8], -) -> ProgramResult { - // Parse args - let args = UpdateToleranceArgs::try_from_bytes(data)?; - - // Load accounts - let [signer, config_info] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - load_signer(signer)?; - load_config(config_info, true)?; - - // Validate signer is admin - let mut config_data = config_info.data.borrow_mut(); - let config = Config::try_from_bytes_mut(&mut config_data)?; - if config.admin.ne(&signer.key) { - return Err(ProgramError::MissingRequiredSignature); - } - - // Sanity checks - if args.tolerance_liveness.ge(&(ONE_MINUTE as u64)) { - return Err(OreError::ToleranceOverflow.into()); - } - if args.tolerance_spam.ge(&(ONE_MINUTE as u64)) { - return Err(OreError::ToleranceOverflow.into()); - } - - // Update tolerances - config.tolerance_liveness = args.tolerance_liveness as i64; - config.tolerance_spam = args.tolerance_spam as i64; - - Ok(()) -} diff --git a/src/state/config.rs b/src/state/config.rs index ea15408..0f4ee03 100644 --- a/src/state/config.rs +++ b/src/state/config.rs @@ -22,12 +22,6 @@ pub struct Config { /// Is mining paused. pub paused: u64, - - /// Seconds prior to a miner's target time during which their hashes will not be penalized. - pub tolerance_spam: i64, - - /// Seconds after a miner's target time during which their hashes will not be penalized. - pub tolerance_liveness: i64, } impl Discriminator for Config { From 4ce510cfa2c2e280504cb6757b860c8c9c4da847 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Mon, 24 Jun 2024 21:08:33 +0000 Subject: [PATCH 080/111] remove all admin functions --- src/error.rs | 20 ++++++------- src/instruction.rs | 50 -------------------------------- src/lib.rs | 2 -- src/processor/initialize.rs | 1 - src/processor/mine.rs | 7 +---- src/processor/mod.rs | 4 --- src/processor/pause.rs | 44 ---------------------------- src/processor/reset.rs | 7 +---- src/processor/update_admin.rs | 54 ----------------------------------- src/state/config.rs | 3 -- 10 files changed, 11 insertions(+), 181 deletions(-) delete mode 100644 src/processor/pause.rs delete mode 100644 src/processor/update_admin.rs diff --git a/src/error.rs b/src/error.rs index 4a1dfd3..a0fdbf9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,26 +5,24 @@ use thiserror::Error; #[derive(Debug, Error, Clone, Copy, PartialEq, Eq, IntoPrimitive)] #[repr(u32)] pub enum OreError { - #[error("Mining is paused")] - IsPaused = 0, #[error("The epoch has ended and needs reset")] - NeedsReset = 1, + NeedsReset = 0, #[error("The provided hash is invalid")] - HashInvalid = 2, + HashInvalid = 1, #[error("The provided hash did not satisfy the minimum required difficulty")] - HashTooEasy = 3, + HashTooEasy = 2, #[error("The claim amount cannot be greater than the claimable rewards")] - ClaimTooLarge = 4, + ClaimTooLarge = 3, #[error("The clock time is invalid")] - ClockInvalid = 5, + ClockInvalid = 4, #[error("You are trying to submit too soon")] - Spam = 6, + Spam = 5, #[error("Only one hash may be validated per transaction")] - TransactionInvalid = 7, + TransactionInvalid = 6, #[error("The tolerance cannot exceed i64 max value")] - ToleranceOverflow = 8, + ToleranceOverflow = 7, #[error("The maximum supply has been reached")] - MaxSupply = 9, + MaxSupply = 8, } impl From for ProgramError { diff --git a/src/instruction.rs b/src/instruction.rs index 038c7c5..17212e6 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -103,16 +103,6 @@ pub enum OreInstruction { #[account(18, name = "mpl_metadata_program", desc = "Metaplex metadata program")] #[account(19, name = "rent", desc = "Solana rent sysvar")] Initialize = 100, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "signer", desc = "Admin signer", signer)] - #[account(2, name = "config", desc = "Ore config account", writable)] - UpdateAdmin = 101, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "signer", desc = "Admin signer", signer)] - #[account(2, name = "config", desc = "Ore config account", writable)] - Pause = 103, } impl OreInstruction { @@ -187,8 +177,6 @@ impl_to_bytes!(MineArgs); impl_to_bytes!(ClaimArgs); impl_to_bytes!(StakeArgs); impl_to_bytes!(UpgradeArgs); -impl_to_bytes!(UpdateAdminArgs); -impl_to_bytes!(PauseArgs); impl_instruction_from_bytes!(InitializeArgs); impl_instruction_from_bytes!(RegisterArgs); @@ -196,8 +184,6 @@ impl_instruction_from_bytes!(MineArgs); impl_instruction_from_bytes!(ClaimArgs); impl_instruction_from_bytes!(StakeArgs); impl_instruction_from_bytes!(UpgradeArgs); -impl_instruction_from_bytes!(UpdateAdminArgs); -impl_instruction_from_bytes!(PauseArgs); /// Builds a reset instruction. pub fn reset(signer: Pubkey) -> Instruction { @@ -414,39 +400,3 @@ pub fn initialize(signer: Pubkey) -> Instruction { .concat(), } } - -/// Build an update_admin instruction. -pub fn update_admin(signer: Pubkey, new_admin: Pubkey) -> Instruction { - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(CONFIG_ADDRESS, false), - ], - data: [ - OreInstruction::UpdateAdmin.to_vec(), - UpdateAdminArgs { new_admin }.to_bytes().to_vec(), - ] - .concat(), - } -} - -/// Build a pause instruction. -pub fn pause(signer: Pubkey, paused: bool) -> Instruction { - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new(CONFIG_ADDRESS, false), - ], - data: [ - OreInstruction::UpdateAdmin.to_vec(), - PauseArgs { - paused: paused as u8, - } - .to_bytes() - .to_vec(), - ] - .concat(), - } -} diff --git a/src/lib.rs b/src/lib.rs index 09a335b..e4d1e9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,8 +41,6 @@ pub fn process_instruction( OreInstruction::Stake => process_stake(program_id, accounts, data)?, OreInstruction::Upgrade => process_upgrade(program_id, accounts, data)?, OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, - OreInstruction::UpdateAdmin => process_update_admin(program_id, accounts, data)?, - OreInstruction::Pause => process_pause(program_id, accounts, data)?, } Ok(()) diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 6bbd628..7717a5f 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -138,7 +138,6 @@ pub fn process_initialize<'a, 'info>( config.admin = *signer.key; config.base_reward_rate = INITIAL_BASE_REWARD_RATE; config.last_reset_at = 0; - config.paused = 1; // Initialize treasury create_pda( diff --git a/src/processor/mine.rs b/src/processor/mine.rs index c075bd7..265bc61 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -66,14 +66,9 @@ pub fn process_mine<'a, 'info>( return Err(OreError::TransactionInvalid.into()); } - // Validate mining is not paused + // Validate epoch is active let config_data = config_info.data.borrow(); let config = Config::try_from_bytes(&config_data)?; - if config.paused.ne(&0) { - return Err(OreError::IsPaused.into()); - } - - // Validate epoch is active let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; if config .last_reset_at diff --git a/src/processor/mod.rs b/src/processor/mod.rs index 79f21c9..3ea9e10 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -2,20 +2,16 @@ mod claim; mod deregister; mod initialize; mod mine; -mod pause; mod register; mod reset; mod stake; -mod update_admin; mod upgrade; pub use claim::*; pub use deregister::*; pub use initialize::*; pub use mine::*; -pub use pause::*; pub use register::*; pub use reset::*; pub use stake::*; -pub use update_admin::*; pub use upgrade::*; diff --git a/src/processor/pause.rs b/src/processor/pause.rs deleted file mode 100644 index 29e4d17..0000000 --- a/src/processor/pause.rs +++ /dev/null @@ -1,44 +0,0 @@ -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, -}; - -use crate::{instruction::PauseArgs, loaders::*, state::Config, utils::AccountDeserialize}; - -/// Pause updates the program's pause flag. Its responsibilities include: -/// 1. Update the pause flag. -/// -/// Safety requirements: -/// - Can only succeed if the signer is the program admin. -/// - Can only succeed if the provided config is valid. -/// -/// Discussion: -/// - This should only be used to address critical contract risks and force migration to a new -/// verison (hardfork). -pub fn process_pause<'a, 'info>( - _program_id: &Pubkey, - accounts: &'a [AccountInfo<'info>], - data: &[u8], -) -> ProgramResult { - // Parse args - let args = PauseArgs::try_from_bytes(data)?; - - // Load accounts - let [signer, config_info] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - load_signer(signer)?; - load_config(config_info, true)?; - - // Validate signer is admin - let mut config_data = config_info.data.borrow_mut(); - let config = Config::try_from_bytes_mut(&mut config_data)?; - if config.admin.ne(&signer.key) { - return Err(ProgramError::MissingRequiredSignature); - } - - // Update paused - config.paused = args.paused as u64; - - Ok(()) -} diff --git a/src/processor/reset.rs b/src/processor/reset.rs index ed385fb..3f2b924 100644 --- a/src/processor/reset.rs +++ b/src/processor/reset.rs @@ -70,14 +70,9 @@ pub fn process_reset<'a, 'info>( bus_7_info, ]; - // Validate mining is not paused + // Validate enough time has passed since last reset let mut config_data = config_info.data.borrow_mut(); let config = Config::try_from_bytes_mut(&mut config_data)?; - if config.paused.ne(&0) { - return Err(OreError::IsPaused.into()); - } - - // Validate enough time has passed since last reset let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; if config .last_reset_at diff --git a/src/processor/update_admin.rs b/src/processor/update_admin.rs deleted file mode 100644 index 1b88148..0000000 --- a/src/processor/update_admin.rs +++ /dev/null @@ -1,54 +0,0 @@ -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, -}; - -use crate::{instruction::UpdateAdminArgs, loaders::*, state::Config, utils::AccountDeserialize}; - -/// UpdateAdmin updates the program's admin account. Its responsibilities include: -/// 1. Update the treasury admin address. -/// -/// Safety requirements: -/// - Can only succeed if the signer is the program admin. -/// - Can only succeed if the provided treasury is valid. -/// -/// Discussion: -/// - The admin authority has one lever of power: the ability to adjust the global -/// mining difficulty. If the difficulty is too easy, miners will find hashes very quickly -/// and the bottleneck for mining will shift from local compute to Solana bandwidth. In essence, -/// if the Ore token has value and difficulty is low, mining becomes an incentivized stress -/// test for the Solana network. -/// - At the same time, if difficulty is too hard, miners will have to wait a very long period -/// of time between finding valid hashes. This will bias rewards to well-resourced miners -/// with large compute operations. Keeping a low difficulty ensures casual miners can -/// consistently earn rewards and undercuts some of the advantage of larger players. -/// - Ultimately admin authority should be delegated to a governance mechanism – either -/// democratic or futarchic – to ensure difficulty is kept at a value that represents the -/// values and interests of the ecosystem. -pub fn process_update_admin<'a, 'info>( - _program_id: &Pubkey, - accounts: &'a [AccountInfo<'info>], - data: &[u8], -) -> ProgramResult { - // Parse args - let args = UpdateAdminArgs::try_from_bytes(data)?; - - // Load accounts - let [signer, config_info] = accounts else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - load_signer(signer)?; - load_config(config_info, true)?; - - // Validate signer is admin - let mut config_data = config_info.data.borrow_mut(); - let config = Config::try_from_bytes_mut(&mut config_data)?; - if config.admin.ne(&signer.key) { - return Err(ProgramError::MissingRequiredSignature); - } - - // Update admin - config.admin = args.new_admin; - - Ok(()) -} diff --git a/src/state/config.rs b/src/state/config.rs index 0f4ee03..9c79cff 100644 --- a/src/state/config.rs +++ b/src/state/config.rs @@ -19,9 +19,6 @@ pub struct Config { /// The timestamp of the last reset pub last_reset_at: i64, - - /// Is mining paused. - pub paused: u64, } impl Discriminator for Config { From f580f7fd58f1e851744a7a3a3dfa171b6c951ab0 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 27 Jun 2024 12:36:14 +0000 Subject: [PATCH 081/111] cleanup ix names --- README.md | 9 ++--- src/instruction.rs | 42 +++++++++++------------ src/lib.rs | 6 ++-- src/processor/{deregister.rs => close.rs} | 4 +-- src/processor/mod.rs | 8 ++--- src/processor/{register.rs => open.rs} | 6 ++-- 6 files changed, 38 insertions(+), 37 deletions(-) rename src/processor/{deregister.rs => close.rs} (90%) rename src/processor/{register.rs => open.rs} (95%) diff --git a/README.md b/README.md index 0d95802..b0c8d93 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,14 @@ cargo install ore-cli ## Instructions -- [`Initialize`](src/processor/initialize.rs) – Initializes the Ore program, creating the bus, mint, and treasury accounts. - [`Reset`](src/processor/reset.rs) – Resets the program for a new epoch. -- [`Register`](src/processor/register.rs) – Creates a new proof account for a prospective miner. +- [`Open`](src/processor/open.rs) – Creates a new proof account for a prospective miner. +- [`Close`](src/processor/close.rs) – Closes a new proof account returns the rent to the owner. - [`Mine`](src/processor/mine.rs) – Verifies a hash provided by a miner and issues claimable rewards. +- [`Stake`](src/processor/stake.rs) – Stakes ORE with a miner to increase their multiplier. - [`Claim`](src/processor/claim.rs) – Distributes claimable rewards as tokens from the treasury to a miner. -- [`UpdateAdmin`](src/processor/update_admin.rs) – Updates the admin authority. -- [`UpdateDifficulty`](src/processor/update_difficulty.rs) - Updates the hashing difficulty. +- [`Upgrade`](src/processor/upgrade.rs) – Migrates v1 ORE tokens to v2 ORE. +- [`Initialize`](src/processor/initialize.rs) – Initializes the Ore program, creating the bus, mint, and treasury accounts. ## State diff --git a/src/instruction.rs b/src/instruction.rs index 17212e6..631fed0 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -38,7 +38,13 @@ pub enum OreInstruction { #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "proof", desc = "Ore proof account", writable)] #[account(3, name = "system_program", desc = "Solana system program")] - Register = 1, + Open = 1, + + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "proof", desc = "Ore proof account", writable)] + #[account(3, name = "system_program", desc = "Solana system program")] + Close = 2, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -47,7 +53,7 @@ pub enum OreInstruction { #[account(4, name = "noise", desc = "Ore noise account")] #[account(5, name = "proof", desc = "Ore proof account", writable)] #[account(6, name = "slot_hashes", desc = "Solana slot hashes sysvar")] - Mine = 2, + Mine = 3, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -56,7 +62,7 @@ pub enum OreInstruction { #[account(4, name = "treasury", desc = "Ore treasury account", writable)] #[account(5, name = "treasury_tokens", desc = "Ore treasury token account", writable)] #[account(6, name = "token_program", desc = "SPL token program")] - Claim = 3, + Claim = 4, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -64,7 +70,7 @@ pub enum OreInstruction { #[account(3, name = "sender", desc = "Signer token account", writable)] #[account(4, name = "treasury_tokens", desc = "Ore treasury token account", writable)] #[account(5, name = "token_program", desc = "SPL token program")] - Stake = 4, + Stake = 5, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -74,13 +80,7 @@ pub enum OreInstruction { #[account(5, name = "mint", desc = "Ore token mint account", writable)] #[account(6, name = "mint_v1", desc = "Ore v1 token mint account", writable)] #[account(7, name = "token_program", desc = "SPL token program")] - Upgrade = 5, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "signer", desc = "Signer", signer)] - #[account(2, name = "proof", desc = "Ore proof account", writable)] - #[account(3, name = "system_program", desc = "Solana system program")] - Deregister = 6, + Upgrade = 6, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] @@ -130,7 +130,7 @@ pub struct InitializeArgs { #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct RegisterArgs { +pub struct OpenArgs { pub bump: u8, } @@ -172,14 +172,14 @@ pub struct PauseArgs { } impl_to_bytes!(InitializeArgs); -impl_to_bytes!(RegisterArgs); +impl_to_bytes!(OpenArgs); impl_to_bytes!(MineArgs); impl_to_bytes!(ClaimArgs); impl_to_bytes!(StakeArgs); impl_to_bytes!(UpgradeArgs); impl_instruction_from_bytes!(InitializeArgs); -impl_instruction_from_bytes!(RegisterArgs); +impl_instruction_from_bytes!(OpenArgs); impl_instruction_from_bytes!(MineArgs); impl_instruction_from_bytes!(ClaimArgs); impl_instruction_from_bytes!(StakeArgs); @@ -213,8 +213,8 @@ pub fn reset(signer: Pubkey) -> Instruction { } } -/// Builds a register instruction. -pub fn register(signer: Pubkey) -> Instruction { +/// Builds an open instruction. +pub fn open(signer: Pubkey) -> Instruction { let proof_pda = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()); Instruction { program_id: crate::id(), @@ -225,15 +225,15 @@ pub fn register(signer: Pubkey) -> Instruction { AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), ], data: [ - OreInstruction::Register.to_vec(), - RegisterArgs { bump: proof_pda.1 }.to_bytes().to_vec(), + OreInstruction::Open.to_vec(), + OpenArgs { bump: proof_pda.1 }.to_bytes().to_vec(), ] .concat(), } } -/// Builds a deregister instruction. -pub fn deregister(signer: Pubkey) -> Instruction { +/// Builds a close instruction. +pub fn close(signer: Pubkey) -> Instruction { let proof_pda = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()); Instruction { program_id: crate::id(), @@ -242,7 +242,7 @@ pub fn deregister(signer: Pubkey) -> Instruction { AccountMeta::new(proof_pda.0, false), AccountMeta::new_readonly(solana_program::system_program::id(), false), ], - data: OreInstruction::Deregister.to_vec(), + data: OreInstruction::Close.to_vec(), } } diff --git a/src/lib.rs b/src/lib.rs index e4d1e9e..a5d064d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,12 +33,12 @@ pub fn process_instruction( .ok_or(ProgramError::InvalidInstructionData)?; match OreInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))? { - OreInstruction::Register => process_register(program_id, accounts, data)?, - OreInstruction::Deregister => process_deregister(program_id, accounts, data)?, - OreInstruction::Reset => process_reset(program_id, accounts, data)?, + OreInstruction::Open => process_open(program_id, accounts, data)?, + OreInstruction::Close => process_close(program_id, accounts, data)?, OreInstruction::Mine => process_mine(program_id, accounts, data)?, OreInstruction::Claim => process_claim(program_id, accounts, data)?, OreInstruction::Stake => process_stake(program_id, accounts, data)?, + OreInstruction::Reset => process_reset(program_id, accounts, data)?, OreInstruction::Upgrade => process_upgrade(program_id, accounts, data)?, OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, } diff --git a/src/processor/deregister.rs b/src/processor/close.rs similarity index 90% rename from src/processor/deregister.rs rename to src/processor/close.rs index 00ba60e..0356341 100644 --- a/src/processor/deregister.rs +++ b/src/processor/close.rs @@ -5,7 +5,7 @@ use solana_program::{ use crate::{loaders::*, state::Proof, utils::AccountDeserialize}; -/// Deregister closes a proof account and returns the rent to the owner. Its responsibilities include: +/// Close closes a proof account and returns the rent to the owner. Its responsibilities include: /// 1. Realloc proof account size to 0. /// 2. Transfer lamports to the owner. /// @@ -13,7 +13,7 @@ use crate::{loaders::*, state::Proof, utils::AccountDeserialize}; /// - Deregister is a permissionless instruction and can be invoked by any singer. /// - Can only succeed if the provided proof acount PDA is valid (associated with the signer). /// - The provided system program must be valid. -pub fn process_deregister<'a, 'info>( +pub fn process_close<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], _data: &[u8], diff --git a/src/processor/mod.rs b/src/processor/mod.rs index 3ea9e10..91b8fb4 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -1,17 +1,17 @@ mod claim; -mod deregister; +mod close; mod initialize; mod mine; -mod register; +mod open; mod reset; mod stake; mod upgrade; pub use claim::*; -pub use deregister::*; +pub use close::*; pub use initialize::*; pub use mine::*; -pub use register::*; +pub use open::*; pub use reset::*; pub use stake::*; pub use upgrade::*; diff --git a/src/processor/register.rs b/src/processor/open.rs similarity index 95% rename from src/processor/register.rs rename to src/processor/open.rs index 74fa0d7..8db78c7 100644 --- a/src/processor/register.rs +++ b/src/processor/open.rs @@ -13,7 +13,7 @@ use solana_program::{ }; use crate::{ - instruction::RegisterArgs, + instruction::OpenArgs, loaders::*, state::Proof, utils::AccountDeserialize, @@ -30,13 +30,13 @@ use crate::{ /// - Can only succeed if the provided proof acount PDA is valid (associated with the signer). /// - Can only succeed if the user does not already have a proof account. /// - The provided system program must be valid. -pub fn process_register<'a, 'info>( +pub fn process_open<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], data: &[u8], ) -> ProgramResult { // Parse args - let args = RegisterArgs::try_from_bytes(data)?; + let args = OpenArgs::try_from_bytes(data)?; // Load accounts let [signer, proof_info, system_program, slot_hashes_info] = accounts else { From 77bf1951395fa9bbf0167d0174a4e96ba7ec7a9b Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 27 Jun 2024 13:17:53 +0000 Subject: [PATCH 082/111] begin testing new staking mechanism --- src/processor/mine.rs | 22 +++++++++++----------- src/processor/stake.rs | 17 ++++++++++++++--- src/state/bus.rs | 1 - src/state/config.rs | 5 ++++- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 265bc61..b327e33 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -102,23 +102,23 @@ pub fn process_mine<'a, 'info>( sol_log(&format!("Base {}", reward)); // Apply staking multiplier. - // The multiplier can range 1x to 2x. To receive the maximum multiplier, the stake balance must be - // greater than or equal to two years worth of rewards at the selected difficulty. Miners are only - // eligable for a multipler if their last stake deposit was more than one minute ago. - if proof - .last_stake_at - .saturating_add(ONE_MINUTE) - .le(&clock.unix_timestamp) + // If user has greater than or equal to the max stake on the network, they will receive 2x multiplier. + // Any less than this, and they will receive between 1x and 2x. Miners are only eligable for a multipler + // if their last stake deposit was more than one minute ago. + if config.max_stake.gt(&0) + && proof + .last_stake_at + .saturating_add(ONE_MINUTE) + .le(&clock.unix_timestamp) { - let upper_bound = reward.saturating_mul(ONE_YEAR); let staking_reward = proof .balance - .min(upper_bound) + .min(config.max_stake) .saturating_mul(reward) - .saturating_div(upper_bound); + .saturating_div(config.max_stake); reward = reward.saturating_add(staking_reward); sol_log(&format!("Staking {}", staking_reward)); - }; + } // Apply spam penalty let t = clock.unix_timestamp; diff --git a/src/processor/stake.rs b/src/processor/stake.rs index 79be3dd..d988520 100644 --- a/src/processor/stake.rs +++ b/src/processor/stake.rs @@ -4,8 +4,11 @@ use solana_program::{ }; use crate::{ - instruction::StakeArgs, loaders::*, state::Proof, utils::AccountDeserialize, MINT_ADDRESS, - TREASURY_ADDRESS, + instruction::StakeArgs, + loaders::*, + state::{Config, Proof}, + utils::AccountDeserialize, + MINT_ADDRESS, TREASURY_ADDRESS, }; /// Stake deposits Ore into a miner's proof account to earn multiplier. Its responsibilies include: @@ -26,10 +29,13 @@ pub fn process_stake<'a, 'info>( let amount = u64::from_le_bytes(args.amount); // Load accounts - let [signer, proof_info, sender_info, treasury_tokens_info, token_program] = accounts else { + let [signer, config_info, proof_info, sender_info, treasury_tokens_info, token_program] = + accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; + load_config(config_info, true)?; load_proof(proof_info, signer.key, true)?; load_token_account(sender_info, Some(signer.key), &MINT_ADDRESS, true)?; load_token_account( @@ -49,6 +55,11 @@ pub fn process_stake<'a, 'info>( let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; proof.last_stake_at = clock.unix_timestamp; + // Update the max stake tracker + let mut config_data = config_info.data.borrow_mut(); + let config = Config::try_from_bytes_mut(&mut config_data)?; + config.max_stake = config.max_stake.max(proof.balance); + // Distribute tokens from signer to treasury solana_program::program::invoke( &spl_token::instruction::transfer( diff --git a/src/state/bus.rs b/src/state/bus.rs index 36b3479..7275119 100644 --- a/src/state/bus.rs +++ b/src/state/bus.rs @@ -8,7 +8,6 @@ use crate::{ /// Bus accounts are responsible for distributing mining rewards. /// There are 8 busses total to minimize write-lock contention and allow for parallel mine operations. -/// Every epoch, the bus account rewards counters are topped up to 0.25 ORE each (2 ORE split amongst 8 busses). #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] pub struct Bus { diff --git a/src/state/config.rs b/src/state/config.rs index 9c79cff..b3de137 100644 --- a/src/state/config.rs +++ b/src/state/config.rs @@ -17,8 +17,11 @@ pub struct Config { /// The base reward rate paid out for a hash of minimum difficulty. pub base_reward_rate: u64, - /// The timestamp of the last reset + /// The timestamp of the last reset. pub last_reset_at: i64, + + /// The largest stake account on the network. + pub max_stake: u64, } impl Discriminator for Config { From 7e115db1ed7f54896b37cc9ee80f47c19a84f41b Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 27 Jun 2024 13:18:16 +0000 Subject: [PATCH 083/111] consts --- src/consts.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/consts.rs b/src/consts.rs index dbd6c0b..47fe499 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -9,10 +9,10 @@ pub const INITIAL_BASE_REWARD_RATE: u64 = 10u64.pow(3u32); pub const INITIAL_TOLERANCE: i64 = 5; /// The minimum difficulty required of all submitted hashes. -pub const MIN_DIFFICULTY: u32 = 8; // 12; +pub const MIN_DIFFICULTY: u32 = 8; -/// The decimal precision of the Ore token. -/// There are 100 billion indivisible units per Ore (called "grains"). +/// The decimal precision of the ORE token. +/// There are 100 billion indivisible units per ORE (called "grains"). pub const TOKEN_DECIMALS: u8 = 11; /// One Ore token, denominated in indivisible units. @@ -27,21 +27,21 @@ pub const ONE_DAY: i64 = 86400; /// The duration of one year, in minutes. pub const ONE_YEAR: u64 = 525600; -/// The number of minutes in an Ore epoch. +/// The number of minutes in an ORE epoch. pub const EPOCH_MINUTES: i64 = 1; -/// The duration of an Ore epoch, in seconds. +/// The duration of a program epoch, in seconds. pub const EPOCH_DURATION: i64 = ONE_MINUTE.saturating_mul(EPOCH_MINUTES); -/// The maximum token supply (42 million). -pub const MAX_SUPPLY: u64 = ONE_ORE.saturating_mul(42_000_000); +/// The maximum token supply (21 million). +pub const MAX_SUPPLY: u64 = ONE_ORE.saturating_mul(21_000_000); /// The target quantity of ORE to be mined per epoch. pub const TARGET_EPOCH_REWARDS: u64 = ONE_ORE.saturating_mul(EPOCH_MINUTES as u64); /// The maximum quantity of ORE that can be mined per epoch. -/// Inflation rate ≈ 1 ORE / min (min 0, max 5) -pub const MAX_EPOCH_REWARDS: u64 = TARGET_EPOCH_REWARDS.saturating_mul(5); +/// Inflation rate ≈ 1 ORE / min (min 0, max 8) +pub const MAX_EPOCH_REWARDS: u64 = TARGET_EPOCH_REWARDS.saturating_mul(BUS_COUNT as u64); /// The quantity of ORE each bus is allowed to issue per epoch. pub const BUS_EPOCH_REWARDS: u64 = MAX_EPOCH_REWARDS.saturating_div(BUS_COUNT as u64); @@ -82,7 +82,7 @@ pub const MINT_NOISE: [u8; 16] = [ ]; /// The name for token metadata. -pub const METADATA_NAME: &str = "Ore"; +pub const METADATA_NAME: &str = "ORE"; /// The ticker symbol for token metadata. pub const METADATA_SYMBOL: &str = "ORE"; From 3608071f1cae693ac9d901e49c5e2effa253e188 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 27 Jun 2024 14:01:52 +0000 Subject: [PATCH 084/111] crown new top staker --- src/consts.rs | 6 ---- src/instruction.rs | 9 +++++- src/lib.rs | 1 + src/loaders.rs | 28 ++++++++++++++++++ src/processor/crown.rs | 57 +++++++++++++++++++++++++++++++++++++ src/processor/initialize.rs | 2 ++ src/processor/mine.rs | 2 +- src/processor/mod.rs | 2 ++ src/processor/stake.rs | 17 ++--------- src/state/config.rs | 5 +++- 10 files changed, 106 insertions(+), 23 deletions(-) create mode 100644 src/processor/crown.rs diff --git a/src/consts.rs b/src/consts.rs index fdc8e35..6d6f2df 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -24,12 +24,6 @@ pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); /// The duration of one minute, in seconds. pub const ONE_MINUTE: i64 = 60; -/// The duration of one day, in seconds. -pub const ONE_DAY: i64 = 86400; - -/// The duration of one year, in minutes. -pub const ONE_YEAR: u64 = 525600; - /// The number of minutes in an Ore epoch. pub const EPOCH_MINUTES: i64 = 1; diff --git a/src/instruction.rs b/src/instruction.rs index 631fed0..d237f6e 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -72,6 +72,13 @@ pub enum OreInstruction { #[account(5, name = "token_program", desc = "SPL token program")] Stake = 5, + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "config", desc = "Ore config account", writable)] + #[account(3, name = "proof", desc = "Ore proof account – current top staker")] + #[account(4, name = "proof_new", desc = "Ore proof account – new top staker")] + Crown = 6, + #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "beneficiary", desc = "Beneficiary token account", writable)] @@ -80,7 +87,7 @@ pub enum OreInstruction { #[account(5, name = "mint", desc = "Ore token mint account", writable)] #[account(6, name = "mint_v1", desc = "Ore v1 token mint account", writable)] #[account(7, name = "token_program", desc = "SPL token program")] - Upgrade = 6, + Upgrade = 7, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] diff --git a/src/lib.rs b/src/lib.rs index a5d064d..5b2b465 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ pub fn process_instruction( match OreInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))? { OreInstruction::Open => process_open(program_id, accounts, data)?, OreInstruction::Close => process_close(program_id, accounts, data)?, + OreInstruction::Crown => process_crown(program_id, accounts, data)?, OreInstruction::Mine => process_mine(program_id, accounts, data)?, OreInstruction::Claim => process_claim(program_id, accounts, data)?, OreInstruction::Stake => process_stake(program_id, accounts, data)?, diff --git a/src/loaders.rs b/src/loaders.rs index 73d7c50..449d369 100644 --- a/src/loaders.rs +++ b/src/loaders.rs @@ -158,6 +158,34 @@ pub fn load_proof<'a, 'info>( Ok(()) } +/// Errors if: +/// - Owner is not Ore program. +/// - Data is empty. +/// - Data cannot deserialize into a proof account. +/// - Expected to be writable, but is not. +pub fn load_any_proof<'a, 'info>( + info: &'a AccountInfo<'info>, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&crate::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + if info.data.borrow()[0].ne(&(Proof::discriminator() as u8)) { + return Err(solana_program::program_error::ProgramError::InvalidAccountData); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + /// Errors if: /// - Owner is not Ore program. /// - Address does not match the expected address. diff --git a/src/processor/crown.rs b/src/processor/crown.rs new file mode 100644 index 0000000..5d18ecb --- /dev/null +++ b/src/processor/crown.rs @@ -0,0 +1,57 @@ +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; + +use crate::{ + loaders::*, + state::{Config, Proof}, + utils::AccountDeserialize, +}; + +/// Crown marks an account as the top staker if their balance is greater than the last known top staker. +pub fn process_crown<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + _data: &[u8], +) -> ProgramResult { + // Load accounts + let [signer, config_info, proof_info, proof_new_info] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer)?; + load_config(config_info, true)?; + load_any_proof(proof_new_info, false)?; + + // Load config + let mut config_data = config_info.data.borrow_mut(); + let config = Config::try_from_bytes_mut(&mut config_data)?; + + // Load proposed new top staker + let proof_new_data = proof_new_info.data.borrow(); + let proof_new = Proof::try_from_bytes(&proof_new_data)?; + + // If top staker is not the default null address, then compare balances + if config.top_staker.ne(&Pubkey::new_from_array([0; 32])) { + // Load current top staker + load_any_proof(proof_info, false)?; + let proof_data = proof_info.data.borrow(); + let proof = Proof::try_from_bytes(&proof_data)?; + + // Require the provided proof account is the current top staker + if proof_info.key.ne(&config.top_staker) { + return Err(ProgramError::InvalidAccountData); + } + + // Compare balances + if proof_new.balance.lt(&proof.balance) { + return Err(ProgramError::InvalidAccountData); + } + } + + // Crown the new top staker + config.max_stake = proof_new.balance; + config.top_staker = *proof_new_info.key; + + Ok(()) +} diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 7717a5f..2ee2aac 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -138,6 +138,8 @@ pub fn process_initialize<'a, 'info>( config.admin = *signer.key; config.base_reward_rate = INITIAL_BASE_REWARD_RATE; config.last_reset_at = 0; + config.max_stake = 0; + config.top_staker = Pubkey::new_from_array([0; 32]); // Initialize treasury create_pda( diff --git a/src/processor/mine.rs b/src/processor/mine.rs index b327e33..b1e4fd9 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -24,7 +24,7 @@ use crate::{ loaders::*, state::{Bus, Config, Proof}, utils::{AccountDeserialize, MineEvent}, - EPOCH_DURATION, MIN_DIFFICULTY, ONE_MINUTE, ONE_YEAR, TOLERANCE, + EPOCH_DURATION, MIN_DIFFICULTY, ONE_MINUTE, TOLERANCE, }; /// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: diff --git a/src/processor/mod.rs b/src/processor/mod.rs index 91b8fb4..11469b5 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -1,5 +1,6 @@ mod claim; mod close; +mod crown; mod initialize; mod mine; mod open; @@ -9,6 +10,7 @@ mod upgrade; pub use claim::*; pub use close::*; +pub use crown::*; pub use initialize::*; pub use mine::*; pub use open::*; diff --git a/src/processor/stake.rs b/src/processor/stake.rs index d988520..79be3dd 100644 --- a/src/processor/stake.rs +++ b/src/processor/stake.rs @@ -4,11 +4,8 @@ use solana_program::{ }; use crate::{ - instruction::StakeArgs, - loaders::*, - state::{Config, Proof}, - utils::AccountDeserialize, - MINT_ADDRESS, TREASURY_ADDRESS, + instruction::StakeArgs, loaders::*, state::Proof, utils::AccountDeserialize, MINT_ADDRESS, + TREASURY_ADDRESS, }; /// Stake deposits Ore into a miner's proof account to earn multiplier. Its responsibilies include: @@ -29,13 +26,10 @@ pub fn process_stake<'a, 'info>( let amount = u64::from_le_bytes(args.amount); // Load accounts - let [signer, config_info, proof_info, sender_info, treasury_tokens_info, token_program] = - accounts - else { + let [signer, proof_info, sender_info, treasury_tokens_info, token_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; - load_config(config_info, true)?; load_proof(proof_info, signer.key, true)?; load_token_account(sender_info, Some(signer.key), &MINT_ADDRESS, true)?; load_token_account( @@ -55,11 +49,6 @@ pub fn process_stake<'a, 'info>( let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; proof.last_stake_at = clock.unix_timestamp; - // Update the max stake tracker - let mut config_data = config_info.data.borrow_mut(); - let config = Config::try_from_bytes_mut(&mut config_data)?; - config.max_stake = config.max_stake.max(proof.balance); - // Distribute tokens from signer to treasury solana_program::program::invoke( &spl_token::instruction::transfer( diff --git a/src/state/config.rs b/src/state/config.rs index b3de137..2c8214f 100644 --- a/src/state/config.rs +++ b/src/state/config.rs @@ -20,8 +20,11 @@ pub struct Config { /// The timestamp of the last reset. pub last_reset_at: i64, - /// The largest stake account on the network. + /// The largest known stake balance on the network. pub max_stake: u64, + + /// The address of the proof account with the highest stake balance. + pub top_staker: Pubkey, } impl Discriminator for Config { From c2ef73a6d00c70763110e30d58b80efeb483801b Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 27 Jun 2024 14:03:02 +0000 Subject: [PATCH 085/111] if --- src/processor/crown.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/processor/crown.rs b/src/processor/crown.rs index 5d18ecb..984fdf7 100644 --- a/src/processor/crown.rs +++ b/src/processor/crown.rs @@ -39,7 +39,7 @@ pub fn process_crown<'a, 'info>( let proof = Proof::try_from_bytes(&proof_data)?; // Require the provided proof account is the current top staker - if proof_info.key.ne(&config.top_staker) { + if config.top_staker.ne(&proof_info.key) { return Err(ProgramError::InvalidAccountData); } From d5e785899f3f027478c611f46870d6051575c169 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 27 Jun 2024 14:46:14 +0000 Subject: [PATCH 086/111] silent error --- src/processor/crown.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/processor/crown.rs b/src/processor/crown.rs index 984fdf7..674f114 100644 --- a/src/processor/crown.rs +++ b/src/processor/crown.rs @@ -40,12 +40,12 @@ pub fn process_crown<'a, 'info>( // Require the provided proof account is the current top staker if config.top_staker.ne(&proof_info.key) { - return Err(ProgramError::InvalidAccountData); + return Ok(()); } // Compare balances if proof_new.balance.lt(&proof.balance) { - return Err(ProgramError::InvalidAccountData); + return Ok(()); } } From 6bdc8cbca22b1bd6fe47fcb9d8b1077d5bfad429 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 27 Jun 2024 15:49:45 +0000 Subject: [PATCH 087/111] split miner authority --- src/instruction.rs | 3 ++- src/loaders.rs | 33 +++++++++++++++++++++++++++++++++ src/processor/mine.rs | 2 +- src/processor/open.rs | 4 +++- src/state/proof.rs | 3 +++ 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/instruction.rs b/src/instruction.rs index f5ebd74..dade54e 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -214,12 +214,13 @@ pub fn reset(signer: Pubkey) -> Instruction { } /// Builds an open instruction. -pub fn open(signer: Pubkey) -> Instruction { +pub fn open(signer: Pubkey, miner: Pubkey) -> Instruction { let proof_pda = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()); Instruction { program_id: crate::id(), accounts: vec![ AccountMeta::new(signer, true), + AccountMeta::new(miner, true), AccountMeta::new(proof_pda.0, false), AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), diff --git a/src/loaders.rs b/src/loaders.rs index 73d7c50..8cc7e07 100644 --- a/src/loaders.rs +++ b/src/loaders.rs @@ -158,6 +158,39 @@ pub fn load_proof<'a, 'info>( Ok(()) } +/// Errors if: +/// - Owner is not Ore program. +/// - Data is empty. +/// - Data cannot deserialize into a proof account. +/// - Proof miner does not match the expected address. +/// - Expected to be writable, but is not. +pub fn load_proof_with_miner<'a, 'info>( + info: &'a AccountInfo<'info>, + miner: &Pubkey, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&crate::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + let proof_data = info.data.borrow(); + let proof = Proof::try_from_bytes(&proof_data)?; + + if proof.miner.ne(&miner) { + return Err(ProgramError::InvalidAccountData); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + /// Errors if: /// - Owner is not Ore program. /// - Address does not match the expected address. diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 265bc61..a3df1c8 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -57,7 +57,7 @@ pub fn process_mine<'a, 'info>( load_signer(signer)?; load_any_bus(bus_info, true)?; load_config(config_info, false)?; - load_proof(proof_info, signer.key, true)?; + load_proof_with_miner(proof_info, signer.key, true)?; load_sysvar(instructions_sysvar, sysvar::instructions::id())?; load_sysvar(slot_hashes_sysvar, sysvar::slot_hashes::id())?; diff --git a/src/processor/open.rs b/src/processor/open.rs index 8db78c7..f9ea4a7 100644 --- a/src/processor/open.rs +++ b/src/processor/open.rs @@ -39,10 +39,11 @@ pub fn process_open<'a, 'info>( let args = OpenArgs::try_from_bytes(data)?; // Load accounts - let [signer, proof_info, system_program, slot_hashes_info] = accounts else { + let [signer, miner_info, proof_info, system_program, slot_hashes_info] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; + load_signer(miner_info)?; load_uninitialized_pda( proof_info, &[PROOF, signer.key.as_ref()], @@ -75,6 +76,7 @@ pub fn process_open<'a, 'info>( proof.last_hash = [0; 32]; proof.last_hash_at = clock.unix_timestamp; proof.last_stake_at = clock.unix_timestamp; + proof.miner = *miner_info.key; proof.total_hashes = 0; proof.total_rewards = 0; diff --git a/src/state/proof.rs b/src/state/proof.rs index 5cc2f9c..6b94a8a 100644 --- a/src/state/proof.rs +++ b/src/state/proof.rs @@ -30,6 +30,9 @@ pub struct Proof { /// The last time stake was deposited into this account. pub last_stake_at: i64, + /// The keypair which can submit hashes for mining. + pub miner: Pubkey, + /// The total lifetime hashes provided by this miner. pub total_hashes: u64, From 2b0b28b8131687f1d6e919ed7f2101f4d47be795 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 27 Jun 2024 15:51:56 +0000 Subject: [PATCH 088/111] comment --- src/state/proof.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/proof.rs b/src/state/proof.rs index 6b94a8a..1d1aa76 100644 --- a/src/state/proof.rs +++ b/src/state/proof.rs @@ -30,7 +30,7 @@ pub struct Proof { /// The last time stake was deposited into this account. pub last_stake_at: i64, - /// The keypair which can submit hashes for mining. + /// The keypair which has permission to submit hashes for mining. pub miner: Pubkey, /// The total lifetime hashes provided by this miner. From 03a953cdd4b3f1bc6ed70a72ecfec4e5ddc28c7b Mon Sep 17 00:00:00 2001 From: Hardhat Chad <155858888+HardhatChad@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:53:03 -0500 Subject: [PATCH 089/111] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b0c8d93..3ec3d10 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # ORE -**ORE is a fair-launch, proof-of-work, cross-border digital currency anyone can mine.** +**ORE is a fair-launch, proof-of-work, digital currency anyone can mine.** + ## Install From 98b6396fda24560e05814e11193941ab7a84b80e Mon Sep 17 00:00:00 2001 From: Hardhat Chad <155858888+HardhatChad@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:53:48 -0500 Subject: [PATCH 090/111] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ec3d10..59fa006 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ORE -**ORE is a fair-launch, proof-of-work, digital currency anyone can mine.** +**ORE is a fair-launch, proof-of-work, digital currency everyone can mine.** ## Install From cddcd493a831bd3f6f84a5131bd0a78b32834561 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 28 Jun 2024 12:42:54 +0000 Subject: [PATCH 091/111] introduce update ix --- src/instruction.rs | 108 +++++++++++++++++++++++----------------- src/lib.rs | 7 +-- src/processor/mod.rs | 2 + src/processor/update.rs | 30 +++++++++++ 4 files changed, 99 insertions(+), 48 deletions(-) create mode 100644 src/processor/update.rs diff --git a/src/instruction.rs b/src/instruction.rs index dade54e..0e9e5a7 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -17,6 +17,36 @@ use crate::{ #[derive(Clone, Copy, Debug, Eq, PartialEq, ShankInstruction, TryFromPrimitive)] #[rustfmt::skip] pub enum OreInstruction { + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "beneficiary", desc = "Beneficiary token account", writable)] + #[account(3, name = "proof", desc = "Ore proof account", writable)] + #[account(4, name = "treasury", desc = "Ore treasury account", writable)] + #[account(5, name = "treasury_tokens", desc = "Ore treasury token account", writable)] + #[account(6, name = "token_program", desc = "SPL token program")] + Claim = 0, + + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "proof", desc = "Ore proof account", writable)] + #[account(3, name = "system_program", desc = "Solana system program")] + Close = 1, + + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "bus", desc = "Ore bus account", writable)] + #[account(3, name = "config", desc = "Ore config account")] + #[account(4, name = "noise", desc = "Ore noise account")] + #[account(5, name = "proof", desc = "Ore proof account", writable)] + #[account(6, name = "slot_hashes", desc = "Solana slot hashes sysvar")] + Mine = 2, + + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "proof", desc = "Ore proof account", writable)] + #[account(3, name = "system_program", desc = "Solana system program")] + Open = 3, + #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "bus_0", desc = "Ore bus account 0", writable)] @@ -32,37 +62,7 @@ pub enum OreInstruction { #[account(12, name = "treasury", desc = "Ore treasury account", writable)] #[account(13, name = "treasury_tokens", desc = "Ore treasury token account", writable)] #[account(14, name = "token_program", desc = "SPL token program")] - Reset = 0, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "signer", desc = "Signer", signer)] - #[account(2, name = "proof", desc = "Ore proof account", writable)] - #[account(3, name = "system_program", desc = "Solana system program")] - Open = 1, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "signer", desc = "Signer", signer)] - #[account(2, name = "proof", desc = "Ore proof account", writable)] - #[account(3, name = "system_program", desc = "Solana system program")] - Close = 2, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "signer", desc = "Signer", signer)] - #[account(2, name = "bus", desc = "Ore bus account", writable)] - #[account(3, name = "config", desc = "Ore config account")] - #[account(4, name = "noise", desc = "Ore noise account")] - #[account(5, name = "proof", desc = "Ore proof account", writable)] - #[account(6, name = "slot_hashes", desc = "Solana slot hashes sysvar")] - Mine = 3, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "signer", desc = "Signer", signer)] - #[account(2, name = "beneficiary", desc = "Beneficiary token account", writable)] - #[account(3, name = "proof", desc = "Ore proof account", writable)] - #[account(4, name = "treasury", desc = "Ore treasury account", writable)] - #[account(5, name = "treasury_tokens", desc = "Ore treasury token account", writable)] - #[account(6, name = "token_program", desc = "SPL token program")] - Claim = 4, + Reset = 4, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -71,6 +71,11 @@ pub enum OreInstruction { #[account(4, name = "treasury_tokens", desc = "Ore treasury token account", writable)] #[account(5, name = "token_program", desc = "SPL token program")] Stake = 5, + + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "proof", desc = "Ore proof account", writable)] + Update = 6, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -80,7 +85,7 @@ pub enum OreInstruction { #[account(5, name = "mint", desc = "Ore token mint account", writable)] #[account(6, name = "mint_v1", desc = "Ore v1 token mint account", writable)] #[account(7, name = "token_program", desc = "SPL token program")] - Upgrade = 6, + Upgrade = 7, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] @@ -153,29 +158,24 @@ pub struct StakeArgs { pub amount: [u8; 8], } +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct UpdateArgs { + pub new_miner: Pubkey, +} + #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct UpgradeArgs { pub amount: [u8; 8], } -#[repr(C)] -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct UpdateAdminArgs { - pub new_admin: Pubkey, -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct PauseArgs { - pub paused: u8, -} - impl_to_bytes!(InitializeArgs); impl_to_bytes!(OpenArgs); impl_to_bytes!(MineArgs); impl_to_bytes!(ClaimArgs); impl_to_bytes!(StakeArgs); +impl_to_bytes!(UpdateArgs); impl_to_bytes!(UpgradeArgs); impl_instruction_from_bytes!(InitializeArgs); @@ -183,6 +183,7 @@ impl_instruction_from_bytes!(OpenArgs); impl_instruction_from_bytes!(MineArgs); impl_instruction_from_bytes!(ClaimArgs); impl_instruction_from_bytes!(StakeArgs); +impl_instruction_from_bytes!(UpdateArgs); impl_instruction_from_bytes!(UpgradeArgs); /// Builds a reset instruction. @@ -331,7 +332,24 @@ pub fn stake(signer: Pubkey, sender: Pubkey, amount: u64) -> Instruction { } } -// build an upgrade instruction. +// Build an update instruction. +pub fn update(signer: Pubkey, new_miner: Pubkey) -> Instruction { + let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(proof, false), + ], + data: [ + OreInstruction::Update.to_vec(), + UpdateArgs { new_miner }.to_bytes().to_vec(), + ] + .concat(), + } +} + +// Build an upgrade instruction. pub fn upgrade(signer: Pubkey, beneficiary: Pubkey, sender: Pubkey, amount: u64) -> Instruction { Instruction { program_id: crate::id(), diff --git a/src/lib.rs b/src/lib.rs index a5d064d..a6cf811 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,12 +33,13 @@ pub fn process_instruction( .ok_or(ProgramError::InvalidInstructionData)?; match OreInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))? { - OreInstruction::Open => process_open(program_id, accounts, data)?, + OreInstruction::Claim => process_claim(program_id, accounts, data)?, OreInstruction::Close => process_close(program_id, accounts, data)?, OreInstruction::Mine => process_mine(program_id, accounts, data)?, - OreInstruction::Claim => process_claim(program_id, accounts, data)?, - OreInstruction::Stake => process_stake(program_id, accounts, data)?, + OreInstruction::Open => process_open(program_id, accounts, data)?, OreInstruction::Reset => process_reset(program_id, accounts, data)?, + OreInstruction::Stake => process_stake(program_id, accounts, data)?, + OreInstruction::Update => process_update(program_id, accounts, data)?, OreInstruction::Upgrade => process_upgrade(program_id, accounts, data)?, OreInstruction::Initialize => process_initialize(program_id, accounts, data)?, } diff --git a/src/processor/mod.rs b/src/processor/mod.rs index 91b8fb4..5ded886 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -5,6 +5,7 @@ mod mine; mod open; mod reset; mod stake; +mod update; mod upgrade; pub use claim::*; @@ -14,4 +15,5 @@ pub use mine::*; pub use open::*; pub use reset::*; pub use stake::*; +pub use update::*; pub use upgrade::*; diff --git a/src/processor/update.rs b/src/processor/update.rs new file mode 100644 index 0000000..c1fa203 --- /dev/null +++ b/src/processor/update.rs @@ -0,0 +1,30 @@ +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; + +use crate::{instruction::UpdateArgs, loaders::*, state::Proof, utils::AccountDeserialize}; + +/// Update updates a proof account. +pub fn process_update<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + data: &[u8], +) -> ProgramResult { + // Parse args + let args = UpdateArgs::try_from_bytes(data)?; + + // Load accounts + let [signer, proof_info] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer)?; + load_proof(proof_info, signer.key, true)?; + + // Update the proof + let mut proof_data = proof_info.data.borrow_mut(); + let proof = Proof::try_from_bytes_mut(&mut proof_data)?; + proof.miner = args.new_miner; + + Ok(()) +} From 45f5d133dfd7f8aee437636d5f49a5f6e9c99c9a Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 28 Jun 2024 12:44:01 +0000 Subject: [PATCH 092/111] comment --- src/processor/update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/processor/update.rs b/src/processor/update.rs index c1fa203..fcb7b17 100644 --- a/src/processor/update.rs +++ b/src/processor/update.rs @@ -5,7 +5,7 @@ use solana_program::{ use crate::{instruction::UpdateArgs, loaders::*, state::Proof, utils::AccountDeserialize}; -/// Update updates a proof account. +/// Update changes the miner authority on a proof account. pub fn process_update<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], From fb33d594341ce1284989eaa6f1e8866639b4bd31 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 28 Jun 2024 13:14:00 +0000 Subject: [PATCH 093/111] cleanup --- src/instruction.rs | 19 ++++--------------- src/processor/open.rs | 2 +- src/processor/update.rs | 12 +++++------- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/instruction.rs b/src/instruction.rs index 0e9e5a7..304ce4c 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -158,12 +158,6 @@ pub struct StakeArgs { pub amount: [u8; 8], } -#[repr(C)] -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct UpdateArgs { - pub new_miner: Pubkey, -} - #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct UpgradeArgs { @@ -175,7 +169,6 @@ impl_to_bytes!(OpenArgs); impl_to_bytes!(MineArgs); impl_to_bytes!(ClaimArgs); impl_to_bytes!(StakeArgs); -impl_to_bytes!(UpdateArgs); impl_to_bytes!(UpgradeArgs); impl_instruction_from_bytes!(InitializeArgs); @@ -183,7 +176,6 @@ impl_instruction_from_bytes!(OpenArgs); impl_instruction_from_bytes!(MineArgs); impl_instruction_from_bytes!(ClaimArgs); impl_instruction_from_bytes!(StakeArgs); -impl_instruction_from_bytes!(UpdateArgs); impl_instruction_from_bytes!(UpgradeArgs); /// Builds a reset instruction. @@ -221,7 +213,7 @@ pub fn open(signer: Pubkey, miner: Pubkey) -> Instruction { program_id: crate::id(), accounts: vec![ AccountMeta::new(signer, true), - AccountMeta::new(miner, true), + AccountMeta::new(miner, false), AccountMeta::new(proof_pda.0, false), AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), @@ -333,19 +325,16 @@ pub fn stake(signer: Pubkey, sender: Pubkey, amount: u64) -> Instruction { } // Build an update instruction. -pub fn update(signer: Pubkey, new_miner: Pubkey) -> Instruction { +pub fn update(signer: Pubkey, miner: Pubkey) -> Instruction { let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; Instruction { program_id: crate::id(), accounts: vec![ AccountMeta::new(signer, true), + AccountMeta::new(miner, false), AccountMeta::new(proof, false), ], - data: [ - OreInstruction::Update.to_vec(), - UpdateArgs { new_miner }.to_bytes().to_vec(), - ] - .concat(), + data: OreInstruction::Update.to_vec(), } } diff --git a/src/processor/open.rs b/src/processor/open.rs index f9ea4a7..0ac2bd4 100644 --- a/src/processor/open.rs +++ b/src/processor/open.rs @@ -43,7 +43,7 @@ pub fn process_open<'a, 'info>( return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; - load_signer(miner_info)?; + load_uninitialized_account(miner_info)?; load_uninitialized_pda( proof_info, &[PROOF, signer.key.as_ref()], diff --git a/src/processor/update.rs b/src/processor/update.rs index fcb7b17..d8c95ad 100644 --- a/src/processor/update.rs +++ b/src/processor/update.rs @@ -3,28 +3,26 @@ use solana_program::{ pubkey::Pubkey, }; -use crate::{instruction::UpdateArgs, loaders::*, state::Proof, utils::AccountDeserialize}; +use crate::{loaders::*, state::Proof, utils::AccountDeserialize}; /// Update changes the miner authority on a proof account. pub fn process_update<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], - data: &[u8], + _data: &[u8], ) -> ProgramResult { - // Parse args - let args = UpdateArgs::try_from_bytes(data)?; - // Load accounts - let [signer, proof_info] = accounts else { + let [signer, miner_info, proof_info] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; + load_uninitialized_account(miner_info)?; load_proof(proof_info, signer.key, true)?; // Update the proof let mut proof_data = proof_info.data.borrow_mut(); let proof = Proof::try_from_bytes_mut(&mut proof_data)?; - proof.miner = args.new_miner; + proof.miner = *miner_info.key; Ok(()) } From 9d0f948f054c4f23f9ebc894966bf98924b7517b Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 28 Jun 2024 13:21:09 +0000 Subject: [PATCH 094/111] account meta --- src/instruction.rs | 4 ++-- src/loaders.rs | 7 ++++--- src/processor/initialize.rs | 2 +- src/processor/open.rs | 2 +- src/processor/update.rs | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/instruction.rs b/src/instruction.rs index 304ce4c..a265ab0 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -213,7 +213,7 @@ pub fn open(signer: Pubkey, miner: Pubkey) -> Instruction { program_id: crate::id(), accounts: vec![ AccountMeta::new(signer, true), - AccountMeta::new(miner, false), + AccountMeta::new_readonly(miner, false), AccountMeta::new(proof_pda.0, false), AccountMeta::new_readonly(solana_program::system_program::id(), false), AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), @@ -331,7 +331,7 @@ pub fn update(signer: Pubkey, miner: Pubkey) -> Instruction { program_id: crate::id(), accounts: vec![ AccountMeta::new(signer, true), - AccountMeta::new(miner, false), + AccountMeta::new_readonly(miner, false), AccountMeta::new(proof, false), ], data: OreInstruction::Update.to_vec(), diff --git a/src/loaders.rs b/src/loaders.rs index 8cc7e07..44a3ffe 100644 --- a/src/loaders.rs +++ b/src/loaders.rs @@ -319,15 +319,16 @@ pub fn load_uninitialized_pda<'a, 'info>( return Err(ProgramError::InvalidSeeds); } - load_uninitialized_account(info) + load_system_account(info, true) } /// Errors if: /// - Owner is not the system program. /// - Data is not empty. /// - Account is not writable. -pub fn load_uninitialized_account<'a, 'info>( +pub fn load_system_account<'a, 'info>( info: &'a AccountInfo<'info>, + is_writable: bool, ) -> Result<(), ProgramError> { if info.owner.ne(&system_program::id()) { return Err(ProgramError::InvalidAccountOwner); @@ -337,7 +338,7 @@ pub fn load_uninitialized_account<'a, 'info>( return Err(ProgramError::AccountAlreadyInitialized); } - if !info.is_writable { + if is_writable && !info.is_writable { return Err(ProgramError::InvalidAccountData); } diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 7717a5f..7c0b0af 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -80,7 +80,7 @@ pub fn process_initialize<'a, 'info>( &crate::id(), )?; load_uninitialized_pda(treasury_info, &[TREASURY], args.treasury_bump, &crate::id())?; - load_uninitialized_account(treasury_tokens_info)?; + load_system_account(treasury_tokens_info, true)?; load_program(system_program, system_program::id())?; load_program(token_program, spl_token::id())?; load_program(associated_token_program, spl_associated_token_account::id())?; diff --git a/src/processor/open.rs b/src/processor/open.rs index 0ac2bd4..3f03f61 100644 --- a/src/processor/open.rs +++ b/src/processor/open.rs @@ -43,7 +43,7 @@ pub fn process_open<'a, 'info>( return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; - load_uninitialized_account(miner_info)?; + load_system_account(miner_info, false)?; load_uninitialized_pda( proof_info, &[PROOF, signer.key.as_ref()], diff --git a/src/processor/update.rs b/src/processor/update.rs index d8c95ad..246c6d8 100644 --- a/src/processor/update.rs +++ b/src/processor/update.rs @@ -16,7 +16,7 @@ pub fn process_update<'a, 'info>( return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; - load_uninitialized_account(miner_info)?; + load_system_account(miner_info, false)?; load_proof(proof_info, signer.key, true)?; // Update the proof From f5c6975b487ae0bf03765577824eb0c570a5ac9f Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 27 Jun 2024 13:17:53 +0000 Subject: [PATCH 095/111] begin testing new staking mechanism --- src/processor/mine.rs | 22 +++++++++++----------- src/processor/stake.rs | 17 ++++++++++++++--- src/state/bus.rs | 1 - src/state/config.rs | 5 ++++- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index a3df1c8..f941747 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -102,23 +102,23 @@ pub fn process_mine<'a, 'info>( sol_log(&format!("Base {}", reward)); // Apply staking multiplier. - // The multiplier can range 1x to 2x. To receive the maximum multiplier, the stake balance must be - // greater than or equal to two years worth of rewards at the selected difficulty. Miners are only - // eligable for a multipler if their last stake deposit was more than one minute ago. - if proof - .last_stake_at - .saturating_add(ONE_MINUTE) - .le(&clock.unix_timestamp) + // If user has greater than or equal to the max stake on the network, they will receive 2x multiplier. + // Any less than this, and they will receive between 1x and 2x. Miners are only eligable for a multipler + // if their last stake deposit was more than one minute ago. + if config.max_stake.gt(&0) + && proof + .last_stake_at + .saturating_add(ONE_MINUTE) + .le(&clock.unix_timestamp) { - let upper_bound = reward.saturating_mul(ONE_YEAR); let staking_reward = proof .balance - .min(upper_bound) + .min(config.max_stake) .saturating_mul(reward) - .saturating_div(upper_bound); + .saturating_div(config.max_stake); reward = reward.saturating_add(staking_reward); sol_log(&format!("Staking {}", staking_reward)); - }; + } // Apply spam penalty let t = clock.unix_timestamp; diff --git a/src/processor/stake.rs b/src/processor/stake.rs index 79be3dd..d988520 100644 --- a/src/processor/stake.rs +++ b/src/processor/stake.rs @@ -4,8 +4,11 @@ use solana_program::{ }; use crate::{ - instruction::StakeArgs, loaders::*, state::Proof, utils::AccountDeserialize, MINT_ADDRESS, - TREASURY_ADDRESS, + instruction::StakeArgs, + loaders::*, + state::{Config, Proof}, + utils::AccountDeserialize, + MINT_ADDRESS, TREASURY_ADDRESS, }; /// Stake deposits Ore into a miner's proof account to earn multiplier. Its responsibilies include: @@ -26,10 +29,13 @@ pub fn process_stake<'a, 'info>( let amount = u64::from_le_bytes(args.amount); // Load accounts - let [signer, proof_info, sender_info, treasury_tokens_info, token_program] = accounts else { + let [signer, config_info, proof_info, sender_info, treasury_tokens_info, token_program] = + accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; + load_config(config_info, true)?; load_proof(proof_info, signer.key, true)?; load_token_account(sender_info, Some(signer.key), &MINT_ADDRESS, true)?; load_token_account( @@ -49,6 +55,11 @@ pub fn process_stake<'a, 'info>( let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; proof.last_stake_at = clock.unix_timestamp; + // Update the max stake tracker + let mut config_data = config_info.data.borrow_mut(); + let config = Config::try_from_bytes_mut(&mut config_data)?; + config.max_stake = config.max_stake.max(proof.balance); + // Distribute tokens from signer to treasury solana_program::program::invoke( &spl_token::instruction::transfer( diff --git a/src/state/bus.rs b/src/state/bus.rs index 36b3479..7275119 100644 --- a/src/state/bus.rs +++ b/src/state/bus.rs @@ -8,7 +8,6 @@ use crate::{ /// Bus accounts are responsible for distributing mining rewards. /// There are 8 busses total to minimize write-lock contention and allow for parallel mine operations. -/// Every epoch, the bus account rewards counters are topped up to 0.25 ORE each (2 ORE split amongst 8 busses). #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)] pub struct Bus { diff --git a/src/state/config.rs b/src/state/config.rs index 9c79cff..b3de137 100644 --- a/src/state/config.rs +++ b/src/state/config.rs @@ -17,8 +17,11 @@ pub struct Config { /// The base reward rate paid out for a hash of minimum difficulty. pub base_reward_rate: u64, - /// The timestamp of the last reset + /// The timestamp of the last reset. pub last_reset_at: i64, + + /// The largest stake account on the network. + pub max_stake: u64, } impl Discriminator for Config { From f1a914683021481c43ee08314382fdea8281863a Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 27 Jun 2024 14:01:52 +0000 Subject: [PATCH 096/111] crown new top staker --- src/consts.rs | 12 ++------ src/instruction.rs | 7 +++++ src/lib.rs | 1 + src/loaders.rs | 28 ++++++++++++++++++ src/processor/crown.rs | 57 +++++++++++++++++++++++++++++++++++++ src/processor/initialize.rs | 2 ++ src/processor/mine.rs | 2 +- src/processor/mod.rs | 2 ++ src/processor/stake.rs | 17 ++--------- src/state/config.rs | 5 +++- 10 files changed, 108 insertions(+), 25 deletions(-) create mode 100644 src/processor/crown.rs diff --git a/src/consts.rs b/src/consts.rs index 6fad3c4..92f437a 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -18,22 +18,16 @@ pub const MIN_DIFFICULTY: u32 = 8; /// There are 100 billion indivisible units per ORE (called "grains"). pub const TOKEN_DECIMALS: u8 = 11; -/// The decimal precision of the Ore v1 token. +/// The decimal precision of the ORE v1 token. pub const TOKEN_DECIMALS_V1: u8 = 9; -/// One Ore token, denominated in indivisible units. +/// One ORE token, denominated in indivisible units. pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); /// The duration of one minute, in seconds. pub const ONE_MINUTE: i64 = 60; -/// The duration of one day, in seconds. -pub const ONE_DAY: i64 = 86400; - -/// The duration of one year, in minutes. -pub const ONE_YEAR: u64 = 525600; - -/// The number of minutes in an ORE epoch. +/// The number of minutes in a program epoch. pub const EPOCH_MINUTES: i64 = 1; /// The duration of a program epoch, in seconds. diff --git a/src/instruction.rs b/src/instruction.rs index a265ab0..40322eb 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -77,6 +77,13 @@ pub enum OreInstruction { #[account(2, name = "proof", desc = "Ore proof account", writable)] Update = 6, + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "config", desc = "Ore config account", writable)] + #[account(3, name = "proof", desc = "Ore proof account – current top staker")] + #[account(4, name = "proof_new", desc = "Ore proof account – new top staker")] + Crown = 6, + #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "beneficiary", desc = "Beneficiary token account", writable)] diff --git a/src/lib.rs b/src/lib.rs index a6cf811..51ede90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ pub fn process_instruction( match OreInstruction::try_from(*tag).or(Err(ProgramError::InvalidInstructionData))? { OreInstruction::Claim => process_claim(program_id, accounts, data)?, OreInstruction::Close => process_close(program_id, accounts, data)?, + OreInstruction::Crown => process_crown(program_id, accounts, data)?, OreInstruction::Mine => process_mine(program_id, accounts, data)?, OreInstruction::Open => process_open(program_id, accounts, data)?, OreInstruction::Reset => process_reset(program_id, accounts, data)?, diff --git a/src/loaders.rs b/src/loaders.rs index 44a3ffe..dd53166 100644 --- a/src/loaders.rs +++ b/src/loaders.rs @@ -191,6 +191,34 @@ pub fn load_proof_with_miner<'a, 'info>( Ok(()) } +/// Errors if: +/// - Owner is not Ore program. +/// - Data is empty. +/// - Data cannot deserialize into a proof account. +/// - Expected to be writable, but is not. +pub fn load_any_proof<'a, 'info>( + info: &'a AccountInfo<'info>, + is_writable: bool, +) -> Result<(), ProgramError> { + if info.owner.ne(&crate::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + + if info.data_is_empty() { + return Err(ProgramError::UninitializedAccount); + } + + if info.data.borrow()[0].ne(&(Proof::discriminator() as u8)) { + return Err(solana_program::program_error::ProgramError::InvalidAccountData); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + /// Errors if: /// - Owner is not Ore program. /// - Address does not match the expected address. diff --git a/src/processor/crown.rs b/src/processor/crown.rs new file mode 100644 index 0000000..5d18ecb --- /dev/null +++ b/src/processor/crown.rs @@ -0,0 +1,57 @@ +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; + +use crate::{ + loaders::*, + state::{Config, Proof}, + utils::AccountDeserialize, +}; + +/// Crown marks an account as the top staker if their balance is greater than the last known top staker. +pub fn process_crown<'a, 'info>( + _program_id: &Pubkey, + accounts: &'a [AccountInfo<'info>], + _data: &[u8], +) -> ProgramResult { + // Load accounts + let [signer, config_info, proof_info, proof_new_info] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + load_signer(signer)?; + load_config(config_info, true)?; + load_any_proof(proof_new_info, false)?; + + // Load config + let mut config_data = config_info.data.borrow_mut(); + let config = Config::try_from_bytes_mut(&mut config_data)?; + + // Load proposed new top staker + let proof_new_data = proof_new_info.data.borrow(); + let proof_new = Proof::try_from_bytes(&proof_new_data)?; + + // If top staker is not the default null address, then compare balances + if config.top_staker.ne(&Pubkey::new_from_array([0; 32])) { + // Load current top staker + load_any_proof(proof_info, false)?; + let proof_data = proof_info.data.borrow(); + let proof = Proof::try_from_bytes(&proof_data)?; + + // Require the provided proof account is the current top staker + if proof_info.key.ne(&config.top_staker) { + return Err(ProgramError::InvalidAccountData); + } + + // Compare balances + if proof_new.balance.lt(&proof.balance) { + return Err(ProgramError::InvalidAccountData); + } + } + + // Crown the new top staker + config.max_stake = proof_new.balance; + config.top_staker = *proof_new_info.key; + + Ok(()) +} diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 7c0b0af..fa614eb 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -138,6 +138,8 @@ pub fn process_initialize<'a, 'info>( config.admin = *signer.key; config.base_reward_rate = INITIAL_BASE_REWARD_RATE; config.last_reset_at = 0; + config.max_stake = 0; + config.top_staker = Pubkey::new_from_array([0; 32]); // Initialize treasury create_pda( diff --git a/src/processor/mine.rs b/src/processor/mine.rs index f941747..0be1928 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -24,7 +24,7 @@ use crate::{ loaders::*, state::{Bus, Config, Proof}, utils::{AccountDeserialize, MineEvent}, - EPOCH_DURATION, MIN_DIFFICULTY, ONE_MINUTE, ONE_YEAR, TOLERANCE, + EPOCH_DURATION, MIN_DIFFICULTY, ONE_MINUTE, TOLERANCE, }; /// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: diff --git a/src/processor/mod.rs b/src/processor/mod.rs index 5ded886..e46eafe 100644 --- a/src/processor/mod.rs +++ b/src/processor/mod.rs @@ -1,5 +1,6 @@ mod claim; mod close; +mod crown; mod initialize; mod mine; mod open; @@ -10,6 +11,7 @@ mod upgrade; pub use claim::*; pub use close::*; +pub use crown::*; pub use initialize::*; pub use mine::*; pub use open::*; diff --git a/src/processor/stake.rs b/src/processor/stake.rs index d988520..79be3dd 100644 --- a/src/processor/stake.rs +++ b/src/processor/stake.rs @@ -4,11 +4,8 @@ use solana_program::{ }; use crate::{ - instruction::StakeArgs, - loaders::*, - state::{Config, Proof}, - utils::AccountDeserialize, - MINT_ADDRESS, TREASURY_ADDRESS, + instruction::StakeArgs, loaders::*, state::Proof, utils::AccountDeserialize, MINT_ADDRESS, + TREASURY_ADDRESS, }; /// Stake deposits Ore into a miner's proof account to earn multiplier. Its responsibilies include: @@ -29,13 +26,10 @@ pub fn process_stake<'a, 'info>( let amount = u64::from_le_bytes(args.amount); // Load accounts - let [signer, config_info, proof_info, sender_info, treasury_tokens_info, token_program] = - accounts - else { + let [signer, proof_info, sender_info, treasury_tokens_info, token_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; - load_config(config_info, true)?; load_proof(proof_info, signer.key, true)?; load_token_account(sender_info, Some(signer.key), &MINT_ADDRESS, true)?; load_token_account( @@ -55,11 +49,6 @@ pub fn process_stake<'a, 'info>( let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; proof.last_stake_at = clock.unix_timestamp; - // Update the max stake tracker - let mut config_data = config_info.data.borrow_mut(); - let config = Config::try_from_bytes_mut(&mut config_data)?; - config.max_stake = config.max_stake.max(proof.balance); - // Distribute tokens from signer to treasury solana_program::program::invoke( &spl_token::instruction::transfer( diff --git a/src/state/config.rs b/src/state/config.rs index b3de137..2c8214f 100644 --- a/src/state/config.rs +++ b/src/state/config.rs @@ -20,8 +20,11 @@ pub struct Config { /// The timestamp of the last reset. pub last_reset_at: i64, - /// The largest stake account on the network. + /// The largest known stake balance on the network. pub max_stake: u64, + + /// The address of the proof account with the highest stake balance. + pub top_staker: Pubkey, } impl Discriminator for Config { From 8730052e4c134c8dde1dd12e6191f6123c830b11 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 27 Jun 2024 14:03:02 +0000 Subject: [PATCH 097/111] if --- src/processor/crown.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/processor/crown.rs b/src/processor/crown.rs index 5d18ecb..984fdf7 100644 --- a/src/processor/crown.rs +++ b/src/processor/crown.rs @@ -39,7 +39,7 @@ pub fn process_crown<'a, 'info>( let proof = Proof::try_from_bytes(&proof_data)?; // Require the provided proof account is the current top staker - if proof_info.key.ne(&config.top_staker) { + if config.top_staker.ne(&proof_info.key) { return Err(ProgramError::InvalidAccountData); } From 2f60bf1f81144295ba29aeedab49b7a03e126f50 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Thu, 27 Jun 2024 14:46:14 +0000 Subject: [PATCH 098/111] silent error --- src/processor/crown.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/processor/crown.rs b/src/processor/crown.rs index 984fdf7..674f114 100644 --- a/src/processor/crown.rs +++ b/src/processor/crown.rs @@ -40,12 +40,12 @@ pub fn process_crown<'a, 'info>( // Require the provided proof account is the current top staker if config.top_staker.ne(&proof_info.key) { - return Err(ProgramError::InvalidAccountData); + return Ok(()); } // Compare balances if proof_new.balance.lt(&proof.balance) { - return Err(ProgramError::InvalidAccountData); + return Ok(()); } } From d718360cafbd9d931b16bdbd795efa7488eb53f6 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 28 Jun 2024 13:52:38 +0000 Subject: [PATCH 099/111] fix instruction counter --- src/instruction.rs | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/instruction.rs b/src/instruction.rs index addb739..fdf9d2a 100644 --- a/src/instruction.rs +++ b/src/instruction.rs @@ -32,6 +32,13 @@ pub enum OreInstruction { #[account(3, name = "system_program", desc = "Solana system program")] Close = 1, + #[account(0, name = "ore_program", desc = "Ore program")] + #[account(1, name = "signer", desc = "Signer", signer)] + #[account(2, name = "config", desc = "Ore config account", writable)] + #[account(3, name = "proof", desc = "Ore proof account – current top staker")] + #[account(4, name = "proof_new", desc = "Ore proof account – new top staker")] + Crown = 2, + #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "bus", desc = "Ore bus account", writable)] @@ -39,13 +46,13 @@ pub enum OreInstruction { #[account(4, name = "noise", desc = "Ore noise account")] #[account(5, name = "proof", desc = "Ore proof account", writable)] #[account(6, name = "slot_hashes", desc = "Solana slot hashes sysvar")] - Mine = 2, + Mine = 3, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "proof", desc = "Ore proof account", writable)] #[account(3, name = "system_program", desc = "Solana system program")] - Open = 3, + Open = 4, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -62,7 +69,7 @@ pub enum OreInstruction { #[account(12, name = "treasury", desc = "Ore treasury account", writable)] #[account(13, name = "treasury_tokens", desc = "Ore treasury token account", writable)] #[account(14, name = "token_program", desc = "SPL token program")] - Reset = 4, + Reset = 5, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -70,26 +77,12 @@ pub enum OreInstruction { #[account(3, name = "sender", desc = "Signer token account", writable)] #[account(4, name = "treasury_tokens", desc = "Ore treasury token account", writable)] #[account(5, name = "token_program", desc = "SPL token program")] - Stake = 5, + Stake = 6, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] #[account(2, name = "proof", desc = "Ore proof account", writable)] - Update = 6, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "signer", desc = "Signer", signer)] - #[account(2, name = "config", desc = "Ore config account", writable)] - #[account(3, name = "proof", desc = "Ore proof account – current top staker")] - #[account(4, name = "proof_new", desc = "Ore proof account – new top staker")] - Crown = 6, - - #[account(0, name = "ore_program", desc = "Ore program")] - #[account(1, name = "signer", desc = "Signer", signer)] - #[account(2, name = "config", desc = "Ore config account", writable)] - #[account(3, name = "proof", desc = "Ore proof account – current top staker")] - #[account(4, name = "proof_new", desc = "Ore proof account – new top staker")] - Crown = 6, + Update = 7, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Signer", signer)] @@ -99,7 +92,7 @@ pub enum OreInstruction { #[account(5, name = "mint", desc = "Ore token mint account", writable)] #[account(6, name = "mint_v1", desc = "Ore v1 token mint account", writable)] #[account(7, name = "token_program", desc = "SPL token program")] - Upgrade = 7, + Upgrade = 8, #[account(0, name = "ore_program", desc = "Ore program")] #[account(1, name = "signer", desc = "Admin signer", signer)] From 3acb7a03be3b13f1969569c79eacb4c4df512f86 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 28 Jun 2024 14:15:13 +0000 Subject: [PATCH 100/111] cleanup --- src/processor/mine.rs | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/src/processor/mine.rs b/src/processor/mine.rs index 0be1928..e66b8aa 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -61,12 +61,12 @@ pub fn process_mine<'a, 'info>( load_sysvar(instructions_sysvar, sysvar::instructions::id())?; load_sysvar(slot_hashes_sysvar, sysvar::slot_hashes::id())?; - // Validate this is the only mine ix in the transaction + // Validate this is the only mine ix in the transaction. if !validate_transaction(&instructions_sysvar.data.borrow()).unwrap_or(false) { return Err(OreError::TransactionInvalid.into()); } - // Validate epoch is active + // Validate epoch is active. let config_data = config_info.data.borrow(); let config = Config::try_from_bytes(&config_data)?; let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?; @@ -78,7 +78,7 @@ pub fn process_mine<'a, 'info>( return Err(OreError::NeedsReset.into()); } - // Validate the digest + // Validate the hash digest. let mut proof_data = proof_info.data.borrow_mut(); let proof = Proof::try_from_bytes_mut(&mut proof_data)?; let solution = Solution::new(args.digest, args.nonce); @@ -86,7 +86,7 @@ pub fn process_mine<'a, 'info>( return Err(OreError::HashInvalid.into()); } - // Validate hash satisfies the minimnum difficulty + // Validate hash satisfies the minimnum difficulty. let hash = solution.to_hash(); let difficulty = hash.difficulty(); sol_log(&format!("Diff {}", difficulty)); @@ -94,17 +94,16 @@ pub fn process_mine<'a, 'info>( return Err(OreError::HashTooEasy.into()); } - // Calculate base reward rate + // Calculate base reward rate. let difficulty = difficulty.saturating_sub(MIN_DIFFICULTY); let mut reward = config .base_reward_rate .saturating_mul(2u64.saturating_pow(difficulty)); - sol_log(&format!("Base {}", reward)); // Apply staking multiplier. - // If user has greater than or equal to the max stake on the network, they will receive 2x multiplier. - // Any less than this, and they will receive between 1x and 2x. Miners are only eligable for a multipler - // if their last stake deposit was more than one minute ago. + // If user has greater than or equal to the max stake on the network, they receive 2x multiplier. + // Any stake less than this will receives between 1x and 2x multipler. The multipler is only active + // if the miner's last stake deposit was more than one minute ago. if config.max_stake.gt(&0) && proof .last_stake_at @@ -117,19 +116,17 @@ pub fn process_mine<'a, 'info>( .saturating_mul(reward) .saturating_div(config.max_stake); reward = reward.saturating_add(staking_reward); - sol_log(&format!("Staking {}", staking_reward)); } - // Apply spam penalty + // Reject spam transactions. let t = clock.unix_timestamp; let t_target = proof.last_hash_at.saturating_add(ONE_MINUTE); let t_spam = t_target.saturating_sub(TOLERANCE); if t.lt(&t_spam) { - sol_log("Spam penalty"); return Err(OreError::Spam.into()); } - // Apply liveness penalty + // Apply liveness penalty. let t_liveness = t_target.saturating_add(TOLERANCE); if t.gt(&t_liveness) { reward = reward.saturating_sub( @@ -137,11 +134,6 @@ pub fn process_mine<'a, 'info>( .saturating_mul(t.saturating_sub(t_liveness) as u64) .saturating_div(ONE_MINUTE as u64), ); - sol_log(&format!( - "Liveness penalty ({} sec) {}", - t.saturating_sub(t_liveness), - reward, - )); } // Limit payout amount to whatever is left in the bus @@ -150,8 +142,6 @@ pub fn process_mine<'a, 'info>( let reward_actual = reward.min(bus.rewards); // Update balances - sol_log(&format!("Total {}", reward)); - sol_log(&format!("Bus {}", bus.rewards)); bus.theoretical_rewards = bus.theoretical_rewards.saturating_add(reward); bus.rewards = bus.rewards.saturating_sub(reward_actual); proof.balance = proof.balance.saturating_add(reward_actual); @@ -215,9 +205,7 @@ fn validate_transaction(msg: &[u8]) -> Result { return Ok(false); } } - COMPUTE_BUDGET_PROGRAM_ID => { - // Noop - } + COMPUTE_BUDGET_PROGRAM_ID => {} // Noop _ => return Ok(false), } } From 111a31d52a14a5140ddf6df1dabcdde1b68da31f Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 28 Jun 2024 15:04:02 +0000 Subject: [PATCH 101/111] nuke tests --- src/loaders.rs | 941 ----------------------------- tests/buffers/metadata_program.bpf | Bin 793904 -> 0 bytes tests/test_initialize.rs | 170 ------ tests/test_mine.rs | 746 ----------------------- tests/test_register.rs | 188 ------ tests/test_reset.rs | 440 -------------- tests/test_update_admin.rs | 128 ---- tests/test_update_difficulty.rs | 125 ---- 8 files changed, 2738 deletions(-) delete mode 100644 tests/buffers/metadata_program.bpf delete mode 100644 tests/test_initialize.rs delete mode 100644 tests/test_mine.rs delete mode 100644 tests/test_register.rs delete mode 100644 tests/test_reset.rs delete mode 100644 tests/test_update_admin.rs delete mode 100644 tests/test_update_difficulty.rs diff --git a/src/loaders.rs b/src/loaders.rs index dd53166..c0b0692 100644 --- a/src/loaders.rs +++ b/src/loaders.rs @@ -423,944 +423,3 @@ pub fn load_program<'a, 'info>( Ok(()) } - -// #[cfg(test)] -// mod tests { -// use solana_program::{ -// account_info::AccountInfo, keccak::Hash as KeccakHash, program_option::COption, -// program_pack::Pack, pubkey::Pubkey, system_program, -// }; -// use spl_token::state::{AccountState, Mint}; - -// use crate::{ -// loaders::{ -// load_account, load_any_bus, load_bus, load_mint, load_proof, load_signer, load_sysvar, -// load_token_account, load_treasury, load_uninitialized_account, load_uninitialized_pda, -// }, -// state::{Bus, Proof, Treasury}, -// utils::Discriminator, -// BUS, BUS_ADDRESSES, BUS_COUNT, MINT_ADDRESS, PROOF, TOKEN_DECIMALS, TREASURY, -// TREASURY_ADDRESS, -// }; - -// use super::load_program; - -// #[test] -// pub fn test_signer_not_signer() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &key, -// false, -// false, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_signer(&info).is_err()); -// } - -// #[test] -// pub fn test_load_bus_bad_account_owner() { -// let key = BUS_ADDRESSES[0]; -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_bus(&info, 0, true).is_err()); -// } - -// #[test] -// pub fn test_load_bus_bad_key() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_bus(&info, 0, true).is_err()); -// } - -// #[test] -// pub fn test_load_bus_empty_data() { -// let key = BUS_ADDRESSES[0]; -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_bus(&info, 0, true).is_err()); -// } - -// #[test] -// pub fn test_load_bus_bad_data() { -// let key = BUS_ADDRESSES[0]; -// let mut lamports = 1_000_000_000; -// let mut data = [ -// &(Treasury::discriminator() as u64).to_le_bytes(), // Bus discriminator -// Bus { id: 0, rewards: 0 }.to_bytes(), -// ] -// .concat(); -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_bus(&info, 0, true).is_err()); -// } - -// #[test] -// pub fn test_load_bus_bad_id() { -// let key = BUS_ADDRESSES[0]; -// let mut lamports = 1_000_000_000; -// let mut data = [ -// &(Bus::discriminator() as u64).to_le_bytes(), // Bus discriminator -// Bus { id: 1, rewards: 0 }.to_bytes(), -// ] -// .concat(); -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_bus(&info, 0, true).is_err()); -// } - -// #[test] -// pub fn test_load_bus_not_writeable() { -// let key = BUS_ADDRESSES[0]; -// let mut lamports = 1_000_000_000; -// let mut data = [ -// &(Bus::discriminator() as u64).to_le_bytes(), -// Bus { id: 0, rewards: 0 }.to_bytes(), -// ] -// .concat(); -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// false, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_bus(&info, 0, true).is_err()); -// } - -// #[test] -// pub fn test_load_any_bus_bad_account_owner() { -// let key = BUS_ADDRESSES[0]; -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_any_bus(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_any_bus_bad_key() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_any_bus(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_any_bus_empty_data() { -// let key = BUS_ADDRESSES[0]; -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_any_bus(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_any_bus_bad_data() { -// let key = BUS_ADDRESSES[0]; -// let mut lamports = 1_000_000_000; -// let mut data = [ -// &(Treasury::discriminator() as u64).to_le_bytes(), // Treasury discriminator -// Bus { id: 0, rewards: 0 }.to_bytes(), -// ] -// .concat(); -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_any_bus(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_any_bus_bad_id() { -// let key = BUS_ADDRESSES[0]; -// let mut lamports = 1_000_000_000; -// let mut data = [ -// &(Bus::discriminator() as u64).to_le_bytes(), -// Bus { -// id: (BUS_COUNT + 1) as u64, -// rewards: 0, -// } -// .to_bytes(), -// ] -// .concat(); -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_any_bus(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_any_bus_mismatch_id() { -// let key = BUS_ADDRESSES[0]; -// let mut lamports = 1_000_000_000; -// let mut data = [ -// &(Bus::discriminator() as u64).to_le_bytes(), -// Bus { -// id: 1 as u64, -// rewards: 0, -// } -// .to_bytes(), -// ] -// .concat(); -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_any_bus(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_any_bus_not_writeable() { -// let key = BUS_ADDRESSES[0]; -// let mut lamports = 1_000_000_000; -// let mut data = [ -// &(Bus::discriminator() as u64).to_le_bytes(), -// Bus { id: 0, rewards: 0 }.to_bytes(), -// ] -// .concat(); -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// false, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_any_bus(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_proof_bad_account_owner() { -// let authority = Pubkey::new_unique(); -// let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &pda.0, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_proof(&info, &authority, true).is_err()); -// } - -// #[test] -// pub fn test_load_proof_bad_key() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_proof(&info, &Pubkey::new_unique(), true).is_err()); -// } - -// #[test] -// pub fn test_load_proof_empty_data() { -// let authority = Pubkey::new_unique(); -// let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = crate::id(); -// let info = AccountInfo::new( -// &pda.0, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_proof(&info, &authority, true).is_err()); -// } - -// #[test] -// pub fn test_load_proof_bad_data() { -// let authority = Pubkey::new_unique(); -// let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); -// let mut lamports = 1_000_000_000; -// let mut data = [ -// &(Bus::discriminator() as u64).to_le_bytes(), // Bus discriminator -// Proof { -// authority, -// balance: 0, -// hash: KeccakHash::new_from_array([u8::MAX; 32]).into(), -// total_hashes: 0, -// total_rewards: 0, -// multiplier: 1, -// last_hash_at: 0, -// } -// .to_bytes(), -// ] -// .concat(); -// let owner = crate::id(); -// let info = AccountInfo::new( -// &pda.0, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_proof(&info, &authority, true).is_err()); -// } - -// #[test] -// pub fn test_load_proof_not_writeable() { -// let authority = Pubkey::new_unique(); -// let pda = Pubkey::find_program_address(&[PROOF, authority.as_ref()], &crate::id()); -// let mut lamports = 1_000_000_000; -// let mut data = [ -// &(Proof::discriminator() as u64).to_le_bytes(), -// Proof { -// authority, -// claimable_rewards: 0, -// hash: KeccakHash::new_from_array([u8::MAX; 32]).into(), -// total_hashes: 0, -// total_rewards: 0, -// } -// .to_bytes(), -// ] -// .concat(); -// let owner = crate::id(); -// let info = AccountInfo::new( -// &pda.0, -// false, -// false, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_proof(&info, &authority, true).is_err()); -// } - -// #[test] -// pub fn test_load_treasury_bad_account_owner() { -// let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &pda.0, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_treasury(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_treasury_bad_key() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_treasury(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_treasury_empty_data() { -// let key = TREASURY_ADDRESS; -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = crate::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_treasury(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_treasury_bad_data() { -// let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); -// let mut lamports = 1_000_000_000; -// let mut data = [ -// &(Bus::discriminator() as u64).to_le_bytes(), // Bus discriminator -// Treasury { -// bump: pda.1 as u64, -// // admin: Pubkey::new_unique(), -// // difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), -// last_reset_at: 0, -// reward_rate: 100, -// total_claimed_rewards: 0, -// } -// .to_bytes(), -// ] -// .concat(); -// let owner = crate::id(); -// let info = AccountInfo::new( -// &pda.0, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_treasury(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_treasury_not_writeable() { -// let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); -// let mut lamports = 1_000_000_000; -// let mut data = [ -// &(Treasury::discriminator() as u64).to_le_bytes(), -// Treasury { -// bump: pda.1 as u64, -// // admin: Pubkey::new_unique(), -// // difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), -// last_reset_at: 0, -// reward_rate: 100, -// total_claimed_rewards: 0, -// } -// .to_bytes(), -// ] -// .concat(); -// let owner = crate::id(); -// let info = AccountInfo::new( -// &pda.0, -// false, -// false, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_treasury(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_mint_bad_account_owner() { -// let key = MINT_ADDRESS; -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); -// } - -// #[test] -// pub fn test_load_mint_bad_key() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = spl_token::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_mint(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_mint_empty_data() { -// let key = MINT_ADDRESS; -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = spl_token::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_mint(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_mint_bad_data() { -// let key = MINT_ADDRESS; -// let mut lamports = 1_000_000_000; -// let mut data = [1]; -// let owner = spl_token::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_mint(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_mint_not_writeable() { -// let mut data: [u8; Mint::LEN] = [0; Mint::LEN]; -// Mint { -// mint_authority: COption::Some(TREASURY_ADDRESS), -// supply: 0, -// decimals: TOKEN_DECIMALS, -// is_initialized: true, -// freeze_authority: COption::None, -// } -// .pack_into_slice(&mut data); -// let key = MINT_ADDRESS; -// let mut lamports = 1_000_000_000; -// let owner = spl_token::id(); -// let info = AccountInfo::new( -// &key, -// false, -// false, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_mint(&info, true).is_err()); -// } - -// #[test] -// pub fn test_load_token_account_bad_account_owner() { -// let mut data: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; -// spl_token::state::Account { -// mint: MINT_ADDRESS, -// owner: TREASURY_ADDRESS, -// amount: 2_000_000_000, -// delegate: COption::None, -// state: AccountState::Initialized, -// is_native: COption::None, -// delegated_amount: 0, -// close_authority: COption::None, -// } -// .pack_into_slice(&mut data); -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); -// } - -// #[test] -// pub fn test_load_token_account_empty_data() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = spl_token::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); -// } - -// #[test] -// pub fn test_load_token_account_bad_data() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = [1]; -// let owner = spl_token::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); -// } - -// #[test] -// pub fn test_load_token_account_bad_owner_mint() { -// let mut data: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; -// spl_token::state::Account { -// mint: MINT_ADDRESS, -// owner: TREASURY_ADDRESS, -// amount: 2_000_000_000, -// delegate: COption::None, -// state: AccountState::Initialized, -// is_native: COption::None, -// delegated_amount: 0, -// close_authority: COption::None, -// } -// .pack_into_slice(&mut data); -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let owner = spl_token::id(); -// let info = AccountInfo::new( -// &key, -// false, -// false, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_token_account(&info, Some(&key), &MINT_ADDRESS, false).is_err()); -// assert!(load_token_account(&info, None, &Pubkey::new_unique(), false).is_err()); -// assert!(load_token_account(&info, None, &MINT_ADDRESS, true).is_err()); -// } - -// #[test] -// pub fn test_load_uninitialized_pda_bad_key_bump() { -// let pda = Pubkey::find_program_address(&[TREASURY], &crate::id()); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &pda.0, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_uninitialized_pda(&info, &[BUS], pda.1, &crate::id()).is_err()); -// assert!(load_uninitialized_pda(&info, &[TREASURY], 0, &crate::id()).is_err()); -// } - -// #[test] -// pub fn test_load_uninitialized_account_bad_owner() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = spl_token::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_uninitialized_account(&info).is_err()); -// } - -// #[test] -// pub fn test_load_uninitialized_account_data_not_empty() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = [0]; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &key, -// false, -// true, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_uninitialized_account(&info).is_err()); -// } - -// #[test] -// pub fn test_load_uninitialized_account_not_writeable() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &key, -// false, -// false, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_uninitialized_account(&info).is_err()); -// } - -// #[test] -// pub fn test_load_sysvar_bad_owner() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &key, -// false, -// false, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_sysvar(&info, key).is_err()); -// } - -// #[test] -// pub fn test_load_account_bad_key() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &key, -// false, -// false, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_account(&info, Pubkey::new_unique(), false).is_err()); -// } - -// #[test] -// pub fn test_load_account_not_writeable() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &key, -// false, -// false, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_account(&info, key, true).is_err()); -// } - -// #[test] -// pub fn test_load_program_bad_key() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &key, -// false, -// false, -// &mut lamports, -// &mut data, -// &owner, -// true, -// 0, -// ); -// assert!(load_program(&info, Pubkey::new_unique()).is_err()); -// } - -// #[test] -// pub fn test_load_program_not_executable() { -// let key = Pubkey::new_unique(); -// let mut lamports = 1_000_000_000; -// let mut data = []; -// let owner = system_program::id(); -// let info = AccountInfo::new( -// &key, -// false, -// false, -// &mut lamports, -// &mut data, -// &owner, -// false, -// 0, -// ); -// assert!(load_program(&info, key).is_err()); -// } -// } diff --git a/tests/buffers/metadata_program.bpf b/tests/buffers/metadata_program.bpf deleted file mode 100644 index 3ebd1b6c5b61569c6a61a3dee5ec687fdea0cace..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 793904 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

G97&oy_Rb_X@TF-w{d5y^u0Lt7)3# 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

PU51CGb@kWVn3;UC5OtXKDD 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�S%@_AQ~MoU4tOTz;9rL8(O=_hlb?7W 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 z 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 z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf z&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf&>7Gf J_#b89{{VHr!x;bo diff --git a/tests/test_initialize.rs b/tests/test_initialize.rs deleted file mode 100644 index 9e6a840..0000000 --- a/tests/test_initialize.rs +++ /dev/null @@ -1,170 +0,0 @@ -// use mpl_token_metadata::{ -// accounts::Metadata, -// types::{Key, TokenStandard}, -// }; -// use ore::{ -// state::{Bus, Treasury}, -// utils::AccountDeserialize, -// BUS_ADDRESSES, BUS_COUNT, INITIAL_DIFFICULTY, INITIAL_REWARD_RATE, METADATA_ADDRESS, -// METADATA_NAME, METADATA_SYMBOL, METADATA_URI, MINT_ADDRESS, TREASURY, -// }; -// use solana_program::{ -// hash::Hash, program_option::COption, program_pack::Pack, pubkey::Pubkey, rent::Rent, -// }; -// use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; -// use solana_sdk::{ -// account::Account, -// signature::{Keypair, Signer}, -// transaction::Transaction, -// }; -// use spl_token::state::{AccountState, Mint}; - -// #[tokio::test] -// async fn test_initialize() { -// // Setup -// let (mut banks, payer, blockhash) = setup_program_test_env().await; - -// // Pdas -// let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); -// let treasury_tokens_address = -// spl_associated_token_account::get_associated_token_address(&treasury_pda.0, &MINT_ADDRESS); - -// // Submit tx -// let ix = ore::instruction::initialize(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Test bus state -// for i in 0..BUS_COUNT { -// let bus_account = banks.get_account(BUS_ADDRESSES[i]).await.unwrap().unwrap(); -// assert_eq!(bus_account.owner, ore::id()); -// let bus = Bus::try_from_bytes(&bus_account.data).unwrap(); -// assert_eq!(bus.id as u8, i as u8); -// assert_eq!(bus.rewards, 0); -// } - -// // Test treasury state -// let treasury_account = banks.get_account(treasury_pda.0).await.unwrap().unwrap(); -// assert_eq!(treasury_account.owner, ore::id()); -// let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); -// assert_eq!(treasury.bump as u8, treasury_pda.1); -// // assert_eq!(treasury.admin, payer.pubkey()); -// // assert_eq!(treasury.difficulty, INITIAL_DIFFICULTY.into()); -// assert_eq!(treasury.last_reset_at as u8, 0); -// assert_eq!(treasury.reward_rate, INITIAL_REWARD_RATE); -// assert_eq!(treasury.total_claimed_rewards as u8, 0); - -// // Test mint state -// let mint_account = banks.get_account(MINT_ADDRESS).await.unwrap().unwrap(); -// assert_eq!(mint_account.owner, spl_token::id()); -// let mint = Mint::unpack(&mint_account.data).unwrap(); -// assert_eq!(mint.mint_authority, COption::Some(treasury_pda.0)); -// assert_eq!(mint.supply, 0); -// assert_eq!(mint.decimals, ore::TOKEN_DECIMALS); -// assert_eq!(mint.is_initialized, true); -// assert_eq!(mint.freeze_authority, COption::None); - -// // Test metadata state -// let metadata_account = banks.get_account(METADATA_ADDRESS).await.unwrap().unwrap(); -// assert_eq!(metadata_account.owner, mpl_token_metadata::ID); -// let metadata = Metadata::from_bytes(&metadata_account.data).unwrap(); -// assert_eq!(metadata.key, Key::MetadataV1); -// assert_eq!(metadata.update_authority, payer.pubkey()); -// assert_eq!(metadata.mint, MINT_ADDRESS); -// assert_eq!(metadata.name.trim_end_matches('\0'), METADATA_NAME); -// assert_eq!(metadata.symbol.trim_end_matches('\0'), METADATA_SYMBOL); -// assert_eq!(metadata.uri.trim_end_matches('\0'), METADATA_URI); -// assert_eq!(metadata.seller_fee_basis_points, 0); -// assert_eq!(metadata.creators, None); -// assert_eq!(metadata.primary_sale_happened, false); -// assert_eq!(metadata.is_mutable, true); -// assert_eq!(metadata.token_standard, Some(TokenStandard::Fungible)); -// assert_eq!(metadata.collection, None); -// assert_eq!(metadata.uses, None); -// assert_eq!(metadata.collection_details, None); -// assert_eq!(metadata.programmable_config, None); - -// // Test treasury token state -// let treasury_tokens_account = banks -// .get_account(treasury_tokens_address) -// .await -// .unwrap() -// .unwrap(); -// assert_eq!(treasury_tokens_account.owner, spl_token::id()); -// let treasury_tokens = spl_token::state::Account::unpack(&treasury_tokens_account.data).unwrap(); -// assert_eq!(treasury_tokens.mint, MINT_ADDRESS); -// assert_eq!(treasury_tokens.owner, treasury_pda.0); -// assert_eq!(treasury_tokens.amount, 0); -// assert_eq!(treasury_tokens.delegate, COption::None); -// assert_eq!(treasury_tokens.state, AccountState::Initialized); -// assert_eq!(treasury_tokens.is_native, COption::None); -// assert_eq!(treasury_tokens.delegated_amount, 0); -// assert_eq!(treasury_tokens.close_authority, COption::None); -// } - -// #[tokio::test] -// async fn test_initialize_not_enough_accounts() { -// // Setup -// let (mut banks, payer, blockhash) = setup_program_test_env().await; - -// // Submit tx -// let mut ix = ore::instruction::initialize(payer.pubkey()); -// ix.accounts.remove(1); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_initialize_bad_key() { -// // Setup -// let (mut banks, payer, blockhash) = setup_program_test_env().await; - -// // Bad addresses -// let bad_pda = Pubkey::find_program_address(&[b"t"], &ore::id()); -// for i in 1..12 { -// let mut ix = ore::instruction::initialize(payer.pubkey()); -// ix.accounts[i].pubkey = bad_pda.0; -// let tx = -// Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } -// } - -// #[tokio::test] -// async fn test_initialize_bad_programs() { -// // Setup -// let (mut banks, payer, blockhash) = setup_program_test_env().await; - -// // Bad addresses -// for i in 13..18 { -// let mut ix = ore::instruction::initialize(payer.pubkey()); -// ix.accounts[i].pubkey = Pubkey::new_unique(); -// let tx = -// Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } -// } - -// async fn setup_program_test_env() -> (BanksClient, Keypair, Hash) { -// let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); -// program_test.prefer_bpf(true); - -// // Setup metadata program -// let data = read_file(&"tests/buffers/metadata_program.bpf"); -// program_test.add_account( -// mpl_token_metadata::ID, -// Account { -// lamports: Rent::default().minimum_balance(data.len()).max(1), -// data, -// owner: solana_sdk::bpf_loader::id(), -// executable: true, -// rent_epoch: 0, -// }, -// ); - -// program_test.start().await -// } diff --git a/tests/test_mine.rs b/tests/test_mine.rs deleted file mode 100644 index ae10349..0000000 --- a/tests/test_mine.rs +++ /dev/null @@ -1,746 +0,0 @@ -// use std::{mem::size_of, str::FromStr}; - -// use ore::{ -// instruction::{MineArgs, OreInstruction}, -// state::{Bus, Proof, Treasury}, -// utils::{AccountDeserialize, Discriminator}, -// BUS_ADDRESSES, BUS_COUNT, EPOCH_DURATION, INITIAL_REWARD_RATE, MINT_ADDRESS, PROOF, START_AT, -// TOKEN_DECIMALS, TREASURY, TREASURY_ADDRESS, -// }; -// use rand::{distributions::Uniform, Rng}; -// use solana_program::{ -// clock::Clock, -// epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, -// hash::Hash, -// instruction::{AccountMeta, Instruction}, -// keccak::{hashv, Hash as KeccakHash}, -// native_token::LAMPORTS_PER_SOL, -// program_option::COption, -// program_pack::Pack, -// pubkey::Pubkey, -// slot_hashes::SlotHash, -// system_program, sysvar, -// }; -// use solana_program_test::{processor, BanksClient, ProgramTest}; -// use solana_sdk::{ -// account::Account, -// signature::{Keypair, Signer}, -// transaction::Transaction, -// }; -// use spl_associated_token_account::{ -// get_associated_token_address, instruction::create_associated_token_account, -// }; -// use spl_token::state::{AccountState, Mint}; - -// #[tokio::test] -// async fn test_mine() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - -// // Submit register tx -// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); -// let ix = ore::instruction::register(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// 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 = Proof::try_from_bytes(&proof_account.data).unwrap(); -// assert_eq!(proof.authority, payer.pubkey()); -// assert_eq!(proof.claimable_rewards, 0); -// assert_eq!(proof.total_hashes, 0); -// assert_eq!(proof.total_rewards, 0); - -// // Find next hash -// let (next_hash, nonce) = find_next_hash( -// proof.hash.into(), -// KeccakHash::new_from_array([u8::MAX; 32]), -// payer.pubkey(), -// ); - -// // Submit mine tx -// let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Assert proof state -// let slot_hashes_account = banks -// .get_account(sysvar::slot_hashes::id()) -// .await -// .unwrap() -// .unwrap(); -// let slot_hash_bytes = &slot_hashes_account.data[0..size_of::()]; -// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); -// assert_eq!(proof_account.owner, ore::id()); -// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); -// assert_eq!(proof.authority, payer.pubkey()); -// assert_eq!(proof.claimable_rewards, INITIAL_REWARD_RATE); -// assert_eq!( -// proof.hash, -// hashv(&[&next_hash.as_ref(), slot_hash_bytes,]).into() -// ); -// assert_eq!(proof.total_hashes, 1); -// assert_eq!(proof.total_rewards, INITIAL_REWARD_RATE); - -// // Submit claim tx -// let amount = proof.claimable_rewards; -// let beneficiary_address = get_associated_token_address(&payer.pubkey(), &ore::MINT_ADDRESS); -// let token_ix = create_associated_token_account( -// &payer.pubkey(), -// &payer.pubkey(), -// &ore::MINT_ADDRESS, -// &spl_token::id(), -// ); -// let ix = ore::instruction::claim(payer.pubkey(), beneficiary_address, amount); -// let tx = Transaction::new_signed_with_payer( -// &[token_ix, ix], -// Some(&payer.pubkey()), -// &[&payer], -// blockhash, -// ); -// 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(); -// let proof_ = Proof::try_from_bytes(&proof_account.data).unwrap(); -// assert_eq!(proof_.authority, proof.authority); -// assert_eq!(proof_.claimable_rewards, 0); -// assert_eq!(proof_.hash, proof.hash); -// assert_eq!(proof_.total_hashes, proof.total_hashes); -// assert_eq!(proof_.total_rewards, proof.total_rewards); - -// // Assert beneficiary state -// let beneficiary_account = banks -// .get_account(beneficiary_address) -// .await -// .unwrap() -// .unwrap(); -// assert_eq!(beneficiary_account.owner, spl_token::id()); -// let beneficiary = spl_token::state::Account::unpack(&beneficiary_account.data).unwrap(); -// assert_eq!(beneficiary.mint, ore::MINT_ADDRESS); -// assert_eq!(beneficiary.owner, payer.pubkey()); -// assert_eq!(beneficiary.amount, amount); -// assert_eq!(beneficiary.delegate, COption::None); -// assert_eq!(beneficiary.state, AccountState::Initialized); -// assert_eq!(beneficiary.is_native, COption::None); -// assert_eq!(beneficiary.delegated_amount, 0); -// assert_eq!(beneficiary.close_authority, COption::None); -// } - -// #[tokio::test] -// async fn test_mine_alt_proof() { -// // Setup -// let (mut banks, payer, payer_alt, blockhash) = -// setup_program_test_env(true, ClockState::Normal).await; - -// // Submit register tx -// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); -// let ix = ore::instruction::register(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Submit register alt tx -// let proof_alt_pda = -// Pubkey::find_program_address(&[PROOF, payer_alt.pubkey().as_ref()], &ore::id()); -// let ix_alt = ore::instruction::register(payer_alt.pubkey()); -// let tx = Transaction::new_signed_with_payer( -// &[ix_alt], -// Some(&payer_alt.pubkey()), -// &[&payer_alt], -// blockhash, -// ); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Submit mine tx with invalid proof -// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); -// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); -// let (_next_hash, nonce) = find_next_hash( -// proof.hash.into(), -// KeccakHash::new_from_array([u8::MAX; 32]), -// payer.pubkey(), -// ); -// let ix = Instruction { -// program_id: ore::id(), -// accounts: vec![ -// AccountMeta::new(payer.pubkey(), true), -// AccountMeta::new(BUS_ADDRESSES[0], false), -// AccountMeta::new(proof_alt_pda.0, false), -// AccountMeta::new(TREASURY_ADDRESS, false), -// AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), -// ], -// data: [ -// OreInstruction::Mine.to_vec(), -// MineArgs { -// nonce: nonce.to_le_bytes(), -// } -// .to_bytes() -// .to_vec(), -// ] -// .concat(), -// }; -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_mine_correct_hash_alt_proof() { -// // Setup -// let (mut banks, payer, payer_alt, blockhash) = -// setup_program_test_env(true, ClockState::Normal).await; - -// // Submit register alt tx -// let proof_alt_pda = -// Pubkey::find_program_address(&[PROOF, payer_alt.pubkey().as_ref()], &ore::id()); -// let ix_alt = ore::instruction::register(payer_alt.pubkey()); -// let tx = Transaction::new_signed_with_payer( -// &[ix_alt], -// Some(&payer_alt.pubkey()), -// &[&payer_alt], -// blockhash, -// ); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Submit with correct hash for invalid proof -// let proof_alt_account = banks.get_account(proof_alt_pda.0).await.unwrap().unwrap(); -// let proof_alt = Proof::try_from_bytes(&proof_alt_account.data).unwrap(); -// let (_next_hash, nonce) = find_next_hash( -// proof_alt.hash.into(), -// KeccakHash::new_from_array([u8::MAX; 32]), -// payer_alt.pubkey(), -// ); -// let ix = Instruction { -// program_id: ore::id(), -// accounts: vec![ -// AccountMeta::new(payer.pubkey(), true), -// AccountMeta::new(BUS_ADDRESSES[0], false), -// AccountMeta::new(proof_alt_pda.0, false), -// AccountMeta::new(TREASURY_ADDRESS, false), -// AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), -// ], -// data: [ -// OreInstruction::Mine.to_vec(), -// MineArgs { -// nonce: nonce.to_le_bytes(), -// } -// .to_bytes() -// .to_vec(), -// ] -// .concat(), -// }; -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_mine_bus_rewards_insufficient() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env(false, ClockState::Normal).await; - -// // Submit register tx -// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); -// let ix = ore::instruction::register(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Find next hash -// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); -// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); -// let (_next_hash, nonce) = find_next_hash( -// proof.hash.into(), -// KeccakHash::new_from_array([u8::MAX; 32]), -// payer.pubkey(), -// ); - -// // Submit mine tx -// let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_claim_too_large() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - -// // Submit register tx -// let ix = ore::instruction::register(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Submit claim tx -// let beneficiary = get_associated_token_address(&payer.pubkey(), &ore::MINT_ADDRESS); -// let token_ix = create_associated_token_account( -// &payer.pubkey(), -// &payer.pubkey(), -// &ore::MINT_ADDRESS, -// &spl_token::id(), -// ); -// let ix = ore::instruction::claim(payer.pubkey(), beneficiary, 1); -// let tx = Transaction::new_signed_with_payer( -// &[token_ix, ix], -// Some(&payer.pubkey()), -// &[&payer], -// blockhash, -// ); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_claim_other_proof() { -// // Setup -// let (mut banks, payer, alt_payer, blockhash) = -// setup_program_test_env(true, ClockState::Normal).await; - -// // Submit register tx -// let ix = ore::instruction::register(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Submit claim tx -// let beneficiary = get_associated_token_address(&alt_payer.pubkey(), &ore::MINT_ADDRESS); -// let token_ix = create_associated_token_account( -// &alt_payer.pubkey(), -// &alt_payer.pubkey(), -// &ore::MINT_ADDRESS, -// &spl_token::id(), -// ); -// let mut ix = ore::instruction::claim(payer.pubkey(), beneficiary, 0); -// ix.accounts[0].pubkey = alt_payer.pubkey(); -// let tx = Transaction::new_signed_with_payer( -// &[token_ix, ix], -// Some(&alt_payer.pubkey()), -// &[&alt_payer], -// blockhash, -// ); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_mine_not_enough_accounts() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - -// // Submit register tx -// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); -// let ix = ore::instruction::register(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Find next hash -// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); -// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); -// let (_next_hash, nonce) = find_next_hash( -// proof.hash.into(), -// KeccakHash::new_from_array([u8::MAX; 32]), -// payer.pubkey(), -// ); - -// // Submit mine tx -// let mut ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); -// ix.accounts.remove(1); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_mine_too_early() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::TooEarly).await; - -// // Submit register tx -// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); -// let ix = ore::instruction::register(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Find next hash -// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); -// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); -// let (_next_hash, nonce) = find_next_hash( -// proof.hash.into(), -// KeccakHash::new_from_array([u8::MAX; 32]), -// payer.pubkey(), -// ); - -// // Submit mine tx -// let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_mine_needs_reset() { -// // Setup -// let (mut banks, payer, _, blockhash) = -// setup_program_test_env(true, ClockState::NeedsReset).await; - -// // Submit register tx -// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); -// let ix = ore::instruction::register(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Find next hash -// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); -// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); -// let (_next_hash, nonce) = find_next_hash( -// proof.hash.into(), -// KeccakHash::new_from_array([u8::MAX; 32]), -// payer.pubkey(), -// ); - -// // Submit mine tx -// let ix = ore::instruction::mine(payer.pubkey(), BUS_ADDRESSES[0], nonce); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_claim_not_enough_accounts() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - -// // Submit register tx -// let ix = ore::instruction::register(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Submit claim tx -// let beneficiary = get_associated_token_address(&payer.pubkey(), &ore::MINT_ADDRESS); -// let token_ix = create_associated_token_account( -// &payer.pubkey(), -// &payer.pubkey(), -// &ore::MINT_ADDRESS, -// &spl_token::id(), -// ); -// let mut ix = ore::instruction::claim(payer.pubkey(), beneficiary, 0); -// ix.accounts.remove(1); -// let tx = Transaction::new_signed_with_payer( -// &[token_ix, ix], -// Some(&payer.pubkey()), -// &[&payer], -// blockhash, -// ); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_mine_fail_bad_data() { -// // Setup -// const FUZZ: usize = 10; -// let (mut banks, payer, _, blockhash) = setup_program_test_env(true, ClockState::Normal).await; - -// // Submit register tx -// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); -// let ix = ore::instruction::register(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Get proof -// let proof_account = banks.get_account(proof_pda.0).await.unwrap().unwrap(); -// let proof = Proof::try_from_bytes(&proof_account.data).unwrap(); - -// // Shared variables for tests. -// let mut rng = rand::thread_rng(); -// let (_next_hash, nonce) = find_next_hash( -// proof.hash.into(), -// KeccakHash::new_from_array([u8::MAX; 32]), -// payer.pubkey(), -// ); -// let signer = payer.pubkey(); -// let proof_address = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &ore::id()).0; - -// // Fuzz randomized instruction data -// for _ in 0..FUZZ { -// let length_range = Uniform::from(5..=256); -// let length = rng.sample(length_range); -// let random_bytes: Vec = (0..length).map(|_| rng.gen()).collect(); -// let ix = Instruction { -// program_id: ore::id(), -// accounts: vec![ -// AccountMeta::new(signer, true), -// AccountMeta::new(BUS_ADDRESSES[0], false), -// AccountMeta::new(proof_address, false), -// AccountMeta::new(TREASURY_ADDRESS, false), -// AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), -// ], -// data: [OreInstruction::Mine.to_vec(), random_bytes].concat(), -// }; -// let tx = -// Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// // Fuzz test random bus addresses -// for _ in 0..FUZZ { -// assert_mine_tx_err( -// &mut banks, -// &payer, -// blockhash, -// payer.pubkey(), -// Pubkey::new_unique(), -// proof_address, -// TREASURY_ADDRESS, -// sysvar::slot_hashes::id(), -// nonce, -// ) -// .await; -// } - -// // Fuzz test random proof addresses -// for _ in 0..FUZZ { -// assert_mine_tx_err( -// &mut banks, -// &payer, -// blockhash, -// payer.pubkey(), -// BUS_ADDRESSES[0], -// Pubkey::new_unique(), -// TREASURY_ADDRESS, -// sysvar::slot_hashes::id(), -// nonce, -// ) -// .await; -// } - -// // Mix up the proof and treasury addresses -// assert_mine_tx_err( -// &mut banks, -// &payer, -// blockhash, -// payer.pubkey(), -// BUS_ADDRESSES[0], -// TREASURY_ADDRESS, -// proof_address, -// sysvar::slot_hashes::id(), -// nonce, -// ) -// .await; - -// // Pass an invalid sysvar -// assert_mine_tx_err( -// &mut banks, -// &payer, -// blockhash, -// payer.pubkey(), -// BUS_ADDRESSES[0], -// proof_address, -// TREASURY_ADDRESS, -// sysvar::clock::id(), -// nonce, -// ) -// .await; -// } - -// async fn assert_mine_tx_err( -// banks: &mut BanksClient, -// payer: &Keypair, -// blockhash: Hash, -// signer: Pubkey, -// bus: Pubkey, -// proof: Pubkey, -// treasury: Pubkey, -// slot_hash: Pubkey, -// nonce: u64, -// ) { -// let ix = Instruction { -// program_id: ore::id(), -// accounts: vec![ -// AccountMeta::new(signer, true), -// AccountMeta::new(bus, false), -// AccountMeta::new(proof, false), -// AccountMeta::new(treasury, false), -// AccountMeta::new_readonly(slot_hash, false), -// ], -// data: [ -// OreInstruction::Mine.to_vec(), -// MineArgs { -// nonce: nonce.to_le_bytes(), -// } -// .to_bytes() -// .to_vec(), -// ] -// .concat(), -// }; -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// 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(&[ -// nonce.to_le_bytes().as_slice(), -// hash.to_bytes().as_slice(), -// signer.to_bytes().as_slice(), -// ]); -// if next_hash.le(&difficulty) { -// break; -// } else { -// println!("Invalid hash: {} Nonce: {:?}", next_hash.to_string(), nonce); -// } -// nonce += 1; -// } -// (next_hash, nonce) -// } - -// enum ClockState { -// Normal, -// TooEarly, -// NeedsReset, -// } - -// async fn setup_program_test_env( -// funded_busses: bool, -// clock_state: ClockState, -// ) -> (BanksClient, Keypair, Keypair, solana_program::hash::Hash) { -// let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); -// program_test.prefer_bpf(true); - -// // Busses -// for i in 0..BUS_COUNT { -// program_test.add_account_with_base64_data( -// BUS_ADDRESSES[i], -// 1057920, -// ore::id(), -// bs64::encode( -// &[ -// &(Bus::discriminator() as u64).to_le_bytes(), -// Bus { -// id: i as u64, -// rewards: if funded_busses { 250_000_000 } else { 0 }, -// } -// .to_bytes(), -// ] -// .concat(), -// ) -// .as_str(), -// ); -// } - -// // Treasury -// let admin_address = Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap(); -// let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); -// program_test.add_account_with_base64_data( -// treasury_pda.0, -// 1614720, -// ore::id(), -// bs64::encode( -// &[ -// &(Treasury::discriminator() as u64).to_le_bytes(), -// Treasury { -// bump: treasury_pda.1 as u64, -// admin: admin_address, -// difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), -// last_reset_at: START_AT, -// reward_rate: INITIAL_REWARD_RATE, -// total_claimed_rewards: 0, -// } -// .to_bytes(), -// ] -// .concat(), -// ) -// .as_str(), -// ); - -// // Mint -// let mut mint_src: [u8; Mint::LEN] = [0; Mint::LEN]; -// Mint { -// mint_authority: COption::Some(TREASURY_ADDRESS), -// supply: 2_000_000_000, -// decimals: TOKEN_DECIMALS, -// is_initialized: true, -// freeze_authority: COption::None, -// } -// .pack_into_slice(&mut mint_src); -// program_test.add_account_with_base64_data( -// MINT_ADDRESS, -// 1461600, -// spl_token::id(), -// bs64::encode(&mint_src).as_str(), -// ); - -// // Treasury tokens -// let tokens_address = spl_associated_token_account::get_associated_token_address( -// &TREASURY_ADDRESS, -// &MINT_ADDRESS, -// ); -// let mut tokens_src: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; -// spl_token::state::Account { -// mint: MINT_ADDRESS, -// owner: TREASURY_ADDRESS, -// amount: 2_000_000_000, -// delegate: COption::None, -// state: AccountState::Initialized, -// is_native: COption::None, -// delegated_amount: 0, -// close_authority: COption::None, -// } -// .pack_into_slice(&mut tokens_src); -// program_test.add_account_with_base64_data( -// tokens_address, -// 2039280, -// spl_token::id(), -// bs64::encode(&tokens_src).as_str(), -// ); - -// // Set sysvar -// let ts = match clock_state { -// ClockState::Normal => START_AT + 1, -// ClockState::TooEarly => START_AT - 1, -// ClockState::NeedsReset => START_AT + EPOCH_DURATION, -// }; -// program_test.add_sysvar_account( -// sysvar::clock::id(), -// &Clock { -// slot: 0, -// epoch_start_timestamp: 0, -// epoch: 0, -// leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, -// unix_timestamp: ts, -// }, -// ); - -// // Setup alt payer -// let payer_alt = Keypair::new(); -// program_test.add_account( -// payer_alt.pubkey(), -// Account { -// lamports: LAMPORTS_PER_SOL, -// data: vec![], -// owner: system_program::id(), -// executable: false, -// rent_epoch: 0, -// }, -// ); - -// let (banks, payer, blockhash) = program_test.start().await; -// (banks, payer, payer_alt, blockhash) -// } diff --git a/tests/test_register.rs b/tests/test_register.rs deleted file mode 100644 index 084b2fe..0000000 --- a/tests/test_register.rs +++ /dev/null @@ -1,188 +0,0 @@ -// use std::str::FromStr; - -// use ore::{ -// instruction::{register, OreInstruction, RegisterArgs}, -// state::{Bus, Treasury}, -// utils::Discriminator, -// BUS_ADDRESSES, BUS_COUNT, INITIAL_REWARD_RATE, MINT_ADDRESS, PROOF, TOKEN_DECIMALS, TREASURY, -// TREASURY_ADDRESS, -// }; -// use solana_program::{ -// clock::Clock, -// epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, -// instruction::{AccountMeta, Instruction}, -// keccak::Hash as KeccakHash, -// program_option::COption, -// program_pack::Pack, -// pubkey::Pubkey, -// rent::Rent, -// sysvar, -// }; -// use solana_program_test::{processor, BanksClient, ProgramTest}; -// use solana_sdk::{ -// signature::{Keypair, Signer}, -// system_transaction::transfer, -// transaction::Transaction, -// }; -// use spl_token::state::{AccountState, Mint}; - -// #[tokio::test] -// async fn test_register_account_with_lamports() { -// let (mut banks, payer, blockhash) = setup_program_test_env().await; - -// // Send lamports to the proof pda -// let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); -// let lamports = Rent::default().minimum_balance(0); -// let tx = transfer(&payer, &proof_pda.0, lamports, blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Assert register succeeds -// let ix = register(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); -// } - -// #[tokio::test] -// async fn test_register_not_enough_accounts() { -// let (mut banks, payer, blockhash) = setup_program_test_env().await; - -// // Assert register fails -// let mut ix = register(payer.pubkey()); -// ix.accounts.remove(1); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_register_fail_other() { -// let (mut banks, payer, blockhash) = setup_program_test_env().await; - -// // Try register for another keypair -// let other = Keypair::new(); -// let proof_pda = Pubkey::find_program_address(&[PROOF, other.pubkey().as_ref()], &ore::id()); -// let ix = Instruction { -// program_id: ore::id(), -// accounts: vec![ -// AccountMeta::new(payer.pubkey(), true), -// AccountMeta::new(proof_pda.0, false), -// AccountMeta::new_readonly(solana_program::system_program::id(), false), -// ], -// data: [ -// OreInstruction::Register.to_vec(), -// RegisterArgs { bump: proof_pda.1 }.to_bytes().to_vec(), -// ] -// .concat(), -// }; -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// 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 -// for i in 0..BUS_COUNT { -// program_test.add_account_with_base64_data( -// BUS_ADDRESSES[i], -// 1057920, -// ore::id(), -// bs64::encode( -// &[ -// &(Bus::discriminator() as u64).to_le_bytes(), -// Bus { -// id: i as u64, -// rewards: 250_000_000, -// } -// .to_bytes(), -// ] -// .concat(), -// ) -// .as_str(), -// ); -// } - -// // Treasury -// let admin_address = Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap(); -// let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); -// program_test.add_account_with_base64_data( -// treasury_pda.0, -// 1614720, -// ore::id(), -// bs64::encode( -// &[ -// &(Treasury::discriminator() as u64).to_le_bytes(), -// Treasury { -// bump: treasury_pda.1 as u64, -// admin: admin_address, -// difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), -// last_reset_at: 100, -// reward_rate: INITIAL_REWARD_RATE, -// total_claimed_rewards: 0, -// } -// .to_bytes(), -// ] -// .concat(), -// ) -// .as_str(), -// ); - -// // Mint -// let mut mint_src: [u8; Mint::LEN] = [0; Mint::LEN]; -// Mint { -// mint_authority: COption::Some(TREASURY_ADDRESS), -// supply: 2_000_000_000, -// decimals: TOKEN_DECIMALS, -// is_initialized: true, -// freeze_authority: COption::None, -// } -// .pack_into_slice(&mut mint_src); -// program_test.add_account_with_base64_data( -// MINT_ADDRESS, -// 1461600, -// spl_token::id(), -// bs64::encode(&mint_src).as_str(), -// ); - -// // Treasury tokens -// let tokens_address = spl_associated_token_account::get_associated_token_address( -// &TREASURY_ADDRESS, -// &MINT_ADDRESS, -// ); -// let mut tokens_src: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; -// spl_token::state::Account { -// mint: MINT_ADDRESS, -// owner: TREASURY_ADDRESS, -// amount: 2_000_000_000, -// delegate: COption::None, -// state: AccountState::Initialized, -// is_native: COption::None, -// delegated_amount: 0, -// close_authority: COption::None, -// } -// .pack_into_slice(&mut tokens_src); -// program_test.add_account_with_base64_data( -// tokens_address, -// 2039280, -// spl_token::id(), -// bs64::encode(&tokens_src).as_str(), -// ); - -// // Set sysvar -// program_test.add_sysvar_account( -// sysvar::clock::id(), -// &Clock { -// slot: 0, -// epoch_start_timestamp: 0, -// epoch: 0, -// leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, -// unix_timestamp: 0, -// }, -// ); - -// program_test.start().await -// } diff --git a/tests/test_reset.rs b/tests/test_reset.rs deleted file mode 100644 index 3f0a774..0000000 --- a/tests/test_reset.rs +++ /dev/null @@ -1,440 +0,0 @@ -// use std::str::FromStr; - -// use ore::{ -// instruction::OreInstruction, -// state::{Bus, Treasury}, -// utils::{AccountDeserialize, Discriminator}, -// BUS, BUS_ADDRESSES, BUS_COUNT, BUS_EPOCH_REWARDS, INITIAL_DIFFICULTY, INITIAL_REWARD_RATE, -// MAX_EPOCH_REWARDS, MINT_ADDRESS, START_AT, TOKEN_DECIMALS, TREASURY, TREASURY_ADDRESS, -// }; -// use rand::seq::SliceRandom; -// use solana_program::{ -// clock::Clock, -// epoch_schedule::DEFAULT_SLOTS_PER_EPOCH, -// hash::Hash, -// instruction::{AccountMeta, Instruction}, -// native_token::LAMPORTS_PER_SOL, -// program_option::COption, -// program_pack::Pack, -// pubkey::Pubkey, -// system_program, sysvar, -// }; -// use solana_program_test::{processor, BanksClient, ProgramTest}; -// use solana_sdk::{ -// account::Account, -// signature::{Keypair, Signer}, -// transaction::Transaction, -// }; -// use spl_token::state::{AccountState, Mint}; - -// #[tokio::test] -// async fn test_reset() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - -// // Pdas -// let bus_pdas = vec![ -// Pubkey::find_program_address(&[BUS, &[0]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[1]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[2]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[3]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[4]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[5]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[6]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[7]], &ore::id()), -// ]; -// // let mint_pda = Pubkey::find_program_address(&[MINT], &ore::id()); -// let treasury_tokens_address = spl_associated_token_account::get_associated_token_address( -// &TREASURY_ADDRESS, -// &MINT_ADDRESS, -// ); - -// // Submit tx -// let ix = ore::instruction::reset(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Test bus state -// for i in 0..BUS_COUNT { -// let bus_account = banks.get_account(bus_pdas[i].0).await.unwrap().unwrap(); -// assert_eq!(bus_account.owner, ore::id()); -// let bus = Bus::try_from_bytes(&bus_account.data).unwrap(); -// assert_eq!(bus.id as u8, i as u8); -// assert_eq!(bus.rewards, BUS_EPOCH_REWARDS); -// } - -// // Test treasury state -// let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); -// assert_eq!(treasury_account.owner, ore::id()); -// let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); -// assert_eq!( -// treasury.admin, -// Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap() -// ); -// assert_eq!(treasury.difficulty, INITIAL_DIFFICULTY.into()); -// assert_eq!(treasury.last_reset_at, START_AT + 1); -// assert_eq!(treasury.reward_rate, INITIAL_REWARD_RATE.saturating_div(2)); -// assert_eq!(treasury.total_claimed_rewards as u8, 0); - -// // Test mint state -// let mint_account = banks.get_account(MINT_ADDRESS).await.unwrap().unwrap(); -// assert_eq!(mint_account.owner, spl_token::id()); -// let mint = Mint::unpack(&mint_account.data).unwrap(); -// assert_eq!(mint.mint_authority, COption::Some(TREASURY_ADDRESS)); -// assert_eq!(mint.supply, MAX_EPOCH_REWARDS); -// assert_eq!(mint.decimals, ore::TOKEN_DECIMALS); -// assert_eq!(mint.is_initialized, true); -// assert_eq!(mint.freeze_authority, COption::None); - -// // Test treasury token state -// let treasury_tokens_account = banks -// .get_account(treasury_tokens_address) -// .await -// .unwrap() -// .unwrap(); -// assert_eq!(treasury_tokens_account.owner, spl_token::id()); -// let treasury_tokens = spl_token::state::Account::unpack(&treasury_tokens_account.data).unwrap(); -// assert_eq!(treasury_tokens.mint, MINT_ADDRESS); -// assert_eq!(treasury_tokens.owner, TREASURY_ADDRESS); -// assert_eq!(treasury_tokens.amount, MAX_EPOCH_REWARDS); -// assert_eq!(treasury_tokens.delegate, COption::None); -// assert_eq!(treasury_tokens.state, AccountState::Initialized); -// assert_eq!(treasury_tokens.is_native, COption::None); -// assert_eq!(treasury_tokens.delegated_amount, 0); -// assert_eq!(treasury_tokens.close_authority, COption::None); -// } - -// #[tokio::test] -// async fn test_reset_bad_key() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - -// // Bad addresses -// let bad_pda = Pubkey::find_program_address(&[b"t"], &ore::id()); -// for i in 1..13 { -// let mut ix = ore::instruction::reset(payer.pubkey()); -// ix.accounts[i].pubkey = bad_pda.0; -// let tx = -// Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } -// } - -// #[tokio::test] -// async fn test_reset_busses_out_of_order_fail() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - -// // Pdas -// let signer = payer.pubkey(); -// let bus_pdas = vec![ -// Pubkey::find_program_address(&[BUS, &[5]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[0]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[6]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[2]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[3]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[7]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[1]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[4]], &ore::id()), -// ]; -// let treasury_tokens = spl_associated_token_account::get_associated_token_address( -// &TREASURY_ADDRESS, -// &MINT_ADDRESS, -// ); - -// // Submit tx -// let ix = Instruction { -// program_id: ore::id(), -// accounts: vec![ -// AccountMeta::new(signer, true), -// AccountMeta::new(bus_pdas[0].0, false), -// AccountMeta::new(bus_pdas[1].0, false), -// AccountMeta::new(bus_pdas[2].0, false), -// AccountMeta::new(bus_pdas[3].0, false), -// AccountMeta::new(bus_pdas[4].0, false), -// AccountMeta::new(bus_pdas[5].0, false), -// AccountMeta::new(bus_pdas[6].0, false), -// AccountMeta::new(bus_pdas[7].0, false), -// AccountMeta::new(MINT_ADDRESS, false), -// AccountMeta::new(TREASURY_ADDRESS, false), -// AccountMeta::new(treasury_tokens, false), -// AccountMeta::new_readonly(spl_token::id(), false), -// ], -// data: OreInstruction::Reset.to_vec(), -// }; -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_reset_race() { -// // Setup -// let (mut banks, payer, payer_alt, blockhash) = setup_program_test_env(ClockState::Normal).await; - -// // Reset one passes -// let ix = ore::instruction::reset(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Reset two fails -// let ix = ore::instruction::reset(payer_alt.pubkey()); -// let tx = Transaction::new_signed_with_payer( -// &[ix], -// Some(&payer_alt.pubkey()), -// &[&payer_alt], -// blockhash, -// ); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_reset_too_early() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::TooEarly).await; - -// // Reset one passes -// let ix = ore::instruction::reset(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_reset_not_enough_keys() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - -// // Reset with missing account -// let mut ix = ore::instruction::reset(payer.pubkey()); -// ix.accounts.remove(1); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_reset_busses_duplicate_fail() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - -// // Pdas -// let signer = payer.pubkey(); -// let bus_pda = Pubkey::find_program_address(&[BUS, &[0]], &ore::id()); -// let treasury_tokens = spl_associated_token_account::get_associated_token_address( -// &TREASURY_ADDRESS, -// &MINT_ADDRESS, -// ); - -// // Submit tx -// let ix = Instruction { -// program_id: ore::id(), -// accounts: vec![ -// AccountMeta::new(signer, true), -// AccountMeta::new(bus_pda.0, false), -// AccountMeta::new(bus_pda.0, false), -// AccountMeta::new(bus_pda.0, false), -// AccountMeta::new(bus_pda.0, false), -// AccountMeta::new(bus_pda.0, false), -// AccountMeta::new(bus_pda.0, false), -// AccountMeta::new(bus_pda.0, false), -// AccountMeta::new(bus_pda.0, false), -// AccountMeta::new(MINT_ADDRESS, false), -// AccountMeta::new(TREASURY_ADDRESS, false), -// AccountMeta::new(treasury_tokens, false), -// AccountMeta::new_readonly(spl_token::id(), false), -// ], -// data: OreInstruction::Reset.to_vec(), -// }; -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_reset_shuffle_error() { -// // Setup -// const FUZZ: u64 = 100; -// let (mut banks, payer, _, blockhash) = setup_program_test_env(ClockState::Normal).await; - -// // Pdas -// let signer = payer.pubkey(); -// let bus_pdas = vec![ -// Pubkey::find_program_address(&[BUS, &[5]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[0]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[6]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[2]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[3]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[7]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[1]], &ore::id()), -// Pubkey::find_program_address(&[BUS, &[4]], &ore::id()), -// ]; -// let treasury_tokens = spl_associated_token_account::get_associated_token_address( -// &TREASURY_ADDRESS, -// &MINT_ADDRESS, -// ); - -// // Fuzz test shuffled accounts. -// // Note some shuffles may still be valid if signer and non-bus accounts are all in correct positions. -// let mut rng = rand::thread_rng(); -// for _ in 0..FUZZ { -// let mut accounts = vec![ -// AccountMeta::new(signer, true), -// AccountMeta::new(bus_pdas[0].0, false), -// AccountMeta::new(bus_pdas[1].0, false), -// AccountMeta::new(bus_pdas[2].0, false), -// AccountMeta::new(bus_pdas[3].0, false), -// AccountMeta::new(bus_pdas[4].0, false), -// AccountMeta::new(bus_pdas[5].0, false), -// AccountMeta::new(bus_pdas[6].0, false), -// AccountMeta::new(bus_pdas[7].0, false), -// AccountMeta::new(MINT_ADDRESS, false), -// AccountMeta::new(TREASURY_ADDRESS, false), -// AccountMeta::new(treasury_tokens, false), -// AccountMeta::new_readonly(spl_token::id(), false), -// ]; -// accounts.shuffle(&mut rng); -// let ix = Instruction { -// program_id: ore::id(), -// accounts, -// data: OreInstruction::Reset.to_vec(), -// }; -// let tx = -// Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } -// } - -// enum ClockState { -// Normal, -// TooEarly, -// } - -// async fn setup_program_test_env(clock_state: ClockState) -> (BanksClient, Keypair, Keypair, Hash) { -// let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); -// program_test.prefer_bpf(true); - -// // Busses -// for i in 0..BUS_COUNT { -// program_test.add_account_with_base64_data( -// BUS_ADDRESSES[i], -// 1057920, -// ore::id(), -// bs64::encode( -// &[ -// &(Bus::discriminator() as u64).to_le_bytes(), -// Bus { -// id: i as u64, -// rewards: 0, -// } -// .to_bytes(), -// ] -// .concat(), -// ) -// .as_str(), -// ); -// } - -// // Treasury -// let admin_address = Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap(); -// let treasury_pda = Pubkey::find_program_address(&[TREASURY], &ore::id()); -// program_test.add_account_with_base64_data( -// treasury_pda.0, -// 1614720, -// ore::id(), -// bs64::encode( -// &[ -// &(Treasury::discriminator() as u64).to_le_bytes(), -// Treasury { -// bump: treasury_pda.1 as u64, -// admin: admin_address, -// difficulty: INITIAL_DIFFICULTY.into(), -// last_reset_at: 0, -// reward_rate: INITIAL_REWARD_RATE, -// total_claimed_rewards: 0, -// } -// .to_bytes(), -// ] -// .concat(), -// ) -// .as_str(), -// ); - -// // Mint -// let mut mint_src: [u8; Mint::LEN] = [0; Mint::LEN]; -// Mint { -// mint_authority: COption::Some(TREASURY_ADDRESS), -// supply: 0, -// decimals: TOKEN_DECIMALS, -// is_initialized: true, -// freeze_authority: COption::None, -// } -// .pack_into_slice(&mut mint_src); -// program_test.add_account_with_base64_data( -// MINT_ADDRESS, -// 1461600, -// spl_token::id(), -// bs64::encode(&mint_src).as_str(), -// ); - -// // Treasury tokens -// let tokens_address = spl_associated_token_account::get_associated_token_address( -// &TREASURY_ADDRESS, -// &MINT_ADDRESS, -// ); -// let mut tokens_src: [u8; spl_token::state::Account::LEN] = [0; spl_token::state::Account::LEN]; -// spl_token::state::Account { -// mint: MINT_ADDRESS, -// owner: TREASURY_ADDRESS, -// amount: 0, -// delegate: COption::None, -// state: AccountState::Initialized, -// is_native: COption::None, -// delegated_amount: 0, -// close_authority: COption::None, -// } -// .pack_into_slice(&mut tokens_src); -// program_test.add_account_with_base64_data( -// tokens_address, -// 2039280, -// spl_token::id(), -// bs64::encode(&tokens_src).as_str(), -// ); - -// // Set sysvar -// let ts = match clock_state { -// ClockState::Normal => START_AT + 1, -// ClockState::TooEarly => START_AT - 1, -// }; -// program_test.add_sysvar_account( -// sysvar::clock::id(), -// &Clock { -// slot: 0, -// epoch_start_timestamp: 0, -// epoch: 0, -// leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, -// unix_timestamp: ts, -// }, -// ); - -// // Setup alt payer -// let payer_alt = Keypair::new(); -// program_test.add_account( -// payer_alt.pubkey(), -// Account { -// lamports: LAMPORTS_PER_SOL, -// data: vec![], -// owner: system_program::id(), -// executable: false, -// rent_epoch: 0, -// }, -// ); - -// let (banks, payer, blockhash) = program_test.start().await; -// (banks, payer, payer_alt, blockhash) -// } diff --git a/tests/test_update_admin.rs b/tests/test_update_admin.rs deleted file mode 100644 index cd00ccd..0000000 --- a/tests/test_update_admin.rs +++ /dev/null @@ -1,128 +0,0 @@ -// use ore::{state::Treasury, utils::AccountDeserialize, TREASURY_ADDRESS}; -// use solana_program::{ -// hash::Hash, native_token::LAMPORTS_PER_SOL, pubkey::Pubkey, rent::Rent, system_program, -// }; -// use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; -// use solana_sdk::{ -// account::Account, -// signature::{Keypair, Signer}, -// transaction::Transaction, -// }; - -// #[tokio::test] -// async fn test_update_admin() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env().await; - -// // Submit tx -// let ix = ore::instruction::initialize(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Get treasury account -// let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); -// let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - -// // Submit update admin ix -// let new_admin = Pubkey::new_unique(); -// let ix = ore::instruction::update_admin(payer.pubkey(), new_admin); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Assert treasury state -// let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); -// let treasury_ = Treasury::try_from_bytes(&treasury_account.data).unwrap(); -// assert_eq!(treasury_.bump, treasury.bump); -// assert_eq!(treasury_.admin, new_admin); -// assert_eq!(treasury_.difficulty, treasury.difficulty); -// assert_eq!(treasury_.last_reset_at, treasury.last_reset_at); -// assert_eq!(treasury_.reward_rate, treasury.reward_rate); -// assert_eq!( -// treasury_.total_claimed_rewards, -// treasury.total_claimed_rewards -// ); - -// // Submit another update admin ix -// let ix = ore::instruction::update_admin(payer.pubkey(), payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_update_admin_bad_signer() { -// // Setup -// let (mut banks, payer, alt_payer, blockhash) = setup_program_test_env().await; - -// // Submit tx -// let ix = ore::instruction::initialize(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Submit ix -// let ix = ore::instruction::update_admin(alt_payer.pubkey(), Pubkey::new_unique()); -// let tx = Transaction::new_signed_with_payer( -// &[ix], -// Some(&alt_payer.pubkey()), -// &[&alt_payer], -// blockhash, -// ); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_update_admin_not_enough_accounts() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env().await; - -// // Submit tx -// let ix = ore::instruction::initialize(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Submit ix without enough accounts -// let mut ix = ore::instruction::update_admin(payer.pubkey(), Pubkey::new_unique()); -// ix.accounts.remove(1); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { -// let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); -// program_test.prefer_bpf(true); - -// // Setup metadata program -// let data = read_file(&"tests/buffers/metadata_program.bpf"); -// program_test.add_account( -// mpl_token_metadata::ID, -// Account { -// lamports: Rent::default().minimum_balance(data.len()).max(1), -// data, -// owner: solana_sdk::bpf_loader::id(), -// executable: true, -// rent_epoch: 0, -// }, -// ); - -// // Setup alt payer -// let payer_alt = Keypair::new(); -// program_test.add_account( -// payer_alt.pubkey(), -// Account { -// lamports: LAMPORTS_PER_SOL, -// data: vec![], -// owner: system_program::id(), -// executable: false, -// rent_epoch: 0, -// }, -// ); - -// let (banks, payer, blockhash) = program_test.start().await; -// (banks, payer, payer_alt, blockhash) -// } diff --git a/tests/test_update_difficulty.rs b/tests/test_update_difficulty.rs deleted file mode 100644 index 4666a8a..0000000 --- a/tests/test_update_difficulty.rs +++ /dev/null @@ -1,125 +0,0 @@ -// use ore::{state::Treasury, utils::AccountDeserialize, TREASURY_ADDRESS}; -// use solana_program::{ -// hash::Hash, keccak::Hash as KeccakHash, native_token::LAMPORTS_PER_SOL, rent::Rent, -// system_program, -// }; -// use solana_program_test::{processor, read_file, BanksClient, ProgramTest}; -// use solana_sdk::{ -// account::Account, -// signature::{Keypair, Signer}, -// transaction::Transaction, -// }; - -// #[tokio::test] -// async fn test_update_difficulty() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env().await; - -// // Submit tx -// let ix = ore::instruction::initialize(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Get treasury account -// let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); -// let treasury = Treasury::try_from_bytes(&treasury_account.data).unwrap(); - -// // Submit update difficulty ix -// let new_difficulty = KeccakHash::new_unique(); -// let ix = ore::instruction::update_difficulty(payer.pubkey(), new_difficulty.into()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Assert treasury state -// let treasury_account = banks.get_account(TREASURY_ADDRESS).await.unwrap().unwrap(); -// let treasury_ = Treasury::try_from_bytes(&treasury_account.data).unwrap(); -// assert_eq!(treasury_.bump, treasury.bump); -// assert_eq!(treasury_.admin, treasury.admin); -// assert_eq!(treasury_.difficulty, new_difficulty.into()); -// assert_eq!(treasury_.last_reset_at, treasury.last_reset_at); -// assert_eq!(treasury_.reward_rate, treasury.reward_rate); -// assert_eq!( -// treasury_.total_claimed_rewards, -// treasury.total_claimed_rewards -// ); -// } - -// #[tokio::test] -// async fn test_update_difficulty_bad_signer() { -// // Setup -// let (mut banks, payer, alt_payer, blockhash) = setup_program_test_env().await; - -// // Submit tx -// let ix = ore::instruction::initialize(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Submit update difficulty ix -// let new_difficulty = KeccakHash::new_unique(); -// let ix = ore::instruction::update_difficulty(alt_payer.pubkey(), new_difficulty.into()); -// let tx = Transaction::new_signed_with_payer( -// &[ix], -// Some(&alt_payer.pubkey()), -// &[&alt_payer], -// blockhash, -// ); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// #[tokio::test] -// async fn test_update_difficulty_not_enough_accounts() { -// // Setup -// let (mut banks, payer, _, blockhash) = setup_program_test_env().await; - -// // Submit tx -// let ix = ore::instruction::initialize(payer.pubkey()); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_ok()); - -// // Submit ix without enough accounts -// let new_difficulty = KeccakHash::new_unique(); -// let mut ix = ore::instruction::update_difficulty(payer.pubkey(), new_difficulty.into()); -// ix.accounts.remove(1); -// let tx = Transaction::new_signed_with_payer(&[ix], Some(&payer.pubkey()), &[&payer], blockhash); -// let res = banks.process_transaction(tx).await; -// assert!(res.is_err()); -// } - -// async fn setup_program_test_env() -> (BanksClient, Keypair, Keypair, Hash) { -// let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); -// program_test.prefer_bpf(true); - -// // Setup metadata program -// let data = read_file(&"tests/buffers/metadata_program.bpf"); -// program_test.add_account( -// mpl_token_metadata::ID, -// Account { -// lamports: Rent::default().minimum_balance(data.len()).max(1), -// data, -// owner: solana_sdk::bpf_loader::id(), -// executable: true, -// rent_epoch: 0, -// }, -// ); - -// // Setup alt payer -// let payer_alt = Keypair::new(); -// program_test.add_account( -// payer_alt.pubkey(), -// Account { -// lamports: LAMPORTS_PER_SOL, -// data: vec![], -// owner: system_program::id(), -// executable: false, -// rent_epoch: 0, -// }, -// ); - -// let (banks, payer, blockhash) = program_test.start().await; -// (banks, payer, payer_alt, blockhash) -// } From edc14397124407401551b592d48532eccaab6587 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Fri, 28 Jun 2024 15:07:32 +0000 Subject: [PATCH 102/111] nuke idl --- idl/ore.json | 618 --------------------------------------------------- 1 file changed, 618 deletions(-) delete mode 100644 idl/ore.json diff --git a/idl/ore.json b/idl/ore.json deleted file mode 100644 index 2157a96..0000000 --- a/idl/ore.json +++ /dev/null @@ -1,618 +0,0 @@ -{ - "version": "0.0.1", - "name": "ore", - "instructions": [ - { - "name": "Reset", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "signer", - "isMut": false, - "isSigner": true, - "docs": [ - "Signer" - ] - }, - { - "name": "bus0", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 0" - ] - }, - { - "name": "bus1", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 1" - ] - }, - { - "name": "bus2", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 2" - ] - }, - { - "name": "bus3", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 3" - ] - }, - { - "name": "bus4", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 4" - ] - }, - { - "name": "bus5", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 5" - ] - }, - { - "name": "bus6", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 6" - ] - }, - { - "name": "bus7", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 7" - ] - }, - { - "name": "mint", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore token mint account" - ] - }, - { - "name": "treasury", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore treasury account" - ] - }, - { - "name": "treasuryTokens", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore treasury token account" - ] - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "SPL token program" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 0 - } - }, - { - "name": "Register", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "signer", - "isMut": false, - "isSigner": true, - "docs": [ - "Signer" - ] - }, - { - "name": "proof", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore miner proof account" - ] - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Solana system program" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 1 - } - }, - { - "name": "Mine", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "signer", - "isMut": false, - "isSigner": true, - "docs": [ - "Signer" - ] - }, - { - "name": "bus", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account" - ] - }, - { - "name": "proof", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore miner proof account" - ] - }, - { - "name": "treasury", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore treasury account" - ] - }, - { - "name": "slotHashes", - "isMut": false, - "isSigner": false, - "docs": [ - "Solana slot hashes sysvar" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 2 - } - }, - { - "name": "Claim", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "signer", - "isMut": false, - "isSigner": true, - "docs": [ - "Signer" - ] - }, - { - "name": "beneficiary", - "isMut": true, - "isSigner": false, - "docs": [ - "Beneficiary token account" - ] - }, - { - "name": "mint", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore token mint account" - ] - }, - { - "name": "proof", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore miner proof account" - ] - }, - { - "name": "treasury", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore treasury account" - ] - }, - { - "name": "treasuryTokens", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore treasury token account" - ] - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "SPL token program" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 3 - } - }, - { - "name": "Initialize", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "admin", - "isMut": false, - "isSigner": true, - "docs": [ - "Admin signer" - ] - }, - { - "name": "bus0", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 0" - ] - }, - { - "name": "bus1", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 1" - ] - }, - { - "name": "bus2", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 2" - ] - }, - { - "name": "bus3", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 3" - ] - }, - { - "name": "bus4", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 4" - ] - }, - { - "name": "bus5", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 5" - ] - }, - { - "name": "bus6", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 6" - ] - }, - { - "name": "bus7", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore bus account 7" - ] - }, - { - "name": "mint", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore token mint account" - ] - }, - { - "name": "treasury", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore treasury account" - ] - }, - { - "name": "treasuryTokens", - "isMut": true, - "isSigner": false, - "docs": [ - "Ore treasury token account" - ] - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Solana system program" - ] - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "SPL token program" - ] - }, - { - "name": "associatedTokenProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "SPL associated token program" - ] - }, - { - "name": "rent", - "isMut": false, - "isSigner": false, - "docs": [ - "Solana rent sysvar" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 100 - } - }, - { - "name": "UpdateAdmin", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "treasury", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore treasury account" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 102 - } - }, - { - "name": "UpdateDifficulty", - "accounts": [ - { - "name": "oreProgram", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore program" - ] - }, - { - "name": "treasury", - "isMut": false, - "isSigner": false, - "docs": [ - "Ore treasury account" - ] - } - ], - "args": [], - "discriminant": { - "type": "u8", - "value": 103 - } - } - ], - "accounts": [ - { - "name": "Bus", - "type": { - "kind": "struct", - "fields": [ - { - "name": "id", - "type": "u64" - }, - { - "name": "rewards", - "type": "u64" - } - ] - } - }, - { - "name": "Proof", - "type": { - "kind": "struct", - "fields": [ - { - "name": "authority", - "type": "publicKey" - }, - { - "name": "claimableRewards", - "type": "u64" - }, - { - "name": "hash", - "type": { - "defined": "Hash" - } - }, - { - "name": "totalHashes", - "type": "u64" - }, - { - "name": "totalRewards", - "type": "u64" - } - ] - } - }, - { - "name": "Treasury", - "type": { - "kind": "struct", - "fields": [ - { - "name": "admin", - "type": "publicKey" - }, - { - "name": "bump", - "type": "u64" - }, - { - "name": "difficulty", - "type": { - "defined": "Hash" - } - }, - { - "name": "epochStartAt", - "type": "i64" - }, - { - "name": "rewardRate", - "type": "u64" - }, - { - "name": "totalClaimedRewards", - "type": "u64" - } - ] - } - } - ], - "errors": [ - { - "code": 0, - "name": "EpochActive", - "msg": "The epoch is still active and cannot be reset" - }, - { - "code": 1, - "name": "EpochExpired", - "msg": "The epoch has expired and needs reset" - }, - { - "code": 2, - "name": "InvalidHash", - "msg": "The provided hash was invalid" - }, - { - "code": 3, - "name": "InsufficientHashDifficulty", - "msg": "The provided hash does not satisfy the difficulty requirement" - }, - { - "code": 4, - "name": "InsufficientBusRewards", - "msg": "The bus has insufficient rewards to mine at this time" - }, - { - "code": 5, - "name": "InvalidClaimAmount", - "msg": "The claim amount cannot be larger than the claimable rewards" - } - ], - "metadata": { - "origin": "shank", - "address": "ore2mSzJwAZhxLyCLbNEnFvYq9U8jvCMvUBrVvbmqDF" - } -} \ No newline at end of file From 462a7021e17b2ef303dddbc7df2b682094923fdf Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 29 Jun 2024 00:59:55 +0000 Subject: [PATCH 103/111] refactor --- Cargo.lock | 22 ++++- Cargo.toml | 35 ++----- core/api/Cargo.toml | 25 +++++ {src => core/api/src}/consts.rs | 0 {src => core/api/src}/error.rs | 0 {src => core/api/src}/instruction.rs | 95 +++++++++---------- core/api/src/lib.rs | 9 ++ {src => core/api/src}/state/bus.rs | 0 {src => core/api/src}/state/config.rs | 0 {src => core/api/src}/state/mod.rs | 0 {src => core/api/src}/state/proof.rs | 0 {src => core/api/src}/state/treasury.rs | 0 {src => core/api/src}/utils.rs | 20 ++-- core/program/Cargo.toml | 34 +++++++ {src => core/program/src}/lib.rs | 18 +--- {src => core/program/src}/loaders.rs | 25 +++-- {src => core/program/src}/processor/claim.rs | 8 +- {src => core/program/src}/processor/close.rs | 3 +- {src => core/program/src}/processor/crown.rs | 10 +- .../program/src}/processor/initialize.rs | 52 +++++----- {src => core/program/src}/processor/mine.rs | 31 +++--- {src => core/program/src}/processor/mod.rs | 0 {src => core/program/src}/processor/open.rs | 19 ++-- {src => core/program/src}/processor/reset.rs | 18 ++-- {src => core/program/src}/processor/stake.rs | 6 +- {src => core/program/src}/processor/update.rs | 3 +- .../program/src}/processor/upgrade.rs | 6 +- stake/api/Cargo.toml | 0 stake/program/Cargo.toml | 0 29 files changed, 245 insertions(+), 194 deletions(-) create mode 100644 core/api/Cargo.toml rename {src => core/api/src}/consts.rs (100%) rename {src => core/api/src}/error.rs (100%) rename {src => core/api/src}/instruction.rs (98%) create mode 100644 core/api/src/lib.rs rename {src => core/api/src}/state/bus.rs (100%) rename {src => core/api/src}/state/config.rs (100%) rename {src => core/api/src}/state/mod.rs (100%) rename {src => core/api/src}/state/proof.rs (100%) rename {src => core/api/src}/state/treasury.rs (100%) rename {src => core/api/src}/utils.rs (98%) create mode 100644 core/program/Cargo.toml rename {src => core/program/src}/lib.rs (78%) rename {src => core/program/src}/loaders.rs (97%) rename {src => core/program/src}/processor/claim.rs (93%) rename {src => core/program/src}/processor/close.rs (94%) rename {src => core/program/src}/processor/crown.rs (97%) rename {src => core/program/src}/processor/initialize.rs (92%) rename {src => core/program/src}/processor/mine.rs (96%) rename {src => core/program/src}/processor/mod.rs (100%) rename {src => core/program/src}/processor/open.rs (93%) rename {src => core/program/src}/processor/reset.rs (96%) rename {src => core/program/src}/processor/stake.rs (94%) rename {src => core/program/src}/processor/update.rs (90%) rename {src => core/program/src}/processor/upgrade.rs (95%) create mode 100644 stake/api/Cargo.toml create mode 100644 stake/program/Cargo.toml diff --git a/Cargo.lock b/Cargo.lock index 412b136..1dd4c24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2591,26 +2591,38 @@ dependencies = [ ] [[package]] -name = "ore-program" +name = "ore-api" version = "2.0.0" dependencies = [ "array-const-fn-init", "bs58 0.5.0", - "bs64", "bytemuck", "const-crypto", "drillx", "mpl-token-metadata", "num_enum 0.7.2", - "rand 0.8.5", "shank", "solana-program", + "spl-associated-token-account", + "spl-token", + "static_assertions", + "thiserror", +] + +[[package]] +name = "ore-program" +version = "2.0.0" +dependencies = [ + "bs64", + "drillx", + "mpl-token-metadata", + "ore-api", + "rand 0.8.5", + "solana-program", "solana-program-test", "solana-sdk", "spl-associated-token-account", "spl-token", - "static_assertions", - "thiserror", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index fa1ecd3..2b46f9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,41 +1,20 @@ -[package] -name = "ore-program" +[workspace] +resolver = "2" +members = ["core/*"] + +[workspace.package] version = "2.0.0" -description = "Ore is a digital currency you can mine from anywhere, at home or on your phone." edition = "2021" license = "Apache-2.0" homepage = "https://ore.supply" documentation = "https://ore.supply" -repository = "https://github.com/hardhatchad/ore" +repository = "https://github.com/regolith-labs/ore" readme = "./README.md" keywords = ["solana", "crypto", "mining"] -[lib] -crate-type = ["cdylib", "lib"] -name = "ore" - -[features] -no-entrypoint = [] -default = [] - -[dependencies] -array-const-fn-init = "0.1.1" -bs58 = "0.5.0" -bytemuck = "1.14.3" -const-crypto = "0.1.0" +[workspace.dependencies] drillx = { git = "https://github.com/regolith-labs/drillx", branch = "master", features = ["solana"] } mpl-token-metadata = "4.1.2" -num_enum = "0.7.2" -shank = "0.3.0" solana-program = "1.18" spl-token = { version = "^4", features = ["no-entrypoint"] } spl-associated-token-account = { version = "^2.2", features = [ "no-entrypoint" ] } -static_assertions = "1.1.0" -thiserror = "1.0.57" - -[dev-dependencies] -bs64 = "0.1.2" -rand = "0.8.5" -solana-program-test = "^1.18" -solana-sdk = "^1.18" -tokio = { version = "1.35", features = ["full"] } diff --git a/core/api/Cargo.toml b/core/api/Cargo.toml new file mode 100644 index 0000000..9477a8c --- /dev/null +++ b/core/api/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "ore-api" +description = "API for interacting with the ORE program" +version.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true +keywords.workspace = true + +[dependencies] +array-const-fn-init = "0.1.1" +bs58 = "0.5.0" +bytemuck = "1.14.3" +const-crypto = "0.1.0" +drillx.workspace = true +mpl-token-metadata.workspace = true +num_enum = "0.7.2" +shank = "0.3.0" +solana-program.workspace = true +spl-token.workspace = true +spl-associated-token-account.workspace = true +static_assertions = "1.1.0" +thiserror = "1.0.57" \ No newline at end of file diff --git a/src/consts.rs b/core/api/src/consts.rs similarity index 100% rename from src/consts.rs rename to core/api/src/consts.rs diff --git a/src/error.rs b/core/api/src/error.rs similarity index 100% rename from src/error.rs rename to core/api/src/error.rs diff --git a/src/instruction.rs b/core/api/src/instruction.rs similarity index 98% rename from src/instruction.rs rename to core/api/src/instruction.rs index fdf9d2a..f87371f 100644 --- a/src/instruction.rs +++ b/core/api/src/instruction.rs @@ -8,10 +8,7 @@ use solana_program::{ system_program, sysvar, }; -use crate::{ - impl_instruction_from_bytes, impl_to_bytes, BUS, BUS_ADDRESSES, CONFIG, CONFIG_ADDRESS, - METADATA, MINT, MINT_ADDRESS, MINT_NOISE, MINT_V1_ADDRESS, PROOF, TREASURY, TREASURY_ADDRESS, -}; +use crate::{consts::*, impl_instruction_from_bytes, impl_to_bytes}; #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, ShankInstruction, TryFromPrimitive)] @@ -185,8 +182,9 @@ impl_instruction_from_bytes!(ClaimArgs); impl_instruction_from_bytes!(StakeArgs); impl_instruction_from_bytes!(UpgradeArgs); -/// Builds a reset instruction. -pub fn reset(signer: Pubkey) -> Instruction { +/// Builds a claim instruction. +pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { + let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; let treasury_tokens = spl_associated_token_account::get_associated_token_address( &TREASURY_ADDRESS, &MINT_ADDRESS, @@ -195,39 +193,20 @@ pub fn reset(signer: Pubkey) -> Instruction { program_id: crate::id(), accounts: vec![ AccountMeta::new(signer, true), - AccountMeta::new(BUS_ADDRESSES[0], false), - AccountMeta::new(BUS_ADDRESSES[1], false), - AccountMeta::new(BUS_ADDRESSES[2], false), - AccountMeta::new(BUS_ADDRESSES[3], false), - AccountMeta::new(BUS_ADDRESSES[4], false), - AccountMeta::new(BUS_ADDRESSES[5], false), - AccountMeta::new(BUS_ADDRESSES[6], false), - AccountMeta::new(BUS_ADDRESSES[7], false), - AccountMeta::new(CONFIG_ADDRESS, false), + AccountMeta::new(beneficiary, false), AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(TREASURY_ADDRESS, false), + AccountMeta::new(proof, false), + AccountMeta::new_readonly(TREASURY_ADDRESS, false), AccountMeta::new(treasury_tokens, false), AccountMeta::new_readonly(spl_token::id(), false), ], - data: OreInstruction::Reset.to_vec(), - } -} - -/// Builds an open instruction. -pub fn open(signer: Pubkey, miner: Pubkey) -> Instruction { - let proof_pda = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()); - Instruction { - program_id: crate::id(), - accounts: vec![ - AccountMeta::new(signer, true), - AccountMeta::new_readonly(miner, false), - AccountMeta::new(proof_pda.0, false), - AccountMeta::new_readonly(solana_program::system_program::id(), false), - AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), - ], data: [ - OreInstruction::Open.to_vec(), - OpenArgs { bump: proof_pda.1 }.to_bytes().to_vec(), + OreInstruction::Claim.to_vec(), + ClaimArgs { + amount: amount.to_le_bytes(), + } + .to_bytes() + .to_vec(), ] .concat(), } @@ -273,9 +252,28 @@ pub fn mine(signer: Pubkey, bus: Pubkey, solution: Solution) -> Instruction { } } -/// Builds a claim instruction. -pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { - let proof = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()).0; +/// Builds an open instruction. +pub fn open(signer: Pubkey, miner: Pubkey) -> Instruction { + let proof_pda = Pubkey::find_program_address(&[PROOF, signer.as_ref()], &crate::id()); + Instruction { + program_id: crate::id(), + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new_readonly(miner, false), + AccountMeta::new(proof_pda.0, false), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new_readonly(sysvar::slot_hashes::id(), false), + ], + data: [ + OreInstruction::Open.to_vec(), + OpenArgs { bump: proof_pda.1 }.to_bytes().to_vec(), + ] + .concat(), + } +} + +/// Builds a reset instruction. +pub fn reset(signer: Pubkey) -> Instruction { let treasury_tokens = spl_associated_token_account::get_associated_token_address( &TREASURY_ADDRESS, &MINT_ADDRESS, @@ -284,22 +282,21 @@ pub fn claim(signer: Pubkey, beneficiary: Pubkey, amount: u64) -> Instruction { program_id: crate::id(), accounts: vec![ AccountMeta::new(signer, true), - AccountMeta::new(beneficiary, false), + AccountMeta::new(BUS_ADDRESSES[0], false), + AccountMeta::new(BUS_ADDRESSES[1], false), + AccountMeta::new(BUS_ADDRESSES[2], false), + AccountMeta::new(BUS_ADDRESSES[3], false), + AccountMeta::new(BUS_ADDRESSES[4], false), + AccountMeta::new(BUS_ADDRESSES[5], false), + AccountMeta::new(BUS_ADDRESSES[6], false), + AccountMeta::new(BUS_ADDRESSES[7], false), + AccountMeta::new(CONFIG_ADDRESS, false), AccountMeta::new(MINT_ADDRESS, false), - AccountMeta::new(proof, false), - AccountMeta::new_readonly(TREASURY_ADDRESS, false), + AccountMeta::new(TREASURY_ADDRESS, false), AccountMeta::new(treasury_tokens, false), AccountMeta::new_readonly(spl_token::id(), false), ], - data: [ - OreInstruction::Claim.to_vec(), - ClaimArgs { - amount: amount.to_le_bytes(), - } - .to_bytes() - .to_vec(), - ] - .concat(), + data: OreInstruction::Reset.to_vec(), } } diff --git a/core/api/src/lib.rs b/core/api/src/lib.rs new file mode 100644 index 0000000..85782b2 --- /dev/null +++ b/core/api/src/lib.rs @@ -0,0 +1,9 @@ +pub mod consts; +pub mod error; +pub mod instruction; +pub mod state; +pub mod utils; + +use solana_program::declare_id; + +declare_id!("mineRHF5r6S7HyD9SppBfVMXMavDkJsxwGesEvxZr2A"); diff --git a/src/state/bus.rs b/core/api/src/state/bus.rs similarity index 100% rename from src/state/bus.rs rename to core/api/src/state/bus.rs diff --git a/src/state/config.rs b/core/api/src/state/config.rs similarity index 100% rename from src/state/config.rs rename to core/api/src/state/config.rs diff --git a/src/state/mod.rs b/core/api/src/state/mod.rs similarity index 100% rename from src/state/mod.rs rename to core/api/src/state/mod.rs diff --git a/src/state/proof.rs b/core/api/src/state/proof.rs similarity index 100% rename from src/state/proof.rs rename to core/api/src/state/proof.rs diff --git a/src/state/treasury.rs b/core/api/src/state/treasury.rs similarity index 100% rename from src/state/treasury.rs rename to core/api/src/state/treasury.rs diff --git a/src/utils.rs b/core/api/src/utils.rs similarity index 98% rename from src/utils.rs rename to core/api/src/utils.rs index cdb9779..669c7d9 100644 --- a/src/utils.rs +++ b/core/api/src/utils.rs @@ -7,7 +7,7 @@ use solana_program::{ /// Creates a new pda #[inline(always)] -pub(crate) fn create_pda<'a, 'info>( +pub fn create_pda<'a, 'info>( target_account: &'a AccountInfo<'info>, owner: &Pubkey, space: usize, @@ -73,14 +73,6 @@ pub(crate) fn create_pda<'a, 'info>( Ok(()) } -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] -pub struct MineEvent { - pub difficulty: u64, - pub reward: u64, - pub timing: i64, -} - #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] pub enum AccountDiscriminator { @@ -152,3 +144,13 @@ macro_rules! impl_instruction_from_bytes { } }; } + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct MineEvent { + pub difficulty: u64, + pub reward: u64, + pub timing: i64, +} + +impl_to_bytes!(MineEvent); diff --git a/core/program/Cargo.toml b/core/program/Cargo.toml new file mode 100644 index 0000000..3ea6d1c --- /dev/null +++ b/core/program/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "ore-program" +description = "ORE is a fair-launch, proof-of-work, digital currency everyone can mine" +version.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true +readme.workspace = true +keywords.workspace = true + +[lib] +crate-type = ["cdylib", "lib"] +name = "ore" + +[features] +no-entrypoint = [] +default = [] + +[dependencies] +drillx.workspace = true +mpl-token-metadata.workspace = true +ore-api = { path = "../api" } +solana-program.workspace = true +spl-token.workspace = true +spl-associated-token-account.workspace = true + +[dev-dependencies] +bs64 = "0.1.2" +rand = "0.8.5" +solana-program-test = "^1.18" +solana-sdk = "^1.18" +tokio = { version = "1.35", features = ["full"] } diff --git a/src/lib.rs b/core/program/src/lib.rs similarity index 78% rename from src/lib.rs rename to core/program/src/lib.rs index 51ede90..2fcd002 100644 --- a/src/lib.rs +++ b/core/program/src/lib.rs @@ -1,21 +1,13 @@ -pub mod consts; -pub mod error; -pub mod instruction; -pub mod loaders; +mod loaders; mod processor; -pub mod state; -pub mod utils; -pub use consts::*; -use instruction::*; +use ore_api::instruction::*; use processor::*; use solana_program::{ - self, account_info::AccountInfo, declare_id, entrypoint::ProgramResult, - program_error::ProgramError, pubkey::Pubkey, + self, account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, }; -declare_id!("oreFHcE6FvJTrsfaYca4mVeZn7J7T6oZS9FAvW9eg4q"); - #[cfg(not(feature = "no-entrypoint"))] solana_program::entrypoint!(process_instruction); @@ -24,7 +16,7 @@ pub fn process_instruction( accounts: &[AccountInfo], data: &[u8], ) -> ProgramResult { - if program_id.ne(&crate::id()) { + if program_id.ne(&ore_api::id()) { return Err(ProgramError::IncorrectProgramId); } diff --git a/src/loaders.rs b/core/program/src/loaders.rs similarity index 97% rename from src/loaders.rs rename to core/program/src/loaders.rs index c0b0692..61cc800 100644 --- a/src/loaders.rs +++ b/core/program/src/loaders.rs @@ -1,15 +1,14 @@ +use ore_api::{ + consts::*, + state::{Bus, Config, Proof, Treasury}, + utils::{AccountDeserialize, Discriminator}, +}; use solana_program::{ account_info::AccountInfo, program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, system_program, sysvar, }; use spl_token::state::Mint; -use crate::{ - state::{Bus, Config, Proof, Treasury}, - utils::{AccountDeserialize, Discriminator}, - BUS_ADDRESSES, CONFIG_ADDRESS, TREASURY_ADDRESS, -}; - /// Errors if: /// - Account is not a signer. pub fn load_signer<'a, 'info>(info: &'a AccountInfo<'info>) -> Result<(), ProgramError> { @@ -32,7 +31,7 @@ pub fn load_bus<'a, 'info>( id: u64, is_writable: bool, ) -> Result<(), ProgramError> { - if info.owner.ne(&crate::id()) { + if info.owner.ne(&ore_api::id()) { return Err(ProgramError::InvalidAccountOwner); } @@ -69,7 +68,7 @@ pub fn load_any_bus<'a, 'info>( info: &'a AccountInfo<'info>, is_writable: bool, ) -> Result<(), ProgramError> { - if info.owner.ne(&crate::id()) { + if info.owner.ne(&ore_api::id()) { return Err(ProgramError::InvalidAccountOwner); } @@ -102,7 +101,7 @@ pub fn load_config<'a, 'info>( info: &'a AccountInfo<'info>, is_writable: bool, ) -> Result<(), ProgramError> { - if info.owner.ne(&crate::id()) { + if info.owner.ne(&ore_api::id()) { return Err(ProgramError::InvalidAccountOwner); } @@ -136,7 +135,7 @@ pub fn load_proof<'a, 'info>( authority: &Pubkey, is_writable: bool, ) -> Result<(), ProgramError> { - if info.owner.ne(&crate::id()) { + if info.owner.ne(&ore_api::id()) { return Err(ProgramError::InvalidAccountOwner); } @@ -169,7 +168,7 @@ pub fn load_proof_with_miner<'a, 'info>( miner: &Pubkey, is_writable: bool, ) -> Result<(), ProgramError> { - if info.owner.ne(&crate::id()) { + if info.owner.ne(&ore_api::id()) { return Err(ProgramError::InvalidAccountOwner); } @@ -200,7 +199,7 @@ pub fn load_any_proof<'a, 'info>( info: &'a AccountInfo<'info>, is_writable: bool, ) -> Result<(), ProgramError> { - if info.owner.ne(&crate::id()) { + if info.owner.ne(&ore_api::id()) { return Err(ProgramError::InvalidAccountOwner); } @@ -229,7 +228,7 @@ pub fn load_treasury<'a, 'info>( info: &'a AccountInfo<'info>, is_writable: bool, ) -> Result<(), ProgramError> { - if info.owner.ne(&crate::id()) { + if info.owner.ne(&ore_api::id()) { return Err(ProgramError::InvalidAccountOwner); } diff --git a/src/processor/claim.rs b/core/program/src/processor/claim.rs similarity index 93% rename from src/processor/claim.rs rename to core/program/src/processor/claim.rs index f41cf0d..7a688d2 100644 --- a/src/processor/claim.rs +++ b/core/program/src/processor/claim.rs @@ -1,12 +1,12 @@ +use ore_api::{ + consts::*, error::OreError, instruction::ClaimArgs, state::Proof, utils::AccountDeserialize, +}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, }; -use crate::{ - error::OreError, instruction::ClaimArgs, loaders::*, state::Proof, utils::AccountDeserialize, - MINT_ADDRESS, TREASURY, TREASURY_BUMP, -}; +use crate::loaders::*; /// Claim distributes Ore from the treasury to a miner. Its responsibilies include: /// 1. Decrement the miner's claimable balance. diff --git a/src/processor/close.rs b/core/program/src/processor/close.rs similarity index 94% rename from src/processor/close.rs rename to core/program/src/processor/close.rs index 0356341..4f6d0f6 100644 --- a/src/processor/close.rs +++ b/core/program/src/processor/close.rs @@ -1,9 +1,10 @@ +use ore_api::{state::Proof, utils::AccountDeserialize}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, system_program, }; -use crate::{loaders::*, state::Proof, utils::AccountDeserialize}; +use crate::loaders::*; /// Close closes a proof account and returns the rent to the owner. Its responsibilities include: /// 1. Realloc proof account size to 0. diff --git a/src/processor/crown.rs b/core/program/src/processor/crown.rs similarity index 97% rename from src/processor/crown.rs rename to core/program/src/processor/crown.rs index 674f114..e977d15 100644 --- a/src/processor/crown.rs +++ b/core/program/src/processor/crown.rs @@ -1,13 +1,13 @@ +use ore_api::{ + state::{Config, Proof}, + utils::AccountDeserialize, +}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, }; -use crate::{ - loaders::*, - state::{Config, Proof}, - utils::AccountDeserialize, -}; +use crate::loaders::*; /// Crown marks an account as the top staker if their balance is greater than the last known top staker. pub fn process_crown<'a, 'info>( diff --git a/src/processor/initialize.rs b/core/program/src/processor/initialize.rs similarity index 92% rename from src/processor/initialize.rs rename to core/program/src/processor/initialize.rs index fa614eb..d01110e 100644 --- a/src/processor/initialize.rs +++ b/core/program/src/processor/initialize.rs @@ -1,5 +1,13 @@ use std::mem::size_of; +use ore_api::{ + consts::*, + instruction::*, + state::{Bus, Config, Treasury}, + utils::create_pda, + utils::AccountDeserialize, + utils::Discriminator, +}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, @@ -10,16 +18,7 @@ use solana_program::{ }; use spl_token::state::Mint; -use crate::{ - instruction::*, - loaders::*, - state::{Bus, Config, Treasury}, - utils::create_pda, - utils::AccountDeserialize, - utils::Discriminator, - BUS, BUS_COUNT, CONFIG, INITIAL_ADMIN, INITIAL_BASE_REWARD_RATE, METADATA, METADATA_NAME, - METADATA_SYMBOL, METADATA_URI, MINT, MINT_ADDRESS, MINT_NOISE, TOKEN_DECIMALS, TREASURY, -}; +use crate::loaders::*; /// Initialize sets up the Ore program. Its responsibilities include: /// 1. Initialize the 8 bus accounts. @@ -54,15 +53,15 @@ pub fn process_initialize<'a, 'info>( return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; - load_uninitialized_pda(bus_0_info, &[BUS, &[0]], args.bus_0_bump, &crate::id())?; - load_uninitialized_pda(bus_1_info, &[BUS, &[1]], args.bus_1_bump, &crate::id())?; - load_uninitialized_pda(bus_2_info, &[BUS, &[2]], args.bus_2_bump, &crate::id())?; - load_uninitialized_pda(bus_3_info, &[BUS, &[3]], args.bus_3_bump, &crate::id())?; - load_uninitialized_pda(bus_4_info, &[BUS, &[4]], args.bus_4_bump, &crate::id())?; - load_uninitialized_pda(bus_5_info, &[BUS, &[5]], args.bus_5_bump, &crate::id())?; - load_uninitialized_pda(bus_6_info, &[BUS, &[6]], args.bus_6_bump, &crate::id())?; - load_uninitialized_pda(bus_7_info, &[BUS, &[7]], args.bus_7_bump, &crate::id())?; - load_uninitialized_pda(config_info, &[CONFIG], args.config_bump, &crate::id())?; + load_uninitialized_pda(bus_0_info, &[BUS, &[0]], args.bus_0_bump, &ore_api::id())?; + load_uninitialized_pda(bus_1_info, &[BUS, &[1]], args.bus_1_bump, &ore_api::id())?; + load_uninitialized_pda(bus_2_info, &[BUS, &[2]], args.bus_2_bump, &ore_api::id())?; + load_uninitialized_pda(bus_3_info, &[BUS, &[3]], args.bus_3_bump, &ore_api::id())?; + load_uninitialized_pda(bus_4_info, &[BUS, &[4]], args.bus_4_bump, &ore_api::id())?; + load_uninitialized_pda(bus_5_info, &[BUS, &[5]], args.bus_5_bump, &ore_api::id())?; + load_uninitialized_pda(bus_6_info, &[BUS, &[6]], args.bus_6_bump, &ore_api::id())?; + load_uninitialized_pda(bus_7_info, &[BUS, &[7]], args.bus_7_bump, &ore_api::id())?; + load_uninitialized_pda(config_info, &[CONFIG], args.config_bump, &ore_api::id())?; load_uninitialized_pda( metadata_info, &[ @@ -77,9 +76,14 @@ pub fn process_initialize<'a, 'info>( mint_info, &[MINT, MINT_NOISE.as_slice()], args.mint_bump, - &crate::id(), + &ore_api::id(), + )?; + load_uninitialized_pda( + treasury_info, + &[TREASURY], + args.treasury_bump, + &ore_api::id(), )?; - load_uninitialized_pda(treasury_info, &[TREASURY], args.treasury_bump, &crate::id())?; load_system_account(treasury_tokens_info, true)?; load_program(system_program, system_program::id())?; load_program(token_program, spl_token::id())?; @@ -110,7 +114,7 @@ pub fn process_initialize<'a, 'info>( for i in 0..BUS_COUNT { create_pda( bus_infos[i], - &crate::id(), + &ore_api::id(), 8 + size_of::(), &[BUS, &[i as u8], &[bus_bumps[i]]], system_program, @@ -126,7 +130,7 @@ pub fn process_initialize<'a, 'info>( // Initialize config create_pda( config_info, - &crate::id(), + &ore_api::id(), 8 + size_of::(), &[CONFIG, &[args.config_bump]], system_program, @@ -144,7 +148,7 @@ pub fn process_initialize<'a, 'info>( // Initialize treasury create_pda( treasury_info, - &crate::id(), + &ore_api::id(), 8 + size_of::(), &[TREASURY, &[args.treasury_bump]], system_program, diff --git a/src/processor/mine.rs b/core/program/src/processor/mine.rs similarity index 96% rename from src/processor/mine.rs rename to core/program/src/processor/mine.rs index e66b8aa..20c0778 100644 --- a/src/processor/mine.rs +++ b/core/program/src/processor/mine.rs @@ -1,6 +1,13 @@ use std::mem::size_of; use drillx::Solution; +use ore_api::{ + consts::*, + error::OreError, + instruction::{MineArgs, OreInstruction}, + state::{Bus, Config, Proof}, + utils::{AccountDeserialize, MineEvent}, +}; use solana_program::program::set_return_data; #[allow(deprecated)] use solana_program::{ @@ -18,14 +25,7 @@ use solana_program::{ sysvar::{self, instructions::load_current_index, Sysvar}, }; -use crate::{ - error::OreError, - instruction::{MineArgs, OreInstruction}, - loaders::*, - state::{Bus, Config, Proof}, - utils::{AccountDeserialize, MineEvent}, - EPOCH_DURATION, MIN_DIFFICULTY, ONE_MINUTE, TOLERANCE, -}; +use crate::loaders::*; /// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: /// 1. Calculate the hash from the provided nonce. @@ -162,11 +162,14 @@ pub fn process_mine<'a, 'info>( proof.total_rewards = proof.total_rewards.saturating_add(reward); // Log the mined rewards - set_return_data(bytemuck::bytes_of(&MineEvent { - difficulty: difficulty as u64, - reward: reward_actual, - timing: t.saturating_sub(t_liveness), - })); + set_return_data( + MineEvent { + difficulty: difficulty as u64, + reward: reward_actual, + timing: t.saturating_sub(t_liveness), + } + .to_bytes(), + ); Ok(()) } @@ -193,7 +196,7 @@ fn validate_transaction(msg: &[u8]) -> Result { c += num_accounts * 33; // Only allow instructions to call ore and the compute budget program. match read_pubkey(&mut c, msg)? { - crate::ID => { + ore_api::ID => { c += 2; if let Ok(ix) = OreInstruction::try_from(read_u8(&mut c, msg)?) { if let OreInstruction::Mine = ix { diff --git a/src/processor/mod.rs b/core/program/src/processor/mod.rs similarity index 100% rename from src/processor/mod.rs rename to core/program/src/processor/mod.rs diff --git a/src/processor/open.rs b/core/program/src/processor/open.rs similarity index 93% rename from src/processor/open.rs rename to core/program/src/processor/open.rs index 3f03f61..782ceab 100644 --- a/src/processor/open.rs +++ b/core/program/src/processor/open.rs @@ -1,5 +1,11 @@ use std::mem::size_of; +use ore_api::{ + consts::*, + instruction::OpenArgs, + state::Proof, + utils::{create_pda, AccountDeserialize, Discriminator}, +}; use solana_program::{ account_info::AccountInfo, blake3::hashv, @@ -12,14 +18,7 @@ use solana_program::{ sysvar::{self, Sysvar}, }; -use crate::{ - instruction::OpenArgs, - loaders::*, - state::Proof, - utils::AccountDeserialize, - utils::{create_pda, Discriminator}, - PROOF, -}; +use crate::loaders::*; /// Register generates a new hash chain for a prospective miner. Its responsibilities include: /// 1. Initialize a new proof account. @@ -48,7 +47,7 @@ pub fn process_open<'a, 'info>( proof_info, &[PROOF, signer.key.as_ref()], args.bump, - &crate::id(), + &ore_api::id(), )?; load_program(system_program, system_program::id())?; load_sysvar(slot_hashes_info, sysvar::slot_hashes::id())?; @@ -56,7 +55,7 @@ pub fn process_open<'a, 'info>( // Initialize proof create_pda( proof_info, - &crate::id(), + &ore_api::id(), 8 + size_of::(), &[PROOF, signer.key.as_ref(), &[args.bump]], system_program, diff --git a/src/processor/reset.rs b/core/program/src/processor/reset.rs similarity index 96% rename from src/processor/reset.rs rename to core/program/src/processor/reset.rs index 3f2b924..ccc1528 100644 --- a/src/processor/reset.rs +++ b/core/program/src/processor/reset.rs @@ -1,19 +1,17 @@ +use ore_api::{ + consts::*, + error::OreError, + state::{Bus, Config}, + utils::AccountDeserialize, +}; use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, sysvar::Sysvar, }; use spl_token::state::Mint; -use crate::{ - error::OreError, - loaders::{ - load_bus, load_config, load_mint, load_program, load_signer, load_token_account, - load_treasury, - }, - state::{Bus, Config}, - utils::AccountDeserialize, - BUS_COUNT, BUS_EPOCH_REWARDS, EPOCH_DURATION, MAX_EPOCH_REWARDS, MAX_SUPPLY, MINT_ADDRESS, - SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS, TREASURY, TREASURY_BUMP, +use crate::loaders::{ + load_bus, load_config, load_mint, load_program, load_signer, load_token_account, load_treasury, }; /// Reset sets up the Ore program for the next epoch. Its responsibilities include: diff --git a/src/processor/stake.rs b/core/program/src/processor/stake.rs similarity index 94% rename from src/processor/stake.rs rename to core/program/src/processor/stake.rs index 79be3dd..e948060 100644 --- a/src/processor/stake.rs +++ b/core/program/src/processor/stake.rs @@ -1,12 +1,10 @@ +use ore_api::{consts::*, instruction::StakeArgs, state::Proof, utils::AccountDeserialize}; use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, }; -use crate::{ - instruction::StakeArgs, loaders::*, state::Proof, utils::AccountDeserialize, MINT_ADDRESS, - TREASURY_ADDRESS, -}; +use crate::loaders::*; /// Stake deposits Ore into a miner's proof account to earn multiplier. Its responsibilies include: /// 1. Transfer tokens from the miner to the treasury account. diff --git a/src/processor/update.rs b/core/program/src/processor/update.rs similarity index 90% rename from src/processor/update.rs rename to core/program/src/processor/update.rs index 246c6d8..ed5313e 100644 --- a/src/processor/update.rs +++ b/core/program/src/processor/update.rs @@ -1,9 +1,10 @@ +use ore_api::{state::Proof, utils::AccountDeserialize}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, }; -use crate::{loaders::*, state::Proof, utils::AccountDeserialize}; +use crate::loaders::*; /// Update changes the miner authority on a proof account. pub fn process_update<'a, 'info>( diff --git a/src/processor/upgrade.rs b/core/program/src/processor/upgrade.rs similarity index 95% rename from src/processor/upgrade.rs rename to core/program/src/processor/upgrade.rs index acfe18d..0f5a044 100644 --- a/src/processor/upgrade.rs +++ b/core/program/src/processor/upgrade.rs @@ -1,13 +1,11 @@ +use ore_api::{consts::*, error::OreError, instruction::StakeArgs}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, }; use spl_token::state::Mint; -use crate::{ - error::OreError, instruction::StakeArgs, loaders::*, MAX_SUPPLY, MINT_ADDRESS, MINT_V1_ADDRESS, - TREASURY, TREASURY_BUMP, -}; +use crate::loaders::*; /// Upgrade allows a user to migrate a v1 token to a v2 token one-for-one. Its responsibilies include: /// 1. Burns the v1 tokens. diff --git a/stake/api/Cargo.toml b/stake/api/Cargo.toml new file mode 100644 index 0000000..e69de29 diff --git a/stake/program/Cargo.toml b/stake/program/Cargo.toml new file mode 100644 index 0000000..e69de29 From eaa90ae0abe0208273050ab3e3be433908e0d65f Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 29 Jun 2024 01:04:47 +0000 Subject: [PATCH 104/111] cleanup --- core/program/src/processor/crown.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/core/program/src/processor/crown.rs b/core/program/src/processor/crown.rs index e977d15..9a2975d 100644 --- a/core/program/src/processor/crown.rs +++ b/core/program/src/processor/crown.rs @@ -9,7 +9,7 @@ use solana_program::{ use crate::loaders::*; -/// Crown marks an account as the top staker if their balance is greater than the last known top staker. +/// Crown flags an account as the top staker if their balance is greater than the last known top staker. pub fn process_crown<'a, 'info>( _program_id: &Pubkey, accounts: &'a [AccountInfo<'info>], @@ -31,19 +31,17 @@ pub fn process_crown<'a, 'info>( let proof_new_data = proof_new_info.data.borrow(); let proof_new = Proof::try_from_bytes(&proof_new_data)?; - // If top staker is not the default null address, then compare balances + // If top staker is the defualt null balance, skip this. if config.top_staker.ne(&Pubkey::new_from_array([0; 32])) { // Load current top staker load_any_proof(proof_info, false)?; - let proof_data = proof_info.data.borrow(); - let proof = Proof::try_from_bytes(&proof_data)?; - - // Require the provided proof account is the current top staker - if config.top_staker.ne(&proof_info.key) { + if proof_info.key.ne(&config.top_staker) { return Ok(()); } // Compare balances + let proof_data = proof_info.data.borrow(); + let proof = Proof::try_from_bytes(&proof_data)?; if proof_new.balance.lt(&proof.balance) { return Ok(()); } From 9c1cc9babf345b898774db1198cac2c2c917e311 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 29 Jun 2024 12:39:11 +0000 Subject: [PATCH 105/111] continued refactor --- Cargo.lock | 10 +++++++++ Cargo.toml | 4 +++- core/api/Cargo.toml | 3 ++- core/api/src/event.rs | 13 +++++++++++ core/api/src/instruction.rs | 5 ++++- core/api/src/lib.rs | 4 +++- core/api/src/state/bus.rs | 11 +++++---- core/api/src/state/config.rs | 11 +++++---- core/api/src/state/mod.rs | 11 +++++++++ core/api/src/state/proof.rs | 11 +++++---- core/api/src/state/treasury.rs | 11 +++++---- core/program/Cargo.toml | 1 + core/program/src/lib.rs | 2 ++ core/program/src/loaders.rs | 3 ++- core/program/src/processor/claim.rs | 6 ++--- core/program/src/processor/close.rs | 4 ++-- core/program/src/processor/crown.rs | 7 ++---- core/program/src/processor/initialize.rs | 8 +++---- core/program/src/processor/mine.rs | 4 ++-- core/program/src/processor/open.rs | 12 +++++----- core/program/src/processor/reset.rs | 15 ++++++++----- core/program/src/processor/stake.rs | 4 ++-- core/program/src/processor/update.rs | 4 ++-- stake/api/src/lib.rs | 0 stake/program/src/lib.rs | 0 utils/Cargo.toml | 18 +++++++++++++++ core/api/src/utils.rs => utils/src/lib.rs | 27 +++-------------------- 27 files changed, 122 insertions(+), 87 deletions(-) create mode 100644 core/api/src/event.rs create mode 100644 stake/api/src/lib.rs create mode 100644 stake/program/src/lib.rs create mode 100644 utils/Cargo.toml rename core/api/src/utils.rs => utils/src/lib.rs (87%) diff --git a/Cargo.lock b/Cargo.lock index 1dd4c24..2b1dd2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2607,6 +2607,7 @@ dependencies = [ "spl-token", "static_assertions", "thiserror", + "utils", ] [[package]] @@ -2624,6 +2625,7 @@ dependencies = [ "spl-associated-token-account", "spl-token", "tokio", + "utils", ] [[package]] @@ -5722,6 +5724,14 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utils" +version = "2.0.0" +dependencies = [ + "bytemuck", + "solana-program", +] + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 2b46f9e..2e1a7ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["core/*"] +members = ["core/*", "utils"] [workspace.package] version = "2.0.0" @@ -13,8 +13,10 @@ readme = "./README.md" keywords = ["solana", "crypto", "mining"] [workspace.dependencies] +bytemuck = "1.14.3" drillx = { git = "https://github.com/regolith-labs/drillx", branch = "master", features = ["solana"] } mpl-token-metadata = "4.1.2" solana-program = "1.18" spl-token = { version = "^4", features = ["no-entrypoint"] } spl-associated-token-account = { version = "^2.2", features = [ "no-entrypoint" ] } +utils = { path = "utils" } diff --git a/core/api/Cargo.toml b/core/api/Cargo.toml index 9477a8c..e8b69b1 100644 --- a/core/api/Cargo.toml +++ b/core/api/Cargo.toml @@ -22,4 +22,5 @@ solana-program.workspace = true spl-token.workspace = true spl-associated-token-account.workspace = true static_assertions = "1.1.0" -thiserror = "1.0.57" \ No newline at end of file +thiserror = "1.0.57" +utils.workspace = true \ No newline at end of file diff --git a/core/api/src/event.rs b/core/api/src/event.rs new file mode 100644 index 0000000..675e9e9 --- /dev/null +++ b/core/api/src/event.rs @@ -0,0 +1,13 @@ +use bytemuck::{Pod, Zeroable}; + +use crate::utils::impl_to_bytes; + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] +pub struct MineEvent { + pub difficulty: u64, + pub reward: u64, + pub timing: i64, +} + +impl_to_bytes!(MineEvent); diff --git a/core/api/src/instruction.rs b/core/api/src/instruction.rs index f87371f..5d00d43 100644 --- a/core/api/src/instruction.rs +++ b/core/api/src/instruction.rs @@ -8,7 +8,10 @@ use solana_program::{ system_program, sysvar, }; -use crate::{consts::*, impl_instruction_from_bytes, impl_to_bytes}; +use crate::{ + consts::*, + utils::{impl_instruction_from_bytes, impl_to_bytes}, +}; #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, ShankInstruction, TryFromPrimitive)] diff --git a/core/api/src/lib.rs b/core/api/src/lib.rs index 85782b2..409cafb 100644 --- a/core/api/src/lib.rs +++ b/core/api/src/lib.rs @@ -1,8 +1,10 @@ pub mod consts; pub mod error; +pub mod event; pub mod instruction; pub mod state; -pub mod utils; + +pub(crate) use utils; use solana_program::declare_id; diff --git a/core/api/src/state/bus.rs b/core/api/src/state/bus.rs index 7275119..2eaa557 100644 --- a/core/api/src/state/bus.rs +++ b/core/api/src/state/bus.rs @@ -1,10 +1,9 @@ use bytemuck::{Pod, Zeroable}; use shank::ShankAccount; -use crate::{ - impl_account_from_bytes, impl_to_bytes, - utils::{AccountDiscriminator, Discriminator}, -}; +use crate::utils::{impl_account_from_bytes, impl_to_bytes, Discriminator}; + +use super::AccountDiscriminator; /// Bus accounts are responsible for distributing mining rewards. /// There are 8 busses total to minimize write-lock contention and allow for parallel mine operations. @@ -23,8 +22,8 @@ pub struct Bus { } impl Discriminator for Bus { - fn discriminator() -> AccountDiscriminator { - AccountDiscriminator::Bus + fn discriminator() -> u8 { + AccountDiscriminator::Bus.into() } } diff --git a/core/api/src/state/config.rs b/core/api/src/state/config.rs index 2c8214f..d08e00d 100644 --- a/core/api/src/state/config.rs +++ b/core/api/src/state/config.rs @@ -2,10 +2,9 @@ use bytemuck::{Pod, Zeroable}; use shank::ShankAccount; use solana_program::pubkey::Pubkey; -use crate::{ - impl_account_from_bytes, impl_to_bytes, - utils::{AccountDiscriminator, Discriminator}, -}; +use crate::utils::{impl_account_from_bytes, impl_to_bytes, Discriminator}; + +use super::AccountDiscriminator; /// Config is a singleton account which manages admin configurable variables. #[repr(C)] @@ -28,8 +27,8 @@ pub struct Config { } impl Discriminator for Config { - fn discriminator() -> AccountDiscriminator { - AccountDiscriminator::Config + fn discriminator() -> u8 { + AccountDiscriminator::Config.into() } } diff --git a/core/api/src/state/mod.rs b/core/api/src/state/mod.rs index 7a3b009..e228cac 100644 --- a/core/api/src/state/mod.rs +++ b/core/api/src/state/mod.rs @@ -7,3 +7,14 @@ pub use bus::*; pub use config::*; pub use proof::*; pub use treasury::*; + +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] +pub enum AccountDiscriminator { + Bus = 100, + Config = 101, + Proof = 102, + Treasury = 103, +} diff --git a/core/api/src/state/proof.rs b/core/api/src/state/proof.rs index 1d1aa76..83e9dd5 100644 --- a/core/api/src/state/proof.rs +++ b/core/api/src/state/proof.rs @@ -2,10 +2,9 @@ use bytemuck::{Pod, Zeroable}; use shank::ShankAccount; use solana_program::pubkey::Pubkey; -use crate::{ - impl_account_from_bytes, impl_to_bytes, - utils::{AccountDiscriminator, Discriminator}, -}; +use crate::utils::{impl_account_from_bytes, impl_to_bytes, Discriminator}; + +use super::AccountDiscriminator; /// Proof accounts track a miner's current hash, claimable rewards, and lifetime stats. /// Every miner is allowed one proof account which is required by the program to mine or claim rewards. @@ -41,8 +40,8 @@ pub struct Proof { } impl Discriminator for Proof { - fn discriminator() -> AccountDiscriminator { - AccountDiscriminator::Proof + fn discriminator() -> u8 { + AccountDiscriminator::Proof.into() } } diff --git a/core/api/src/state/treasury.rs b/core/api/src/state/treasury.rs index e699ae2..5b78b6c 100644 --- a/core/api/src/state/treasury.rs +++ b/core/api/src/state/treasury.rs @@ -1,10 +1,9 @@ use bytemuck::{Pod, Zeroable}; use shank::ShankAccount; -use crate::{ - impl_account_from_bytes, impl_to_bytes, - utils::{AccountDiscriminator, Discriminator}, -}; +use crate::utils::{impl_account_from_bytes, impl_to_bytes, Discriminator}; + +use super::AccountDiscriminator; /// Treasury is a singleton account which manages all program wide variables. /// It is the mint authority for the Ore token and also the authority of the program-owned token account. @@ -13,8 +12,8 @@ use crate::{ pub struct Treasury {} impl Discriminator for Treasury { - fn discriminator() -> AccountDiscriminator { - AccountDiscriminator::Treasury + fn discriminator() -> u8 { + AccountDiscriminator::Treasury.into() } } diff --git a/core/program/Cargo.toml b/core/program/Cargo.toml index 3ea6d1c..cb97a8e 100644 --- a/core/program/Cargo.toml +++ b/core/program/Cargo.toml @@ -25,6 +25,7 @@ ore-api = { path = "../api" } solana-program.workspace = true spl-token.workspace = true spl-associated-token-account.workspace = true +utils.workspace = true [dev-dependencies] bs64 = "0.1.2" diff --git a/core/program/src/lib.rs b/core/program/src/lib.rs index 2fcd002..010e69c 100644 --- a/core/program/src/lib.rs +++ b/core/program/src/lib.rs @@ -8,6 +8,8 @@ use solana_program::{ pubkey::Pubkey, }; +pub(crate) use utils; + #[cfg(not(feature = "no-entrypoint"))] solana_program::entrypoint!(process_instruction); diff --git a/core/program/src/loaders.rs b/core/program/src/loaders.rs index 61cc800..1daaa96 100644 --- a/core/program/src/loaders.rs +++ b/core/program/src/loaders.rs @@ -1,7 +1,6 @@ use ore_api::{ consts::*, state::{Bus, Config, Proof, Treasury}, - utils::{AccountDeserialize, Discriminator}, }; use solana_program::{ account_info::AccountInfo, program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, @@ -9,6 +8,8 @@ use solana_program::{ }; use spl_token::state::Mint; +use crate::utils::{AccountDeserialize, Discriminator}; + /// Errors if: /// - Account is not a signer. pub fn load_signer<'a, 'info>(info: &'a AccountInfo<'info>) -> Result<(), ProgramError> { diff --git a/core/program/src/processor/claim.rs b/core/program/src/processor/claim.rs index 7a688d2..554f824 100644 --- a/core/program/src/processor/claim.rs +++ b/core/program/src/processor/claim.rs @@ -1,12 +1,10 @@ -use ore_api::{ - consts::*, error::OreError, instruction::ClaimArgs, state::Proof, utils::AccountDeserialize, -}; +use ore_api::{consts::*, error::OreError, instruction::ClaimArgs, state::Proof}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, }; -use crate::loaders::*; +use crate::{loaders::*, utils::AccountDeserialize}; /// Claim distributes Ore from the treasury to a miner. Its responsibilies include: /// 1. Decrement the miner's claimable balance. diff --git a/core/program/src/processor/close.rs b/core/program/src/processor/close.rs index 4f6d0f6..107f1c0 100644 --- a/core/program/src/processor/close.rs +++ b/core/program/src/processor/close.rs @@ -1,10 +1,10 @@ -use ore_api::{state::Proof, utils::AccountDeserialize}; +use ore_api::state::Proof; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, system_program, }; -use crate::loaders::*; +use crate::{loaders::*, utils::AccountDeserialize}; /// Close closes a proof account and returns the rent to the owner. Its responsibilities include: /// 1. Realloc proof account size to 0. diff --git a/core/program/src/processor/crown.rs b/core/program/src/processor/crown.rs index 9a2975d..aeb1827 100644 --- a/core/program/src/processor/crown.rs +++ b/core/program/src/processor/crown.rs @@ -1,13 +1,10 @@ -use ore_api::{ - state::{Config, Proof}, - utils::AccountDeserialize, -}; +use ore_api::state::{Config, Proof}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, }; -use crate::loaders::*; +use crate::{loaders::*, utils::AccountDeserialize}; /// Crown flags an account as the top staker if their balance is greater than the last known top staker. pub fn process_crown<'a, 'info>( diff --git a/core/program/src/processor/initialize.rs b/core/program/src/processor/initialize.rs index d01110e..d4200f9 100644 --- a/core/program/src/processor/initialize.rs +++ b/core/program/src/processor/initialize.rs @@ -4,9 +4,6 @@ use ore_api::{ consts::*, instruction::*, state::{Bus, Config, Treasury}, - utils::create_pda, - utils::AccountDeserialize, - utils::Discriminator, }; use solana_program::{ account_info::AccountInfo, @@ -18,7 +15,10 @@ use solana_program::{ }; use spl_token::state::Mint; -use crate::loaders::*; +use crate::{ + loaders::*, + utils::{create_pda, AccountDeserialize, Discriminator}, +}; /// Initialize sets up the Ore program. Its responsibilities include: /// 1. Initialize the 8 bus accounts. diff --git a/core/program/src/processor/mine.rs b/core/program/src/processor/mine.rs index 20c0778..d362ed0 100644 --- a/core/program/src/processor/mine.rs +++ b/core/program/src/processor/mine.rs @@ -4,9 +4,9 @@ use drillx::Solution; use ore_api::{ consts::*, error::OreError, + event::MineEvent, instruction::{MineArgs, OreInstruction}, state::{Bus, Config, Proof}, - utils::{AccountDeserialize, MineEvent}, }; use solana_program::program::set_return_data; #[allow(deprecated)] @@ -25,7 +25,7 @@ use solana_program::{ sysvar::{self, instructions::load_current_index, Sysvar}, }; -use crate::loaders::*; +use crate::{loaders::*, utils::AccountDeserialize}; /// Mine is the primary workhorse instruction of the Ore program. Its responsibilities include: /// 1. Calculate the hash from the provided nonce. diff --git a/core/program/src/processor/open.rs b/core/program/src/processor/open.rs index 782ceab..66e603a 100644 --- a/core/program/src/processor/open.rs +++ b/core/program/src/processor/open.rs @@ -1,11 +1,6 @@ use std::mem::size_of; -use ore_api::{ - consts::*, - instruction::OpenArgs, - state::Proof, - utils::{create_pda, AccountDeserialize, Discriminator}, -}; +use ore_api::{consts::*, instruction::OpenArgs, state::Proof}; use solana_program::{ account_info::AccountInfo, blake3::hashv, @@ -18,7 +13,10 @@ use solana_program::{ sysvar::{self, Sysvar}, }; -use crate::loaders::*; +use crate::{ + loaders::*, + utils::{create_pda, AccountDeserialize, Discriminator}, +}; /// Register generates a new hash chain for a prospective miner. Its responsibilities include: /// 1. Initialize a new proof account. diff --git a/core/program/src/processor/reset.rs b/core/program/src/processor/reset.rs index ccc1528..dd656a3 100644 --- a/core/program/src/processor/reset.rs +++ b/core/program/src/processor/reset.rs @@ -2,7 +2,6 @@ use ore_api::{ consts::*, error::OreError, state::{Bus, Config}, - utils::AccountDeserialize, }; use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, @@ -10,8 +9,12 @@ use solana_program::{ }; use spl_token::state::Mint; -use crate::loaders::{ - load_bus, load_config, load_mint, load_program, load_signer, load_token_account, load_treasury, +use crate::{ + loaders::{ + load_bus, load_config, load_mint, load_program, load_signer, load_token_account, + load_treasury, + }, + utils::AccountDeserialize, }; /// Reset sets up the Ore program for the next epoch. Its responsibilities include: @@ -165,9 +168,9 @@ pub(crate) fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) - mod tests { use rand::{distributions::Uniform, Rng}; - use crate::{ - calculate_new_reward_rate, BUS_EPOCH_REWARDS, MAX_EPOCH_REWARDS, SMOOTHING_FACTOR, - TARGET_EPOCH_REWARDS, + use crate::calculate_new_reward_rate; + use ore_api::consts::{ + BUS_EPOCH_REWARDS, MAX_EPOCH_REWARDS, SMOOTHING_FACTOR, TARGET_EPOCH_REWARDS, }; const FUZZ_SIZE: u64 = 10_000; diff --git a/core/program/src/processor/stake.rs b/core/program/src/processor/stake.rs index e948060..d72ddb3 100644 --- a/core/program/src/processor/stake.rs +++ b/core/program/src/processor/stake.rs @@ -1,10 +1,10 @@ -use ore_api::{consts::*, instruction::StakeArgs, state::Proof, utils::AccountDeserialize}; +use ore_api::{consts::*, instruction::StakeArgs, state::Proof}; use solana_program::{ account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, sysvar::Sysvar, }; -use crate::loaders::*; +use crate::{loaders::*, utils::AccountDeserialize}; /// Stake deposits Ore into a miner's proof account to earn multiplier. Its responsibilies include: /// 1. Transfer tokens from the miner to the treasury account. diff --git a/core/program/src/processor/update.rs b/core/program/src/processor/update.rs index ed5313e..35f384c 100644 --- a/core/program/src/processor/update.rs +++ b/core/program/src/processor/update.rs @@ -1,10 +1,10 @@ -use ore_api::{state::Proof, utils::AccountDeserialize}; +use ore_api::state::Proof; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, }; -use crate::loaders::*; +use crate::{loaders::*, utils::AccountDeserialize}; /// Update changes the miner authority on a proof account. pub fn process_update<'a, 'info>( diff --git a/stake/api/src/lib.rs b/stake/api/src/lib.rs new file mode 100644 index 0000000..e69de29 diff --git a/stake/program/src/lib.rs b/stake/program/src/lib.rs new file mode 100644 index 0000000..e69de29 diff --git a/utils/Cargo.toml b/utils/Cargo.toml new file mode 100644 index 0000000..cc44d0f --- /dev/null +++ b/utils/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "utils" +description = "Utils for building ORE programs" +version.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +documentation.workspace = true +repository.workspace = true +keywords.workspace = true + +[lib] +crate-type = ["cdylib", "lib"] +name = "utils" + +[dependencies] +bytemuck.workspace = true +solana-program.workspace = true diff --git a/core/api/src/utils.rs b/utils/src/lib.rs similarity index 87% rename from core/api/src/utils.rs rename to utils/src/lib.rs index 669c7d9..1694c98 100644 --- a/core/api/src/utils.rs +++ b/utils/src/lib.rs @@ -1,5 +1,3 @@ -use bytemuck::{Pod, Zeroable}; -use num_enum::{IntoPrimitive, TryFromPrimitive}; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, rent::Rent, sysvar::Sysvar, @@ -73,17 +71,8 @@ pub fn create_pda<'a, 'info>( Ok(()) } -#[repr(u8)] -#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] -pub enum AccountDiscriminator { - Bus = 100, - Config = 101, - Proof = 102, - Treasury = 103, -} - pub trait Discriminator { - fn discriminator() -> AccountDiscriminator; + fn discriminator() -> u8; //AccountDiscriminator; } pub trait AccountDeserialize { @@ -109,7 +98,7 @@ macro_rules! impl_account_from_bytes { fn try_from_bytes( data: &[u8], ) -> Result<&Self, solana_program::program_error::ProgramError> { - if (Self::discriminator() as u8).ne(&data[0]) { + if Self::discriminator().ne(&data[0]) { return Err(solana_program::program_error::ProgramError::InvalidAccountData); } bytemuck::try_from_bytes::(&data[8..]).or(Err( @@ -119,7 +108,7 @@ macro_rules! impl_account_from_bytes { fn try_from_bytes_mut( data: &mut [u8], ) -> Result<&mut Self, solana_program::program_error::ProgramError> { - if (Self::discriminator() as u8).ne(&data[0]) { + if Self::discriminator().ne(&data[0]) { return Err(solana_program::program_error::ProgramError::InvalidAccountData); } bytemuck::try_from_bytes_mut::(&mut data[8..]).or(Err( @@ -144,13 +133,3 @@ macro_rules! impl_instruction_from_bytes { } }; } - -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)] -pub struct MineEvent { - pub difficulty: u64, - pub reward: u64, - pub timing: i64, -} - -impl_to_bytes!(MineEvent); From 54fd80e7352d07bcb7129c2c7520cfff48ed47ab Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 29 Jun 2024 12:44:32 +0000 Subject: [PATCH 106/111] refactor --- Cargo.lock | 26 ++++++++------------------ Cargo.toml | 6 ++++++ core/api/Cargo.toml | 15 +++++++-------- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b1dd2f..8d229be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -670,15 +670,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" -[[package]] -name = "bs58" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" -dependencies = [ - "tinyvec", -] - [[package]] name = "bs64" version = "0.1.2" @@ -2595,7 +2586,6 @@ name = "ore-api" version = "2.0.0" dependencies = [ "array-const-fn-init", - "bs58 0.5.0", "bytemuck", "const-crypto", "drillx", @@ -3676,7 +3666,7 @@ dependencies = [ "Inflector", "base64 0.21.7", "bincode", - "bs58 0.4.0", + "bs58", "bv", "lazy_static", "serde", @@ -3986,7 +3976,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35a0b24cc4d0ebd5fd45d6bd47bed3790f8a75ade67af8ff24a3d719a8bc93bc" dependencies = [ "block-buffer 0.10.4", - "bs58 0.4.0", + "bs58", "bv", "either", "generic-array", @@ -4139,7 +4129,7 @@ dependencies = [ "borsh 0.10.3", "borsh 0.9.3", "borsh 1.5.0", - "bs58 0.4.0", + "bs58", "bv", "bytemuck", "cc", @@ -4325,7 +4315,7 @@ dependencies = [ "async-trait", "base64 0.21.7", "bincode", - "bs58 0.4.0", + "bs58", "indicatif", "log", "reqwest", @@ -4349,7 +4339,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31feddef24d3e0aab189571adea7f109639ef6179fcd3cd34ffc8c73d3409f1" dependencies = [ "base64 0.21.7", - "bs58 0.4.0", + "bs58", "jsonrpc-core", "reqwest", "semver", @@ -4465,7 +4455,7 @@ dependencies = [ "bincode", "bitflags 2.5.0", "borsh 1.5.0", - "bs58 0.4.0", + "bs58", "bytemuck", "byteorder", "chrono", @@ -4515,7 +4505,7 @@ version = "1.18.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cb099b2f9c0a65a6f23ced791325141cd68c27b04d11c04fef838a00f613861" dependencies = [ - "bs58 0.4.0", + "bs58", "proc-macro2", "quote", "rustversion", @@ -4655,7 +4645,7 @@ dependencies = [ "base64 0.21.7", "bincode", "borsh 0.10.3", - "bs58 0.4.0", + "bs58", "lazy_static", "log", "serde", diff --git a/Cargo.toml b/Cargo.toml index 2e1a7ce..9e3dcb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,16 @@ readme = "./README.md" keywords = ["solana", "crypto", "mining"] [workspace.dependencies] +array-const-fn-init = "0.1.1" bytemuck = "1.14.3" +const-crypto = "0.1.0" drillx = { git = "https://github.com/regolith-labs/drillx", branch = "master", features = ["solana"] } mpl-token-metadata = "4.1.2" +num_enum = "0.7.2" +shank = "0.3.0" solana-program = "1.18" spl-token = { version = "^4", features = ["no-entrypoint"] } spl-associated-token-account = { version = "^2.2", features = [ "no-entrypoint" ] } +static_assertions = "1.1.0" +thiserror = "1.0.57" utils = { path = "utils" } diff --git a/core/api/Cargo.toml b/core/api/Cargo.toml index e8b69b1..224c77f 100644 --- a/core/api/Cargo.toml +++ b/core/api/Cargo.toml @@ -10,17 +10,16 @@ repository.workspace = true keywords.workspace = true [dependencies] -array-const-fn-init = "0.1.1" -bs58 = "0.5.0" -bytemuck = "1.14.3" -const-crypto = "0.1.0" +array-const-fn-init.workspace = true +bytemuck.workspace = true +const-crypto.workspace = true drillx.workspace = true mpl-token-metadata.workspace = true -num_enum = "0.7.2" -shank = "0.3.0" +num_enum.workspace = true +shank.workspace = true solana-program.workspace = true spl-token.workspace = true spl-associated-token-account.workspace = true -static_assertions = "1.1.0" -thiserror = "1.0.57" +static_assertions.workspace = true +thiserror.workspace = true utils.workspace = true \ No newline at end of file From 742314f5cb9488e2185d485553a4d4c364e00532 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 29 Jun 2024 13:02:30 +0000 Subject: [PATCH 107/111] readme --- README.md | 51 -------------------------------------------------- core/README.md | 44 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 51 deletions(-) delete mode 100644 README.md create mode 100644 core/README.md diff --git a/README.md b/README.md deleted file mode 100644 index 59fa006..0000000 --- a/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# ORE - -**ORE is a fair-launch, proof-of-work, digital currency everyone can mine.** - - -## Install - -```sh -cargo install ore-cli -``` - - -## Program -- [`Consts`](src/consts.rs) – Program constants. -- [`Entrypoint`](src/lib.rs) – The program entrypoint. -- [`Errors`](src/error.rs) – Custom program errors. -- [`Idl`](idl/ore.json) – Interface for clients, explorers, and programs. -- [`Instruction`](src/instruction.rs) – Declared instructions and arguments. -- [`Loaders`](src/loaders.rs) – Validation logic for loading Solana accounts. - - -## Instructions -- [`Reset`](src/processor/reset.rs) – Resets the program for a new epoch. -- [`Open`](src/processor/open.rs) – Creates a new proof account for a prospective miner. -- [`Close`](src/processor/close.rs) – Closes a new proof account returns the rent to the owner. -- [`Mine`](src/processor/mine.rs) – Verifies a hash provided by a miner and issues claimable rewards. -- [`Stake`](src/processor/stake.rs) – Stakes ORE with a miner to increase their multiplier. -- [`Claim`](src/processor/claim.rs) – Distributes claimable rewards as tokens from the treasury to a miner. -- [`Upgrade`](src/processor/upgrade.rs) – Migrates v1 ORE tokens to v2 ORE. -- [`Initialize`](src/processor/initialize.rs) – Initializes the Ore program, creating the bus, mint, and treasury accounts. - - -## State - - [`Bus`](src/state/bus.rs) - An account (8 total) which tracks and limits the amount mined rewards each epoch. - - [`Proof`](src/state/proof.rs) - An account (1 per miner) which tracks a miner's hash, claimable rewards, and lifetime stats. - - [`Treasury`](src/state/treasury.rs) – A singleton account which manages program-wide variables and authorities. - - -## Tests - -To run the test suite, use the Solana toolchain: - -``` -cargo test-sbf -``` - -For line coverage, use llvm-cov: - -``` -cargo llvm-cov -``` diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..e0a25e1 --- /dev/null +++ b/core/README.md @@ -0,0 +1,44 @@ +# ORE + +**ORE is a fair-launch, proof-of-work, digital currency everyone can mine.** + + +## API +- [`Consts`](api/src/consts.rs) – Program constants. +- [`Entrypoint`](api/src/lib.rs) – The program entrypoint. +- [`Error`](api/src/error.rs) – Custom program errors. +- [`Event`](api/src/error.rs) – Custom program events. +- [`Instruction`](api/src/instruction.rs) – Declared instructions and arguments. + +## Instructions +- [`Claim`](program/src/processor/claim.rs) – Distributes claimable rewards as tokens from the treasury to a miner. +- [`Close`](program/src/processor/close.rs) – Closes a proof account returns the rent to the owner. +- [`Crown`](program/src/processor/crown.rs) – Flags a proof account as the top staker on the network. +- [`Open`](program/src/processor/open.rs) – Creates a new proof account for a prospective miner. +- [`Mine`](program/src/processor/mine.rs) – Verifies a hash provided by a miner and issues claimable rewards. +- [`Stake`](program/src/processor/stake.rs) – Stakes ORE with a miner to increase their multiplier. +- [`Reset`](program/src/processor/reset.rs) – Resets the program for a new epoch. +- [`Update`](program/src/processor/update.rs) – Updates a proof account's miner authority. +- [`Upgrade`](program/src/processor/upgrade.rs) – Migrates v1 ORE tokens to v2 ORE. +- [`Initialize`](program/src/processor/initialize.rs) – Initializes the Ore program, creating the bus, mint, and treasury accounts. + +## State + - [`Bus`](src/state/bus.rs) - An account (8 total) which tracks and limits the amount mined rewards each epoch. + - [`Proof`](src/state/proof.rs) - An account (1 per miner) which tracks a miner's hash, claimable rewards, and lifetime stats. + - [`Treasury`](src/state/treasury.rs) – A singleton account which manages program-wide variables and authorities. + + + +## Tests + +To run the test suite, use the Solana toolchain: + +``` +cargo test-sbf +``` + +For line coverage, use llvm-cov: + +``` +cargo llvm-cov +``` From d41c7909982a941eca9983448789652ccc3a4316 Mon Sep 17 00:00:00 2001 From: Hardhat Chad <155858888+HardhatChad@users.noreply.github.com> Date: Sat, 29 Jun 2024 08:03:26 -0500 Subject: [PATCH 108/111] Update README.md --- core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/README.md b/core/README.md index e0a25e1..e66939e 100644 --- a/core/README.md +++ b/core/README.md @@ -19,7 +19,7 @@ - [`Stake`](program/src/processor/stake.rs) – Stakes ORE with a miner to increase their multiplier. - [`Reset`](program/src/processor/reset.rs) – Resets the program for a new epoch. - [`Update`](program/src/processor/update.rs) – Updates a proof account's miner authority. -- [`Upgrade`](program/src/processor/upgrade.rs) – Migrates v1 ORE tokens to v2 ORE. +- [`Upgrade`](program/src/processor/upgrade.rs) – Migrates ORE v1 tokens to ORE v2, one-for-one. - [`Initialize`](program/src/processor/initialize.rs) – Initializes the Ore program, creating the bus, mint, and treasury accounts. ## State From df34091b3808d5608a94aa277113e460a114a1c9 Mon Sep 17 00:00:00 2001 From: Hardhat Chad <155858888+HardhatChad@users.noreply.github.com> Date: Sat, 29 Jun 2024 08:05:25 -0500 Subject: [PATCH 109/111] Update README.md --- core/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/README.md b/core/README.md index e66939e..9cd0ce8 100644 --- a/core/README.md +++ b/core/README.md @@ -23,10 +23,10 @@ - [`Initialize`](program/src/processor/initialize.rs) – Initializes the Ore program, creating the bus, mint, and treasury accounts. ## State - - [`Bus`](src/state/bus.rs) - An account (8 total) which tracks and limits the amount mined rewards each epoch. - - [`Proof`](src/state/proof.rs) - An account (1 per miner) which tracks a miner's hash, claimable rewards, and lifetime stats. - - [`Treasury`](src/state/treasury.rs) – A singleton account which manages program-wide variables and authorities. - + - [`Bus`](api/src/state/bus.rs) - An account (8 total) which tracks and limits the amount ORE mined each epoch. + - [`Config`](api/src/state/config.rs) – A singleton account which manages program-wide variables. + - [`Proof`](api/src/state/proof.rs) - An account (1 per user) which tracks a miner's current hash and current stake. + - [`Treasury`](api/src/state/treasury.rs) – A singleton account which has authority to mint ORE and holds onto user stake. ## Tests From c022ba192e55c584faeea5f5a9be1a190678d9bf Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 29 Jun 2024 13:21:42 +0000 Subject: [PATCH 110/111] README --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..2264f89 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# ORE + +**ORE is a fair-launch, proof-of-work, digital currency everyone can mine.** + + +## Programs +- [`Core`](core) - ORE mining program. +- [`Stake`](stake) - ORE staking program. From f4d0751775c896cfdcd5177fbce2e5330b773bb3 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Sat, 29 Jun 2024 13:24:09 +0000 Subject: [PATCH 111/111] cleanup --- core/README.md | 20 ++++++++-------- core/program/src/{processor => }/claim.rs | 0 core/program/src/{processor => }/close.rs | 0 core/program/src/{processor => }/crown.rs | 0 .../program/src/{processor => }/initialize.rs | 0 core/program/src/lib.rs | 23 +++++++++++++++++-- core/program/src/{processor => }/mine.rs | 0 core/program/src/{processor => }/open.rs | 0 core/program/src/processor/mod.rs | 21 ----------------- core/program/src/{processor => }/reset.rs | 0 core/program/src/{processor => }/stake.rs | 0 core/program/src/{processor => }/update.rs | 0 core/program/src/{processor => }/upgrade.rs | 0 13 files changed, 31 insertions(+), 33 deletions(-) rename core/program/src/{processor => }/claim.rs (100%) rename core/program/src/{processor => }/close.rs (100%) rename core/program/src/{processor => }/crown.rs (100%) rename core/program/src/{processor => }/initialize.rs (100%) rename core/program/src/{processor => }/mine.rs (100%) rename core/program/src/{processor => }/open.rs (100%) delete mode 100644 core/program/src/processor/mod.rs rename core/program/src/{processor => }/reset.rs (100%) rename core/program/src/{processor => }/stake.rs (100%) rename core/program/src/{processor => }/update.rs (100%) rename core/program/src/{processor => }/upgrade.rs (100%) diff --git a/core/README.md b/core/README.md index 9cd0ce8..816aaeb 100644 --- a/core/README.md +++ b/core/README.md @@ -11,16 +11,16 @@ - [`Instruction`](api/src/instruction.rs) – Declared instructions and arguments. ## Instructions -- [`Claim`](program/src/processor/claim.rs) – Distributes claimable rewards as tokens from the treasury to a miner. -- [`Close`](program/src/processor/close.rs) – Closes a proof account returns the rent to the owner. -- [`Crown`](program/src/processor/crown.rs) – Flags a proof account as the top staker on the network. -- [`Open`](program/src/processor/open.rs) – Creates a new proof account for a prospective miner. -- [`Mine`](program/src/processor/mine.rs) – Verifies a hash provided by a miner and issues claimable rewards. -- [`Stake`](program/src/processor/stake.rs) – Stakes ORE with a miner to increase their multiplier. -- [`Reset`](program/src/processor/reset.rs) – Resets the program for a new epoch. -- [`Update`](program/src/processor/update.rs) – Updates a proof account's miner authority. -- [`Upgrade`](program/src/processor/upgrade.rs) – Migrates ORE v1 tokens to ORE v2, one-for-one. -- [`Initialize`](program/src/processor/initialize.rs) – Initializes the Ore program, creating the bus, mint, and treasury accounts. +- [`Claim`](program/src/claim.rs) – Distributes claimable rewards as tokens from the treasury to a miner. +- [`Close`](program/src/close.rs) – Closes a proof account returns the rent to the owner. +- [`Crown`](program/src/crown.rs) – Flags a proof account as the top staker on the network. +- [`Open`](program/src/open.rs) – Creates a new proof account for a prospective miner. +- [`Mine`](program/src/mine.rs) – Verifies a hash provided by a miner and issues claimable rewards. +- [`Stake`](program/src/stake.rs) – Stakes ORE with a miner to increase their multiplier. +- [`Reset`](program/src/reset.rs) – Resets the program for a new epoch. +- [`Update`](program/src/update.rs) – Updates a proof account's miner authority. +- [`Upgrade`](program/src/upgrade.rs) – Migrates ORE v1 tokens to ORE v2, one-for-one. +- [`Initialize`](program/src/initialize.rs) – Initializes the Ore program, creating the bus, mint, and treasury accounts. ## State - [`Bus`](api/src/state/bus.rs) - An account (8 total) which tracks and limits the amount ORE mined each epoch. diff --git a/core/program/src/processor/claim.rs b/core/program/src/claim.rs similarity index 100% rename from core/program/src/processor/claim.rs rename to core/program/src/claim.rs diff --git a/core/program/src/processor/close.rs b/core/program/src/close.rs similarity index 100% rename from core/program/src/processor/close.rs rename to core/program/src/close.rs diff --git a/core/program/src/processor/crown.rs b/core/program/src/crown.rs similarity index 100% rename from core/program/src/processor/crown.rs rename to core/program/src/crown.rs diff --git a/core/program/src/processor/initialize.rs b/core/program/src/initialize.rs similarity index 100% rename from core/program/src/processor/initialize.rs rename to core/program/src/initialize.rs diff --git a/core/program/src/lib.rs b/core/program/src/lib.rs index 010e69c..bac4a88 100644 --- a/core/program/src/lib.rs +++ b/core/program/src/lib.rs @@ -1,8 +1,27 @@ +mod claim; +mod close; +mod crown; +mod initialize; mod loaders; -mod processor; +mod mine; +mod open; +mod reset; +mod stake; +mod update; +mod upgrade; + +use claim::*; +use close::*; +use crown::*; +use initialize::*; +use mine::*; +use open::*; +use reset::*; +use stake::*; +use update::*; +use upgrade::*; use ore_api::instruction::*; -use processor::*; use solana_program::{ self, account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, pubkey::Pubkey, diff --git a/core/program/src/processor/mine.rs b/core/program/src/mine.rs similarity index 100% rename from core/program/src/processor/mine.rs rename to core/program/src/mine.rs diff --git a/core/program/src/processor/open.rs b/core/program/src/open.rs similarity index 100% rename from core/program/src/processor/open.rs rename to core/program/src/open.rs diff --git a/core/program/src/processor/mod.rs b/core/program/src/processor/mod.rs deleted file mode 100644 index e46eafe..0000000 --- a/core/program/src/processor/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -mod claim; -mod close; -mod crown; -mod initialize; -mod mine; -mod open; -mod reset; -mod stake; -mod update; -mod upgrade; - -pub use claim::*; -pub use close::*; -pub use crown::*; -pub use initialize::*; -pub use mine::*; -pub use open::*; -pub use reset::*; -pub use stake::*; -pub use update::*; -pub use upgrade::*; diff --git a/core/program/src/processor/reset.rs b/core/program/src/reset.rs similarity index 100% rename from core/program/src/processor/reset.rs rename to core/program/src/reset.rs diff --git a/core/program/src/processor/stake.rs b/core/program/src/stake.rs similarity index 100% rename from core/program/src/processor/stake.rs rename to core/program/src/stake.rs diff --git a/core/program/src/processor/update.rs b/core/program/src/update.rs similarity index 100% rename from core/program/src/processor/update.rs rename to core/program/src/update.rs diff --git a/core/program/src/processor/upgrade.rs b/core/program/src/upgrade.rs similarity index 100% rename from core/program/src/processor/upgrade.rs rename to core/program/src/upgrade.rs