mirror of
https://github.com/d0zingcat/ore.git
synced 2026-05-14 07:26:51 +00:00
proof of wager
This commit is contained in:
81
program/src/bet.rs
Normal file
81
program/src/bet.rs
Normal 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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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
62
program/src/payout.rs
Normal 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(())
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
Reference in New Issue
Block a user