mirror of
https://github.com/d0zingcat/ore.git
synced 2026-05-13 23:16:52 +00:00
comments
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
use solana_program::{keccak::Hash, pubkey, pubkey::Pubkey};
|
||||
|
||||
// TODO Set this before deployment
|
||||
/// The unix timestamp after which mining is allowed.
|
||||
pub const START_AT: i64 = 0;
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ use thiserror::Error;
|
||||
pub enum OreError {
|
||||
#[error("The epoch is still active and cannot be reset")]
|
||||
EpochActive = 0,
|
||||
#[error("The epoch has expired and needs reset")]
|
||||
EpochExpired = 1,
|
||||
#[error("The epoch has ended and needs reset")]
|
||||
EpochEnded = 1,
|
||||
#[error("The provided hash was invalid")]
|
||||
InvalidHash = 2,
|
||||
#[error("The provided hash does not satisfy the difficulty requirement")]
|
||||
|
||||
@@ -14,7 +14,9 @@ use solana_program::{
|
||||
program_error::ProgramError, pubkey::Pubkey,
|
||||
};
|
||||
|
||||
// TODO Increase decimals?
|
||||
// TODO Increase TOKEN_DECIMALS to 12?
|
||||
// TODO Set START_AT before launch.
|
||||
// TODO Set pubkey consts for derived mainnet pdas before lanch.
|
||||
|
||||
declare_id!("oreoDL2qcXyBdaYTEfd7F5MFLY5PAqqDooPLZv1XdBP");
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ use crate::{
|
||||
BUS_ADDRESSES, BUS_COUNT, MINT_ADDRESS, TREASURY_ADDRESS,
|
||||
};
|
||||
|
||||
/// Errors if:
|
||||
/// - Account is not a signer.
|
||||
pub fn load_signer<'a, 'info>(info: &'a AccountInfo<'info>) -> Result<(), ProgramError> {
|
||||
if !info.is_signer {
|
||||
return Err(ProgramError::MissingRequiredSignature);
|
||||
@@ -18,6 +20,13 @@ pub fn load_signer<'a, 'info>(info: &'a AccountInfo<'info>) -> Result<(), Progra
|
||||
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 0-7 range.
|
||||
/// - Address is not in set of valid bus address.
|
||||
/// - Expected to be writable, but is not.
|
||||
pub fn load_bus<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
is_writable: bool,
|
||||
@@ -48,6 +57,12 @@ pub fn load_bus<'a, 'info>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Errors if:
|
||||
/// - Account is not owned by Ore program.
|
||||
/// - Data is empty.
|
||||
/// - Data cannot deserialize into a proof account.
|
||||
/// - Proof authority does not match the expected address.
|
||||
/// - Expected to be writable, but is not.
|
||||
pub fn load_proof<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
authority: &Pubkey,
|
||||
@@ -75,6 +90,12 @@ pub fn load_proof<'a, 'info>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Errors if:
|
||||
/// - Account is not owned by Ore program.
|
||||
/// - Data is empty.
|
||||
/// - Data cannot deserialize into a treasury account.
|
||||
/// - Address does not match the expected address.
|
||||
/// - Expected to be writable, but is not.
|
||||
pub fn load_treasury<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
is_writable: bool,
|
||||
@@ -98,6 +119,12 @@ pub fn load_treasury<'a, 'info>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Errors if:
|
||||
/// - Account is not owned by SPL token program.
|
||||
/// - Data is empty.
|
||||
/// - Data cannot deserialize into a mint account.
|
||||
/// - Address does not match the expected mint address.
|
||||
/// - Expected to be writable, but is not.
|
||||
pub fn load_mint<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
is_writable: bool,
|
||||
@@ -126,6 +153,13 @@ pub fn load_mint<'a, 'info>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Errors if:
|
||||
/// - Account is not owned by SPL token program.
|
||||
/// - Data is empty.
|
||||
/// - Data cannot deserialize into a token account.
|
||||
/// - Token account owner does not match the expected owner address.
|
||||
/// - Token account mint does not match the expected mint address.
|
||||
/// - Expected to be writable, but is not.
|
||||
pub fn load_token_account<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
owner: Option<&Pubkey>,
|
||||
@@ -161,6 +195,9 @@ pub fn load_token_account<'a, 'info>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Errors if:
|
||||
/// - Address does not match PDA derived from provided seeds.
|
||||
/// - Cannot load as an uninitialized account.
|
||||
pub fn load_uninitialized_pda<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
seeds: &[&[u8]],
|
||||
@@ -172,6 +209,10 @@ pub fn load_uninitialized_pda<'a, 'info>(
|
||||
load_uninitialized_account(info)
|
||||
}
|
||||
|
||||
/// Errors if:
|
||||
/// - Account is not owned by the system program.
|
||||
/// - Data is not empty.
|
||||
/// - Account is not writable.
|
||||
pub fn load_uninitialized_account<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
) -> Result<(), ProgramError> {
|
||||
@@ -189,6 +230,8 @@ pub fn load_uninitialized_account<'a, 'info>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Errors if:
|
||||
/// - Account cannot load with the expected address.
|
||||
pub fn load_sysvar<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
key: Pubkey,
|
||||
@@ -196,6 +239,9 @@ pub fn load_sysvar<'a, 'info>(
|
||||
load_account(info, key, false)
|
||||
}
|
||||
|
||||
/// Errors if:
|
||||
/// - Account does not match the expected value.
|
||||
/// - Expected to be writable, but is not.
|
||||
pub fn load_account<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
key: Pubkey,
|
||||
@@ -212,6 +258,9 @@ pub fn load_account<'a, 'info>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Errors if:
|
||||
/// - Address does not match the expected value.
|
||||
/// - Account is not executable.
|
||||
pub fn load_program<'a, 'info>(
|
||||
info: &'a AccountInfo<'info>,
|
||||
key: Pubkey,
|
||||
|
||||
@@ -12,6 +12,15 @@ use crate::{
|
||||
TREASURY,
|
||||
};
|
||||
|
||||
/// Claim distributes owed token rewards from the treasury to the miner. It has 4 responsibilies:
|
||||
/// 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.
|
||||
///
|
||||
/// Safety requirements:
|
||||
/// - Claim is a permissionless instruction and can be called by any miner.
|
||||
/// - Can only succeed if the claimed amount is less than or equal to the miner's claimable rewards.
|
||||
/// - The provided beneficiary token account, mint, treasury, treasury token account, and token program must be valid.
|
||||
pub fn process_claim<'a, 'info>(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &'a [AccountInfo<'info>],
|
||||
|
||||
@@ -21,6 +21,21 @@ use crate::{
|
||||
TOKEN_DECIMALS, TREASURY, TREASURY_ADDRESS,
|
||||
};
|
||||
|
||||
/// Initialize sets up the Ore program state. Has 4 responisbilities:
|
||||
/// 1. Initializes the 8 bus accounts.
|
||||
/// 2. Initializes the treasury account.
|
||||
/// 3. Initializes the Ore mint account.
|
||||
/// 4. Initializes the treasury token account.
|
||||
/// 5. Sets the admin address as the signer.
|
||||
///
|
||||
/// Safety requirements:
|
||||
/// - Initialize is a permissionless instruction and can be called by anyone.
|
||||
/// - Can only succeed once for the entire lifetime of the program.
|
||||
/// - Can only succeed if all provided PDAs match their expected values.
|
||||
/// - Can only succeed if provided system program, token program, associated token program, and rent sysvar are valid.
|
||||
///
|
||||
/// Discussion
|
||||
/// - The caller of this instruction is set as the admin of the program.
|
||||
pub fn process_initialize<'a, 'info>(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &'a [AccountInfo<'info>],
|
||||
@@ -108,7 +123,7 @@ pub fn process_initialize<'a, 'info>(
|
||||
let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?;
|
||||
treasury.bump = args.treasury_bump as u64;
|
||||
treasury.admin = *signer.key;
|
||||
treasury.epoch_start_at = 0;
|
||||
treasury.last_reset_at = 0;
|
||||
treasury.difficulty = INITIAL_DIFFICULTY.into();
|
||||
treasury.reward_rate = INITIAL_REWARD_RATE;
|
||||
treasury.total_claimed_rewards = 0;
|
||||
|
||||
@@ -22,6 +22,18 @@ use crate::{
|
||||
EPOCH_DURATION,
|
||||
};
|
||||
|
||||
/// Mine is the primary workhorse instruction of the Ore program. It has 4 responsibilities including:
|
||||
/// 1. Verify the provided hash is valid.
|
||||
/// 2. Increment the user's claimable rewards counter.
|
||||
/// 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 miner.
|
||||
/// - 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.
|
||||
/// - The the provided proof account must be associated with the signer.
|
||||
/// - The provided bus, treasury, and slot hash sysvar must be valid.
|
||||
pub fn process_mine<'a, 'info>(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &'a [AccountInfo<'info>],
|
||||
@@ -44,9 +56,9 @@ pub fn process_mine<'a, 'info>(
|
||||
let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?;
|
||||
let treasury_data = treasury_info.data.borrow();
|
||||
let treasury = Treasury::try_from_bytes(&treasury_data)?;
|
||||
let epoch_end_at = treasury.epoch_start_at.saturating_add(EPOCH_DURATION);
|
||||
if clock.unix_timestamp.ge(&epoch_end_at) {
|
||||
return Err(OreError::EpochExpired.into());
|
||||
let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION);
|
||||
if clock.unix_timestamp.ge(&threshold) {
|
||||
return Err(OreError::EpochEnded.into());
|
||||
}
|
||||
|
||||
// Validate provided hash
|
||||
|
||||
@@ -14,6 +14,15 @@ use crate::{
|
||||
PROOF,
|
||||
};
|
||||
|
||||
/// Register generates a new hash chain for a prospective miner. It has 2 responsibilities:
|
||||
/// 1. Initializes a new proof account.
|
||||
/// 2. Generates an initial hash for the miner from the signer's key.
|
||||
///
|
||||
/// Safety requirements:
|
||||
/// - Register is a permissionless instruction and can be called by anyone.
|
||||
/// - 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_register<'a, 'info>(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &'a [AccountInfo<'info>],
|
||||
|
||||
@@ -12,6 +12,22 @@ use crate::{
|
||||
TARGET_EPOCH_REWARDS, TREASURY,
|
||||
};
|
||||
|
||||
/// Reset transitions the Ore program from one epoch to the next. It is the most complex instruction in the
|
||||
/// Ore program and has three primary responsibilities including:
|
||||
/// 1. Reset bus account rewards counters.
|
||||
/// 2. Adjust the reward rate to stabilize inflation.
|
||||
/// 3. Top up the treasury token account to backup claims.
|
||||
///
|
||||
/// Safety requirements:
|
||||
/// - Reset is a permissionless crank function and can be invoked by anyone.
|
||||
/// - Can only succeed if more 60 seconds or more have passed since the last successful reset.
|
||||
/// - The busses, mint, treasury, treasury token account, and token program must all be valid.
|
||||
///
|
||||
/// Discussion:
|
||||
/// - It is critical 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 (measured hashpower) to
|
||||
/// target an average supply growth rate of 1 ORE/min.
|
||||
pub fn process_reset<'a, 'info>(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &'a [AccountInfo<'info>],
|
||||
@@ -46,30 +62,32 @@ pub fn process_reset<'a, 'info>(
|
||||
bus_7_info,
|
||||
];
|
||||
|
||||
// Validate epoch has ended
|
||||
// Validate at least 60 seconds have passed since last reset
|
||||
let clock = Clock::get().or(Err(ProgramError::InvalidAccountData))?;
|
||||
let mut treasury_data = treasury_info.data.borrow_mut();
|
||||
let treasury = Treasury::try_from_bytes_mut(&mut treasury_data)?;
|
||||
let epoch_end_at = treasury.epoch_start_at.saturating_add(EPOCH_DURATION);
|
||||
if clock.unix_timestamp.lt(&epoch_end_at) {
|
||||
let threshold = treasury.last_reset_at.saturating_add(EPOCH_DURATION);
|
||||
if clock.unix_timestamp.lt(&threshold) {
|
||||
return Err(OreError::EpochActive.into());
|
||||
}
|
||||
|
||||
// Reset busses
|
||||
let mut total_bus_rewards = 0u64;
|
||||
// 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_bus_rewards = total_bus_rewards.saturating_add(bus.rewards);
|
||||
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 the reward rate for the next epoch
|
||||
let total_epoch_rewards = MAX_EPOCH_REWARDS.saturating_sub(total_bus_rewards);
|
||||
// Update reward rate for next epoch
|
||||
treasury.reward_rate = calculate_new_reward_rate(treasury.reward_rate, total_epoch_rewards);
|
||||
treasury.epoch_start_at = clock.unix_timestamp;
|
||||
|
||||
// Top up treasury token account
|
||||
// Fund treasury token account
|
||||
let treasury_bump = treasury.bump as u8;
|
||||
drop(treasury_data);
|
||||
solana_program::program::invoke_signed(
|
||||
@@ -93,8 +111,13 @@ pub fn process_reset<'a, 'info>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This function calculates what the new reward rate should be based on how many total rewards were mined in the prior epoch.
|
||||
/// The math is largely identitical to that used by the Bitcoin network for updating the difficulty between each epoch.
|
||||
/// new_rate = current_rate * (target_rewards / actual_rewards)
|
||||
/// The new rate is then smoothed by a constant factor to avoid unexpectedly large fluctuations.
|
||||
/// In Ore's case, the epochs are so short (60 seconds) that the smoothing factor of 2 has been chosen.
|
||||
pub(crate) fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) -> u64 {
|
||||
// Avoid division by zero. Leave the reward rate unchanged.
|
||||
// Avoid division by zero. Leave the reward rate unchanged, if detected.
|
||||
if epoch_rewards.eq(&0) {
|
||||
return current_rate;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,12 @@ use solana_program::{
|
||||
|
||||
use crate::{instruction::UpdateAdminArgs, loaders::*, state::Treasury, utils::AccountDeserialize};
|
||||
|
||||
/// UpdateAdmin updates the program's admin account. It has 1 responsibility:
|
||||
/// 1. Update the treasury admin address.
|
||||
///
|
||||
/// Safety requirements:
|
||||
/// - Can only succeed if the signer is the current program admin.
|
||||
/// - Can only succeed if the provided treasury is valid.
|
||||
pub fn process_update_admin<'a, 'info>(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &'a [AccountInfo<'info>],
|
||||
|
||||
@@ -7,6 +7,12 @@ use crate::{
|
||||
instruction::UpdateDifficultyArgs, loaders::*, state::Treasury, utils::AccountDeserialize,
|
||||
};
|
||||
|
||||
/// UpdateDifficulty updates the program's global difficulty value. It has 1 responsibility:
|
||||
/// 1. Update the difficulty.
|
||||
///
|
||||
/// Safety requirements:
|
||||
/// - Can only succeed if the signer is the current program admin.
|
||||
/// - Can only succeed if the provided treasury is valid.
|
||||
pub fn process_update_difficulty<'a, 'info>(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &'a [AccountInfo<'info>],
|
||||
|
||||
@@ -6,6 +6,9 @@ use crate::{
|
||||
utils::{AccountDiscriminator, Discriminator},
|
||||
};
|
||||
|
||||
/// 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 {
|
||||
|
||||
@@ -5,6 +5,7 @@ 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]);
|
||||
|
||||
@@ -8,6 +8,8 @@ use crate::{
|
||||
utils::{AccountDiscriminator, Discriminator},
|
||||
};
|
||||
|
||||
/// 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.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)]
|
||||
pub struct Proof {
|
||||
|
||||
@@ -8,6 +8,8 @@ use crate::{
|
||||
utils::{AccountDiscriminator, Discriminator},
|
||||
};
|
||||
|
||||
/// 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.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Pod, ShankAccount, Zeroable)]
|
||||
pub struct Treasury {
|
||||
@@ -20,8 +22,8 @@ pub struct Treasury {
|
||||
/// The hash difficulty.
|
||||
pub difficulty: Hash,
|
||||
|
||||
/// The timestamp of the start of the current epoch.
|
||||
pub epoch_start_at: i64,
|
||||
/// 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,
|
||||
|
||||
@@ -43,7 +43,7 @@ async fn test_initialize() {
|
||||
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.epoch_start_at as u8, 0);
|
||||
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);
|
||||
|
||||
|
||||
@@ -184,7 +184,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(),
|
||||
epoch_start_at: 100,
|
||||
last_reset_at: 100,
|
||||
reward_rate: INITIAL_REWARD_RATE,
|
||||
total_claimed_rewards: 0,
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ async fn test_reset() {
|
||||
Pubkey::from_str("AeNqnoLwFanMd3ig9WoMxQZVwQHtCtqKMMBsT1sTrvz6").unwrap()
|
||||
);
|
||||
assert_eq!(treasury.difficulty, INITIAL_DIFFICULTY.into());
|
||||
assert_eq!(treasury.epoch_start_at as u8, 100);
|
||||
assert_eq!(treasury.last_reset_at as u8, 100);
|
||||
assert_eq!(treasury.reward_rate, INITIAL_REWARD_RATE.saturating_div(2));
|
||||
assert_eq!(treasury.total_claimed_rewards as u8, 0);
|
||||
|
||||
@@ -134,7 +134,7 @@ async fn setup_program_test_env() -> (BanksClient, Keypair, Hash) {
|
||||
bump: treasury_pda.1 as u64,
|
||||
admin: admin_address,
|
||||
difficulty: INITIAL_DIFFICULTY.into(),
|
||||
epoch_start_at: 0,
|
||||
last_reset_at: 0,
|
||||
reward_rate: INITIAL_REWARD_RATE,
|
||||
total_claimed_rewards: 0,
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ async fn test_update_admin() {
|
||||
assert_eq!(treasury_.bump, treasury.bump);
|
||||
assert_eq!(treasury_.admin, new_admin);
|
||||
assert_eq!(treasury_.difficulty, treasury.difficulty);
|
||||
assert_eq!(treasury_.epoch_start_at, treasury.epoch_start_at);
|
||||
assert_eq!(treasury_.last_reset_at, treasury.last_reset_at);
|
||||
assert_eq!(treasury_.reward_rate, treasury.reward_rate);
|
||||
assert_eq!(
|
||||
treasury_.total_claimed_rewards,
|
||||
|
||||
@@ -34,7 +34,7 @@ async fn test_update_difficulty() {
|
||||
assert_eq!(treasury_.bump, treasury.bump);
|
||||
assert_eq!(treasury_.admin, treasury.admin);
|
||||
assert_eq!(treasury_.difficulty, new_difficulty.into());
|
||||
assert_eq!(treasury_.epoch_start_at, treasury.epoch_start_at);
|
||||
assert_eq!(treasury_.last_reset_at, treasury.last_reset_at);
|
||||
assert_eq!(treasury_.reward_rate, treasury.reward_rate);
|
||||
assert_eq!(
|
||||
treasury_.total_claimed_rewards,
|
||||
|
||||
Reference in New Issue
Block a user