proof of wager

This commit is contained in:
Hardhat Chad
2025-05-23 16:29:31 -07:00
parent 379ebae250
commit b5a50622a2
23 changed files with 312 additions and 805 deletions

81
program/src/bet.rs Normal file
View File

@@ -0,0 +1,81 @@
use std::mem::size_of;
use ore_api::prelude::*;
use solana_program::{keccak::hashv, slot_hashes::SlotHash};
use steel::*;
pub fn process_bet(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
// Parse data.
let args = Bet::try_from_bytes(data)?;
let amount = u64::from_le_bytes(args.amount);
// Load accounts.
let clock = Clock::get()?;
let [signer_info, block_info, wager_info, block_bets_info, sender_info, system_program, token_program, slot_hashes_sysvar] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
let block = block_info
.as_account_mut::<Block>(&ore_api::ID)?
.assert_mut(|b| b.ends_at > clock.slot)?
.assert_mut(|b| b.payed_out != 0)?;
block_bets_info.as_associated_token_account(block_info.key, &block.mint)?;
sender_info.as_associated_token_account(signer_info.key, &block.mint)?;
wager_info.is_writable()?.is_empty()?.has_seeds(
&[
WAGER,
&block.current_round.to_le_bytes(),
&block.bet_count.to_le_bytes(),
],
&ore_api::ID,
)?;
system_program.is_program(&system_program::ID)?;
token_program.is_program(&spl_token::ID)?;
slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?;
// Create wager account.
create_program_account::<Wager>(
&wager_info,
&system_program,
&signer_info,
&ore_api::ID,
&[
WAGER,
&block.current_round.to_le_bytes(),
&block.bet_count.to_le_bytes(),
],
)?;
let wager = wager_info.as_account_mut::<Wager>(&ore_api::ID)?;
wager.amount = amount;
wager.authority = *signer_info.key;
wager.id = block.bet_count;
wager.round = block.current_round;
wager.timestamp = clock.unix_timestamp as u64;
wager.cumulative_bets = block.total_bets;
// Update block.
block.total_bets += amount;
block.bet_count += 1;
// Hash client seed into block noise. Use a recent slot hash if no seed is provided.
// This follows the scheme for provable randomness.
let seed: &[u8] = if args.seed == [0; 32] {
&slot_hashes_sysvar.data.borrow()[0..size_of::<SlotHash>()]
} else {
args.seed.as_slice()
};
block.noise = hashv(&[&block.noise, seed]).to_bytes();
// Transfer wagers.
transfer(
&signer_info,
&sender_info,
&block_bets_info,
&token_program,
amount,
)?;
Ok(())
}

View File

@@ -1,52 +0,0 @@
use ore_api::prelude::*;
use steel::*;
/// Claim distributes claimable ORE from the treasury to a miner.
pub fn process_claim(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
// Parse args.
let args = Claim::try_from_bytes(data)?;
let amount = u64::from_le_bytes(args.amount);
// Load accounts.
let clock = Clock::get()?;
let [signer_info, beneficiary_info, proof_info, treasury_info, treasury_tokens_info, token_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
beneficiary_info
.is_writable()?
.as_token_account()?
.assert(|t| t.mint() == MINT_ADDRESS)?;
let proof = proof_info
.as_account_mut::<Proof>(&ore_api::ID)?
.assert_mut_err(
|p| p.authority == *signer_info.key,
ProgramError::MissingRequiredSignature,
)?;
treasury_info.is_treasury()?;
treasury_tokens_info.is_writable()?.is_treasury_tokens()?;
token_program.is_program(&spl_token::ID)?;
// Update miner balance.
proof.balance = proof
.balance
.checked_sub(amount)
.ok_or(OreError::ClaimTooLarge)?;
// Update last claim timestamp.
proof.last_claim_at = clock.unix_timestamp;
// Transfer tokens from treasury to beneficiary.
transfer_signed(
treasury_info,
treasury_tokens_info,
beneficiary_info,
token_program,
amount,
&[TREASURY],
)?;
Ok(())
}

View File

@@ -1,25 +1,21 @@
use ore_api::prelude::*;
use steel::*;
/// Close closes a proof account and returns the rent to the owner.
pub fn process_close(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts.
let [signer_info, proof_info, system_program] = accounts else {
let [signer_info, block_info, wager_info, system_program] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
proof_info
.is_writable()?
.as_account::<Proof>(&ore_api::ID)?
.assert_err(
|p| p.authority == *signer_info.key,
ProgramError::MissingRequiredSignature,
)?
.assert(|p| p.balance == 0)?;
let block = block_info.as_account::<Block>(&ore_api::ID)?;
wager_info
.as_account_mut::<Wager>(&ore_api::ID)?
.assert_mut(|w| w.authority == *signer_info.key)?
.assert_mut(|w| w.round < block.current_round)?;
system_program.is_program(&system_program::ID)?;
// Return rent to signer.
proof_info.close(signer_info)?;
// Close the wager account
wager_info.close(&signer_info)?;
Ok(())
}

View File

@@ -1,122 +1,52 @@
use ore_api::prelude::*;
use solana_program::program_pack::Pack;
use spl_token::state::Mint;
use steel::*;
/// Initialize sets up the ORE program to begin mining.
pub fn process_initialize(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts.
let [signer_info, config_info, metadata_info, mint_info, treasury_info, treasury_tokens_info, system_program, token_program, associated_token_program, metadata_program, rent_sysvar] =
let [signer_info, block_info, block_bets_info, sol_mint_info, system_program, token_program, associated_token_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?.has_address(&INITIALIZER_ADDRESS)?;
config_info
block_info
.is_empty()?
.is_writable()?
.has_seeds(&[CONFIG], &ore_api::ID)?;
metadata_info.is_empty()?.is_writable()?.has_seeds(
&[
METADATA,
mpl_token_metadata::ID.as_ref(),
MINT_ADDRESS.as_ref(),
],
&mpl_token_metadata::ID,
)?;
mint_info
.is_empty()?
.is_writable()?
.has_seeds(&[MINT, MINT_NOISE.as_slice()], &ore_api::ID)?;
treasury_info
.is_empty()?
.is_writable()?
.has_seeds(&[TREASURY], &ore_api::ID)?;
treasury_tokens_info.is_empty()?.is_writable()?;
.has_seeds(&[BLOCK], &ore_api::ID)?;
block_bets_info.is_empty()?.is_writable()?;
sol_mint_info
.has_address(&spl_token::native_mint::ID)?
.as_mint()?;
system_program.is_program(&system_program::ID)?;
token_program.is_program(&spl_token::ID)?;
associated_token_program.is_program(&spl_associated_token_account::ID)?;
metadata_program.is_program(&mpl_token_metadata::ID)?;
rent_sysvar.is_sysvar(&sysvar::rent::ID)?;
// Initialize config.
create_program_account::<Config>(
config_info,
create_program_account::<Block>(
block_info,
system_program,
signer_info,
&ore_api::ID,
&[CONFIG],
&[BLOCK],
)?;
let config = config_info.as_account_mut::<Config>(&ore_api::ID)?;
// config.base_reward_rate = INITIAL_BASE_REWARD_RATE;
config.last_reset_at = 0;
config.best_hash = [u8::MAX; 32];
config.best_proof = Pubkey::default();
config.challenge = [0; 32];
config.block_reward = 0;
// Initialize treasury.
create_program_account::<Treasury>(
treasury_info,
system_program,
signer_info,
&ore_api::ID,
&[TREASURY],
)?;
// Initialize mint.
allocate_account_with_bump(
mint_info,
system_program,
signer_info,
Mint::LEN,
&spl_token::ID,
&[MINT, MINT_NOISE.as_slice()],
MINT_BUMP,
)?;
initialize_mint_signed_with_bump(
mint_info,
treasury_info,
None,
token_program,
rent_sysvar,
TOKEN_DECIMALS,
&[MINT, MINT_NOISE.as_slice()],
MINT_BUMP,
)?;
// Initialize mint metadata.
mpl_token_metadata::instructions::CreateMetadataAccountV3Cpi {
__program: metadata_program,
metadata: metadata_info,
mint: mint_info,
mint_authority: treasury_info,
payer: signer_info,
update_authority: (signer_info, true),
system_program,
rent: Some(rent_sysvar),
__args: mpl_token_metadata::instructions::CreateMetadataAccountV3InstructionArgs {
data: mpl_token_metadata::types::DataV2 {
name: METADATA_NAME.to_string(),
symbol: METADATA_SYMBOL.to_string(),
uri: METADATA_URI.to_string(),
seller_fee_basis_points: 0,
creators: None,
collection: None,
uses: None,
},
is_mutable: true,
collection_details: None,
},
}
.invoke_signed(&[&[TREASURY, &[TREASURY_BUMP]]])?;
let block = block_info.as_account_mut::<Block>(&ore_api::ID)?;
block.current_round = 0;
block.total_bets = 0;
block.bet_count = 0;
block.started_at = 0;
block.ends_at = 0;
block.payed_out = 0;
block.mint = spl_token::native_mint::ID;
block.reward = 0;
block.noise = [0; 32];
// Initialize treasury token account.
create_associated_token_account(
signer_info,
treasury_info,
treasury_tokens_info,
mint_info,
block_info,
block_bets_info,
sol_mint_info,
system_program,
token_program,
associated_token_program,

View File

@@ -1,22 +1,17 @@
mod claim;
mod bet;
mod close;
mod initialize;
mod migrate;
mod mine;
mod open;
mod payout;
mod reset;
mod update;
use claim::*;
use bet::*;
use close::*;
use initialize::*;
use migrate::*;
use mine::*;
use open::*;
use ore_api::instruction::*;
use payout::*;
use reset::*;
use ore_api::instruction::*;
use steel::*;
use update::*;
pub fn process_instruction(
program_id: &Pubkey,
@@ -26,14 +21,11 @@ pub fn process_instruction(
let (ix, data) = parse_instruction(&ore_api::ID, program_id, data)?;
match ix {
OreInstruction::Claim => process_claim(accounts, data)?,
OreInstruction::Bet => process_bet(accounts, data)?,
OreInstruction::Close => process_close(accounts, data)?,
OreInstruction::Mine => process_mine(accounts, data)?,
OreInstruction::Open => process_open(accounts, data)?,
OreInstruction::Reset => process_reset(accounts, data)?,
OreInstruction::Update => process_update(accounts, data)?,
OreInstruction::Initialize => process_initialize(accounts, data)?,
OreInstruction::Migrate => process_migrate(accounts, data)?,
OreInstruction::Payout => process_payout(accounts, data)?,
}
Ok(())

View File

@@ -1,124 +0,0 @@
use ore_api::prelude::*;
use solana_program::hash;
use steel::*;
/// Mine validates hashes and increments a miner's claimable balance.
pub fn process_migrate(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
// Parse args.
let args = Migrate::try_from_bytes(data)?;
// Load accounts.
let clock = Clock::get()?;
let t: i64 = clock.unix_timestamp;
let [signer_info, 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, system_program, slot_hashes_sysvar] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?.has_address(&INITIALIZER_ADDRESS);
let config = config_info
.as_account_mut::<OldConfig>(&ore_api::ID)?
.assert_mut_err(
|c| t < c.last_reset_at + EPOCH_DURATION,
OreError::NeedsReset.into(),
)?;
let bus_0 = bus_0_info
.as_account_mut::<Bus>(&ore_api::ID)?
.assert_mut(|b| b.id == 0)?;
let bus_1 = bus_1_info
.as_account_mut::<Bus>(&ore_api::ID)?
.assert_mut(|b| b.id == 1)?;
let bus_2 = bus_2_info
.as_account_mut::<Bus>(&ore_api::ID)?
.assert_mut(|b| b.id == 2)?;
let bus_3 = bus_3_info
.as_account_mut::<Bus>(&ore_api::ID)?
.assert_mut(|b| b.id == 3)?;
let bus_4 = bus_4_info
.as_account_mut::<Bus>(&ore_api::ID)?
.assert_mut(|b| b.id == 4)?;
let bus_5 = bus_5_info
.as_account_mut::<Bus>(&ore_api::ID)?
.assert_mut(|b| b.id == 5)?;
let bus_6 = bus_6_info
.as_account_mut::<Bus>(&ore_api::ID)?
.assert_mut(|b| b.id == 6)?;
let bus_7 = bus_7_info
.as_account_mut::<Bus>(&ore_api::ID)?
.assert_mut(|b| b.id == 7)?;
mint_info
.is_writable()?
.has_address(&MINT_ADDRESS)?
.as_mint()?;
treasury_info
.has_address(&TREASURY_ADDRESS)?
.is_writable()?;
treasury_tokens_info
.has_address(&TREASURY_TOKENS_ADDRESS)?
.as_associated_token_account(&TREASURY_ADDRESS, &MINT_ADDRESS)?;
token_program.is_program(&spl_token::ID)?;
system_program.is_program(&system_program::ID)?;
slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?;
let mut total_bus_balance = 0;
total_bus_balance += bus_0.rewards;
total_bus_balance += bus_1.rewards;
total_bus_balance += bus_2.rewards;
total_bus_balance += bus_3.rewards;
total_bus_balance += bus_4.rewards;
total_bus_balance += bus_5.rewards;
total_bus_balance += bus_6.rewards;
total_bus_balance += bus_7.rewards;
// Reset bus balances
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;
// Delete bus accounts
bus_0_info.close(signer_info)?;
bus_1_info.close(signer_info)?;
bus_2_info.close(signer_info)?;
bus_3_info.close(signer_info)?;
bus_4_info.close(signer_info)?;
bus_5_info.close(signer_info)?;
bus_6_info.close(signer_info)?;
bus_7_info.close(signer_info)?;
// Burn all tokens in the bus balances
burn_signed(
treasury_tokens_info,
mint_info,
treasury_info,
token_program,
total_bus_balance,
&[TREASURY],
)?;
// let proof = proof_info
// .as_account_mut::<Proof>(&ore_api::ID)?
// .assert_mut_err(
// |p| p.miner == *signer_info.key,
// ProgramError::MissingRequiredSignature,
// )?;
// Compute the hash.
// let solution = hash::hashv(&[
// args.nonce.as_slice(),
// config.challenge.as_slice(),
// proof.authority.to_bytes().as_slice(),
// ]);
// // Update the best solution.
// if solution.to_bytes() < config.best_hash {
// config.best_hash = solution.to_bytes();
// config.best_proof = *proof_info.key;
// }
Ok(())
}

View File

@@ -1,44 +0,0 @@
use ore_api::prelude::*;
use solana_program::hash;
use steel::*;
/// Mine validates hashes and increments a miner's claimable balance.
pub fn process_mine(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
// Parse args.
let args = Mine::try_from_bytes(data)?;
// Load accounts.
let clock = Clock::get()?;
let t: i64 = clock.unix_timestamp;
let [signer_info, config_info, proof_info] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
let config = config_info.as_account_mut::<Config>(&ore_api::ID)?;
let proof = proof_info
.as_account_mut::<Proof>(&ore_api::ID)?
.assert_mut_err(
|p| p.miner == *signer_info.key,
ProgramError::MissingRequiredSignature,
)?;
// Compute the hash.
let solution = hash::hashv(&[
args.nonce.as_slice(),
config.challenge.as_slice(),
proof.authority.to_bytes().as_slice(),
]);
// Update the best solution.
if solution.to_bytes() < config.best_hash {
config.best_hash = solution.to_bytes();
config.best_proof = *proof_info.key;
}
// Update the proof.
proof.last_hash = solution.to_bytes();
proof.last_hash_at = t;
proof.total_hashes += 1;
Ok(())
}

View File

@@ -1,48 +0,0 @@
use std::mem::size_of;
use ore_api::prelude::*;
use solana_program::{keccak::hashv, slot_hashes::SlotHash};
use steel::*;
/// Open creates a new proof account to track a miner's state.
pub fn process_open(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts.
let [signer_info, miner_info, payer_info, proof_info, system_program, slot_hashes_info] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
payer_info.is_signer()?;
proof_info
.is_empty()?
.is_writable()?
.has_seeds(&[PROOF, signer_info.key.as_ref()], &ore_api::ID)?;
system_program.is_program(&system_program::ID)?;
slot_hashes_info.is_sysvar(&sysvar::slot_hashes::ID)?;
// Initialize proof.
create_program_account::<Proof>(
proof_info,
system_program,
payer_info,
&ore_api::ID,
&[PROOF, signer_info.key.as_ref()],
)?;
let clock = Clock::get()?;
let proof = proof_info.as_account_mut::<Proof>(&ore_api::ID)?;
proof.authority = *signer_info.key;
proof.balance = 0;
proof.challenge = hashv(&[
signer_info.key.as_ref(),
&slot_hashes_info.data.borrow()[0..size_of::<SlotHash>()],
])
.0;
proof.last_hash = [0; 32];
proof.last_hash_at = clock.unix_timestamp;
proof.miner = *miner_info.key;
proof.total_hashes = 0;
proof.total_rewards = 0;
Ok(())
}

62
program/src/payout.rs Normal file
View File

@@ -0,0 +1,62 @@
use std::mem::size_of;
use ore_api::prelude::*;
use solana_program::{keccak::hashv, slot_hashes::SlotHash};
use steel::*;
pub fn process_payout(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts.
let clock = Clock::get()?;
let [signer_info, block_info, wager_info, recipient_info, treasury_info, treasury_tokens_info, system_program, token_program, slot_hashes_sysvar] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
let block = block_info
.as_account_mut::<Block>(&ore_api::ID)?
.assert_mut(|b| b.ends_at < clock.slot)?
.assert_mut(|b| b.payed_out == 0)?;
let wager = wager_info.as_account::<Wager>(&ore_api::ID)?;
recipient_info.as_associated_token_account(&wager.authority, &MINT_ADDRESS)?;
treasury_info.has_address(&TREASURY_ADDRESS)?;
treasury_tokens_info
.has_address(&TREASURY_TOKENS_ADDRESS)?
.is_writable()?;
system_program.is_program(&system_program::ID)?;
token_program.is_program(&spl_token::ID)?;
slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?;
// Select the slothash from the slot at when the round ended.
// The represents the server seed for the provably fair random number.
let offset = clock.slot - block.ends_at;
let size = size_of::<SlotHash>();
let i = offset as usize * size;
let slot_hash = &slot_hashes_sysvar.data.borrow()[i..i + size];
block.noise = hashv(&[&block.noise, slot_hash]).to_bytes();
// Calculate the random number.
let x = u64::from_le_bytes(block.noise[0..8].try_into().unwrap());
let y = u64::from_le_bytes(block.noise[8..16].try_into().unwrap());
let z = u64::from_le_bytes(block.noise[16..24].try_into().unwrap());
let w = u64::from_le_bytes(block.noise[24..32].try_into().unwrap());
let roll = (x ^ y ^ z ^ w) % block.total_bets;
// Assert that the wager account passed in is the winner.
assert!(roll >= wager.cumulative_bets && roll < wager.cumulative_bets + wager.amount);
// Mark the block as paid out.
block.payed_out = 1;
// Transfer the winnings to the recipient.
transfer_signed(
&treasury_info,
&treasury_tokens_info,
&recipient_info,
&token_program,
ONE_ORE / 2,
&[TREASURY],
)?;
Ok(())
}

View File

@@ -1,102 +1,72 @@
use drillx::difficulty;
use ore_api::prelude::*;
use ore_boost_api::state::Config as BoostConfig;
use solana_program::{hash::hashv, slot_hashes::SlotHash};
use ore_boost_api::{consts::DENOMINATOR_BPS, prelude::Config as BoostConfig};
use steel::*;
/// Reset tops up the bus balances and updates the emissions and reward rates.
pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts.
let clock = Clock::get()?;
let (required_accounts, boost_accounts) = accounts.split_at(7);
let [signer_info, config_info, mint_info, proof_info, treasury_info, treasury_tokens_info, token_program, slot_hashes_sysvar] =
let (required_accounts, boost_accounts) = accounts.split_at(6);
let [signer_info, block_info, mint_info, treasury_info, treasury_tokens_info, token_program, slot_hashes_sysvar] =
required_accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
let config = config_info
.is_config()?
.as_account_mut::<Config>(&ore_api::ID)?;
let block = block_info
.as_account_mut::<Block>(&ore_api::ID)?
.assert_mut(|b| b.ends_at < clock.slot)?
.assert_mut(|b| b.payed_out != 0)?;
let mint = mint_info
.has_address(&MINT_ADDRESS)?
.is_writable()?
.as_mint()?;
let proof = proof_info
.as_account_mut::<Proof>(&ore_api::ID)?
.assert_mut(|p| p.authority == config.best_proof)?;
treasury_info.is_treasury()?.is_writable()?;
treasury_tokens_info.is_treasury_tokens()?.is_writable()?;
treasury_info.has_address(&TREASURY_ADDRESS)?;
treasury_tokens_info.has_address(&TREASURY_TOKENS_ADDRESS)?;
token_program.is_program(&spl_token::ID)?;
slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?;
// Parse boost accounts.
let [boost_config_info, boost_proof_info] = boost_accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
let boost_config = boost_config_info.as_account::<BoostConfig>(&ore_api::ID)?;
let boost_config = boost_config_info.as_account::<BoostConfig>(&ore_boost_api::ID)?;
let boost_proof = boost_proof_info
.as_account_mut::<Proof>(&ore_api::ID)?
.assert_mut(|p| p.authority == *boost_config_info.key)?;
// Validate enough time has passed since the last reset.
if clock.unix_timestamp < config.last_reset_at + EPOCH_DURATION {
return Ok(());
}
// Record difficulty.
let score = difficulty(config.best_hash) as u64;
// Reset the challenge.
config.challenge = hashv(&[
config.challenge.as_slice(),
&slot_hashes_sysvar.data.borrow()[0..size_of::<SlotHash>()],
])
.to_bytes();
// Reset the config.
let block_reward = get_block_reward(mint.supply());
config.block_reward = block_reward;
config.best_proof = Pubkey::default();
config.best_hash = [u8::MAX; 32];
config.last_reset_at = clock.unix_timestamp;
// Calculate boost reward.
let take_rate = boost_config.take_rate.min(9900); // Cap at 99%
let boost_reward = block_reward * take_rate / ore_boost_api::consts::DENOMINATOR_BPS;
let miner_reward = block_reward - boost_reward;
// Update proof balances.
proof.balance += miner_reward;
proof.total_rewards += miner_reward;
// Payout to boosts.
let net_emissions = get_target_emissions_rate(mint.supply());
let boost_reward =
(net_emissions as u128 * boost_config.take_rate as u128 / DENOMINATOR_BPS as u128) as u64;
boost_proof.balance += boost_reward;
boost_proof.total_rewards += boost_reward;
// Reset the block.
block.reward = net_emissions - boost_reward;
block.started_at = clock.slot;
block.ends_at = clock.slot + 150; // 60 seconds
block.payed_out = 0;
block.total_bets = 0;
block.bet_count = 0;
block.noise = [0; 32];
block.current_round += 1;
// Fund the treasury.
mint_to_signed(
mint_info,
treasury_tokens_info,
treasury_info,
token_program,
block_reward,
net_emissions,
&[TREASURY],
)?;
// Emit event.
BlockEvent {
score,
block_reward,
boost_reward,
ts: clock.unix_timestamp as u64,
}
.log_return();
Ok(())
}
/// This function calculates the block reward (ORE / min) based on the current supply.
/// It is designed to reduce emissions by 10% approximately every 12 months with a hard stop at 5 million ORE.
pub(crate) fn get_block_reward(current_supply: u64) -> u64 {
/// This function calculates the target emissions rate (ORE / min) based on the current supply.
/// It is designed to reduce emissions by 10% approximately every 12 months with a hardcap at 5 million ORE.
pub(crate) fn get_target_emissions_rate(current_supply: u64) -> u64 {
match current_supply {
n if n < ONE_ORE * 525_600 => 100_000_000_000, // Year ~1
n if n < ONE_ORE * 998_640 => 90_000_000_000, // Year ~2
@@ -126,52 +96,7 @@ pub(crate) fn get_block_reward(current_supply: u64) -> u64 {
n if n < ONE_ORE * 4_916_405 => 7_178_979_874, // Year ~26
n if n < ONE_ORE * 4_950_365 => 6_461_081_886, // Year ~27
n if n < ONE_ORE * 4_980_928 => 5_814_973_607, // Year ~28
n if n < MAX_SUPPLY => 5_233_476_327.min(MAX_SUPPLY - current_supply), // Year ~29
n if n < ONE_ORE * 5_000_000 => 5_233_476_327, // Year ~29
_ => 0,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_block_reward_max_supply() {
let max_supply = ONE_ORE * 5_000_000;
// Test at max supply
assert_eq!(get_block_reward(max_supply), 0);
// Test slightly below max supply
let near_max = max_supply - 1;
assert_eq!(get_block_reward(near_max), 1);
// Test at max supply - 1000
let below_max = max_supply - 1000;
assert_eq!(get_block_reward(below_max), 1000);
// Test that reward never exceeds remaining supply
let supply_4_999_990 = ONE_ORE * 4_999_990;
assert!(get_block_reward(supply_4_999_990) <= max_supply - supply_4_999_990);
}
#[test]
fn test_block_reward_boundaries() {
// Test first tier boundary
let year1_supply = ONE_ORE * 525_599;
assert_eq!(get_block_reward(year1_supply), 100_000_000_000);
// Test middle tier boundary
let year15_supply = ONE_ORE * 4_173_835;
assert_eq!(get_block_reward(year15_supply), 22_876_792_454);
// Test last tier boundary before max supply logic
let last_tier_supply = ONE_ORE * 4_980_927;
assert_eq!(get_block_reward(last_tier_supply), 5_814_973_607);
}
#[test]
fn test_block_reward_zero_supply() {
assert_eq!(get_block_reward(0), 100_000_000_000);
}
}

View File

@@ -1,22 +0,0 @@
use ore_api::prelude::*;
use steel::*;
/// Update changes the miner authority on a proof account.
pub fn process_update(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts.
let [signer_info, miner_info, proof_info] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
let proof = proof_info
.as_account_mut::<Proof>(&ore_api::ID)?
.assert_mut_err(
|p| p.authority == *signer_info.key,
ProgramError::MissingRequiredSignature,
)?;
// Update the proof's miner authority.
proof.miner = *miner_info.key;
Ok(())
}