diff --git a/Cargo.lock b/Cargo.lock index f180650..b4cad85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2044,6 +2044,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "meteora-pools-sdk" +version = "0.1.1" +source = "git+https://github.com/regolith-labs/meteora-pools-sdk?branch=master#6f5a7eab4a460435591765a7d278f117a946b3bd" +dependencies = [ + "borsh 0.10.4", + "num-derive 0.4.2", + "num-traits", + "solana-program", + "thiserror 2.0.12", +] + [[package]] name = "mime" version = "0.3.17" @@ -2412,6 +2424,7 @@ name = "ore-program" version = "3.7.0-alpha" dependencies = [ "bincode", + "meteora-pools-sdk", "mpl-token-metadata", "ore-api", "rand 0.8.5", diff --git a/Cargo.toml b/Cargo.toml index d827c46..b8a069d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ bincode = "1.3.3" bytemuck = "1.14.3" bytemuck_derive = "1.7.0" const-crypto = "0.1.0" +meteora-pools-sdk = "0.1" mpl-token-metadata = "5.1" num_enum = "0.7.2" ore-api = { path = "./api" } @@ -40,6 +41,7 @@ thiserror = "1.0.57" tokio = { version = "1.37.0", features = ["full"] } [patch.crates-io] +meteora-pools-sdk = { git = "https://github.com/regolith-labs/meteora-pools-sdk", branch = "master" } [profile.release] overflow-checks = true diff --git a/api/src/instruction.rs b/api/src/instruction.rs index 109faa0..ab87eb6 100644 --- a/api/src/instruction.rs +++ b/api/src/instruction.rs @@ -10,10 +10,10 @@ pub enum OreInstruction { Deploy = 3, Initialize = 4, Log = 5, - Redeem = 6, Reset = 7, // Admin + Bury = 8, SetAdmin = 9, SetFeeCollector = 10, @@ -41,7 +41,7 @@ pub struct ClaimORE { #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct Deploy { pub amount: [u8; 8], - pub square_id: [u8; 8], + pub squares: [u8; 4], } #[repr(C)] @@ -52,12 +52,6 @@ pub struct Initialize {} #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct Log {} -#[repr(C)] -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct Redeem { - pub amount: [u8; 8], -} - #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct Reset {} @@ -89,12 +83,6 @@ pub struct SetAdmin { pub admin: [u8; 32], } -#[repr(C)] -#[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct SetBlockDuration { - pub block_duration: [u8; 8], -} - #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] pub struct SetFeeCollector { @@ -109,8 +97,8 @@ pub struct SetFeeRate { #[repr(C)] #[derive(Clone, Copy, Debug, Pod, Zeroable)] -pub struct SetSniperFeeDuration { - pub sniper_fee_duration: [u8; 8], +pub struct Bury { + pub min_amount_out: [u8; 8], } #[repr(C)] @@ -123,7 +111,7 @@ instruction!(OreInstruction, ClaimORE); instruction!(OreInstruction, Deploy); instruction!(OreInstruction, Initialize); instruction!(OreInstruction, Log); -instruction!(OreInstruction, Redeem); +instruction!(OreInstruction, Bury); instruction!(OreInstruction, Reset); instruction!(OreInstruction, SetAdmin); instruction!(OreInstruction, SetFeeCollector); diff --git a/api/src/sdk.rs b/api/src/sdk.rs index b13ca5c..b89c9fc 100644 --- a/api/src/sdk.rs +++ b/api/src/sdk.rs @@ -113,11 +113,24 @@ pub fn claim_ore(signer: Pubkey, amount: u64) -> Instruction { // let [signer_info, board_info, config_info, fee_collector_info, miner_info, sender_info, square_info, system_program] = -pub fn deploy(signer: Pubkey, fee_collector: Pubkey, amount: u64, square_id: u64) -> Instruction { +pub fn deploy( + signer: Pubkey, + fee_collector: Pubkey, + amount: u64, + squares: [bool; 25], +) -> Instruction { let board_address = board_pda().0; let config_address = config_pda().0; let miner_address = miner_pda(signer).0; let square_address = square_pda().0; + + let mut mask: u32 = 0; + for (i, &square) in squares.iter().enumerate() { + if square { + mask |= 1 << i; + } + } + Instruction { program_id: crate::ID, accounts: vec![ @@ -131,13 +144,13 @@ pub fn deploy(signer: Pubkey, fee_collector: Pubkey, amount: u64, square_id: u64 ], data: Deploy { amount: amount.to_le_bytes(), - square_id: square_id.to_le_bytes(), + squares: mask.to_le_bytes(), } .to_bytes(), } } -pub fn redeem(signer: Pubkey, amount: u64) -> Instruction { +pub fn bury(signer: Pubkey, min_amount_out: u64) -> Instruction { let mint_address = MINT_ADDRESS; let sender_address = get_associated_token_address(&signer, &MINT_ADDRESS); let treasury_address = TREASURY_ADDRESS; @@ -151,8 +164,8 @@ pub fn redeem(signer: Pubkey, amount: u64) -> Instruction { AccountMeta::new_readonly(system_program::ID, false), AccountMeta::new_readonly(spl_token::ID, false), ], - data: Redeem { - amount: amount.to_le_bytes(), + data: Bury { + min_amount_out: min_amount_out.to_le_bytes(), } .to_bytes(), } diff --git a/cli/src/main.rs b/cli/src/main.rs index 31f7c2a..e49e472 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -52,8 +52,8 @@ async fn main() { "initialize" => { initialize(&rpc, &payer).await.unwrap(); } - "redeem" => { - redeem(&rpc, &payer).await.unwrap(); + "bury" => { + bury(&rpc, &payer).await.unwrap(); } "reset" => { reset(&rpc, &payer).await.unwrap(); @@ -67,8 +67,8 @@ async fn main() { "deploy" => { deploy(&rpc, &payer).await.unwrap(); } - "deploy_some" => { - deploy_some(&rpc, &payer).await.unwrap(); + "deploy_all" => { + deploy_all(&rpc, &payer).await.unwrap(); } "square" => { log_square(&rpc).await.unwrap(); @@ -162,13 +162,13 @@ async fn claim_ore( Ok(()) } -async fn redeem( +async fn bury( rpc: &RpcClient, payer: &solana_sdk::signer::keypair::Keypair, ) -> Result<(), anyhow::Error> { let amount = std::env::var("AMOUNT").expect("Missing AMOUNT env var"); let amount = u64::from_str(&amount).expect("Invalid AMOUNT"); - let ix = ore_api::sdk::redeem(payer.pubkey(), amount); + let ix = ore_api::sdk::bury(payer.pubkey(), amount); submit_transaction(rpc, payer, &[ix]).await?; Ok(()) } @@ -201,24 +201,25 @@ async fn deploy( let square_id = std::env::var("SQUARE").expect("Missing SQUARE env var"); let square_id = u64::from_str(&square_id).expect("Invalid SQUARE"); let config = get_config(rpc).await?; - let ix = ore_api::sdk::deploy(payer.pubkey(), config.fee_collector, amount, square_id); + + let mut squares = [false; 25]; + squares[square_id as usize] = true; + + let ix = ore_api::sdk::deploy(payer.pubkey(), config.fee_collector, amount, squares); submit_transaction(rpc, payer, &[ix]).await?; Ok(()) } -async fn deploy_some( +async fn deploy_all( rpc: &RpcClient, payer: &solana_sdk::signer::keypair::Keypair, ) -> Result<(), anyhow::Error> { let amount = std::env::var("AMOUNT").expect("Missing AMOUNT env var"); let amount = u64::from_str(&amount).expect("Invalid AMOUNT"); let config = get_config(rpc).await?; - let mut ixs = vec![]; - for i in 0..8 { - let ix = ore_api::sdk::deploy(payer.pubkey(), config.fee_collector, amount, i as u64); - ixs.push(ix); - } - submit_transaction(rpc, payer, &ixs).await?; + let squares = [true; 25]; + let ix = ore_api::sdk::deploy(payer.pubkey(), config.fee_collector, amount, squares); + submit_transaction(rpc, payer, &[ix]).await?; Ok(()) } diff --git a/program/Cargo.toml b/program/Cargo.toml index 3a64c8e..2d31ed9 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -19,6 +19,7 @@ default = [] [dependencies] bincode.workspace = true +meteora-pools-sdk.workspace = true mpl-token-metadata.workspace = true ore-api.workspace = true solana-nostd-keccak.workspace = true diff --git a/program/src/bury.rs b/program/src/bury.rs new file mode 100644 index 0000000..b9e5661 --- /dev/null +++ b/program/src/bury.rs @@ -0,0 +1,80 @@ +use meteora_pools_sdk::instructions::SwapInstructionArgs; +use ore_api::prelude::*; +use steel::*; + +/// Redeem ORE for SOL backing. +pub fn process_redeem(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { + // Parse data. + let args = Bury::try_from_bytes(data)?; + let min_amount_out = u64::from_le_bytes(args.min_amount_out); + + // Load accounts. + let [ore_accounts, meteora_accounts] = accounts.split_at(6); + let [signer_info, config_info, mint_info, treasury_info, system_program, token_program] = + ore_accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + signer_info.is_signer()?; + let config = config_info + .as_account::(&ore_api::ID)? + .assert_mut(|c| c.admin == *signer_info.key)?; + let mint = mint_info.has_address(&MINT_ADDRESS)?.as_mint()?; + let treasury = treasury_info.as_account_mut::(&ore_api::ID)?; + system_program.is_program(&system_program::ID)?; + token_program.is_program(&spl_token::ID)?; + + let [pool, user_source_token, user_destination_token, a_vault, b_vault, a_token_vault, b_token_vault, a_vault_lp_mint, b_vault_lp_mint, a_vault_lp, b_vault_lp, protocol_token_fee, vault_program] = + meteora_accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + // Execute swap from SOL to ORE in Meteora + let swap = meteora_pools_sdk::instructions::Swap { + pool: *pool.key, + user_source_token: *user_source_token.key, + user_destination_token: *user_destination_token.key, + a_vault: *a_vault.key, + b_vault: *b_vault.key, + a_token_vault: *a_token_vault.key, + b_token_vault: *b_token_vault.key, + a_vault_lp_mint: *a_vault_lp_mint.key, + b_vault_lp_mint: *b_vault_lp_mint.key, + a_vault_lp: *a_vault_lp.key, + b_vault_lp: *b_vault_lp.key, + protocol_token_fee: *protocol_token_fee.key, + user: *user.key, + vault_program: *vault_program.key, + token_program: *token_program.key, + b_token_vault: *b_token_vault.key, + a_vault_lp_mint: *a_vault_lp_mint.key, + b_vault_lp_mint: *b_vault_lp_mint.key, + a_vault_lp: *a_vault_lp.key, + b_vault_lp: *b_vault_lp.key, + protocol_token_fee: *protocol_token_fee.key, + user: *user.key, + vault_program: *vault_program.key, + token_program: *token_program.key, + } + .instruction_with_remaining_accounts( + SwapInstructionArgs { + in_amount: treasury.balance, + minimum_out_amount: min_amount_out, + }, + &meteora_accounts, + ); + + // Burn ORE. + burn(sender_info, mint_info, signer_info, token_program, amount)?; + + // // Transfer SOL to recipient. + // assert!( + // treasury.balance >= redemption_amount, + // "Redemption too large" + // ); + // treasury_info.send(redemption_amount, signer_info); + // treasury.balance -= redemption_amount; + + Ok(()) +} diff --git a/program/src/deploy.rs b/program/src/deploy.rs index 7812a2c..7c9b97d 100644 --- a/program/src/deploy.rs +++ b/program/src/deploy.rs @@ -8,7 +8,13 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul // Parse data. let args = Deploy::try_from_bytes(data)?; let amount = u64::from_le_bytes(args.amount); - let square_id = usize::from_le_bytes(args.square_id); + let mask = u32::from_le_bytes(args.squares); + + // Parse squares. + let mut squares = [false; 25]; + for i in 0..25 { + squares[i] = (mask & (1 << i)) != 0; + } // Load accounts. let clock = Clock::get()?; @@ -94,23 +100,36 @@ pub fn process_deploy(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResul let fee = amount / 100; let amount = amount - fee; - // Update miner - let is_first_move = miner.deployed[square_id] == 0; - miner.deployed[square_id] += amount; + // Make all deployments. + let mut total_fee = 0; + let mut total_amount = 0; + for (square_id, &should_deploy) in squares.iter().enumerate() { + if square_id > 24 { + break; + } + if should_deploy { + total_fee += fee; + total_amount += amount; - // Update square - if is_first_move { - square.miners[square_id][square.count[square_id] as usize] = *signer_info.key; - square.count[square_id] += 1; + // Update miner + let is_first_move = miner.deployed[square_id] == 0; + miner.deployed[square_id] += amount; + + // Update square + if is_first_move { + square.miners[square_id][square.count[square_id] as usize] = *signer_info.key; + square.count[square_id] += 1; + } + + // Update board + board.deployed[square_id] += amount; + board.total_deployed += amount; + } } - // Update board - board.deployed[square_id] += amount; - board.total_deployed += amount; - // Transfer deployed. - board_info.collect(amount, &signer_info)?; - fee_collector_info.collect(fee, &signer_info)?; + board_info.collect(total_amount, &signer_info)?; + fee_collector_info.collect(total_fee, &signer_info)?; Ok(()) } diff --git a/program/src/lib.rs b/program/src/lib.rs index d239968..07dd555 100644 --- a/program/src/lib.rs +++ b/program/src/lib.rs @@ -1,4 +1,5 @@ mod boost; +// mod bury; mod claim_ore; mod claim_seeker; mod claim_sol; @@ -11,6 +12,7 @@ mod set_fee_collector; mod whitelist; use boost::*; +// use bury::*; use claim_ore::*; use claim_seeker::*; use claim_sol::*; diff --git a/program/src/redeem.rs b/program/src/redeem.rs deleted file mode 100644 index 188d502..0000000 --- a/program/src/redeem.rs +++ /dev/null @@ -1,41 +0,0 @@ -use ore_api::prelude::*; -use steel::*; - -/// Redeem ORE for SOL backing. -pub fn process_redeem(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult { - // Parse data. - let args = Redeem::try_from_bytes(data)?; - let amount = u64::from_le_bytes(args.amount); - - // Load accounts. - let [signer_info, mint_info, sender_info, treasury_info, system_program, token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - signer_info.is_signer()?; - let mint = mint_info.has_address(&MINT_ADDRESS)?.as_mint()?; - let sender = sender_info.as_associated_token_account(&signer_info.key, &mint_info.key)?; - let treasury = treasury_info.as_account_mut::(&ore_api::ID)?; - system_program.is_program(&system_program::ID)?; - token_program.is_program(&spl_token::ID)?; - - // Normalize amount. - let amount = amount.min(sender.amount()); - - // Load redemption amount. - let redemption_amount = treasury.balance * amount / mint.supply(); - - // Burn ORE. - burn(sender_info, mint_info, signer_info, token_program, amount)?; - - // Transfer SOL to recipient. - assert!( - treasury.balance >= redemption_amount, - "Redemption too large" - ); - treasury_info.send(redemption_amount, signer_info); - treasury.balance -= redemption_amount; - - Ok(()) -}