From 9f6ef5dce199134410cb302aad5a371ecb65625e Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Tue, 19 Mar 2024 03:08:47 +0000 Subject: [PATCH] security fixes --- Cargo.lock | 2 +- src/loaders.rs | 63 +++++++++++++++++++++--- src/processor/claim.rs | 13 +++-- src/processor/initialize.rs | 47 ++++++++---------- src/processor/mine.rs | 10 ++-- src/processor/register.rs | 3 +- src/processor/reset.rs | 36 +++++++++----- src/processor/update_admin.rs | 2 +- src/processor/update_difficulty.rs | 2 +- tests/test_mine.rs | 78 ++++++++++++++++++++++++++---- tests/test_register.rs | 4 +- tests/test_reset.rs | 56 ++++++++++++++++----- 12 files changed, 231 insertions(+), 85 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b99dec..b7fd3b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2366,7 +2366,7 @@ dependencies = [ [[package]] name = "ore-program" -version = "0.0.4" +version = "0.0.5" dependencies = [ "bs58 0.5.0", "bs64", diff --git a/src/loaders.rs b/src/loaders.rs index 40fbeb6..d175335 100644 --- a/src/loaders.rs +++ b/src/loaders.rs @@ -1,6 +1,6 @@ use solana_program::{ account_info::AccountInfo, program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, - system_program, + system_program, sysvar, }; use spl_token::state::Mint; @@ -24,10 +24,48 @@ pub fn load_signer<'a, 'info>(info: &'a AccountInfo<'info>) -> Result<(), Progra /// - Account is not owned by Ore program. /// - Data is empty. /// - Data cannot deserialize into a bus account. -/// - Bus ID is not in 0-7 range. -/// - Address is not in set of valid bus address. +/// - Bus ID does not match the expected ID. +/// - Address does not match the expected bus address. /// - Expected to be writable, but is not. pub fn load_bus<'a, 'info>( + info: &'a AccountInfo<'info>, + id: u64, + 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 bus_data = info.data.borrow(); + let bus = Bus::try_from_bytes(&bus_data)?; + + if bus.id.ne(&id) { + return Err(ProgramError::InvalidAccountData); + } + + if info.key.ne(&BUS_ADDRESSES[id as usize]) { + return Err(ProgramError::InvalidAccountData); + } + + if is_writable && !info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + Ok(()) +} + +/// Errors if: +/// - Account is not owned by Ore program. +/// - Data is empty. +/// - Data cannot deserialize into a bus account. +/// - Bus ID is not in the expected range. +/// - Address is not in set of valid bus address. +/// - Expected to be writable, but is not. +pub fn load_any_bus<'a, 'info>( info: &'a AccountInfo<'info>, is_writable: bool, ) -> Result<(), ProgramError> { @@ -201,12 +239,19 @@ pub fn load_token_account<'a, 'info>( pub fn load_uninitialized_pda<'a, 'info>( info: &'a AccountInfo<'info>, seeds: &[&[u8]], + bump: u8, program_id: &Pubkey, ) -> Result<(), ProgramError> { - let key = Pubkey::create_program_address(seeds, program_id)?; - if info.key.ne(&key) { + let pda = Pubkey::find_program_address(seeds, program_id); + + if info.key.ne(&pda.0) { return Err(ProgramError::InvalidSeeds); } + + if bump.ne(&pda.1) { + return Err(ProgramError::InvalidSeeds); + } + load_uninitialized_account(info) } @@ -218,7 +263,7 @@ pub fn load_uninitialized_account<'a, 'info>( info: &'a AccountInfo<'info>, ) -> Result<(), ProgramError> { if info.owner.ne(&system_program::id()) { - return Err(ProgramError::AccountAlreadyInitialized); + return Err(ProgramError::InvalidAccountOwner); } if !info.data_is_empty() { @@ -237,6 +282,10 @@ pub fn load_sysvar<'a, 'info>( info: &'a AccountInfo<'info>, key: Pubkey, ) -> Result<(), ProgramError> { + if info.owner.ne(&sysvar::id()) { + return Err(ProgramError::InvalidAccountOwner); + } + load_account(info, key, false) } @@ -267,7 +316,7 @@ pub fn load_program<'a, 'info>( key: Pubkey, ) -> Result<(), ProgramError> { if info.key.ne(&key) { - return Err(ProgramError::InvalidAccountData); + return Err(ProgramError::IncorrectProgramId); } if !info.executable { diff --git a/src/processor/claim.rs b/src/processor/claim.rs index 9222db1..a96199a 100644 --- a/src/processor/claim.rs +++ b/src/processor/claim.rs @@ -39,6 +39,7 @@ pub fn process_claim<'a, 'info>( load_signer(signer)?; load_token_account(beneficiary_info, None, mint_info.key, true)?; load_mint(mint_info, true)?; + load_proof(proof_info, signer.key, true)?; load_treasury(treasury_info, true)?; load_token_account( treasury_tokens_info, @@ -48,15 +49,13 @@ pub fn process_claim<'a, 'info>( )?; load_program(token_program, spl_token::id())?; - // Validate claim amout + // Update claimable amount let mut proof_data = proof_info.data.borrow_mut(); let proof = Proof::try_from_bytes_mut(&mut proof_data)?; - if proof.claimable_rewards.lt(&amount) { - return Err(OreError::ClaimTooLarge.into()); - } - - // Update claimable amount - proof.claimable_rewards = proof.claimable_rewards.saturating_sub(amount); + proof.claimable_rewards = proof + .claimable_rewards + .checked_sub(amount) + .ok_or(OreError::ClaimTooLarge)?; // Update lifetime status let mut treasury_data = treasury_info.data.borrow_mut(); diff --git a/src/processor/initialize.rs b/src/processor/initialize.rs index 32f9476..b2f21fc 100644 --- a/src/processor/initialize.rs +++ b/src/processor/initialize.rs @@ -55,34 +55,31 @@ 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(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( metadata_info, &[ METADATA, mpl_token_metadata::ID.as_ref(), MINT_ADDRESS.as_ref(), - &[args.metadata_bump], ], + args.metadata_bump, &mpl_token_metadata::ID, )?; load_uninitialized_pda( mint_info, - &[MINT, MINT_NOISE.as_slice(), &[args.mint_bump]], - &crate::id(), - )?; - load_uninitialized_pda( - treasury_info, - &[TREASURY, &[args.treasury_bump]], + &[MINT, MINT_NOISE.as_slice()], + args.mint_bump, &crate::id(), )?; + load_uninitialized_pda(treasury_info, &[TREASURY], args.treasury_bump, &crate::id())?; load_uninitialized_account(treasury_tokens_info)?; load_program(system_program, system_program::id())?; load_program(token_program, spl_token::id())?; @@ -90,17 +87,6 @@ pub fn process_initialize<'a, 'info>( load_program(metadata_program, mpl_token_metadata::ID)?; load_sysvar(rent_sysvar, sysvar::rent::id())?; - // Verify keys - if metadata_info.key.ne(&METADATA_ADDRESS) { - return Err(ProgramError::InvalidSeeds); - } - if mint_info.key.ne(&MINT_ADDRESS) { - return Err(ProgramError::InvalidSeeds); - } - if treasury_info.key.ne(&TREASURY_ADDRESS) { - return Err(ProgramError::InvalidSeeds); - } - // 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, @@ -136,6 +122,9 @@ pub fn process_initialize<'a, 'info>( } // Initialize treasury + if treasury_info.key.ne(&TREASURY_ADDRESS) { + return Err(ProgramError::InvalidSeeds); + } create_pda( treasury_info, &crate::id(), @@ -156,6 +145,9 @@ pub fn process_initialize<'a, 'info>( drop(treasury_data); // Initialize mint + if mint_info.key.ne(&MINT_ADDRESS) { + return Err(ProgramError::InvalidSeeds); + } create_pda( mint_info, &spl_token::id(), @@ -182,6 +174,9 @@ pub fn process_initialize<'a, 'info>( )?; // Initialize mint metadata + if metadata_info.key.ne(&METADATA_ADDRESS) { + return Err(ProgramError::InvalidSeeds); + } mpl_token_metadata::instructions::CreateMetadataAccountV3Cpi { __program: metadata_program, metadata: metadata_info, diff --git a/src/processor/mine.rs b/src/processor/mine.rs index c03ef59..c0a9a63 100644 --- a/src/processor/mine.rs +++ b/src/processor/mine.rs @@ -47,7 +47,7 @@ pub fn process_mine<'a, 'info>( return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; - load_bus(bus_info, true)?; + load_any_bus(bus_info, true)?; load_proof(proof_info, signer.key, true)?; load_treasury(treasury_info, false)?; load_sysvar(slot_hashes_info, sysvar::slot_hashes::id())?; @@ -80,10 +80,10 @@ pub fn process_mine<'a, 'info>( // Update claimable rewards let mut bus_data = bus_info.data.borrow_mut(); let bus = Bus::try_from_bytes_mut(&mut bus_data)?; - if bus.rewards.lt(&treasury.reward_rate) { - return Err(OreError::BusRewardsInsufficient.into()); - } - bus.rewards = bus.rewards.saturating_sub(treasury.reward_rate); + bus.rewards = bus + .rewards + .checked_sub(treasury.reward_rate) + .ok_or(OreError::BusRewardsInsufficient)?; proof.claimable_rewards = proof.claimable_rewards.saturating_add(treasury.reward_rate); // Hash recent slot hash into the next challenge to prevent pre-mining attacks diff --git a/src/processor/register.rs b/src/processor/register.rs index 2c283c9..f9245ab 100644 --- a/src/processor/register.rs +++ b/src/processor/register.rs @@ -38,7 +38,8 @@ pub fn process_register<'a, 'info>( load_signer(signer)?; load_uninitialized_pda( proof_info, - &[PROOF, signer.key.as_ref(), &[args.bump]], + &[PROOF, signer.key.as_ref()], + args.bump, &crate::id(), )?; load_program(system_program, system_program::id())?; diff --git a/src/processor/reset.rs b/src/processor/reset.rs index 8940f0b..25bd61b 100644 --- a/src/processor/reset.rs +++ b/src/processor/reset.rs @@ -39,14 +39,14 @@ pub fn process_reset<'a, 'info>( return Err(ProgramError::NotEnoughAccountKeys); }; load_signer(signer)?; - load_bus(bus_0_info, true)?; - load_bus(bus_1_info, true)?; - load_bus(bus_2_info, true)?; - load_bus(bus_3_info, true)?; - load_bus(bus_4_info, true)?; - load_bus(bus_5_info, true)?; - load_bus(bus_6_info, true)?; - load_bus(bus_7_info, true)?; + 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( @@ -55,7 +55,7 @@ pub fn process_reset<'a, 'info>( mint_info.key, true, )?; - load_sysvar(token_program, spl_token::id())?; + 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, @@ -127,9 +127,9 @@ pub(crate) fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) - } // Calculate new reward rate. - let new_rate = (current_rate as u128) - .saturating_mul(TARGET_EPOCH_REWARDS as u128) - .saturating_div(epoch_rewards as u128) as u64; + let new_rate = (current_rate) + .saturating_mul(TARGET_EPOCH_REWARDS) + .saturating_div(epoch_rewards) 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); @@ -218,4 +218,16 @@ mod tests { let new_rate = calculate_new_reward_rate(current_rate, u64::MAX); assert!(new_rate.eq(¤t_rate.saturating_div(SMOOTHING_FACTOR))); } + + #[test] + fn test_calculate_new_reward_rate_max_inputs() { + let new_rate = calculate_new_reward_rate(BUS_EPOCH_REWARDS, MAX_EPOCH_REWARDS); + assert!(new_rate.eq(&BUS_EPOCH_REWARDS.saturating_div(SMOOTHING_FACTOR))); + } + + #[test] + fn test_calculate_new_reward_rate_min_inputs() { + let new_rate = calculate_new_reward_rate(1, 1); + assert!(new_rate.eq(&1u64.saturating_mul(SMOOTHING_FACTOR))); + } } diff --git a/src/processor/update_admin.rs b/src/processor/update_admin.rs index b74e324..773164f 100644 --- a/src/processor/update_admin.rs +++ b/src/processor/update_admin.rs @@ -40,7 +40,7 @@ pub fn process_update_admin<'a, 'info>( load_signer(signer)?; load_treasury(treasury_info, true)?; - // Validate admin signer + // 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) { diff --git a/src/processor/update_difficulty.rs b/src/processor/update_difficulty.rs index 3fd337b..78a6299 100644 --- a/src/processor/update_difficulty.rs +++ b/src/processor/update_difficulty.rs @@ -41,7 +41,7 @@ pub fn process_update_difficulty<'a, 'info>( load_signer(signer)?; load_treasury(treasury_info, true)?; - // Validate admin signer + // 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) { diff --git a/tests/test_mine.rs b/tests/test_mine.rs index f46bad5..0a329a0 100644 --- a/tests/test_mine.rs +++ b/tests/test_mine.rs @@ -4,8 +4,8 @@ use ore::{ instruction::{MineArgs, OreInstruction}, state::{Bus, Proof, Treasury}, utils::{AccountDeserialize, Discriminator}, - BUS_ADDRESSES, BUS_COUNT, INITIAL_REWARD_RATE, MINT_ADDRESS, PROOF, TOKEN_DECIMALS, TREASURY, - TREASURY_ADDRESS, + BUS_ADDRESSES, BUS_COUNT, INITIAL_REWARD_RATE, MINT_ADDRESS, PROOF, START_AT, TOKEN_DECIMALS, + TREASURY, TREASURY_ADDRESS, }; use rand::{distributions::Uniform, Rng}; use solana_program::{ @@ -33,7 +33,7 @@ use spl_token::state::{AccountState, Mint}; #[tokio::test] async fn test_mine() { // Setup - let (mut banks, payer, blockhash) = setup_program_test_env().await; + let (mut banks, payer, blockhash) = setup_program_test_env(true).await; // Submit register tx let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); @@ -130,11 +130,69 @@ async fn test_mine() { assert_eq!(beneficiary.close_authority, COption::None); } +#[tokio::test] +async fn test_mine_unfunded_bus() { + // Setup + let (mut banks, payer, blockhash) = setup_program_test_env(false).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], next_hash.into(), 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_exceptional_claim() { + // Setup + let (mut banks, payer, blockhash) = setup_program_test_env(false).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 mine 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_mine_fail_bad_data() { // Setup const FUZZ: usize = 10; - let (mut banks, payer, blockhash) = setup_program_test_env().await; + let (mut banks, payer, blockhash) = setup_program_test_env(true).await; // Submit register tx let proof_pda = Pubkey::find_program_address(&[PROOF, payer.pubkey().as_ref()], &ore::id()); @@ -319,7 +377,9 @@ fn find_next_hash(hash: KeccakHash, difficulty: KeccakHash, signer: Pubkey) -> ( (next_hash, nonce) } -async fn setup_program_test_env() -> (BanksClient, Keypair, solana_program::hash::Hash) { +async fn setup_program_test_env( + funded_busses: bool, +) -> (BanksClient, Keypair, solana_program::hash::Hash) { let mut program_test = ProgramTest::new("ore", ore::ID, processor!(ore::process_instruction)); program_test.prefer_bpf(true); @@ -334,7 +394,7 @@ async fn setup_program_test_env() -> (BanksClient, Keypair, solana_program::hash &(Bus::discriminator() as u64).to_le_bytes(), Bus { id: i as u64, - rewards: 250_000_000, + rewards: if funded_busses { 250_000_000 } else { 0 }, } .to_bytes(), ] @@ -358,7 +418,7 @@ async fn setup_program_test_env() -> (BanksClient, Keypair, solana_program::hash bump: treasury_pda.1 as u64, admin: admin_address, difficulty: KeccakHash::new_from_array([u8::MAX; 32]).into(), - last_reset_at: 100, + last_reset_at: START_AT, reward_rate: INITIAL_REWARD_RATE, total_claimed_rewards: 0, } @@ -414,11 +474,11 @@ async fn setup_program_test_env() -> (BanksClient, Keypair, solana_program::hash program_test.add_sysvar_account( sysvar::clock::id(), &Clock { - slot: 10, + slot: 0, epoch_start_timestamp: 0, epoch: 0, leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, - unix_timestamp: 100, + unix_timestamp: START_AT + 1, }, ); diff --git a/tests/test_register.rs b/tests/test_register.rs index 8342b79..e3aa4b4 100644 --- a/tests/test_register.rs +++ b/tests/test_register.rs @@ -164,11 +164,11 @@ async fn setup_program_test_env() -> (BanksClient, Keypair, solana_program::hash program_test.add_sysvar_account( sysvar::clock::id(), &Clock { - slot: 10, + slot: 0, epoch_start_timestamp: 0, epoch: 0, leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, - unix_timestamp: 100, + unix_timestamp: 0, }, ); diff --git a/tests/test_reset.rs b/tests/test_reset.rs index 8a5e1a9..6f231eb 100644 --- a/tests/test_reset.rs +++ b/tests/test_reset.rs @@ -5,7 +5,7 @@ use ore::{ state::{Bus, Treasury}, utils::{AccountDeserialize, Discriminator}, BUS, BUS_ADDRESSES, BUS_COUNT, BUS_EPOCH_REWARDS, INITIAL_DIFFICULTY, INITIAL_REWARD_RATE, - MAX_EPOCH_REWARDS, MINT_ADDRESS, TOKEN_DECIMALS, TREASURY, TREASURY_ADDRESS, + MAX_EPOCH_REWARDS, MINT_ADDRESS, START_AT, TOKEN_DECIMALS, TREASURY, TREASURY_ADDRESS, }; use rand::seq::SliceRandom; use solana_program::{ @@ -71,7 +71,7 @@ async fn test_reset() { Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap() ); assert_eq!(treasury.difficulty, INITIAL_DIFFICULTY.into()); - assert_eq!(treasury.last_reset_at as u8, 100); + assert_eq!(treasury.last_reset_at, START_AT); assert_eq!(treasury.reward_rate, INITIAL_REWARD_RATE.saturating_div(2)); assert_eq!(treasury.total_claimed_rewards as u8, 0); @@ -104,7 +104,7 @@ async fn test_reset() { } #[tokio::test] -async fn test_reset_busses_out_of_order() { +async fn test_reset_busses_out_of_order_fail() { // Setup let (mut banks, payer, blockhash) = setup_program_test_env().await; @@ -147,15 +147,45 @@ async fn test_reset_busses_out_of_order() { }; 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!(res.is_err()); +} - // 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.rewards, BUS_EPOCH_REWARDS); - } +#[tokio::test] +async fn test_reset_busses_duplicate_fail() { + // Setup + let (mut banks, payer, blockhash) = setup_program_test_env().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] @@ -308,11 +338,11 @@ async fn setup_program_test_env() -> (BanksClient, Keypair, Hash) { program_test.add_sysvar_account( sysvar::clock::id(), &Clock { - slot: 10, + slot: 0, epoch_start_timestamp: 0, epoch: 0, leader_schedule_epoch: DEFAULT_SLOTS_PER_EPOCH, - unix_timestamp: 100, + unix_timestamp: START_AT, }, );