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] [workspace]
resolver = "2" resolver = "2"
members = ["api", "program"] members = ["api", "program", "cli"]
[workspace.package] [workspace.package]
version = "3.7.0-alpha" version = "3.7.0-alpha"

View File

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

View File

@@ -1,6 +1,6 @@
use steel::*; use steel::*;
use crate::state::board_pda; use crate::state::{board_pda, OreAccountOLD};
use super::OreAccount; use super::OreAccount;
@@ -17,6 +17,40 @@ pub struct Board {
pub end_slot: u64, 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 { impl Board {
pub fn pda(&self) -> (Pubkey, u8) { pub fn pda(&self) -> (Pubkey, u8) {
board_pda() board_pda()
@@ -24,3 +58,4 @@ impl Board {
} }
account!(OreAccount, Board); account!(OreAccount, Board);
account!(OreAccountOLD, BoardOLD);

View File

@@ -1,6 +1,6 @@
use steel::*; use steel::*;
use crate::state::miner_pda; use crate::state::{miner_pda, OreAccountOLD};
use super::OreAccount; use super::OreAccount;
@@ -38,6 +38,38 @@ pub struct Miner {
pub lifetime_rewards_ore: u64, 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 { impl Miner {
pub fn pda(&self) -> (Pubkey, u8) { pub fn pda(&self) -> (Pubkey, u8) {
miner_pda(self.authority) miner_pda(self.authority)
@@ -45,3 +77,4 @@ impl Miner {
} }
account!(OreAccount, Miner); account!(OreAccount, Miner);
account!(OreAccountOLD, MinerOLD);

View File

@@ -35,6 +35,13 @@ pub enum OreAccount {
Round = 109, 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) { pub fn automation_pda(authority: Pubkey) -> (Pubkey, u8) {
Pubkey::find_program_address(&[AUTOMATION, &authority.to_bytes()], &crate::ID) Pubkey::find_program_address(&[AUTOMATION, &authority.to_bytes()], &crate::ID)
} }

View File

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

View File

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

View File

@@ -1,13 +1,12 @@
use ore_api::prelude::*; use ore_api::prelude::*;
use solana_program::rent::Rent; use solana_program::{log::sol_log, rent::Rent};
use steel::*; use steel::*;
/// Checkpoints a miner's rewards. /// Checkpoints a miner's rewards.
pub fn process_checkpoint(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { pub fn process_checkpoint(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts. // Load accounts.
let clock = Clock::get()?; let clock = Clock::get()?;
let [signer_info, automation_info, board_info, miner_info, round_info, treasury_info, system_program] = let [signer_info, board_info, miner_info, round_info, treasury_info, system_program] = accounts
accounts
else { else {
return Err(ProgramError::NotEnoughAccountKeys); return Err(ProgramError::NotEnoughAccountKeys);
}; };
@@ -40,37 +39,25 @@ pub fn process_checkpoint(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program
return Ok(()); 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; let mut bot_fee = 0;
if clock.slot >= round.expires_at - ONE_DAY_SLOTS { if clock.slot >= round.expires_at - ONE_DAY_SLOTS {
// The round expires in less than 24h. // 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; bot_fee = miner.checkpoint_fee;
miner.checkpoint_fee = 0; 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. // Calculate miner rewards.
let mut rewards_sol = 0; let mut rewards_sol = 0;
let mut rewards_ore = 0; let mut rewards_ore = 0;
let r = round.rng();
let winning_square = round.winning_square(r) as usize; let winning_square = round.winning_square(r) as usize;
if miner.deployed[winning_square] > 0 { if miner.deployed[winning_square] > 0 {
// Sanity check. // Sanity check.
@@ -101,6 +88,8 @@ pub fn process_checkpoint(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program
} }
} }
sol_log("I");
// Checkpoint miner. // Checkpoint miner.
miner.checkpoint_id = round.id; miner.checkpoint_id = round.id;
miner.rewards_ore += rewards_ore; 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); 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. // Assert round has sufficient funds for rent + debts.
// TODO Debts
let account_size = 8 + std::mem::size_of::<Round>(); let account_size = 8 + std::mem::size_of::<Round>();
let required_rent = Rent::get()?.minimum_balance(account_size); let required_rent = Rent::get()?.minimum_balance(account_size);
assert!( assert!(

View File

@@ -1,11 +1,14 @@
use ore_api::prelude::*; use ore_api::prelude::*;
use solana_program::rent::Rent;
use steel::*; use steel::*;
/// Closes a round accound, and returns the rent to the rent payer. /// Closes a round accound, and returns the rent to the rent payer.
pub fn process_close(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { pub fn process_close(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
// Load accounts. // Load accounts.
let clock = Clock::get()?; 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); return Err(ProgramError::NotEnoughAccountKeys);
}; };
signer_info.is_signer()?; 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.id < board.round_id)?
.assert_mut(|r| r.expires_at < clock.slot)? // Ensure round has expired. .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. .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)?; 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. // Close the account.
round_info.close(rent_payer_info)?; 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 solana_program::{keccak::hashv, log::sol_log, native_token::lamports_to_sol};
use steel::*; use steel::*;
use crate::AUTHORIZED_ACCOUNTS;
/// Deploys capital to prospect on a square. /// Deploys capital to prospect on a square.
pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
// Parse data. // Parse data.
@@ -18,16 +20,25 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul
}; };
signer_info.is_signer()?; signer_info.is_signer()?;
authority_info.is_writable()?; 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 let board = board_info
.as_account_mut::<Board>(&ore_api::ID)? .as_account_mut::<Board>(&ore_api::ID)?
.assert_mut(|b| clock.slot >= b.start_slot && clock.slot < b.end_slot)?; .assert_mut(|b| clock.slot >= b.start_slot && clock.slot < b.end_slot)?;
let round = round_info let round = round_info
.as_account_mut::<Round>(&ore_api::ID)? .as_account_mut::<Round>(&ore_api::ID)?
.assert_mut(|r| r.id == board.round_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)?; 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. // Check if signer is the automation executor.
let automation = if !automation_info.data_is_empty() { let automation = if !automation_info.data_is_empty() {
let automation = automation_info 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. // Open miner account.
let miner = if miner_info.data_is_empty() { let miner = if miner_info.data_is_empty() {
create_program_account::<Miner>( create_program_account::<Miner>(
@@ -127,6 +127,7 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul
// Calculate all deployments. // Calculate all deployments.
let mut total_amount = 0; let mut total_amount = 0;
let mut total_squares = 0;
for (square_id, &should_deploy) in squares.iter().enumerate() { for (square_id, &should_deploy) in squares.iter().enumerate() {
// Skip if square index is out of bounds. // Skip if square index is out of bounds.
if square_id > 24 { if square_id > 24 {
@@ -154,8 +155,9 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul
round.total_deployed += amount; round.total_deployed += amount;
round.count[square_id] += 1; round.count[square_id] += 1;
// Update total amount // Update totals.
total_amount += amount; total_amount += amount;
total_squares += 1;
// Exit early if automation does not have enough balance for another square. // Exit early if automation does not have enough balance for another square.
if let Some(automation) = &automation { 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)?; 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(()) Ok(())
} }

View File

@@ -9,9 +9,6 @@ mod claim_yield;
mod close; mod close;
mod deploy; mod deploy;
mod deposit; mod deposit;
mod migrate_board;
mod migrate_miner;
// mod initialize;
mod log; mod log;
mod reset; mod reset;
mod set_admin; mod set_admin;
@@ -31,9 +28,6 @@ use claim_yield::*;
use close::*; use close::*;
use deploy::*; use deploy::*;
use deposit::*; use deposit::*;
use migrate_board::*;
use migrate_miner::*;
// use initialize::*;
use log::*; use log::*;
use reset::*; use reset::*;
use set_admin::*; use set_admin::*;
@@ -62,7 +56,6 @@ pub fn process_instruction(
OreInstruction::Deploy => process_deploy(accounts, data)?, OreInstruction::Deploy => process_deploy(accounts, data)?,
OreInstruction::Log => process_log(accounts, data)?, OreInstruction::Log => process_log(accounts, data)?,
OreInstruction::Close => process_close(accounts, data)?, OreInstruction::Close => process_close(accounts, data)?,
// OreInstruction::Initialize => process_initialize(accounts, data)?,
OreInstruction::Reset => process_reset(accounts, data)?, OreInstruction::Reset => process_reset(accounts, data)?,
// Staker // 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)?; ore_program.is_program(&ore_api::ID)?;
slot_hashes_sysvar.is_sysvar(&sysvar::slot_hashes::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. // Sample slot hash.
let mut r = 0; let mut r = 0;
let (winning_square, square_deployed) = let (winning_square, square_deployed) =
if let Ok(slot_hash) = get_slot_hash(board.end_slot, slot_hashes_sysvar) { if let Ok(slot_hash) = get_slot_hash(board.end_slot, slot_hashes_sysvar) {
round.slot_hash = slot_hash; round.slot_hash = slot_hash;
r = round.rng(); if let Some(rng) = round.rng() {
r = rng;
let winning_square = round.winning_square(r); let winning_square = round.winning_square(r);
let square_deployed = round.deployed[winning_square]; let square_deployed = round.deployed[winning_square];
(winning_square, square_deployed) (winning_square, square_deployed)
@@ -47,6 +70,11 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
// Cannot get slot hash. No one wins. // Cannot get slot hash. No one wins.
round.slot_hash = [u8::MAX; 32]; round.slot_hash = [u8::MAX; 32];
(u64::MAX as usize, 0) (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. // Collect admin fees.
@@ -54,8 +82,8 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
// No one won. Vault all deployed. // No one won. Vault all deployed.
if square_deployed == 0 { if square_deployed == 0 {
// Update board. // Vault all deployed.
round.total_vaulted = round.total_deployed; round.total_vaulted = round.total_deployed - total_admin_fee;
treasury.balance += round.total_deployed - total_admin_fee; treasury.balance += round.total_deployed - total_admin_fee;
// Emit event. // Emit event.
@@ -78,9 +106,14 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
.to_bytes(), .to_bytes(),
)?; )?;
// Update board
board.round_id += 1;
board.start_slot = clock.slot + 1;
board.end_slot = board.start_slot + 150;
// Do SOL transfers. // Do SOL transfers.
board_info.send(total_admin_fee, &fee_collector_info); round_info.send(total_admin_fee, &fee_collector_info);
board_info.send(round.total_deployed - total_admin_fee, &treasury_info); round_info.send(round.total_deployed - total_admin_fee, &treasury_info);
return Ok(()); return Ok(());
} }
@@ -153,15 +186,16 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
} }
// Validate top miner. // Validate top miner.
// TODO Safety checks here. // TODO Safety checks here (if no one won).
let top_miner_sample = round.top_miner_sample(r, winning_square); // let mut top_miner_address = Pubkey::default();
let top_miner = top_miner_info // let top_miner_sample = round.top_miner_sample(r, winning_square);
.as_account::<Miner>(&ore_api::ID)? // let top_miner = top_miner_info
.assert(|m| m.round_id == round.id)? // .as_account::<Miner>(&ore_api::ID)?
.assert(|m| { // .assert(|m| m.round_id == round.id)?
m.cumulative[winning_square] >= top_miner_sample // .assert(|m| {
&& top_miner_sample < m.cumulative[winning_square] + m.deployed[winning_square] // m.cumulative[winning_square] >= top_miner_sample
})?; // && top_miner_sample < m.cumulative[winning_square] + m.deployed[winning_square]
// })?;
// Emit event. // Emit event.
program_log( program_log(
@@ -172,7 +206,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
start_slot: board.start_slot, start_slot: board.start_slot,
end_slot: board.end_slot, end_slot: board.end_slot,
winning_square: winning_square as u64, winning_square: winning_square as u64,
top_miner: top_miner.authority, top_miner: Pubkey::default(), // top_miner.authority,
num_winners: round.count[winning_square], num_winners: round.count[winning_square],
total_deployed: round.total_deployed, total_deployed: round.total_deployed,
total_vaulted: round.total_vaulted, 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.start_slot = clock.slot + 1;
board.end_slot = board.start_slot + 150; 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. // Do SOL transfers.
round_info.send(total_admin_fee, &fee_collector_info); round_info.send(total_admin_fee, &fee_collector_info);
round_info.send(vault_amount, &treasury_info); round_info.send(vault_amount, &treasury_info);

View File

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