diff --git a/program/src/claim.rs b/program/src/claim.rs index 009188b..b6b809a 100644 --- a/program/src/claim.rs +++ b/program/src/claim.rs @@ -6,14 +6,7 @@ use solana_program::{ use crate::utils::AccountDeserialize; -/// 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 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. +/// Claim distributes ORE from the treasury to a miner. pub fn process_claim<'a, 'info>(accounts: &'a [AccountInfo<'info>], data: &[u8]) -> ProgramResult { // Parse args let args = ClaimArgs::try_from_bytes(data)?; diff --git a/program/src/close.rs b/program/src/close.rs index 6898c7d..b266329 100644 --- a/program/src/close.rs +++ b/program/src/close.rs @@ -6,14 +6,7 @@ use solana_program::{ use crate::utils::AccountDeserialize; -/// 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. -/// -/// 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. +/// Close closes a proof account and returns the rent to the owner. pub fn process_close<'a, 'info>(accounts: &'a [AccountInfo<'info>], _data: &[u8]) -> ProgramResult { // Load accounts let [signer, proof_info, system_program] = accounts else { diff --git a/program/src/initialize.rs b/program/src/initialize.rs index 3ce4bf6..a1ac336 100644 --- a/program/src/initialize.rs +++ b/program/src/initialize.rs @@ -18,24 +18,7 @@ use spl_token::state::Mint; use crate::utils::{create_pda, AccountDeserialize, Discriminator}; -/// Initialize sets up the Ore program. Its responsibilities include: -/// 1. Initialize the 8 bus accounts. -/// 2. Initialize the treasury account. -/// 3. Initialize the Ore mint account. -/// 4. Initialize the mint metadata account. -/// 5. Initialize the treasury token account. -/// 6. Set the signer as the program admin. -/// -/// 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, metadata program, and rent sysvar are valid. -/// -/// Discussion -/// - The signer of this instruction is set as the program admin and the -/// upgrade authority of the mint metadata account. +/// Initialize sets up the ORE program to begin mining. pub fn process_initialize<'a, 'info>( accounts: &'a [AccountInfo<'info>], data: &[u8], diff --git a/program/src/mine.rs b/program/src/mine.rs index 74c5f11..6de4314 100644 --- a/program/src/mine.rs +++ b/program/src/mine.rs @@ -16,7 +16,6 @@ use solana_program::{ blake3::hashv, clock::Clock, entrypoint::ProgramResult, - log::sol_log, program_error::ProgramError, pubkey::Pubkey, sanitize::SanitizeError, @@ -27,20 +26,7 @@ use solana_program::{ use crate::utils::AccountDeserialize; -/// 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. -/// 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 mining is not paused. -/// - Can only succeed if the last reset was less than 60 seconds ago. -/// - Can only succeed if the provided hash satisfies the minimum difficulty requirement. -/// - Can only succeed if the miners proof pubkey matches the declared proof pubkey. -/// - The provided proof account must be associated with the signer. -/// - The provided bus, config, noise, stake, and slot hash sysvar must be valid. +/// Mine validates hashes and increments a miner's collectable balance. pub fn process_mine<'a, 'info>(accounts: &'a [AccountInfo<'info>], data: &[u8]) -> ProgramResult { // Parse args let args = MineArgs::try_from_bytes(data)?; @@ -59,13 +45,7 @@ pub fn process_mine<'a, 'info>(accounts: &'a [AccountInfo<'info>], data: &[u8]) load_sysvar(slot_hashes_sysvar, sysvar::slot_hashes::id())?; // Authenticate the proof account - if let Ok(Some(auth_address)) = authenticate(&instructions_sysvar.data.borrow()) { - if auth_address.ne(proof_info.key) { - return Err(OreError::AuthFailed.into()); - } - } else { - return Err(OreError::AuthFailed.into()); - } + authenticate(&instructions_sysvar.data.borrow(), proof_info.key)?; // Validate epoch is active. let config_data = config_info.data.borrow(); @@ -110,7 +90,6 @@ pub fn process_mine<'a, 'info>(accounts: &'a [AccountInfo<'info>], data: &[u8]) .base_reward_rate .checked_mul(2u64.checked_pow(normalized_difficulty).unwrap()) .unwrap(); - sol_log(&format!("Diff {}", difficulty)); // Apply staking multiplier. // If user has greater than or equal to the max stake on the network, they receive 2x multiplier. @@ -183,24 +162,29 @@ pub fn process_mine<'a, 'info>(accounts: &'a [AccountInfo<'info>], data: &[u8]) Ok(()) } -/// Get the authenticated pubkey. +/// Authenticate the proof account. /// -/// The intent here is to disincentivize sybil. If a user can fit multiple hashes into 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. +/// This process is necessary to prevent sybil attacks. If a user can pack multiple hashes into a single +/// transaction, then there is a financial incentive to mine across multiple keypairs and submit as many hashes +/// as possible in 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. -/// -/// We solve this by "authenticating" the proof account on every mine instruction. That is, -/// every transaction with a `mine` instruction needs to include an `auth` instruction that -/// specifies the proof account that will be used. The `auth` instruction must be first ORE -/// instruction in the transaction. The `mine` instruction should error out if the provided proof -/// account doesn't match the authenticated address. -/// -/// Errors if: -/// - Fails to find and parse an authentication address. -fn authenticate(data: &[u8]) -> Result, SanitizeError> { +/// We prevent this by forcing every transaction to declare the proof account being mined with upfont. +/// The authentication process includes passing the 32 byte pubkey address as instruction data to the +/// CU-optimized noop program. We parse this address through transaction introspection and use it to +/// ensure only one proof account can be used for `mine` instructions for a given transaction. +fn authenticate(data: &[u8], proof_address: &Pubkey) -> ProgramResult { + if let Ok(Some(auth_address)) = parse_auth_address(data) { + if proof_address.ne(&auth_address) { + return Err(OreError::AuthFailed.into()); + } + } else { + return Err(OreError::AuthFailed.into()); + } + Ok(()) +} + +/// Use transaction introspection to parse the authenticated pubkey. +fn parse_auth_address(data: &[u8]) -> Result, SanitizeError> { // Start the current byte index at 0 let mut curr = 0; let num_instructions = read_u16(&mut curr, data)?; diff --git a/program/src/open.rs b/program/src/open.rs index 3ea203d..81c88d2 100644 --- a/program/src/open.rs +++ b/program/src/open.rs @@ -14,15 +14,11 @@ use solana_program::{ use crate::utils::{create_pda, AccountDeserialize, Discriminator}; -/// 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. +/// Open creates a new proof account to track a miner's state. /// /// 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 if the user does not already have a proof account. -/// - The provided system program must be valid. pub fn process_open<'a, 'info>(accounts: &'a [AccountInfo<'info>], data: &[u8]) -> ProgramResult { // Parse args let args = OpenArgs::try_from_bytes(data)?; diff --git a/program/src/reset.rs b/program/src/reset.rs index 5b21d1c..aa15315 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -12,25 +12,7 @@ use spl_token::state::Mint; use crate::utils::AccountDeserialize; -/// 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 fund claims. -/// -/// Safety requirements: -/// - Reset is a permissionless instruction and can be invoked by any signer. -/// - Can only succeed if START_AT has passed. -/// - Can only succeed if more tha 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 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 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. +/// Reset tops up the busses, updates the base reward rate, and generally sets up the ORE program for the next epoch. 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, config_info, mint_info, treasury_info, treasury_tokens_info, token_program] = diff --git a/program/src/stake.rs b/program/src/stake.rs index 9ef6a89..b94edf5 100644 --- a/program/src/stake.rs +++ b/program/src/stake.rs @@ -7,14 +7,7 @@ use solana_program::{ use crate::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. -/// 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. +/// Stake deposits ORE into a proof account to earn multiplier. pub fn process_stake<'a, 'info>(accounts: &'a [AccountInfo<'info>], data: &[u8]) -> ProgramResult { // Parse args let args = StakeArgs::try_from_bytes(data)?; diff --git a/program/src/upgrade.rs b/program/src/upgrade.rs index 7c1861c..48df436 100644 --- a/program/src/upgrade.rs +++ b/program/src/upgrade.rs @@ -6,13 +6,7 @@ use solana_program::{ }; use spl_token::state::Mint; -/// 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. +/// Upgrade allows a user to migrate a v1 token to a v2 token at a 1:1 exchange rate. pub fn process_upgrade<'a, 'info>( accounts: &'a [AccountInfo<'info>], data: &[u8],