This commit is contained in:
Hardhat Chad
2024-03-07 18:48:01 +00:00
parent cba2c748e2
commit 3af8f31089
20 changed files with 165 additions and 27 deletions

View File

@@ -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>],

View File

@@ -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;

View File

@@ -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

View File

@@ -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>],

View File

@@ -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;
}

View File

@@ -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>],

View File

@@ -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>],