From f5fc6f25905d08f955fb7886899c364eace07044 Mon Sep 17 00:00:00 2001 From: Hardhat Chad Date: Wed, 10 Jan 2024 17:14:40 +0000 Subject: [PATCH] Bus token accounts --- programs/ore/src/lib.rs | 287 ++++++++++++++++++++++++++++++++-------- 1 file changed, 235 insertions(+), 52 deletions(-) diff --git a/programs/ore/src/lib.rs b/programs/ore/src/lib.rs index 97314ec..2b167e2 100644 --- a/programs/ore/src/lib.rs +++ b/programs/ore/src/lib.rs @@ -7,7 +7,10 @@ use anchor_lang::{ system_program, sysvar, }, }; -use anchor_spl::token::{self, Mint, MintTo, TokenAccount}; +use anchor_spl::{ + associated_token, + token::{self, Mint, MintTo, TokenAccount}, +}; declare_id!("CeJShZEAzBLwtcLQvbZc7UT38e4nUTn63Za5UFyYYDTS"); @@ -24,16 +27,19 @@ pub const START_AT: i64 = 0; /// The duration of an epoch, in units of seconds. pub const EPOCH_DURATION: i64 = 60; -/// The quantity of ORE expected to be minted per epoch, in units of non-divisible nanoORE. +/// One ORE token, denominated in indivisible units of nanoORE. +pub const ONE_ORE: u64 = 10u64.pow(TOKEN_DECIMALS as u32); + +/// The quantity of ORE expected to be minted per epoch, in units of indivisible nanoORE. /// Inflation rate = 1 ORE / epoch -pub const EXPECTED_EPOCH_REWARDS: u64 = 10u64.pow(TOKEN_DECIMALS as u32); +pub const EXPECTED_EPOCH_REWARDS: u64 = ONE_ORE; // 10u64.pow(TOKEN_DECIMALS as u32); /// The smoothing factor for reward rate changes. The reward rate cannot change by more or less /// than factor of this constant from one epoch to the next. -pub const SMOOTHING_FACTOR: u64 = 16; +pub const SMOOTHING_FACTOR: u64 = 8; /// The decimal precision of the ORE token. -/// Using SI prefixes, the smallest non-divisible unit of ORE is a nanoORE. +/// 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; @@ -73,6 +79,11 @@ mod ore { Ok(()) } + /// Initializes an associated token account for a bus. Can only be invoked once per bus. + pub fn initialize_bus_tokens(_ctx: Context) -> Result<()> { + Ok(()) + } + /// Initializes a proof account for a new miner. pub fn register(ctx: Context) -> Result<()> { let proof = &mut ctx.accounts.proof; @@ -114,27 +125,68 @@ mod ore { // Update the reward amount for the next epoch. metadata.reward_rate = calculate_new_reward_rate(metadata.reward_rate, total_epoch_rewards); + metadata.epoch_start_at = clock.unix_timestamp; // Reset bus accounts. - bus_0.hashes = 0; - bus_1.hashes = 0; - bus_2.hashes = 0; - bus_3.hashes = 0; - bus_4.hashes = 0; - bus_5.hashes = 0; - bus_6.hashes = 0; - bus_7.hashes = 0; - bus_0.rewards = 0; - bus_1.rewards = 0; - bus_2.rewards = 0; - bus_3.rewards = 0; - bus_4.rewards = 0; - bus_5.rewards = 0; - bus_6.rewards = 0; - bus_7.rewards = 0; - - // Record the new epoch start time. - metadata.epoch_start_at = clock.unix_timestamp; + let mint = &ctx.accounts.mint; + let metadata = &ctx.accounts.metadata; + let token_program = &ctx.accounts.token_program; + reset_bus( + bus_0, + &mut ctx.accounts.bus_0_tokens, + mint, + metadata, + token_program, + )?; + reset_bus( + bus_1, + &mut ctx.accounts.bus_1_tokens, + mint, + metadata, + token_program, + )?; + reset_bus( + bus_2, + &mut ctx.accounts.bus_2_tokens, + mint, + metadata, + token_program, + )?; + reset_bus( + bus_3, + &mut ctx.accounts.bus_3_tokens, + mint, + metadata, + token_program, + )?; + reset_bus( + bus_4, + &mut ctx.accounts.bus_4_tokens, + mint, + metadata, + token_program, + )?; + reset_bus( + bus_5, + &mut ctx.accounts.bus_5_tokens, + mint, + metadata, + token_program, + )?; + reset_bus( + bus_6, + &mut ctx.accounts.bus_6_tokens, + mint, + metadata, + token_program, + )?; + reset_bus( + bus_7, + &mut ctx.accounts.bus_7_tokens, + mint, + metadata, + token_program, + )?; Ok(()) } @@ -183,23 +235,26 @@ mod ore { // That is, Solana should reach its network saturation point long before this quota // is enforced here. require!( - ctx.accounts - .bus - .hashes + bus.hashes .le(&EXPECTED_EPOCH_REWARDS.saturating_div(BUS_COUNT as u64)), ProgramError::BusQuotaFilled ); - // Mint tokens to beneficiary. - token::mint_to( + // Issue tokens from bus to beneficiary. + let bus_tokens = &ctx.accounts.bus_tokens; + require!( + bus_tokens.amount.ge(&metadata.reward_rate), + ProgramError::BusInsufficientFunds + ); + token::transfer( CpiContext::new_with_signer( ctx.accounts.token_program.to_account_info(), - MintTo { - authority: metadata.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), + token::Transfer { + from: bus_tokens.to_account_info(), to: ctx.accounts.beneficiary.to_account_info(), + authority: bus.to_account_info(), }, - &[&[METADATA, &[metadata.bump]]], + &[&[BUS, &[bus.id], &[bus.bump]]], ), metadata.reward_rate, )?; @@ -251,6 +306,37 @@ fn calculate_new_reward_rate(current_rate: u64, epoch_rewards: u64) -> u64 { new_rate_smoothed.max(1) } +fn reset_bus<'info>( + bus: &mut Account, + bus_tokens: &mut Account<'info, TokenAccount>, + mint: &Account<'info, Mint>, + metadata: &Account<'info, Metadata>, + token_program: &Program<'info, token::Token>, +) -> Result<()> { + // Reset bus state. + bus.hashes = 0; + bus.rewards = 0; + + // Top up bus account with 1 ORE. + let amount = ONE_ORE.saturating_sub(bus_tokens.amount); + if amount.gt(&0) { + token::mint_to( + CpiContext::new_with_signer( + token_program.to_account_info(), + MintTo { + authority: metadata.to_account_info(), + mint: mint.to_account_info(), + to: bus_tokens.to_account_info(), + }, + &[&[METADATA, &[metadata.bump]]], + ), + amount, + )?; + } + + Ok(()) +} + /// The seed of the bus account PDA. pub const BUS: &[u8] = b"bus"; @@ -345,6 +431,14 @@ pub struct InitializeBusses<'info> { #[account(mut)] pub signer: Signer<'info>, + /// The metadata account. + #[account(has_one = mint)] + pub metadata: Account<'info, Metadata>, + + /// The Ore token mint account. + #[account()] + pub mint: Account<'info, Mint>, + /// Bus account 0. #[account(init, seeds = [BUS, &[0]], bump, payer = signer, space = 8 + size_of::())] pub bus_0: Account<'info, Bus>, @@ -382,6 +476,45 @@ pub struct InitializeBusses<'info> { pub system_program: Program<'info, System>, } +#[derive(Accounts)] +pub struct InitializeBusTokens<'info> { + /// The signer of the transaction. + #[account(mut)] + pub signer: Signer<'info>, + + /// The metadata account. + #[account(has_one = mint)] + pub metadata: Account<'info, Metadata>, + + /// The Ore token mint account. + #[account()] + pub mint: Account<'info, Mint>, + + /// The bus account. + #[account()] + pub bus: Account<'info, Bus>, + + /// The bus token account. + #[account(init, associated_token::mint = mint, associated_token::authority = bus, payer = signer)] + pub bus_tokens: Account<'info, TokenAccount>, + + /// The rent sysvar account. + #[account(address = sysvar::rent::ID)] + pub rent: Sysvar<'info, Rent>, + + /// The Solana system program. + #[account(address = system_program::ID)] + pub system_program: Program<'info, System>, + + /// The SPL token program. + #[account(address = anchor_spl::token::ID)] + pub token_program: Program<'info, token::Token>, + + /// The SPL associated token program. + #[account(address = anchor_spl::associated_token::ID)] + pub associated_token_program: Program<'info, associated_token::AssociatedToken>, +} + /// Register registers a new miner and initialize a proof account for them. #[derive(Accounts)] pub struct Register<'info> { @@ -406,40 +539,84 @@ pub struct ResetEpoch<'info> { pub signer: Signer<'info>, /// Bus account 0. - #[account(mut, seeds = [BUS, &[0]], bump)] - pub bus_0: Account<'info, Bus>, + #[account(mut, seeds = [BUS, &[0]], bump = bus_0.bump)] + pub bus_0: Box>, /// Bus account 1. - #[account(mut, seeds = [BUS, &[1]], bump)] - pub bus_1: Account<'info, Bus>, + #[account(mut, seeds = [BUS, &[1]], bump = bus_1.bump)] + pub bus_1: Box>, /// Bus account 2. - #[account(mut, seeds = [BUS, &[2]], bump)] - pub bus_2: Account<'info, Bus>, + #[account(mut, seeds = [BUS, &[2]], bump = bus_2.bump)] + pub bus_2: Box>, /// Bus account 3. - #[account(mut, seeds = [BUS, &[3]], bump)] - pub bus_3: Account<'info, Bus>, + #[account(mut, seeds = [BUS, &[3]], bump = bus_3.bump)] + pub bus_3: Box>, /// Bus account 4. - #[account(mut, seeds = [BUS, &[4]], bump)] - pub bus_4: Account<'info, Bus>, + #[account(mut, seeds = [BUS, &[4]], bump = bus_4.bump)] + pub bus_4: Box>, /// Bus account 5. - #[account(mut, seeds = [BUS, &[5]], bump)] - pub bus_5: Account<'info, Bus>, + #[account(mut, seeds = [BUS, &[5]], bump = bus_5.bump)] + pub bus_5: Box>, /// Bus account 6. - #[account(mut, seeds = [BUS, &[6]], bump)] - pub bus_6: Account<'info, Bus>, + #[account(mut, seeds = [BUS, &[6]], bump = bus_6.bump)] + pub bus_6: Box>, /// Bus account 7. - #[account(mut, seeds = [BUS, &[7]], bump)] - pub bus_7: Account<'info, Bus>, + #[account(mut, seeds = [BUS, &[7]], bump = bus_7.bump)] + pub bus_7: Box>, + + /// Bus token account 0. + #[account(mut, associated_token::mint = mint, associated_token::authority = bus_0)] + pub bus_0_tokens: Box>, + + /// Bus token account 1. + #[account(mut, associated_token::mint = mint, associated_token::authority = bus_1)] + pub bus_1_tokens: Box>, + + /// Bus token account 2. + #[account(mut, associated_token::mint = mint, associated_token::authority = bus_2)] + pub bus_2_tokens: Box>, + + /// Bus token account 3. + #[account(mut, associated_token::mint = mint, associated_token::authority = bus_3)] + pub bus_3_tokens: Box>, + + /// Bus token account 4. + #[account(mut, associated_token::mint = mint, associated_token::authority = bus_4)] + pub bus_4_tokens: Box>, + + /// Bus token account 5. + #[account(mut, associated_token::mint = mint, associated_token::authority = bus_5)] + pub bus_5_tokens: Box>, + + /// Bus token account 6. + #[account(mut, associated_token::mint = mint, associated_token::authority = bus_6)] + pub bus_6_tokens: Box>, + + /// Bus token account 7. + #[account(mut, associated_token::mint = mint, associated_token::authority = bus_7)] + pub bus_7_tokens: Box>, + + /// The Ore token mint account. + #[account(mut)] + pub mint: Account<'info, Mint>, /// The metadata account. - #[account(mut, seeds = [METADATA], bump)] + #[account(mut, seeds = [METADATA], bump = metadata.bump, has_one = mint)] pub metadata: Account<'info, Metadata>, + + /// The SPL token program. + #[account(address = anchor_spl::token::ID)] + pub token_program: Program<'info, token::Token>, + + /// The SPL associated token program. + #[account(address = anchor_spl::associated_token::ID)] + pub associated_token_program: Program<'info, associated_token::AssociatedToken>, } #[derive(Accounts)] @@ -453,10 +630,14 @@ pub struct Mine<'info> { #[account(mut, token::mint = mint)] pub beneficiary: Account<'info, TokenAccount>, - /// One of the bus accounts. + /// A bus account. #[account(mut)] pub bus: Account<'info, Bus>, + /// The bus' token account. + #[account(mut, associated_token::mint = mint, associated_token::authority = bus)] + pub bus_tokens: Account<'info, TokenAccount>, + /// The metadata account. #[account(seeds = [METADATA], bump = metadata.bump, has_one = mint)] pub metadata: Account<'info, Metadata>, @@ -466,7 +647,7 @@ pub struct Mine<'info> { pub proof: Account<'info, Proof>, /// The Ore token mint account. - #[account(mut)] + #[account()] pub mint: Account<'info, Mint>, /// The SPL token program. @@ -512,6 +693,8 @@ pub enum ProgramError { EpochNeedsReset, #[msg("This bus hash reached its hash quota for this epoch")] BusQuotaFilled, + #[msg("This bus does not have enough tokens to pay the reward")] + BusInsufficientFunds, } #[cfg(test)]