Hardhat/refund (#135)

* refund

* comment

* cleanup

* check
This commit is contained in:
Hardhat Chad
2025-10-05 16:03:23 -05:00
committed by GitHub
parent af6f69e116
commit bbfbb82157
2 changed files with 108 additions and 73 deletions

View File

@@ -28,12 +28,12 @@ pub fn process_checkpoint(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program
return Ok(());
}
let round = round_info.as_account_mut::<Round>(&ore_api::ID)?;
let round = round_info.as_account_mut::<Round>(&ore_api::ID)?; // Round has been closed.
treasury_info.as_account::<Treasury>(&ore_api::ID)?;
system_program.is_program(&system_program::ID)?;
// If round is current round, or the miner round ID does not match the provided round, return.
if round.id == board.round_id || round.id != miner.round_id {
if round.id == board.round_id || round.id != miner.round_id || round.slot_hash == [0; 32] {
return Ok(());
}
@@ -44,13 +44,6 @@ pub fn process_checkpoint(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program
return Ok(());
}
// Get the RNG.
// If the round has no RNG, no one wins.
let Some(r) = round.rng() else {
miner.checkpoint_id = miner.round_id;
return Ok(());
};
// Calculate bot fee.
// If the round expires in less than 12h, anyone may checkpoint this account and collect the bot fee.
let mut bot_fee = 0;
@@ -62,43 +55,60 @@ pub fn process_checkpoint(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program
// Calculate miner rewards.
let mut rewards_sol = 0;
let mut rewards_ore = 0;
let winning_square = round.winning_square(r) as usize;
if miner.deployed[winning_square] > 0 {
// Sanity check.
assert!(
round.deployed[winning_square] >= miner.deployed[winning_square],
"Invalid round deployed amount"
);
// Calculate SOL rewards.
let original_deployment = miner.deployed[winning_square];
let admin_fee = (original_deployment / 100).max(1);
rewards_sol = original_deployment - admin_fee;
rewards_sol += ((round.total_winnings as u128 * miner.deployed[winning_square] as u128)
/ round.deployed[winning_square] as u128) as u64;
// Get the RNG.
if let Some(r) = round.rng() {
// Get the winning square.
let winning_square = round.winning_square(r) as usize;
// Calculate ORE rewards.
if round.top_miner == SPLIT_ADDRESS {
// If round is split, split the reward evenly among all miners.
rewards_ore = ((round.top_miner_reward * miner.deployed[winning_square])
/ round.deployed[winning_square]) as u64;
} else {
// If round is not split, payout to the top miner.
let top_miner_sample = round.top_miner_sample(r, winning_square);
if top_miner_sample >= miner.cumulative[winning_square]
&& top_miner_sample
< miner.cumulative[winning_square] + miner.deployed[winning_square]
{
rewards_ore = round.top_miner_reward;
round.top_miner = miner.authority;
// If the miner deployed to the winning square, calculate rewards.
if miner.deployed[winning_square] > 0 {
// Sanity check.
assert!(
round.deployed[winning_square] >= miner.deployed[winning_square],
"Invalid round deployed amount"
);
// Calculate SOL rewards.
let original_deployment = miner.deployed[winning_square];
let admin_fee = (original_deployment / 100).max(1);
rewards_sol = original_deployment - admin_fee;
rewards_sol += ((round.total_winnings as u128 * miner.deployed[winning_square] as u128)
/ round.deployed[winning_square] as u128) as u64;
// Calculate ORE rewards.
if round.top_miner == SPLIT_ADDRESS {
// If round is split, split the reward evenly among all miners.
rewards_ore = ((round.top_miner_reward * miner.deployed[winning_square])
/ round.deployed[winning_square]) as u64;
} else {
// If round is not split, payout to the top miner.
let top_miner_sample = round.top_miner_sample(r, winning_square);
if top_miner_sample >= miner.cumulative[winning_square]
&& top_miner_sample
< miner.cumulative[winning_square] + miner.deployed[winning_square]
{
rewards_ore = round.top_miner_reward;
round.top_miner = miner.authority;
}
}
// Calculate motherlode rewards.
if round.motherlode > 0 {
rewards_ore += ((round.motherlode as u128 * miner.deployed[winning_square] as u128)
/ round.deployed[winning_square] as u128) as u64;
}
}
} else {
// Sanity check.
// If there is no rng, total deployed should have been reset to zero.
assert!(
round.total_deployed == 0,
"Round total deployed should be zero."
);
// Calculate motherlode rewards.
if round.motherlode > 0 {
rewards_ore += ((round.motherlode as u128 * miner.deployed[winning_square] as u128)
/ round.deployed[winning_square] as u128) as u64;
}
// Round has no slot hash, refund all SOL.
rewards_sol = miner.deployed.iter().sum::<u64>();
}
// Checkpoint miner.
@@ -116,6 +126,7 @@ pub fn process_checkpoint(accounts: &[AccountInfo<'_>], _data: &[u8]) -> Program
miner_info.send(bot_fee, &signer_info);
}
// Assert miner account has sufficient funds for rent and rewards.
let account_size = 8 + std::mem::size_of::<Miner>();
let required_rent = Rent::get()?.minimum_balance(account_size);
assert!(

View File

@@ -1,5 +1,5 @@
use ore_api::prelude::*;
use solana_program::{pubkey, slot_hashes::SlotHashes};
use solana_program::slot_hashes::SlotHashes;
use steel::*;
/// Pays out the winners and block reward.
@@ -47,7 +47,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
round_next.deployed = [0; 25];
round_next.slot_hash = [0; 32];
round_next.count = [0; 25];
round_next.expires_at = u64::MAX; // clock.slot + 150 + ONE_WEEK_SLOTS;
round_next.expires_at = u64::MAX; // Set to max, to indicate round is waiting for first deploy to begin.
round_next.rent_payer = *signer_info.key;
round_next.motherlode = 0;
round_next.top_miner = Pubkey::default();
@@ -57,31 +57,55 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
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;
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)
} else {
// 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)
};
if let Ok(slot_hash) = get_slot_hash(board.end_slot, slot_hashes_sysvar) {
round.slot_hash = slot_hash;
} else {
round.slot_hash = [u8::MAX; 32];
}
// Collect admin fees.
// Exit early if no slot hash was found.
let Some(r) = round.rng() else {
// Slot hash could not be found, refund all SOL.
round.total_vaulted = 0;
round.total_winnings = 0;
round.total_deployed = 0;
// Emit event.
program_log(
&[board_info.clone(), ore_program.clone()],
ResetEvent {
disc: 0,
round_id: round.id,
start_slot: board.start_slot,
end_slot: board.end_slot,
winning_square: u64::MAX,
top_miner: Pubkey::default(),
num_winners: 0,
motherlode: 0,
total_deployed: round.total_deployed,
total_vaulted: round.total_vaulted,
total_winnings: round.total_winnings,
total_minted: 0,
ts: clock.unix_timestamp,
}
.to_bytes(),
)?;
// Update board for next round.
board.round_id += 1;
board.start_slot = clock.slot + 1;
board.end_slot = u64::MAX;
return Ok(());
};
// Caculate admin fees.
let total_admin_fee = round.total_deployed / 100;
// No one won. Vault all deployed.
if square_deployed == 0 {
// Get the winning square.
let winning_square = round.winning_square(r);
// If no one deployed on the winning square, vault all deployed.
if round.deployed[winning_square] == 0 {
// Vault all deployed.
round.total_vaulted = round.total_deployed - total_admin_fee;
treasury.balance += round.total_deployed - total_admin_fee;
@@ -107,10 +131,10 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
.to_bytes(),
)?;
// Update board
// Update board for next round.
board.round_id += 1;
board.start_slot = clock.slot + 1;
board.end_slot = u64::MAX; // board.start_slot + 150;
board.end_slot = u64::MAX;
// Do SOL transfers.
round_info.send(total_admin_fee, &fee_collector_info);
@@ -118,12 +142,12 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
return Ok(());
}
// Get winnings amount (total deployed on all non-winning squares).
// Get winnings amount (total deployed on all non-winning squares, minus admin fee).
let winnings = round.calculate_total_winnings(winning_square);
let winnings_admin_fee = winnings / 100; // 1% admin fee.
let winnings = winnings - winnings_admin_fee;
// Get vault amount.
// Subtract vault amount from winnings.
let vault_amount = winnings / 10; // 10% of winnings.
let winnings = winnings - vault_amount;
round.total_winnings = winnings;
@@ -139,7 +163,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
+ winnings_admin_fee
);
// Mint 1 ORE for the winning miner.
// Mint +1 ORE for the winning miner(s).
let mint_amount = MAX_SUPPLY.saturating_sub(mint.supply()).min(ONE_ORE);
round.top_miner_reward = mint_amount;
mint_to_signed(
@@ -151,18 +175,18 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul
&[TREASURY],
)?;
// Set top miner to split reward address if the round is split.
// With 1 in 4 odds, split the +1 ORE reward.
if round.is_split_reward(r) {
round.top_miner = SPLIT_ADDRESS;
}
// Reset the motherlode if it was activated.
// Payout the motherlode if it was activated.
if round.did_hit_motherlode(r) {
round.motherlode = treasury.motherlode;
treasury.motherlode = 0;
}
// Top up the motherlode rewards pool.
// Mint +0.2 ORE to the motherlode rewards pool.
let mint = mint_info.as_mint()?;
let motherlode_mint_amount = MAX_SUPPLY.saturating_sub(mint.supply()).min(ONE_ORE / 5);
if motherlode_mint_amount > 0 {