This commit is contained in:
Hardhat Chad
2025-10-02 11:49:35 -07:00
parent c745ed19e9
commit eb40335fa3
16 changed files with 3798 additions and 247 deletions

3564
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["api", "program"]
members = ["api", "program", "cli"]
[workspace.package]
version = "3.7.0-alpha"

View File

@@ -307,11 +307,11 @@ pub fn reset(
data: Reset {}.to_bytes(),
}
}
// let [signer_info, automation_info, board_info, miner_info, round_info, treasury_info, system_program] =
pub fn checkpoint(signer: Pubkey, authority: Pubkey, round_id: u64) -> Instruction {
let miner_address = miner_pda(authority).0;
let automation_address = automation_pda(authority).0;
let board_address = board_pda().0;
let round_address = round_pda(round_id).0;
let treasury_address = TREASURY_ADDRESS;
@@ -319,7 +319,6 @@ pub fn checkpoint(signer: Pubkey, authority: Pubkey, round_id: u64) -> Instructi
program_id: crate::ID,
accounts: vec![
AccountMeta::new(signer, true),
AccountMeta::new(automation_address, false),
AccountMeta::new(board_address, false),
AccountMeta::new(miner_address, false),
AccountMeta::new(round_address, false),

View File

@@ -1,6 +1,6 @@
use steel::*;
use crate::state::board_pda;
use crate::state::{board_pda, OreAccountOLD};
use super::OreAccount;
@@ -17,6 +17,40 @@ pub struct Board {
pub end_slot: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct BoardOLD {
/// The round number.
pub id: u64,
/// The deployed SOL for the round.
pub deployed: [u64; 25],
/// The timestamp at which the block starts mining.
pub start_at: i64,
/// The slot at which the block starts trading.
pub start_slot: u64,
/// The slot at which the block ends trading.
pub end_slot: u64,
/// The hash of the end slot, provided by solana, used for random number generation.
pub slot_hash: [u8; 32],
/// The top miner of the round.
pub top_miner: Pubkey,
/// The total amount of SOL deployed in the round.
pub total_deployed: u64,
/// The total amount of SOL put in the ORE vault.
pub total_vaulted: u64,
/// The total amount of SOL won by miners for the round.
pub total_winnings: u64,
}
impl Board {
pub fn pda(&self) -> (Pubkey, u8) {
board_pda()
@@ -24,3 +58,4 @@ impl Board {
}
account!(OreAccount, Board);
account!(OreAccountOLD, BoardOLD);

View File

@@ -1,6 +1,6 @@
use steel::*;
use crate::state::miner_pda;
use crate::state::{miner_pda, OreAccountOLD};
use super::OreAccount;
@@ -38,6 +38,38 @@ pub struct Miner {
pub lifetime_rewards_ore: u64,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct MinerOLD {
/// The authority of this miner account.
pub authority: Pubkey,
/// The miner's prospects in the current round.
pub deployed: [u64; 25],
/// Unused buffer.
#[deprecated(note = "No longer used")]
pub buffer: [u8; 32],
/// The amount of SOL this miner has had refunded and may claim.
pub refund_sol: u64,
/// The amount of SOL this miner can claim.
pub rewards_sol: u64,
/// The amount of ORE this miner can claim.
pub rewards_ore: u64,
/// The ID of the round this miner last played in.
pub round_id: u64,
/// The total amount of SOL this miner has mined across all blocks.
pub lifetime_rewards_sol: u64,
/// The total amount of ORE this miner has mined across all blocks.
pub lifetime_rewards_ore: u64,
}
impl Miner {
pub fn pda(&self) -> (Pubkey, u8) {
miner_pda(self.authority)
@@ -45,3 +77,4 @@ impl Miner {
}
account!(OreAccount, Miner);
account!(OreAccountOLD, MinerOLD);

View File

@@ -35,6 +35,13 @@ pub enum OreAccount {
Round = 109,
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
pub enum OreAccountOLD {
MinerOLD = 103,
BoardOLD = 105,
}
pub fn automation_pda(authority: Pubkey) -> (Pubkey, u8) {
Pubkey::find_program_address(&[AUTOMATION, &authority.to_bytes()], &crate::ID)
}

View File

@@ -49,16 +49,16 @@ impl Round {
round_pda(self.id)
}
pub fn rng(&self) -> u64 {
if self.slot_hash == [0; 32] {
return 0;
pub fn rng(&self) -> Option<u64> {
if self.slot_hash == [0; 32] || self.slot_hash == [u8::MAX; 32] {
return None;
}
let r1 = u64::from_le_bytes(self.slot_hash[0..8].try_into().unwrap());
let r2 = u64::from_le_bytes(self.slot_hash[8..16].try_into().unwrap());
let r3 = u64::from_le_bytes(self.slot_hash[16..24].try_into().unwrap());
let r4 = u64::from_le_bytes(self.slot_hash[24..32].try_into().unwrap());
let r = r1 ^ r2 ^ r3 ^ r4;
r
Some(r)
}
pub fn winning_square(&self, rng: u64) -> usize {

View File

@@ -80,8 +80,8 @@ async fn main() {
"deploy_all" => {
deploy_all(&rpc, &payer).await.unwrap();
}
"square" => {
log_square(&rpc).await.unwrap();
"round" => {
log_round(&rpc).await.unwrap();
}
"seeker" => {
log_seeker(&rpc).await.unwrap();
@@ -130,32 +130,32 @@ async fn main() {
// 34QyjRFFU2Vp7ZAxdNm3FRCChEMbStAh9Zf58W84q7Fh
async fn test_kick(rpc: &RpcClient) -> Result<(), anyhow::Error> {
let mut kps = vec![];
for i in 1..=20 {
let home_dir = dirs::home_dir()
.expect("Could not find home directory")
.display()
.to_string();
let path = format!("{}/.config/solana/tester-{}.json", home_dir, i);
kps.push(read_keypair_file(&path).unwrap());
}
let mut alt_miners = kps.iter().map(|kp| kp.pubkey()).collect::<Vec<Pubkey>>();
alt_miners.push(pubkey!("pqspJ298ryBjazPAr95J9sULCVpZe3HbZTWkbC1zrkS"));
// let mut kps = vec![];
// for i in 1..=20 {
// let home_dir = dirs::home_dir()
// .expect("Could not find home directory")
// .display()
// .to_string();
// let path = format!("{}/.config/solana/tester-{}.json", home_dir, i);
// kps.push(read_keypair_file(&path).unwrap());
// }
// let mut alt_miners = kps.iter().map(|kp| kp.pubkey()).collect::<Vec<Pubkey>>();
// alt_miners.push(pubkey!("pqspJ298ryBjazPAr95J9sULCVpZe3HbZTWkbC1zrkS"));
for (i, kp) in kps.iter().enumerate() {
let amount = 1000 + i as u64;
let mut squares = [false; 25];
squares[0] = true;
let deploy_ix = ore_api::sdk::deploy(
kp.pubkey(),
kp.pubkey(),
amount,
squares,
alt_miners.clone(),
);
println!("Deploying {} to square 0 for {}", amount, kp.pubkey());
submit_transaction_no_confirm(rpc, &kp, &[deploy_ix]).await?;
}
// for (i, kp) in kps.iter().enumerate() {
// let amount = 1000 + i as u64;
// let mut squares = [false; 25];
// squares[0] = true;
// let deploy_ix = ore_api::sdk::deploy(
// kp.pubkey(),
// kp.pubkey(),
// amount,
// squares,
// alt_miners.clone(),
// );
// println!("Deploying {} to square 0 for {}", amount, kp.pubkey());
// submit_transaction_no_confirm(rpc, &kp, &[deploy_ix]).await?;
// }
Ok(())
}
@@ -269,15 +269,19 @@ async fn reset(
let board = get_board(rpc).await?;
let config = get_config(rpc).await?;
let slot_hashes = get_slot_hashes(rpc).await?;
let mut miners = vec![];
if let Some(slot_hash) = slot_hashes.get(&board.end_slot) {
let id = get_winning_square(&slot_hash.to_bytes());
let square = get_square(rpc).await?;
// let square = get_square(rpc).await?;
println!("Winning square: {}", id);
// println!("Miners: {:?}", square.miners);
miners = square.miners[id as usize].to_vec();
// miners = square.miners[id as usize].to_vec();
};
let reset_ix = ore_api::sdk::reset(payer.pubkey(), config.fee_collector, miners);
let reset_ix = ore_api::sdk::reset(
payer.pubkey(),
config.fee_collector,
board.round_id,
Pubkey::default(),
);
// simulate_transaction(rpc, payer, &[reset_ix]).await;
submit_transaction(rpc, payer, &[reset_ix]).await?;
Ok(())
@@ -291,11 +295,16 @@ async fn deploy(
let amount = u64::from_str(&amount).expect("Invalid AMOUNT");
let square_id = std::env::var("SQUARE").expect("Missing SQUARE env var");
let square_id = u64::from_str(&square_id).expect("Invalid SQUARE");
let board = get_board(rpc).await?;
let mut squares = [false; 25];
squares[square_id as usize] = true;
let ix = ore_api::sdk::deploy(payer.pubkey(), payer.pubkey(), amount, squares, vec![]);
let ix = ore_api::sdk::deploy(
payer.pubkey(),
payer.pubkey(),
amount,
board.round_id,
squares,
);
submit_transaction(rpc, payer, &[ix]).await?;
Ok(())
}
@@ -306,8 +315,15 @@ async fn deploy_all(
) -> Result<(), anyhow::Error> {
let amount = std::env::var("AMOUNT").expect("Missing AMOUNT env var");
let amount = u64::from_str(&amount).expect("Invalid AMOUNT");
let board = get_board(rpc).await?;
let squares = [true; 25];
let ix = ore_api::sdk::deploy(payer.pubkey(), payer.pubkey(), amount, squares, vec![]);
let ix = ore_api::sdk::deploy(
payer.pubkey(),
payer.pubkey(),
board.round_id,
amount,
squares,
);
submit_transaction(rpc, payer, &[ix]).await?;
Ok(())
}
@@ -416,14 +432,32 @@ async fn log_treasury(rpc: &RpcClient) -> Result<(), anyhow::Error> {
Ok(())
}
async fn log_square(rpc: &RpcClient) -> Result<(), anyhow::Error> {
async fn log_round(rpc: &RpcClient) -> Result<(), anyhow::Error> {
let id = std::env::var("ID").expect("Missing ID env var");
let id = usize::from_str(&id).expect("Invalid ID");
let square = get_square(rpc).await?;
println!("Square");
println!(" count: {:?}", square.count[id]);
println!(" deployed: {:?}", square.deployed[id]);
println!(" miners: {:?}", square.miners[id]);
let id = u64::from_str(&id).expect("Invalid ID");
let round_address = round_pda(id).0;
let round = get_round(rpc, id).await?;
let rng = round.rng();
println!("Round");
println!(" Address: {}", round_address);
println!(" Count: {:?}", round.count);
println!(" Deployed: {:?}", round.deployed);
println!(" Expires at: {}", round.expires_at);
println!(" Id: {:?}", round.id);
println!(" Motherlode: {}", round.motherlode);
println!(" Rent payer: {}", round.rent_payer);
println!(" Slot hash: {:?}", round.slot_hash);
println!(" Top miner: {:?}", round.top_miner);
println!(" Top miner reward: {}", round.top_miner_reward);
println!(" Total deployed: {}", round.total_deployed);
println!(" Total vaulted: {}", round.total_vaulted);
println!(" Total winnings: {}", round.total_winnings);
if let Some(rng) = rng {
println!(" Winning square: {}", round.winning_square(rng));
}
// if round.slot_hash != [0; 32] {
// println!(" Winning square: {}", get_winning_square(&round.slot_hash));
// }
Ok(())
}
@@ -439,10 +473,11 @@ async fn log_miner(
println!(" address: {}", miner_address);
println!(" authority: {}", authority);
println!(" deployed: {:?}", miner.deployed);
println!(" refund_sol: {}", miner.refund_sol);
println!(" cumulative: {:?}", miner.cumulative);
println!(" rewards_sol: {}", miner.rewards_sol);
println!(" rewards_ore: {}", miner.rewards_ore);
println!(" round_id: {}", miner.round_id);
println!(" checkpoint_id: {}", miner.checkpoint_id);
println!(" lifetime_rewards_sol: {}", miner.lifetime_rewards_sol);
println!(" lifetime_rewards_ore: {}", miner.lifetime_rewards_ore);
Ok(())
@@ -489,18 +524,9 @@ async fn log_board(rpc: &RpcClient) -> Result<(), anyhow::Error> {
fn print_board(board: Board, clock: &Clock) {
let current_slot = clock.slot;
println!("Board");
println!(" Id: {:?}", board.id);
println!(" Slot hash: {:?}", board.slot_hash);
println!(" Id: {:?}", board.round_id);
println!(" Start slot: {}", board.start_slot);
println!(" End slot: {}", board.end_slot);
println!(" deployed: {:?}", board.deployed);
println!(" Top miner: {:?}", board.top_miner);
println!(" Total deployed: {}", board.total_deployed);
println!(" Total vaulted: {}", board.total_vaulted);
println!(" Total winnings: {}", board.total_winnings);
if board.slot_hash != [0; 32] {
println!(" Winning square: {}", get_winning_square(&board.slot_hash));
}
println!(
" Time remaining: {} sec",
(board.end_slot.saturating_sub(current_slot) as f64) * 0.4
@@ -544,11 +570,11 @@ async fn get_slot_hashes(rpc: &RpcClient) -> Result<SlotHashes, anyhow::Error> {
Ok(slot_hashes)
}
async fn get_square(rpc: &RpcClient) -> Result<Square, anyhow::Error> {
let square_pda = ore_api::state::square_pda();
let account = rpc.get_account(&square_pda.0).await?;
let square = Square::try_from_bytes(&account.data)?;
Ok(*square)
async fn get_round(rpc: &RpcClient, id: u64) -> Result<Round, anyhow::Error> {
let round_pda = ore_api::state::round_pda(id);
let account = rpc.get_account(&round_pda.0).await?;
let round = Round::try_from_bytes(&account.data)?;
Ok(*round)
}
async fn get_treasury(rpc: &RpcClient) -> Result<Treasury, anyhow::Error> {
@@ -598,6 +624,11 @@ async fn get_miners(rpc: &RpcClient) -> Result<Vec<(Pubkey, Miner)>, anyhow::Err
Ok(miners)
}
async fn get_miners_old(rpc: &RpcClient) -> Result<Vec<(Pubkey, MinerOLD)>, anyhow::Error> {
let miners = get_program_accounts::<MinerOLD>(rpc, ore_api::ID, vec![]).await?;
Ok(miners)
}
fn get_winning_square(slot_hash: &[u8]) -> u64 {
// Use slot hash to generate a random u64
let r1 = u64::from_le_bytes(slot_hash[0..8].try_into().unwrap());

View File

@@ -1,13 +1,12 @@
use ore_api::prelude::*;
use solana_program::rent::Rent;
use solana_program::{log::sol_log, rent::Rent};
use steel::*;
/// Checkpoints a miner's rewards.
pub fn process_checkpoint(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts.
let clock = Clock::get()?;
let [signer_info, automation_info, board_info, miner_info, round_info, treasury_info, system_program] =
accounts
let [signer_info, board_info, miner_info, round_info, treasury_info, system_program] = accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
@@ -40,37 +39,25 @@ pub fn process_checkpoint(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program
return Ok(());
}
// Calculate bot fee permissions.
// Get the RNG.
let Some(r) = round.rng() else {
// If the round has no RNG, no one wins.
miner.checkpoint_id = miner.round_id;
return Ok(());
};
// Calculate bot fee.
let mut bot_fee = 0;
if clock.slot >= round.expires_at - ONE_DAY_SLOTS {
// The round expires in less than 24h.
// Anyone is allowed to checkpoint this account and may collect the bot fee.
// Anyone may checkpoint this account and collect the bot fee.
bot_fee = miner.checkpoint_fee;
miner.checkpoint_fee = 0;
} else {
// There is still time remaining before the round expires.
// Bots may not yet checkpoint this account.
automation_info.has_seeds(&[AUTOMATION, &miner.authority.to_bytes()], &ore_api::ID)?;
if !automation_info.data_is_empty() {
let automation = automation_info
.as_account::<Automation>(&ore_api::ID)?
.assert(|a| a.authority == miner.authority)?;
assert!(
*signer_info.key == miner.authority || *signer_info.key == automation.executor,
"Only the miner or automation executor can checkpoint this account"
);
} else {
assert!(
*signer_info.key == miner.authority,
"Only the miner can checkpoint this account"
);
}
}
// Calculate miner rewards.
let mut rewards_sol = 0;
let mut rewards_ore = 0;
let r = round.rng();
let winning_square = round.winning_square(r) as usize;
if miner.deployed[winning_square] > 0 {
// Sanity check.
@@ -101,6 +88,8 @@ pub fn process_checkpoint(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program
}
}
sol_log("I");
// Checkpoint miner.
miner.checkpoint_id = round.id;
miner.rewards_ore += rewards_ore;
@@ -116,8 +105,9 @@ pub fn process_checkpoint(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program
miner_info.send(bot_fee, &signer_info);
}
// TODO Round debts. Track total checkpointed and/or num miner's checkpointed.
// Assert round has sufficient funds for rent + debts.
// TODO Debts
let account_size = 8 + std::mem::size_of::<Round>();
let required_rent = Rent::get()?.minimum_balance(account_size);
assert!(

View File

@@ -1,11 +1,14 @@
use ore_api::prelude::*;
use solana_program::rent::Rent;
use steel::*;
/// Closes a round accound, and returns the rent to the rent payer.
pub fn process_close(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts.
let clock = Clock::get()?;
let [signer_info, board_info, rent_payer_info, round_info, system_program] = accounts else {
let [signer_info, board_info, rent_payer_info, round_info, treasury_info, system_program] =
accounts
else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
@@ -16,8 +19,18 @@ pub fn process_close(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
.assert_mut(|r| r.id < board.round_id)?
.assert_mut(|r| r.expires_at < clock.slot)? // Ensure round has expired.
.assert_mut(|r| r.rent_payer == *rent_payer_info.key)?; // Ensure the rent payer is the correct one.
let treasury = treasury_info.as_account_mut::<Treasury>(&ore_api::ID)?;
system_program.is_program(&system_program::ID)?;
// Vault all unclaimed rewards.
let size = 8 + std::mem::size_of::<Round>();
let min_rent = Rent::get()?.minimum_balance(size);
let unclaimed_sol = round_info.lamports() - min_rent;
if unclaimed_sol > 0 {
round_info.send(unclaimed_sol, treasury_info);
treasury.balance += unclaimed_sol;
}
// Close the account.
round_info.close(rent_payer_info)?;

View File

@@ -2,6 +2,8 @@ use ore_api::prelude::*;
use solana_program::{keccak::hashv, log::sol_log, native_token::lamports_to_sol};
use steel::*;
use crate::AUTHORIZED_ACCOUNTS;
/// Deploys capital to prospect on a square.
pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
// Parse data.
@@ -18,16 +20,25 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul
};
signer_info.is_signer()?;
authority_info.is_writable()?;
automation_info.is_writable()?;
automation_info
.is_writable()?
.has_seeds(&[AUTOMATION, &authority_info.key.to_bytes()], &ore_api::ID)?;
let board = board_info
.as_account_mut::<Board>(&ore_api::ID)?
.assert_mut(|b| clock.slot >= b.start_slot && clock.slot < b.end_slot)?;
let round = round_info
.as_account_mut::<Round>(&ore_api::ID)?
.assert_mut(|r| r.id == board.round_id)?;
miner_info.is_writable()?;
miner_info
.is_writable()?
.has_seeds(&[MINER, &authority_info.key.to_bytes()], &ore_api::ID)?;
system_program.is_program(&system_program::ID)?;
// Check whitelist
if !AUTHORIZED_ACCOUNTS.contains(&signer_info.key) {
return Err(trace("Not authorized", OreError::NotAuthorized.into()));
}
// Check if signer is the automation executor.
let automation = if !automation_info.data_is_empty() {
let automation = automation_info
@@ -68,17 +79,6 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul
}
}
// Log
sol_log(
&format!(
"Round {}. Deploying {} SOL to {} squares",
round.id,
lamports_to_sol(amount),
squares.iter().filter(|&&s| s).count(),
)
.as_str(),
);
// Open miner account.
let miner = if miner_info.data_is_empty() {
create_program_account::<Miner>(
@@ -127,6 +127,7 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul
// Calculate all deployments.
let mut total_amount = 0;
let mut total_squares = 0;
for (square_id, &should_deploy) in squares.iter().enumerate() {
// Skip if square index is out of bounds.
if square_id > 24 {
@@ -154,8 +155,9 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul
round.total_deployed += amount;
round.count[square_id] += 1;
// Update total amount
// Update totals.
total_amount += amount;
total_squares += 1;
// Exit early if automation does not have enough balance for another square.
if let Some(automation) = &automation {
@@ -185,6 +187,17 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul
round_info.collect(total_amount, &signer_info)?;
}
// Log
sol_log(
&format!(
"Round #{}: deploying {} SOL to {} squares",
round.id,
lamports_to_sol(amount),
total_squares,
)
.as_str(),
);
Ok(())
}

View File

@@ -9,9 +9,6 @@ mod claim_yield;
mod close;
mod deploy;
mod deposit;
mod migrate_board;
mod migrate_miner;
// mod initialize;
mod log;
mod reset;
mod set_admin;
@@ -31,9 +28,6 @@ use claim_yield::*;
use close::*;
use deploy::*;
use deposit::*;
use migrate_board::*;
use migrate_miner::*;
// use initialize::*;
use log::*;
use reset::*;
use set_admin::*;
@@ -62,7 +56,6 @@ pub fn process_instruction(
OreInstruction::Deploy => process_deploy(accounts, data)?,
OreInstruction::Log => process_log(accounts, data)?,
OreInstruction::Close => process_close(accounts, data)?,
// OreInstruction::Initialize => process_initialize(accounts, data)?,
OreInstruction::Reset => process_reset(accounts, data)?,
// Staker

View File

@@ -1,19 +0,0 @@
use ore_api::prelude::*;
use solana_program::slot_hashes::SlotHashes;
use steel::*;
/// Pays out the winners and block reward.
pub fn process_migrate_board(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts.
let clock = Clock::get()?;
let [signer_info, board_info, system_program] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
let board = board_info.as_account_mut::<Board>(&ore_api::ID)?;
system_program.is_program(&system_program::ID)?;
// TODO Migrate miner account.
Ok(())
}

View File

@@ -1,20 +0,0 @@
use ore_api::prelude::*;
use solana_program::slot_hashes::SlotHashes;
use steel::*;
/// Pays out the winners and block reward.
pub fn process_migrate_miner(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts.
let clock = Clock::get()?;
let [signer_info, miner_info, system_program] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};
signer_info.is_signer()?;
let miner = miner_info.as_account_mut::<Miner>(&ore_api::ID)?;
system_program.is_program(&system_program::ID)?;
// TODO Migrate miner account.
// TODO Move refund_sol into rewards_sol.
Ok(())
}

View File

@@ -34,12 +34,35 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
ore_program.is_program(&ore_api::ID)?;
slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::ID)?;
// Open next round account.
create_program_account::<Round>(
round_next_info,
ore_program,
signer_info,
&ore_api::ID,
&[ROUND, &(board.round_id + 1).to_le_bytes()],
)?;
let round_next = round_next_info.as_account_mut::<Round>(&ore_api::ID)?;
round_next.id = board.round_id + 1;
round_next.deployed = [0; 25];
round_next.slot_hash = [0; 32];
round_next.count = [0; 25];
round_next.expires_at = clock.slot + 150 + ONE_WEEK_SLOTS;
round_next.rent_payer = *signer_info.key;
round_next.motherlode = 0;
round_next.top_miner = Pubkey::default();
round_next.top_miner_reward = 0;
round_next.total_deployed = 0;
round_next.total_vaulted = 0;
round_next.total_winnings = 0;
// Sample slot hash.
let mut r = 0;
let (winning_square, square_deployed) =
if let Ok(slot_hash) = get_slot_hash(board.end_slot, slot_hashes_sysvar) {
round.slot_hash = slot_hash;
r = round.rng();
if let Some(rng) = round.rng() {
r = rng;
let winning_square = round.winning_square(r);
let square_deployed = round.deployed[winning_square];
(winning_square, square_deployed)
@@ -47,6 +70,11 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
// Cannot get slot hash. No one wins.
round.slot_hash = [u8::MAX; 32];
(u64::MAX as usize, 0)
}
} else {
// Cannot get slot hash. No one wins.
round.slot_hash = [u8::MAX; 32];
(u64::MAX as usize, 0)
};
// Collect admin fees.
@@ -54,8 +82,8 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
// No one won. Vault all deployed.
if square_deployed == 0 {
// Update board.
round.total_vaulted = round.total_deployed;
// Vault all deployed.
round.total_vaulted = round.total_deployed - total_admin_fee;
treasury.balance += round.total_deployed - total_admin_fee;
// Emit event.
@@ -78,9 +106,14 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
.to_bytes(),
)?;
// Update board
board.round_id += 1;
board.start_slot = clock.slot + 1;
board.end_slot = board.start_slot + 150;
// Do SOL transfers.
board_info.send(total_admin_fee, &fee_collector_info);
board_info.send(round.total_deployed - total_admin_fee, &treasury_info);
round_info.send(total_admin_fee, &fee_collector_info);
round_info.send(round.total_deployed - total_admin_fee, &treasury_info);
return Ok(());
}
@@ -153,15 +186,16 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
}
// Validate top miner.
// TODO Safety checks here.
let top_miner_sample = round.top_miner_sample(r, winning_square);
let top_miner = top_miner_info
.as_account::<Miner>(&ore_api::ID)?
.assert(|m| m.round_id == round.id)?
.assert(|m| {
m.cumulative[winning_square] >= top_miner_sample
&& top_miner_sample < m.cumulative[winning_square] + m.deployed[winning_square]
})?;
// TODO Safety checks here (if no one won).
// let mut top_miner_address = Pubkey::default();
// let top_miner_sample = round.top_miner_sample(r, winning_square);
// let top_miner = top_miner_info
// .as_account::<Miner>(&ore_api::ID)?
// .assert(|m| m.round_id == round.id)?
// .assert(|m| {
// m.cumulative[winning_square] >= top_miner_sample
// && top_miner_sample < m.cumulative[winning_square] + m.deployed[winning_square]
// })?;
// Emit event.
program_log(
@@ -172,7 +206,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
start_slot: board.start_slot,
end_slot: board.end_slot,
winning_square: winning_square as u64,
top_miner: top_miner.authority,
top_miner: Pubkey::default(), // top_miner.authority,
num_winners: round.count[winning_square],
total_deployed: round.total_deployed,
total_vaulted: round.total_vaulted,
@@ -188,28 +222,6 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
board.start_slot = clock.slot + 1;
board.end_slot = board.start_slot + 150;
// Open next round account.
create_program_account::<Round>(
round_next_info,
ore_program,
signer_info,
&ore_api::ID,
&[ROUND, &board.round_id.to_le_bytes()],
)?;
let round_next = round_next_info.as_account_mut::<Round>(&ore_api::ID)?;
round_next.id = board.round_id;
round_next.deployed = [0; 25];
round_next.slot_hash = [0; 32];
round_next.count = [0; 25];
round_next.expires_at = board.end_slot + ONE_WEEK_SLOTS;
round_next.rent_payer = *signer_info.key;
round_next.motherlode = 0;
round_next.top_miner = Pubkey::default();
round_next.top_miner_reward = round.top_miner_reward;
round_next.total_deployed = 0;
round_next.total_vaulted = 0;
round_next.total_winnings = 0;
// Do SOL transfers.
round_info.send(total_admin_fee, &fee_collector_info);
round_info.send(vault_amount, &treasury_info);

View File

@@ -1,9 +1,9 @@
use solana_program::pubkey;
use steel::*;
pub const AUTHORIZED_ACCOUNTS: [Pubkey; 4] = [
pub const AUTHORIZED_ACCOUNTS: [Pubkey; 1] = [
pubkey!("pqspJ298ryBjazPAr95J9sULCVpZe3HbZTWkbC1zrkS"),
pubkey!("By5JFFueXCqeqLk5MzR8ZSwFxASz3SKWX2TVfT1LTFbX"),
pubkey!("5Nb2ibzu4bWrwis2vNVD4mJprt6KTchzW6wgbVWM2PkY"),
pubkey!("6tUUXB6LuTE1Pzpe6sP4mZL9CNA5XQYGWYbn1oqPpKeH"),
// pubkey!("By5JFFueXCqeqLk5MzR8ZSwFxASz3SKWX2TVfT1LTFbX"),
// pubkey!("5Nb2ibzu4bWrwis2vNVD4mJprt6KTchzW6wgbVWM2PkY"),
// pubkey!("6tUUXB6LuTE1Pzpe6sP4mZL9CNA5XQYGWYbn1oqPpKeH"),
];