diff --git a/api/src/state/config.rs b/api/src/state/config.rs index 7959f4b..f9c1cbd 100644 --- a/api/src/state/config.rs +++ b/api/src/state/config.rs @@ -14,6 +14,7 @@ pub struct Config { pub last_boost: i64, // The minimum amount of SOL that can be deploy. + #[deprecated(since = "1.0.0", note = "Unused")] pub min_deploy_amount: u64, // The address that receives fees. diff --git a/api/src/state/miner.rs b/api/src/state/miner.rs index c8b3da4..c1a0a0a 100644 --- a/api/src/state/miner.rs +++ b/api/src/state/miner.rs @@ -17,6 +17,9 @@ pub struct Miner { #[deprecated(note = "Use automation executor instead")] pub executor: Pubkey, + /// 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, diff --git a/api/src/state/square.rs b/api/src/state/square.rs index af3bc66..79bd81a 100644 --- a/api/src/state/square.rs +++ b/api/src/state/square.rs @@ -10,6 +10,9 @@ pub struct Square { /// The count of miners on this square. pub count: [u64; 25], + /// The deployments of all players. + pub deployed: [[u64; 16]; 25], + /// The miners in each square. pub miners: [[Pubkey; 16]; 25], } diff --git a/program/src/deploy.rs b/program/src/deploy.rs index 5243255..9945b5c 100644 --- a/program/src/deploy.rs +++ b/program/src/deploy.rs @@ -13,8 +13,9 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul // Load accounts. let clock = Clock::get()?; + let (required_accounts, miner_accounts) = accounts.split_at(9); let [signer_info, authority_info, automation_info, board_info, config_info, fee_collector_info, miner_info, square_info, system_program] = - accounts + required_accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -80,11 +81,6 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul } } - // Check minimum amount. - if amount < config.min_deploy_amount { - return Err(trace("Amount too small", OreError::AmountTooSmall.into())); - } - // Log sol_log( &format!( @@ -156,29 +152,118 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul let amount = amount - fee; // Calculate all deployments. + let mut refund_amounts = [0; 25]; + let mut refund_miner_infos = [None; 25]; let mut total_fee = 0; let mut total_amount = 0; - for (square_id, &should_deploy) in squares.iter().enumerate() { + 'deploy: for (square_id, &should_deploy) in squares.iter().enumerate() { + // Skip if square index is out of bounds. if square_id > 24 { break; } - if should_deploy { - total_fee += fee; - total_amount += amount; - // Update miner - let is_first_move = miner.deployed[square_id] == 0; - miner.deployed[square_id] += amount; + // Skip if square is not deployed to. + if !should_deploy { + continue; + } - // Update square - if is_first_move { - square.miners[square_id][square.count[square_id] as usize] = miner.authority; - square.count[square_id] += 1; + // Get deployment metadata. + let is_first_move = miner.deployed[square_id] == 0; + let mut idx = if is_first_move { + // Insert at end of the list. + square.count[square_id] as usize + } else { + // Find the miner's index in the list. + let mut idx = 0; + for i in 0..16 { + if square.miners[square_id][i] == miner.authority { + idx = i; + break; + } + } + idx + }; + + // If the square is full, refund the miner with the smallest deployment and kick them off the square. + if idx == 16 { + // Find miner with the smallest deployment. + let mut smallest_miner = Pubkey::default(); + let mut smallest_deployment = u64::MAX; + for i in 0..16 { + if square.deployed[square_id][i] < smallest_deployment { + smallest_deployment = square.deployed[square_id][i]; + smallest_miner = square.miners[square_id][i]; + idx = i; + } } - // Update board - board.deployed[square_id] += amount; - board.total_deployed += amount; + // Safety check. + // This should never happen. + assert!(smallest_miner != Pubkey::default()); + + // If deploy amount is less than smallest deployment, skip this square. + if amount < smallest_deployment { + continue 'deploy; + } + + // Refund the smallest miner and kick them off the square. + for miner_info in miner_accounts { + if *miner_info.key == miner_pda(smallest_miner).0 { + let smallest_miner = miner_info + .as_account_mut::(&ore_api::ID)? + .assert_mut(|m| m.authority == smallest_miner)?; + smallest_miner.refund_sol += smallest_deployment; + smallest_miner.deployed[square_id] -= smallest_deployment; + refund_amounts[square_id] = smallest_deployment; + refund_miner_infos[square_id] = Some(miner_info); + + // Remove smallest miner from square + board.deployed[square_id] -= smallest_deployment; + board.total_deployed -= smallest_deployment; + square.deployed[square_id][idx] -= smallest_deployment; + square.miners[square_id][idx] = Pubkey::default(); + square.count[square_id] -= 1; + + break; + } + } + } + + // Safety check. + // This should never happen. + assert!(idx < 16); + + // Safety check. + // Skip if square count is >= 16. This can only happen if the signer didn't provide a miner account to refund. + if square.count[square_id] >= 16 { + continue 'deploy; + } + + // Update miner + miner.deployed[square_id] += amount; + + // Update square + if is_first_move { + square.miners[square_id][idx] = miner.authority; + square.count[square_id] += 1; + } + + // Update board + board.deployed[square_id] += amount; + board.total_deployed += amount; + + // Update square deployed + square.deployed[square_id][idx] += amount; + + // Update total fee and amount + total_fee += fee; + total_amount += amount; + } + + // Transfer SOL refunds. + for (square_id, refund_amount) in refund_amounts.iter().enumerate() { + if let Some(refund_miner_info) = refund_miner_infos[square_id] { + board_info.send(*refund_amount, &refund_miner_info); } } diff --git a/program/src/reset.rs b/program/src/reset.rs index 1a0479e..6598c53 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -17,7 +17,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul .as_account_mut::(&ore_api::ID)? .assert_mut(|b| b.slot_hash == [0; 32])? .assert_mut(|b| clock.slot > b.end_slot)?; - let config = config_info.as_account_mut::(&ore_api::ID)?; + config_info.as_account::(&ore_api::ID)?; let mint = mint_info.has_address(&MINT_ADDRESS)?.as_mint()?; let square = square_info.as_account_mut::(&ore_api::ID)?; let treasury = treasury_info.as_account_mut::(&ore_api::ID)?; @@ -141,25 +141,6 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul // Update board. board.total_winnings = winnings; - // Update min deploy amount. - let capacity: u64 = 16 * 25; - let threshold = (capacity * 3) / 4; - let mut occupancy = 0; - for i in 0..25 { - occupancy += square.count[i]; - } - if occupancy == capacity { - // If board is full, double the minimum deploy amount. - config.min_deploy_amount = 1u64.max(config.min_deploy_amount * 2); - } else if occupancy < threshold { - // If board is less than 75% full, reduce minimum deploy amount linearly. - let availability = capacity.saturating_sub(occupancy); - let pct = (availability * 100) / capacity; - let chg = (pct.saturating_sub(25) * 100) / 75; - let dif = (config.min_deploy_amount * chg) / 100; - config.min_deploy_amount = config.min_deploy_amount.saturating_sub(dif); - } - // Emit event. program_log( &[board_info.clone(), ore_program.clone()], diff --git a/program/src/whitelist.rs b/program/src/whitelist.rs index e3ff4ce..e72dfb1 100644 --- a/program/src/whitelist.rs +++ b/program/src/whitelist.rs @@ -1,7 +1,7 @@ use solana_program::pubkey; use steel::*; -pub const AUTHORIZED_ACCOUNTS: [Pubkey; 280] = [ +pub const AUTHORIZED_ACCOUNTS: [Pubkey; 264] = [ pubkey!("pqspJ298ryBjazPAr95J9sULCVpZe3HbZTWkbC1zrkS"), pubkey!("HNWhK5f8RMWBqcA7mXJPaxdTPGrha3rrqUrri7HSKb3T"), pubkey!("6B9PjpHfbhPcSakS5UQ7ZctgbPujfsryVRpDecskGLiz"), @@ -248,22 +248,22 @@ pub const AUTHORIZED_ACCOUNTS: [Pubkey; 280] = [ pubkey!("6Fg49wV8MGW5PeTTF1LoFRR8FzUGiMZXC3gWZiagAWzg"), pubkey!("BwCwfRRe2y6Rxv6SQZMRQ499TTakKnKEDbPofN8JQ7p"), pubkey!("5D1oAw6sE14YvdSPwGWGbFCj7SVTbivnTxWXxbvNrur6"), - pubkey!("CqJ5a1XB3c2SLcAcxAvZQ4hzYmLTSAtMA6RBy8ovWmeY"), - pubkey!("273HADhxAy3NWKdRiTTZQG6WukHz9TiJZeiQjnSe2pxG"), - pubkey!("2JVo9kDtAn77vQC9KLrPBmJ2hnCnjaPpgQym6sCKnGRj"), - pubkey!("DW9ugDkNyPxNz5egzBVV4EBdWMKv5hUbLgadA7TZysyg"), - pubkey!("9W3B4bKyqBrGjaJKEjLStuRmLTf42fbSqCEiML7LHaoe"), - pubkey!("9g4VpomWV3HoX6AgsjAVumysrLzMn49SxSbHm5xP8SUJ"), - pubkey!("6ptnvnBHhR7bjgLK5ZimxKFhhHzNWeEdeNB8oZiCeaLM"), - pubkey!("AGruT4qWZf3FNQEQewLw5NfFpMrMjDhgB8H8UGKQPwms"), - pubkey!("7p2vJUJPG9pfRrzVans3zmYe1oDZdzS5YjYM8dP3L4EZ"), - pubkey!("5SwFSBuYTmGcHvLbL9tQjZRKNgGNijzrCMVoQrrjXUSg"), - pubkey!("CZdNF9Hz9ayGdDhVDvK4KhKCX2PS7BNkEp7cwJggAkvi"), - pubkey!("8F18uJ2JaTGq38pL7qsdyP6TJkyxeSXQd1LcHmg8FpEZ"), - pubkey!("9vMYgsTUpGSHnqjrgkQekzFq23eRpENnUESyZ3B9BzVT"), - pubkey!("CXcLK7roVYsEZnN66fWq7h2DKSkfLmWaM3P9CvqfeQRh"), - pubkey!("HPK2t9HkM4g2cVn4yaHz7tWwYLp9KwJFM4DTZkyBrwg5"), - pubkey!("RX71VvEgsKh4fePR1XjywC3Nr9hmjSiBgcskCDMoGoH"), + // pubkey!("CqJ5a1XB3c2SLcAcxAvZQ4hzYmLTSAtMA6RBy8ovWmeY"), + // pubkey!("273HADhxAy3NWKdRiTTZQG6WukHz9TiJZeiQjnSe2pxG"), + // pubkey!("2JVo9kDtAn77vQC9KLrPBmJ2hnCnjaPpgQym6sCKnGRj"), + // pubkey!("DW9ugDkNyPxNz5egzBVV4EBdWMKv5hUbLgadA7TZysyg"), + // pubkey!("9W3B4bKyqBrGjaJKEjLStuRmLTf42fbSqCEiML7LHaoe"), + // pubkey!("9g4VpomWV3HoX6AgsjAVumysrLzMn49SxSbHm5xP8SUJ"), + // pubkey!("6ptnvnBHhR7bjgLK5ZimxKFhhHzNWeEdeNB8oZiCeaLM"), + // pubkey!("AGruT4qWZf3FNQEQewLw5NfFpMrMjDhgB8H8UGKQPwms"), + // pubkey!("7p2vJUJPG9pfRrzVans3zmYe1oDZdzS5YjYM8dP3L4EZ"), + // pubkey!("5SwFSBuYTmGcHvLbL9tQjZRKNgGNijzrCMVoQrrjXUSg"), + // pubkey!("CZdNF9Hz9ayGdDhVDvK4KhKCX2PS7BNkEp7cwJggAkvi"), + // pubkey!("8F18uJ2JaTGq38pL7qsdyP6TJkyxeSXQd1LcHmg8FpEZ"), + // pubkey!("9vMYgsTUpGSHnqjrgkQekzFq23eRpENnUESyZ3B9BzVT"), + // pubkey!("CXcLK7roVYsEZnN66fWq7h2DKSkfLmWaM3P9CvqfeQRh"), + // pubkey!("HPK2t9HkM4g2cVn4yaHz7tWwYLp9KwJFM4DTZkyBrwg5"), + // pubkey!("RX71VvEgsKh4fePR1XjywC3Nr9hmjSiBgcskCDMoGoH"), pubkey!("E8x8J8nJMNWrQXQF1BdBSY9QA12S9qmUycJPPjQhvz9H"), pubkey!("5EFvY7HwqUh5vfkcDtQAbQoNCdiEWL6eqG1zuiRihzk5"), pubkey!("BgVtFam2Xvj5vte1a5efvMC47uVdNugx6bqGzDLkBo6i"),