diff --git a/api/src/instruction.rs b/api/src/instruction.rs index 9bf33c1..c2e9089 100644 --- a/api/src/instruction.rs +++ b/api/src/instruction.rs @@ -21,6 +21,7 @@ pub enum OreInstruction { // Seeker ClaimSeeker = 15, + MigrateMiner = 16, } #[repr(C)] @@ -121,6 +122,10 @@ pub struct Bury { #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct ClaimSeeker {} +#[repr(C)] +#[derive(Clone, Copy, Debug, Pod, Zeroable)] +pub struct MigrateMiner {} + instruction!(OreInstruction, Automate); instruction!(OreInstruction, Boost); instruction!(OreInstruction, ClaimSOL); @@ -134,3 +139,4 @@ instruction!(OreInstruction, Reset); instruction!(OreInstruction, SetAdmin); instruction!(OreInstruction, SetFeeCollector); instruction!(OreInstruction, ClaimSeeker); +instruction!(OreInstruction, MigrateMiner); diff --git a/api/src/sdk.rs b/api/src/sdk.rs index 6c24fca..e981963 100644 --- a/api/src/sdk.rs +++ b/api/src/sdk.rs @@ -359,3 +359,17 @@ pub fn claim_seeker(signer: Pubkey, mint: Pubkey) -> Instruction { data: ClaimSeeker {}.to_bytes(), } } + +pub fn migrate_miner(signer: Pubkey, address: Pubkey) -> Instruction { + let config_address = config_pda().0; + Instruction { + program_id: crate::ID, + accounts: vec![ + AccountMeta::new(signer, true), + AccountMeta::new(config_address, false), + AccountMeta::new(address, false), + AccountMeta::new_readonly(system_program::ID, false), + ], + data: MigrateMiner {}.to_bytes(), + } +} diff --git a/api/src/state/config.rs b/api/src/state/config.rs index f9c1cbd..4a17c2b 100644 --- a/api/src/state/config.rs +++ b/api/src/state/config.rs @@ -13,9 +13,8 @@ pub struct Config { // The last boost timestamp. 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, + // Whether seeker activation is enabled. + pub is_seeker_activation_enabled: u64, // The address that receives fees. pub fee_collector: Pubkey, diff --git a/cli/src/main.rs b/cli/src/main.rs index f8b68cd..a5d1e39 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -80,6 +80,9 @@ async fn main() { "square" => { log_square(&rpc).await.unwrap(); } + "migrate_miners" => { + migrate_miners(&rpc, &payer).await.unwrap(); + } "test_kick" => { test_kick(&rpc).await.unwrap(); } @@ -101,6 +104,25 @@ async fn main() { _ => panic!("Invalid command"), }; } + +async fn migrate_miners( + rpc: &RpcClient, + payer: &solana_sdk::signer::keypair::Keypair, +) -> Result<(), anyhow::Error> { + let miners = get_miners(rpc).await?; + for (i, (address, miner)) in miners.iter().enumerate() { + println!( + "[{}/{}] Migrating miner {}", + i + 1, + miners.len(), + miner.authority + ); + let ix = ore_api::sdk::migrate_miner(payer.pubkey(), *address); + submit_transaction(rpc, payer, &[ix]).await?; + } + Ok(()) +} + // Dmy2fqxpkUocwvkALMDwfCRFeYfkdGqgB5PLfmZW5ASR // Az9Xia5f6EXU9MGHuuMCKyHMy3MfNnsoyTbh7HTuFw5G // 5muLAbcjsAMcP8438KPfo2Jqw2vgAtuSDvMFNWb6Bexi diff --git a/program/src/deploy.rs b/program/src/deploy.rs index 8d68ea3..c2e9eb7 100644 --- a/program/src/deploy.rs +++ b/program/src/deploy.rs @@ -100,6 +100,7 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul let miner = miner_info.as_account_mut::(&ore_api::ID)?; miner.authority = *signer_info.key; miner.deployed = [0; 25]; + miner.is_seeker = 0; miner.refund_sol = 0; miner.rewards_sol = 0; miner.rewards_ore = 0; diff --git a/program/src/lib.rs b/program/src/lib.rs index a7e3316..ec82380 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -7,6 +7,7 @@ mod claim_sol; mod deploy; mod initialize; mod log; +mod migrate_miner; mod reset; mod set_admin; mod set_fee_collector; @@ -22,6 +23,7 @@ use claim_sol::*; use deploy::*; use initialize::*; use log::*; +use migrate_miner::*; use reset::*; use set_admin::*; use set_fee_collector::*; @@ -56,6 +58,7 @@ pub fn process_instruction( // Seeker OreInstruction::ClaimSeeker => process_claim_seeker(accounts, data)?, + OreInstruction::MigrateMiner => process_migrate_miner(accounts, data)?, // _ => return Err(ProgramError::InvalidInstructionData), } diff --git a/program/src/migrate_miner.rs b/program/src/migrate_miner.rs new file mode 100644 index 0000000..0dc66ff --- /dev/null +++ b/program/src/migrate_miner.rs @@ -0,0 +1,28 @@ +use ore_api::prelude::*; +use steel::*; + +/// Sets the admin. +pub fn process_migrate_miner(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult { + // Load accounts. + let [signer_info, config_info, miner_info, system_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + signer_info.is_signer()?; + let miner = miner_info.as_account_mut::(&ore_api::ID)?; + let config = config_info + .as_account_mut::(&ore_api::ID)? + .assert_mut_err( + |c| c.admin == *signer_info.key, + OreError::NotAuthorized.into(), + )?; + system_program.is_program(&system_program::ID)?; + + // Set seeker activation flag. + config.is_seeker_activation_enabled = 0; + + // Set seeker flag. + miner.is_seeker = 0; + miner.buffer = [0; 24]; + + Ok(()) +} diff --git a/program/src/reset.rs b/program/src/reset.rs index 3342aab..c46731b 100644 --- a/program/src/reset.rs +++ b/program/src/reset.rs @@ -102,6 +102,8 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul let motherlode_activated = is_motherlode_activated(r); // Record miner rewards. + let mut total_seeker_deployed = 0; + let mut is_seeker = [false; 16]; let mut miner_deployments = [0; 16]; let mut rewards_sol = [0; 16]; let mut checksum = 0; @@ -122,6 +124,12 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul miner.lifetime_rewards_sol += rewards; rewards_sol[i] = rewards; miner_deployments[i] = miner_deployed; + is_seeker[i] = miner.is_seeker == 1; + + // Record Seeker deployed balance. + if is_seeker[i] { + total_seeker_deployed += miner_deployed; + } // Record ORE motherlode winnings. if motherlode_activated { @@ -175,6 +183,34 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul treasury.motherlode = 0; } + // If activation is enabled, pay out 1 ORE to all Seeker miners, proportional to their deployment. + let mint = mint_info.as_mint()?; + let seeker_mint_amount = ONE_ORE.min(MAX_SUPPLY - mint.supply()); + if config.is_seeker_activation_enabled == 1 + && total_seeker_deployed > 0 + && seeker_mint_amount > 0 + { + // Record Seeker rewards. + for (i, is_seeker) in is_seeker.iter().enumerate() { + if *is_seeker { + let miner = miner_accounts[i].as_account_mut::(&ore_api::ID)?; + let reward = seeker_mint_amount * miner_deployments[i] / total_seeker_deployed; + miner.rewards_ore += reward; + miner.lifetime_rewards_ore += reward; + } + } + + // Mint 1 ORE to Seeker miners. + mint_to_signed( + mint_info, + treasury_tokens_info, + treasury_info, + token_program, + seeker_mint_amount, + &[TREASURY], + )?; + } + // Top up the motherlode rewards pool. let mint = mint_info.as_mint()?; let motherlode_mint_amount = (ONE_ORE / 5).min(MAX_SUPPLY - mint.supply()); @@ -207,7 +243,7 @@ pub fn process_reset(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResul total_deployed: board.total_deployed, total_vaulted: board.total_vaulted, total_winnings: board.total_winnings, - total_minted: mint_amount + motherlode_mint_amount, + total_minted: mint_amount + motherlode_mint_amount + seeker_mint_amount, ts: clock.unix_timestamp, } .to_bytes(),