From cea4d49ef5acc643cb04fb4db96af6d73a5f0a1d Mon Sep 17 00:00:00 2001 From: RainRaydium <106001368+RainRaydium@users.noreply.github.com> Date: Wed, 25 Oct 2023 17:12:01 +0800 Subject: [PATCH] Fix: fix for client support token2022 (#51) * Fix: fix for client support token2022 * Fix: fix for client support tickarray bitmap extension and update dependencies version * Fix: fix for overflowing_pow error during build contract * Fix: fix for remaining accounts sequence over the decrease_liquidity and update client * Style: update client dependencies --- client/Cargo.toml | 34 +- client/src/instructions/amm_instructions.rs | 124 +- .../instructions/events_instructions_parse.rs | 785 ++++ client/src/instructions/mod.rs | 2 + client/src/instructions/rpc.rs | 18 +- client/src/instructions/token_instructions.rs | 114 +- client/src/instructions/utils.rs | 246 +- client/src/main.rs | 3469 +++++++++-------- client_config.ini | 3 +- programs/amm/Cargo.toml | 4 +- .../instructions/collect_remaining_rewards.rs | 13 +- .../src/instructions/decrease_liquidity.rs | 55 +- programs/amm/src/instructions/swap_v2.rs | 13 +- programs/amm/src/lib.rs | 1 + programs/amm/src/libraries/big_num.rs | 498 ++- programs/amm/src/states/tick_array.rs | 6 +- .../src/states/tickarray_bitmap_extension.rs | 37 +- programs/amm/src/util/token.rs | 9 + 18 files changed, 3428 insertions(+), 2003 deletions(-) create mode 100644 client/src/instructions/events_instructions_parse.rs diff --git a/client/Cargo.toml b/client/Cargo.toml index 2b40e764..ece268a8 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -5,24 +5,28 @@ rust-version = "1.56" edition = "2021" [dependencies] -anchor-client = "0.26.0" -anchor-lang = "0.26.0" -raydium-amm-v3 = {path = "../programs/amm", features = ["no-entrypoint"]} -solana-sdk = "1.13.5" -solana-client = "1.13.5" -solana-account-decoder = "1.13.5" -spl-token = { version = "3.5.0", features=["no-entrypoint"] } -spl-associated-token-account = "1.0.3" -mpl-token-metadata = { version = "1.2.7", features = ["no-entrypoint"] } -clap = { version = "3.0.0-rc.0", features = ["derive"] } -shellexpand = "2.1.0" +anchor-client = "0.28.0" +anchor-lang = "0.28.0" +raydium-amm-v3 = {path = "../programs/amm", features = ["no-entrypoint", "client"]} +solana-sdk = ">=1.14, <1.17" +solana-client = ">=1.14, <1.17" +solana-account-decoder = ">=1.14, <1.17" +solana-transaction-status = ">=1.14, <1.17" +spl-token = { version = "4.0.0", features=["no-entrypoint"] } +spl-token-client = "0.7.0" +spl-memo = "4.0.0" +spl-associated-token-account = { version = "2.2.0", features = ["no-entrypoint"]} +spl-token-2022 = { version = "0.9.0", features = ["no-entrypoint"]} +mpl-token-metadata = { version = "^1.11.0", features = ["no-entrypoint"] } +clap = { version = "4.1.8", features = ["derive"] } anyhow = "1.0.32" rand = "0.7.3" hex = "0.4.3" configparser = "3.0.0" -borsh = "0.9.0" serde_json = { version = "1.0.78" } serde = { version = "1.0", features = ["derive"] } -arrayref = "0.3.6" -bs58 = { version = "0.4.0"} -bincode = { version = "1.3.3" } \ No newline at end of file +arrayref = "0.3.7" +bs58 = { version = "0.5.0"} +bincode = { version = "1.3.3" } +regex = "1" +colorful = "0.2.2" \ No newline at end of file diff --git a/client/src/instructions/amm_instructions.rs b/client/src/instructions/amm_instructions.rs index da134fd5..38686590 100644 --- a/client/src/instructions/amm_instructions.rs +++ b/client/src/instructions/amm_instructions.rs @@ -27,7 +27,7 @@ pub fn create_amm_config_instr( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(config.raydium_v3_program); + let program = client.program(config.raydium_v3_program)?; let (amm_config_key, __bump) = Pubkey::find_program_address( &[AMM_CONFIG_SEED.as_bytes(), &config_index.to_be_bytes()], &program.id(), @@ -62,7 +62,7 @@ pub fn update_amm_config_instr( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(config.raydium_v3_program); + let program = client.program(config.raydium_v3_program)?; let instructions = program .request() .accounts(raydium_accounts::UpdateAmmConfig { @@ -80,7 +80,7 @@ pub fn create_operation_account_instr(config: &ClientConfig) -> Result Result> { @@ -132,7 +135,7 @@ pub fn create_pool_instr( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(config.raydium_v3_program); + let program = client.program(config.raydium_v3_program)?; let (pool_account_key, __bump) = Pubkey::find_program_address( &[ POOL_SEED.as_bytes(), @@ -169,7 +172,9 @@ pub fn create_pool_instr( token_vault_0, token_vault_1, observation_state: observation_key, - token_program: spl_token::id(), + tick_array_bitmap, + token_program_0, + token_program_1, system_program: system_program::id(), rent: sysvar::rent::id(), }) @@ -186,10 +191,13 @@ pub fn open_position_instr( pool_account_key: Pubkey, token_vault_0: Pubkey, token_vault_1: Pubkey, + token_mint_0: Pubkey, + token_mint_1: Pubkey, nft_mint_key: Pubkey, nft_to_owner: Pubkey, user_token_account_0: Pubkey, user_token_account_1: Pubkey, + remaining_accounts: Vec, liquidity: u128, amount_0_max: u64, amount_1_max: u64, @@ -197,12 +205,13 @@ pub fn open_position_instr( tick_upper_index: i32, tick_array_lower_start_index: i32, tick_array_upper_start_index: i32, + with_matedata: bool, ) -> Result> { let payer = read_keypair_file(&config.payer_path)?; let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(config.raydium_v3_program); + let program = client.program(config.raydium_v3_program)?; let nft_ata_token_account = spl_associated_token_account::get_associated_token_address(&program.payer(), &nft_mint_key); let (metadata_account_key, _bump) = Pubkey::find_program_address( @@ -244,7 +253,7 @@ pub fn open_position_instr( ); let instructions = program .request() - .accounts(raydium_accounts::OpenPosition { + .accounts(raydium_accounts::OpenPositionV2 { payer: program.payer(), position_nft_owner: nft_to_owner, position_nft_mint: nft_mint_key, @@ -264,8 +273,12 @@ pub fn open_position_instr( token_program: spl_token::id(), associated_token_program: spl_associated_token_account::id(), metadata_program: mpl_token_metadata::id(), + token_program_2022: spl_token_2022::id(), + vault_0_mint: token_mint_0, + vault_1_mint: token_mint_1, }) - .args(raydium_instruction::OpenPosition { + .accounts(remaining_accounts) + .args(raydium_instruction::OpenPositionV2 { liquidity, amount_0_max, amount_1_max, @@ -273,6 +286,8 @@ pub fn open_position_instr( tick_upper_index, tick_array_lower_start_index, tick_array_upper_start_index, + with_matedata, + base_flag: None, }) .instructions()?; Ok(instructions) @@ -283,9 +298,12 @@ pub fn increase_liquidity_instr( pool_account_key: Pubkey, token_vault_0: Pubkey, token_vault_1: Pubkey, + token_mint_0: Pubkey, + token_mint_1: Pubkey, nft_mint_key: Pubkey, user_token_account_0: Pubkey, user_token_account_1: Pubkey, + remaining_accounts: Vec, liquidity: u128, amount_0_max: u64, amount_1_max: u64, @@ -298,7 +316,7 @@ pub fn increase_liquidity_instr( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(config.raydium_v3_program); + let program = client.program(config.raydium_v3_program)?; let nft_ata_token_account = spl_associated_token_account::get_associated_token_address(&program.payer(), &nft_mint_key); let (tick_array_lower, __bump) = Pubkey::find_program_address( @@ -333,7 +351,7 @@ pub fn increase_liquidity_instr( let instructions = program .request() - .accounts(raydium_accounts::IncreaseLiquidity { + .accounts(raydium_accounts::IncreaseLiquidityV2 { nft_owner: program.payer(), nft_account: nft_ata_token_account, pool_state: pool_account_key, @@ -346,11 +364,16 @@ pub fn increase_liquidity_instr( token_vault_0, token_vault_1, token_program: spl_token::id(), + token_program_2022: spl_token_2022::id(), + vault_0_mint: token_mint_0, + vault_1_mint: token_mint_1, }) - .args(raydium_instruction::IncreaseLiquidity { + .accounts(remaining_accounts) + .args(raydium_instruction::IncreaseLiquidityV2 { liquidity, amount_0_max, amount_1_max, + base_flag: None, }) .instructions()?; Ok(instructions) @@ -361,6 +384,8 @@ pub fn decrease_liquidity_instr( pool_account_key: Pubkey, token_vault_0: Pubkey, token_vault_1: Pubkey, + token_mint_0: Pubkey, + token_mint_1: Pubkey, nft_mint_key: Pubkey, user_token_account_0: Pubkey, user_token_account_1: Pubkey, @@ -377,7 +402,7 @@ pub fn decrease_liquidity_instr( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(config.raydium_v3_program); + let program = client.program(config.raydium_v3_program)?; let nft_ata_token_account = spl_associated_token_account::get_associated_token_address(&program.payer(), &nft_mint_key); let (personal_position_key, __bump) = Pubkey::find_program_address( @@ -411,7 +436,7 @@ pub fn decrease_liquidity_instr( ); let instructions = program .request() - .accounts(raydium_accounts::DecreaseLiquidity { + .accounts(raydium_accounts::DecreaseLiquidityV2 { nft_owner: program.payer(), nft_account: nft_ata_token_account, personal_position: personal_position_key, @@ -424,9 +449,13 @@ pub fn decrease_liquidity_instr( recipient_token_account_0: user_token_account_0, recipient_token_account_1: user_token_account_1, token_program: spl_token::id(), + token_program_2022: spl_token_2022::id(), + memo_program: spl_memo::id(), + vault_0_mint: token_mint_0, + vault_1_mint: token_mint_1, }) .accounts(remaining_accounts) - .args(raydium_instruction::DecreaseLiquidity { + .args(raydium_instruction::DecreaseLiquidityV2 { liquidity, amount_0_min, amount_1_min, @@ -443,7 +472,7 @@ pub fn close_personal_position_instr( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(config.raydium_v3_program); + let program = client.program(config.raydium_v3_program)?; let nft_ata_token_account = spl_associated_token_account::get_associated_token_address(&program.payer(), &nft_mint_key); let (personal_position_key, __bump) = Pubkey::find_program_address( @@ -485,7 +514,7 @@ pub fn swap_instr( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(config.raydium_v3_program); + let program = client.program(config.raydium_v3_program)?; let instructions = program .request() .accounts(raydium_accounts::SwapSingle { @@ -511,6 +540,56 @@ pub fn swap_instr( Ok(instructions) } +pub fn swap_v2_instr( + config: &ClientConfig, + amm_config: Pubkey, + pool_account_key: Pubkey, + input_vault: Pubkey, + output_vault: Pubkey, + observation_state: Pubkey, + user_input_token: Pubkey, + user_out_put_token: Pubkey, + input_vault_mint: Pubkey, + output_vault_mint: Pubkey, + remaining_accounts: Vec, + amount: u64, + other_amount_threshold: u64, + sqrt_price_limit_x64: Option, + is_base_input: bool, +) -> Result> { + let payer = read_keypair_file(&config.payer_path)?; + let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); + // Client. + let client = Client::new(url, Rc::new(payer)); + let program = client.program(config.raydium_v3_program)?; + let instructions = program + .request() + .accounts(raydium_accounts::SwapSingleV2 { + payer: program.payer(), + amm_config, + pool_state: pool_account_key, + input_token_account: user_input_token, + output_token_account: user_out_put_token, + input_vault, + output_vault, + observation_state, + token_program: spl_token::id(), + token_program_2022: spl_token_2022::id(), + memo_program: spl_memo::id(), + input_vault_mint, + output_vault_mint, + }) + .accounts(remaining_accounts) + .args(raydium_instruction::Swap { + amount, + other_amount_threshold, + sqrt_price_limit_x64: sqrt_price_limit_x64.unwrap_or(0u128), + is_base_input, + }) + .instructions()?; + Ok(instructions) +} + pub fn initialize_reward_instr( config: &ClientConfig, pool_account_key: Pubkey, @@ -519,6 +598,7 @@ pub fn initialize_reward_instr( reward_token_mint: Pubkey, reward_token_vault: Pubkey, user_reward_token: Pubkey, + reward_token_program: Pubkey, open_time: u64, end_time: u64, emissions_per_second_x64: u128, @@ -527,7 +607,7 @@ pub fn initialize_reward_instr( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(admin)); - let program = client.program(config.raydium_v3_program); + let program = client.program(config.raydium_v3_program)?; let instructions = program .request() @@ -539,7 +619,7 @@ pub fn initialize_reward_instr( operation_state: operation_account_key, reward_token_mint, reward_token_vault, - token_program: spl_token::id(), + reward_token_program, system_program: system_program::id(), rent: sysvar::rent::id(), }) @@ -570,7 +650,7 @@ pub fn set_reward_params_instr( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(admin)); - let program = client.program(config.raydium_v3_program); + let program = client.program(config.raydium_v3_program)?; let remaining_accounts = vec![ AccountMeta::new(reward_token_vault, false), @@ -585,6 +665,8 @@ pub fn set_reward_params_instr( amm_config, pool_state: pool_account_key, operation_state: operation_account_key, + token_program: spl_token::id(), + token_program_2022: spl_token_2022::id(), }) .accounts(remaining_accounts) .args(raydium_instruction::SetRewardParams { @@ -606,7 +688,7 @@ pub fn transfer_reward_owner( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(admin)); - let program = client.program(config.raydium_v3_program); + let program = client.program(config.raydium_v3_program)?; let instructions = program .request() diff --git a/client/src/instructions/events_instructions_parse.rs b/client/src/instructions/events_instructions_parse.rs new file mode 100644 index 00000000..b7d30e88 --- /dev/null +++ b/client/src/instructions/events_instructions_parse.rs @@ -0,0 +1,785 @@ +use anchor_client::ClientError; +use anchor_lang::prelude::Pubkey; +use anchor_lang::Discriminator; +use anyhow::Result; +use colorful::Color; +use colorful::Colorful; +use raydium_amm_v3::instruction; +use raydium_amm_v3::instructions::*; +use raydium_amm_v3::states::*; +use regex::Regex; +use solana_transaction_status::{ + option_serializer::OptionSerializer, EncodedTransaction, UiTransactionStatusMeta, +}; + +const PROGRAM_LOG: &str = "Program log: "; +const PROGRAM_DATA: &str = "Program data: "; + +pub enum InstructionDecodeType { + BaseHex, + Base64, + Base58, +} + +pub fn parse_program_event( + self_program_str: &str, + meta: Option, +) -> Result<(), ClientError> { + let logs: Vec = if let Some(meta_data) = meta { + let log_messages = if let OptionSerializer::Some(log_messages) = meta_data.log_messages { + log_messages + } else { + Vec::new() + }; + log_messages + } else { + Vec::new() + }; + let mut logs = &logs[..]; + if !logs.is_empty() { + if let Ok(mut execution) = Execution::new(&mut logs) { + for l in logs { + let (new_program, did_pop) = + if !execution.is_empty() && self_program_str == execution.program() { + handle_program_log(self_program_str, &l, true).unwrap_or_else(|e| { + println!("Unable to parse log: {e}"); + std::process::exit(1); + }) + } else { + let (program, did_pop) = handle_system_log(self_program_str, l); + (program, did_pop) + }; + // Switch program context on CPI. + if let Some(new_program) = new_program { + execution.push(new_program); + } + // Program returned. + if did_pop { + execution.pop(); + } + } + } + } else { + println!("log is empty"); + } + Ok(()) +} + +struct Execution { + stack: Vec, +} + +impl Execution { + pub fn new(logs: &mut &[String]) -> Result { + let l = &logs[0]; + *logs = &logs[1..]; + + let re = Regex::new(r"^Program (.*) invoke.*$").unwrap(); + let c = re + .captures(l) + .ok_or_else(|| ClientError::LogParseError(l.to_string()))?; + let program = c + .get(1) + .ok_or_else(|| ClientError::LogParseError(l.to_string()))? + .as_str() + .to_string(); + Ok(Self { + stack: vec![program], + }) + } + + pub fn program(&self) -> String { + assert!(!self.stack.is_empty()); + self.stack[self.stack.len() - 1].clone() + } + + pub fn is_empty(&self) -> bool { + self.stack.is_empty() + } + + pub fn push(&mut self, new_program: String) { + self.stack.push(new_program); + } + + pub fn pop(&mut self) { + assert!(!self.stack.is_empty()); + self.stack.pop().unwrap(); + } +} + +pub fn handle_program_log( + self_program_str: &str, + l: &str, + with_prefix: bool, +) -> Result<(Option, bool), ClientError> { + // Log emitted from the current program. + if let Some(log) = if with_prefix { + l.strip_prefix(PROGRAM_LOG) + .or_else(|| l.strip_prefix(PROGRAM_DATA)) + } else { + Some(l) + } { + if l.starts_with(&format!("Program log:")) { + // not log event + return Ok((None, false)); + } + let borsh_bytes = match anchor_lang::__private::base64::decode(log) { + Ok(borsh_bytes) => borsh_bytes, + _ => { + println!("Could not base64 decode log: {}", log); + return Ok((None, false)); + } + }; + + let mut slice: &[u8] = &borsh_bytes[..]; + let disc: [u8; 8] = { + let mut disc = [0; 8]; + disc.copy_from_slice(&borsh_bytes[..8]); + slice = &slice[8..]; + disc + }; + match disc { + CreateConfigEvent::DISCRIMINATOR => { + println!("{:#?}", decode_event::(&mut slice)?); + } + CollectPersonalFeeEvent::DISCRIMINATOR => { + println!( + "{:#?}", + decode_event::(&mut slice)? + ); + } + CollectProtocolFeeEvent::DISCRIMINATOR => { + println!( + "{:#?}", + decode_event::(&mut slice)? + ); + } + CreatePersonalPositionEvent::DISCRIMINATOR => { + println!( + "{:#?}", + decode_event::(&mut slice)? + ); + } + DecreaseLiquidityEvent::DISCRIMINATOR => { + println!("{:#?}", decode_event::(&mut slice)?); + } + IncreaseLiquidityEvent::DISCRIMINATOR => { + println!("{:#?}", decode_event::(&mut slice)?); + } + LiquidityCalculateEvent::DISCRIMINATOR => { + println!( + "{:#?}", + decode_event::(&mut slice)? + ); + } + LiquidityChangeEvent::DISCRIMINATOR => { + println!("{:#?}", decode_event::(&mut slice)?); + } + UpdaterConfigEvent::DISCRIMINATOR => { + println!("{:#?}", decode_event::(&mut slice)?); + } + PriceChangeEvent::DISCRIMINATOR => { + println!("{:#?}", decode_event::(&mut slice)?); + } + SwapEvent::DISCRIMINATOR => { + println!("{:#?}", decode_event::(&mut slice)?); + } + PoolCreatedEvent::DISCRIMINATOR => { + println!("{:#?}", decode_event::(&mut slice)?); + } + _ => { + println!("unknow event: {}", l); + } + } + return Ok((None, false)); + } else { + let (program, did_pop) = handle_system_log(self_program_str, l); + return Ok((program, did_pop)); + } +} + +fn handle_system_log(this_program_str: &str, log: &str) -> (Option, bool) { + if log.starts_with(&format!("Program {this_program_str} invoke")) { + (Some(this_program_str.to_string()), false) + } else if log.contains("invoke") { + (Some("cpi".to_string()), false) // Any string will do. + } else { + let re = Regex::new(r"^Program (.*) success*$").unwrap(); + if re.is_match(log) { + (None, true) + } else { + (None, false) + } + } +} + +fn decode_event( + slice: &mut &[u8], +) -> Result { + let event: T = anchor_lang::AnchorDeserialize::deserialize(slice) + .map_err(|e| ClientError::LogParseError(e.to_string()))?; + Ok(event) +} + +pub fn parse_program_instruction( + self_program_str: &str, + encoded_transaction: EncodedTransaction, + meta: Option, +) -> Result<(), ClientError> { + let ui_raw_msg = match encoded_transaction { + solana_transaction_status::EncodedTransaction::Json(ui_tx) => { + let ui_message = ui_tx.message; + // println!("{:#?}", ui_message); + match ui_message { + solana_transaction_status::UiMessage::Raw(ui_raw_msg) => ui_raw_msg, + _ => solana_transaction_status::UiRawMessage { + header: solana_sdk::message::MessageHeader::default(), + account_keys: Vec::new(), + recent_blockhash: "".to_string(), + instructions: Vec::new(), + address_table_lookups: None, + }, + } + } + _ => solana_transaction_status::UiRawMessage { + header: solana_sdk::message::MessageHeader::default(), + account_keys: Vec::new(), + recent_blockhash: "".to_string(), + instructions: Vec::new(), + address_table_lookups: None, + }, + }; + // append lookup table keys if necessary + if meta.is_some() { + let mut account_keys = ui_raw_msg.account_keys; + let meta = meta.clone().unwrap(); + match meta.loaded_addresses { + OptionSerializer::Some(addresses) => { + let mut writeable_address = addresses.writable; + let mut readonly_address = addresses.readonly; + account_keys.append(&mut writeable_address); + account_keys.append(&mut readonly_address); + } + _ => {} + } + let program_index = account_keys + .iter() + .position(|r| r == self_program_str) + .unwrap(); + // println!("{}", program_index); + // println!("{:#?}", account_keys); + for (i, ui_compiled_instruction) in ui_raw_msg.instructions.iter().enumerate() { + if (ui_compiled_instruction.program_id_index as usize) == program_index { + let out_put = format!("instruction #{}", i + 1); + println!("{}", out_put.gradient(Color::Green)); + handle_program_instruction( + &ui_compiled_instruction.data, + InstructionDecodeType::Base58, + )?; + } + } + + match meta.inner_instructions { + OptionSerializer::Some(inner_instructions) => { + for inner in inner_instructions { + for (i, instruction) in inner.instructions.iter().enumerate() { + match instruction { + solana_transaction_status::UiInstruction::Compiled( + ui_compiled_instruction, + ) => { + if (ui_compiled_instruction.program_id_index as usize) + == program_index + { + let out_put = + format!("inner_instruction #{}.{}", inner.index + 1, i + 1); + println!("{}", out_put.gradient(Color::Green)); + handle_program_instruction( + &ui_compiled_instruction.data, + InstructionDecodeType::Base58, + )?; + } + } + _ => {} + } + } + } + } + _ => {} + } + } + Ok(()) +} + +pub fn handle_program_instruction( + instr_data: &str, + decode_type: InstructionDecodeType, +) -> Result<(), ClientError> { + let data; + match decode_type { + InstructionDecodeType::BaseHex => { + data = hex::decode(instr_data).unwrap(); + } + InstructionDecodeType::Base64 => { + let borsh_bytes = match anchor_lang::__private::base64::decode(instr_data) { + Ok(borsh_bytes) => borsh_bytes, + _ => { + println!("Could not base64 decode instruction: {}", instr_data); + return Ok(()); + } + }; + data = borsh_bytes; + } + InstructionDecodeType::Base58 => { + let borsh_bytes = match bs58::decode(instr_data).into_vec() { + Ok(borsh_bytes) => borsh_bytes, + _ => { + println!("Could not base58 decode instruction: {}", instr_data); + return Ok(()); + } + }; + data = borsh_bytes; + } + } + + let mut ix_data: &[u8] = &data[..]; + let disc: [u8; 8] = { + let mut disc = [0; 8]; + disc.copy_from_slice(&data[..8]); + ix_data = &ix_data[8..]; + disc + }; + // println!("{:?}", disc); + + match disc { + instruction::CreateAmmConfig::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct CreateAmmConfig { + pub index: u16, + pub tick_spacing: u16, + pub trade_fee_rate: u32, + pub protocol_fee_rate: u32, + pub fund_fee_rate: u32, + } + impl From for CreateAmmConfig { + fn from(instr: instruction::CreateAmmConfig) -> CreateAmmConfig { + CreateAmmConfig { + index: instr.index, + tick_spacing: instr.tick_spacing, + trade_fee_rate: instr.trade_fee_rate, + protocol_fee_rate: instr.protocol_fee_rate, + fund_fee_rate: instr.fund_fee_rate, + } + } + } + println!("{:#?}", CreateAmmConfig::from(ix)); + } + instruction::UpdateAmmConfig::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct UpdateAmmConfig { + pub param: u8, + pub value: u32, + } + impl From for UpdateAmmConfig { + fn from(instr: instruction::UpdateAmmConfig) -> UpdateAmmConfig { + UpdateAmmConfig { + param: instr.param, + value: instr.value, + } + } + } + println!("{:#?}", UpdateAmmConfig::from(ix)); + } + instruction::CreatePool::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct CreatePool { + pub sqrt_price_x64: u128, + pub open_time: u64, + } + impl From for CreatePool { + fn from(instr: instruction::CreatePool) -> CreatePool { + CreatePool { + sqrt_price_x64: instr.sqrt_price_x64, + open_time: instr.open_time, + } + } + } + println!("{:#?}", CreatePool::from(ix)); + } + instruction::UpdatePoolStatus::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct UpdatePoolStatus { + pub status: u8, + } + impl From for UpdatePoolStatus { + fn from(instr: instruction::UpdatePoolStatus) -> UpdatePoolStatus { + UpdatePoolStatus { + status: instr.status, + } + } + } + println!("{:#?}", UpdatePoolStatus::from(ix)); + } + instruction::CreateOperationAccount::DISCRIMINATOR => { + let ix = + decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct CreateOperationAccount; + impl From for CreateOperationAccount { + fn from(_instr: instruction::CreateOperationAccount) -> CreateOperationAccount { + CreateOperationAccount + } + } + println!("{:#?}", CreateOperationAccount::from(ix)); + } + instruction::UpdateOperationAccount::DISCRIMINATOR => { + let ix = + decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct UpdateOperationAccount { + pub param: u8, + pub keys: Vec, + } + impl From for UpdateOperationAccount { + fn from(instr: instruction::UpdateOperationAccount) -> UpdateOperationAccount { + UpdateOperationAccount { + param: instr.param, + keys: instr.keys, + } + } + } + println!("{:#?}", UpdateOperationAccount::from(ix)); + } + instruction::TransferRewardOwner::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct TransferRewardOwner { + pub new_owner: Pubkey, + } + impl From for TransferRewardOwner { + fn from(instr: instruction::TransferRewardOwner) -> TransferRewardOwner { + TransferRewardOwner { + new_owner: instr.new_owner, + } + } + } + println!("{:#?}", TransferRewardOwner::from(ix)); + } + instruction::InitializeReward::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct InitializeReward { + pub param: InitializeRewardParam, + } + impl From for InitializeReward { + fn from(instr: instruction::InitializeReward) -> InitializeReward { + InitializeReward { param: instr.param } + } + } + println!("{:#?}", InitializeReward::from(ix)); + } + instruction::CollectRemainingRewards::DISCRIMINATOR => { + let ix = + decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct CollectRemainingRewards { + pub reward_index: u8, + } + impl From for CollectRemainingRewards { + fn from(instr: instruction::CollectRemainingRewards) -> CollectRemainingRewards { + CollectRemainingRewards { + reward_index: instr.reward_index, + } + } + } + println!("{:#?}", CollectRemainingRewards::from(ix)); + } + instruction::UpdateRewardInfos::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct UpdateRewardInfos; + impl From for UpdateRewardInfos { + fn from(_instr: instruction::UpdateRewardInfos) -> UpdateRewardInfos { + UpdateRewardInfos + } + } + println!("{:#?}", UpdateRewardInfos::from(ix)); + } + instruction::SetRewardParams::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct SetRewardParams { + pub reward_index: u8, + pub emissions_per_second_x64: u128, + pub open_time: u64, + pub end_time: u64, + } + impl From for SetRewardParams { + fn from(instr: instruction::SetRewardParams) -> SetRewardParams { + SetRewardParams { + reward_index: instr.reward_index, + emissions_per_second_x64: instr.emissions_per_second_x64, + open_time: instr.open_time, + end_time: instr.end_time, + } + } + } + println!("{:#?}", SetRewardParams::from(ix)); + } + instruction::CollectProtocolFee::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct CollectProtocolFee { + pub amount_0_requested: u64, + pub amount_1_requested: u64, + } + impl From for CollectProtocolFee { + fn from(instr: instruction::CollectProtocolFee) -> CollectProtocolFee { + CollectProtocolFee { + amount_0_requested: instr.amount_0_requested, + amount_1_requested: instr.amount_1_requested, + } + } + } + println!("{:#?}", CollectProtocolFee::from(ix)); + } + instruction::CollectFundFee::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct CollectFundFee { + pub amount_0_requested: u64, + pub amount_1_requested: u64, + } + impl From for CollectFundFee { + fn from(instr: instruction::CollectFundFee) -> CollectFundFee { + CollectFundFee { + amount_0_requested: instr.amount_0_requested, + amount_1_requested: instr.amount_1_requested, + } + } + } + println!("{:#?}", CollectFundFee::from(ix)); + } + instruction::OpenPosition::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct OpenPosition { + pub tick_lower_index: i32, + pub tick_upper_index: i32, + pub tick_array_lower_start_index: i32, + pub tick_array_upper_start_index: i32, + pub liquidity: u128, + pub amount_0_max: u64, + pub amount_1_max: u64, + } + impl From for OpenPosition { + fn from(instr: instruction::OpenPosition) -> OpenPosition { + OpenPosition { + tick_lower_index: instr.tick_lower_index, + tick_upper_index: instr.tick_upper_index, + tick_array_lower_start_index: instr.tick_array_lower_start_index, + tick_array_upper_start_index: instr.tick_array_upper_start_index, + liquidity: instr.liquidity, + amount_0_max: instr.amount_0_max, + amount_1_max: instr.amount_1_max, + } + } + } + println!("{:#?}", OpenPosition::from(ix)); + } + instruction::OpenPositionV2::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct OpenPositionV2 { + pub tick_lower_index: i32, + pub tick_upper_index: i32, + pub tick_array_lower_start_index: i32, + pub tick_array_upper_start_index: i32, + pub liquidity: u128, + pub amount_0_max: u64, + pub amount_1_max: u64, + pub base_flag: Option, + pub with_metadata: bool, + } + impl From for OpenPositionV2 { + fn from(instr: instruction::OpenPositionV2) -> OpenPositionV2 { + OpenPositionV2 { + tick_lower_index: instr.tick_lower_index, + tick_upper_index: instr.tick_upper_index, + tick_array_lower_start_index: instr.tick_array_lower_start_index, + tick_array_upper_start_index: instr.tick_array_upper_start_index, + liquidity: instr.liquidity, + amount_0_max: instr.amount_0_max, + amount_1_max: instr.amount_1_max, + base_flag: instr.base_flag, + with_metadata: instr.with_matedata, + } + } + } + println!("{:#?}", OpenPositionV2::from(ix)); + } + instruction::ClosePosition::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct ClosePosition; + impl From for ClosePosition { + fn from(_instr: instruction::ClosePosition) -> ClosePosition { + ClosePosition + } + } + println!("{:#?}", ClosePosition::from(ix)); + } + instruction::IncreaseLiquidity::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct IncreaseLiquidity { + pub liquidity: u128, + pub amount_0_max: u64, + pub amount_1_max: u64, + } + impl From for IncreaseLiquidity { + fn from(instr: instruction::IncreaseLiquidity) -> IncreaseLiquidity { + IncreaseLiquidity { + liquidity: instr.liquidity, + amount_0_max: instr.amount_0_max, + amount_1_max: instr.amount_1_max, + } + } + } + println!("{:#?}", IncreaseLiquidity::from(ix)); + } + instruction::IncreaseLiquidityV2::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct IncreaseLiquidityV2 { + pub liquidity: u128, + pub amount_0_max: u64, + pub amount_1_max: u64, + pub base_flag: Option, + } + impl From for IncreaseLiquidityV2 { + fn from(instr: instruction::IncreaseLiquidityV2) -> IncreaseLiquidityV2 { + IncreaseLiquidityV2 { + liquidity: instr.liquidity, + amount_0_max: instr.amount_0_max, + amount_1_max: instr.amount_1_max, + base_flag: instr.base_flag, + } + } + } + println!("{:#?}", IncreaseLiquidityV2::from(ix)); + } + instruction::DecreaseLiquidity::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct DecreaseLiquidity { + pub liquidity: u128, + pub amount_0_min: u64, + pub amount_1_min: u64, + } + impl From for DecreaseLiquidity { + fn from(instr: instruction::DecreaseLiquidity) -> DecreaseLiquidity { + DecreaseLiquidity { + liquidity: instr.liquidity, + amount_0_min: instr.amount_0_min, + amount_1_min: instr.amount_1_min, + } + } + } + println!("{:#?}", DecreaseLiquidity::from(ix)); + } + instruction::DecreaseLiquidityV2::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct DecreaseLiquidityV2 { + pub liquidity: u128, + pub amount_0_min: u64, + pub amount_1_min: u64, + } + impl From for DecreaseLiquidityV2 { + fn from(instr: instruction::DecreaseLiquidityV2) -> DecreaseLiquidityV2 { + DecreaseLiquidityV2 { + liquidity: instr.liquidity, + amount_0_min: instr.amount_0_min, + amount_1_min: instr.amount_1_min, + } + } + } + println!("{:#?}", DecreaseLiquidityV2::from(ix)); + } + instruction::Swap::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct Swap { + pub amount: u64, + pub other_amount_threshold: u64, + pub sqrt_price_limit_x64: u128, + pub is_base_input: bool, + } + impl From for Swap { + fn from(instr: instruction::Swap) -> Swap { + Swap { + amount: instr.amount, + other_amount_threshold: instr.other_amount_threshold, + sqrt_price_limit_x64: instr.sqrt_price_limit_x64, + is_base_input: instr.is_base_input, + } + } + } + println!("{:#?}", Swap::from(ix)); + } + instruction::SwapV2::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct SwapV2 { + pub amount: u64, + pub other_amount_threshold: u64, + pub sqrt_price_limit_x64: u128, + pub is_base_input: bool, + } + impl From for SwapV2 { + fn from(instr: instruction::SwapV2) -> SwapV2 { + SwapV2 { + amount: instr.amount, + other_amount_threshold: instr.other_amount_threshold, + sqrt_price_limit_x64: instr.sqrt_price_limit_x64, + is_base_input: instr.is_base_input, + } + } + } + println!("{:#?}", SwapV2::from(ix)); + } + instruction::SwapRouterBaseIn::DISCRIMINATOR => { + let ix = decode_instruction::(&mut ix_data).unwrap(); + #[derive(Debug)] + pub struct SwapRouterBaseIn { + pub amount_in: u64, + pub amount_out_minimum: u64, + } + impl From for SwapRouterBaseIn { + fn from(instr: instruction::SwapRouterBaseIn) -> SwapRouterBaseIn { + SwapRouterBaseIn { + amount_in: instr.amount_in, + amount_out_minimum: instr.amount_out_minimum, + } + } + } + println!("{:#?}", SwapRouterBaseIn::from(ix)); + } + _ => { + println!("unknow instruction: {}", instr_data); + } + } + Ok(()) +} + +fn decode_instruction( + slice: &mut &[u8], +) -> Result { + let instruction: T = anchor_lang::AnchorDeserialize::deserialize(slice) + .map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotDeserialize)?; + Ok(instruction) +} diff --git a/client/src/instructions/mod.rs b/client/src/instructions/mod.rs index 262cb5dc..fdbefecd 100644 --- a/client/src/instructions/mod.rs +++ b/client/src/instructions/mod.rs @@ -6,3 +6,5 @@ pub mod rpc; pub use rpc::*; pub mod utils; pub use utils::*; +pub mod events_instructions_parse; +pub use events_instructions_parse::*; diff --git a/client/src/instructions/rpc.rs b/client/src/instructions/rpc.rs index 57f45306..7dc2ff63 100644 --- a/client/src/instructions/rpc.rs +++ b/client/src/instructions/rpc.rs @@ -6,12 +6,8 @@ use solana_client::{ rpc_response::{RpcResult, RpcSimulateTransactionResult}, }; use solana_sdk::{ - commitment_config::CommitmentConfig, - program_pack::Pack as TokenPack, - pubkey::Pubkey, - signature::Signature, - transaction::Transaction, - account::Account, + account::Account, commitment_config::CommitmentConfig, program_pack::Pack as TokenPack, + pubkey::Pubkey, signature::Signature, transaction::Transaction, }; use std::convert::Into; @@ -35,8 +31,7 @@ pub fn send_txn(client: &RpcClient, txn: &Transaction, wait_confirm: bool) -> Re txn, if wait_confirm { CommitmentConfig::confirmed() - } - else { + } else { CommitmentConfig::processed() }, RpcSendTransactionConfig { @@ -54,6 +49,9 @@ pub fn get_token_account(client: &RpcClient, addr: &Pubkey) -> Res T::unpack_from_slice(&account.data).map_err(Into::into) } -pub fn get_multiple_accounts(client: &RpcClient, pubkeys: &[Pubkey]) -> Result>> { +pub fn get_multiple_accounts( + client: &RpcClient, + pubkeys: &[Pubkey], +) -> Result>> { Ok(client.get_multiple_accounts(pubkeys)?) -} \ No newline at end of file +} diff --git a/client/src/instructions/token_instructions.rs b/client/src/instructions/token_instructions.rs index a2a1f15e..fb47542c 100644 --- a/client/src/instructions/token_instructions.rs +++ b/client/src/instructions/token_instructions.rs @@ -1,46 +1,65 @@ use super::super::{read_keypair_file, ClientConfig}; use anchor_client::{Client, Cluster}; use anyhow::Result; +use solana_client::rpc_client::RpcClient; use solana_sdk::{ + account::WritableAccount, instruction::Instruction, program_pack::Pack, pubkey::Pubkey, signature::{Keypair, Signer}, system_instruction, }; +use spl_token_2022::{ + extension::{BaseStateWithExtensions, ExtensionType, StateWithExtensionsMut}, + state::{Account, Mint}, +}; +use spl_token_client::token::ExtensionInitializationParams; use std::rc::Rc; pub fn create_and_init_mint_instr( config: &ClientConfig, + token_program: Pubkey, mint_key: &Pubkey, mint_authority: &Pubkey, + freeze_authority: Option<&Pubkey>, + extension_init_params: Vec, decimals: u8, ) -> Result> { let payer = read_keypair_file(&config.payer_path)?; let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(spl_token::id()); + let program = if token_program == spl_token::id() { + client.program(spl_token::id())? + } else { + client.program(spl_token_2022::id())? + }; + let extension_types = extension_init_params + .iter() + .map(|e| e.extension()) + .collect::>(); + let space = ExtensionType::try_calculate_account_len::(&extension_types)?; - let instructions = program - .request() - .instruction(system_instruction::create_account( - &program.payer(), - mint_key, - program - .rpc() - .get_minimum_balance_for_rent_exemption(spl_token::state::Mint::LEN)?, - spl_token::state::Mint::LEN as u64, - &program.id(), - )) - .instruction(spl_token::instruction::initialize_mint( - &program.id(), - mint_key, - mint_authority, - None, - decimals, - )?) - .instructions()?; + let mut instructions = vec![system_instruction::create_account( + &program.payer(), + mint_key, + program + .rpc() + .get_minimum_balance_for_rent_exemption(space)?, + space as u64, + &program.id(), + )]; + for params in extension_init_params { + instructions.push(params.instruction(&token_program, &mint_key)?); + } + instructions.push(spl_token_2022::instruction::initialize_mint( + &program.id(), + mint_key, + mint_authority, + freeze_authority, + decimals, + )?); Ok(instructions) } @@ -54,7 +73,7 @@ pub fn create_account_rent_exmpt_instr( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(owner); + let program = client.program(owner)?; let instructions = program .request() .instruction(system_instruction::create_account( @@ -72,6 +91,7 @@ pub fn create_account_rent_exmpt_instr( pub fn create_ata_token_account_instr( config: &ClientConfig, + token_program: Pubkey, mint: &Pubkey, owner: &Pubkey, ) -> Result> { @@ -79,7 +99,7 @@ pub fn create_ata_token_account_instr( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(spl_token::id()); + let program = client.program(token_program)?; let instructions = program .request() .instruction( @@ -87,14 +107,14 @@ pub fn create_ata_token_account_instr( &program.payer(), owner, mint, - &spl_token::ID, + &token_program, ), ) .instructions()?; Ok(instructions) } -pub fn create_and_init_spl_token( +pub fn create_and_init_auxiliary_token( config: &ClientConfig, new_account_key: &Pubkey, mint: &Pubkey, @@ -102,9 +122,30 @@ pub fn create_and_init_spl_token( ) -> Result> { let payer = read_keypair_file(&config.payer_path)?; let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); + let mint_account = &mut RpcClient::new(config.http_url.to_string()).get_account(&mint)?; // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(spl_associated_token_account::id()); + let (program, space) = if mint_account.owner == spl_token::id() { + ( + client.program(spl_token::id())?, + spl_token::state::Account::LEN, + ) + } else { + let mut extensions = vec![]; + extensions.push(ExtensionType::ImmutableOwner); + let mint_state = StateWithExtensionsMut::::unpack(mint_account.data_as_mut_slice())?; + let mint_extension_types = mint_state.get_extension_types()?; + let mut required_extensions = + ExtensionType::get_required_init_account_extensions(&mint_extension_types); + for extension_type in extensions.into_iter() { + if !required_extensions.contains(&extension_type) { + required_extensions.push(extension_type); + } + } + let space = ExtensionType::try_calculate_account_len::(&required_extensions)?; + + (client.program(spl_token_2022::id())?, space) + }; let instructions = program .request() @@ -113,11 +154,15 @@ pub fn create_and_init_spl_token( &mint, program .rpc() - .get_minimum_balance_for_rent_exemption(spl_token::state::Account::LEN)?, - spl_token::state::Account::LEN as u64, + .get_minimum_balance_for_rent_exemption(space)?, + space as u64, &program.id(), )) - .instruction(spl_token::instruction::initialize_account( + .instruction(spl_token_2022::instruction::initialize_immutable_owner( + &program.id(), + new_account_key, + )?) + .instruction(spl_token_2022::instruction::initialize_account( &program.id(), new_account_key, mint, @@ -137,7 +182,7 @@ pub fn close_token_account( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(spl_token::id()); + let program = client.program(spl_token::id())?; let instructions = program .request() .instruction(spl_token::instruction::close_account( @@ -163,7 +208,7 @@ pub fn spl_token_transfer_instr( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(spl_token::id()); + let program = client.program(spl_token::id())?; let instructions = program .request() .instruction(spl_token::instruction::transfer( @@ -181,6 +226,7 @@ pub fn spl_token_transfer_instr( pub fn spl_token_mint_to_instr( config: &ClientConfig, + token_program: Pubkey, mint: &Pubkey, to: &Pubkey, amount: u64, @@ -190,10 +236,14 @@ pub fn spl_token_mint_to_instr( let url = Cluster::Custom(config.http_url.clone(), config.ws_url.clone()); // Client. let client = Client::new(url, Rc::new(payer)); - let program = client.program(spl_token::id()); + let program = if token_program == spl_token::id() { + client.program(spl_token::id())? + } else { + client.program(spl_token_2022::id())? + }; let instructions = program .request() - .instruction(spl_token::instruction::mint_to( + .instruction(spl_token_2022::instruction::mint_to( &program.id(), mint, to, diff --git a/client/src/instructions/utils.rs b/client/src/instructions/utils.rs index 16da8226..53bd8555 100644 --- a/client/src/instructions/utils.rs +++ b/client/src/instructions/utils.rs @@ -1,19 +1,237 @@ -use std::collections::VecDeque; - use anchor_lang::AccountDeserialize; use anyhow::Result; use raydium_amm_v3::libraries::fixed_point_64; use raydium_amm_v3::libraries::*; use raydium_amm_v3::states::*; -use solana_sdk::account::Account; -use std::ops::DerefMut; -use std::ops::Neg; +use solana_client::rpc_client::RpcClient; +use solana_sdk::{account::Account, pubkey::Pubkey}; +use spl_token_2022::{ + extension::{ + confidential_transfer::{ConfidentialTransferAccount, ConfidentialTransferMint}, + cpi_guard::CpiGuard, + default_account_state::DefaultAccountState, + immutable_owner::ImmutableOwner, + interest_bearing_mint::InterestBearingConfig, + memo_transfer::MemoTransfer, + mint_close_authority::MintCloseAuthority, + non_transferable::{NonTransferable, NonTransferableAccount}, + permanent_delegate::PermanentDelegate, + transfer_fee::{TransferFeeAmount, TransferFeeConfig, MAX_FEE_BASIS_POINTS}, + BaseState, BaseStateWithExtensions, ExtensionType, StateWithExtensionsMut, + }, + state::Mint, +}; +use std::collections::VecDeque; +use std::ops::{DerefMut, Mul, Neg}; pub fn deserialize_anchor_account(account: &Account) -> Result { let mut data: &[u8] = &account.data; T::try_deserialize(&mut data).map_err(Into::into) } +#[derive(Debug)] +pub enum ExtensionStruct { + ConfidentialTransferAccount(ConfidentialTransferAccount), + ConfidentialTransferMint(ConfidentialTransferMint), + CpiGuard(CpiGuard), + DefaultAccountState(DefaultAccountState), + ImmutableOwner(ImmutableOwner), + InterestBearingConfig(InterestBearingConfig), + MemoTransfer(MemoTransfer), + MintCloseAuthority(MintCloseAuthority), + NonTransferable(NonTransferable), + NonTransferableAccount(NonTransferableAccount), + PermanentDelegate(PermanentDelegate), + TransferFeeConfig(TransferFeeConfig), + TransferFeeAmount(TransferFeeAmount), +} + +#[derive(Debug)] +pub struct TransferFeeInfo { + pub mint: Pubkey, + pub owner: Pubkey, + pub transfer_fee: u64, +} + +pub fn amount_with_slippage(amount: u64, slippage: f64, round_up: bool) -> u64 { + if round_up { + (amount as f64).mul(1_f64 + slippage).ceil() as u64 + } else { + (amount as f64).mul(1_f64 - slippage).floor() as u64 + } +} + +pub fn get_pool_mints_inverse_fee( + rpc_client: &RpcClient, + token_mint_0: Pubkey, + token_mint_1: Pubkey, + post_fee_amount_0: u64, + post_fee_amount_1: u64, +) -> (TransferFeeInfo, TransferFeeInfo) { + let load_accounts = vec![token_mint_0, token_mint_1]; + let rsps = rpc_client.get_multiple_accounts(&load_accounts).unwrap(); + let epoch = rpc_client.get_epoch_info().unwrap().epoch; + let mut mint0_account = rsps[0].clone().ok_or("load mint0 rps error!").unwrap(); + let mut mint1_account = rsps[1].clone().ok_or("load mint0 rps error!").unwrap(); + let mint0_state = StateWithExtensionsMut::::unpack(&mut mint0_account.data).unwrap(); + let mint1_state = StateWithExtensionsMut::::unpack(&mut mint1_account.data).unwrap(); + ( + TransferFeeInfo { + mint: token_mint_0, + owner: mint0_account.owner, + transfer_fee: get_transfer_inverse_fee(&mint0_state, post_fee_amount_0, epoch), + }, + TransferFeeInfo { + mint: token_mint_1, + owner: mint1_account.owner, + transfer_fee: get_transfer_inverse_fee(&mint1_state, post_fee_amount_1, epoch), + }, + ) +} + +pub fn get_pool_mints_transfer_fee( + rpc_client: &RpcClient, + token_mint_0: Pubkey, + token_mint_1: Pubkey, + pre_fee_amount_0: u64, + pre_fee_amount_1: u64, +) -> (TransferFeeInfo, TransferFeeInfo) { + let load_accounts = vec![token_mint_0, token_mint_1]; + let rsps = rpc_client.get_multiple_accounts(&load_accounts).unwrap(); + let epoch = rpc_client.get_epoch_info().unwrap().epoch; + let mut mint0_account = rsps[0].clone().ok_or("load mint0 rps error!").unwrap(); + let mut mint1_account = rsps[1].clone().ok_or("load mint0 rps error!").unwrap(); + let mint0_state = StateWithExtensionsMut::::unpack(&mut mint0_account.data).unwrap(); + let mint1_state = StateWithExtensionsMut::::unpack(&mut mint1_account.data).unwrap(); + ( + TransferFeeInfo { + mint: token_mint_0, + owner: mint0_account.owner, + transfer_fee: get_transfer_fee(&mint0_state, pre_fee_amount_0, epoch), + }, + TransferFeeInfo { + mint: token_mint_1, + owner: mint1_account.owner, + transfer_fee: get_transfer_fee(&mint1_state, pre_fee_amount_1, epoch), + }, + ) +} + +/// Calculate the fee for output amount +pub fn get_transfer_inverse_fee<'data, S: BaseState>( + account_state: &StateWithExtensionsMut<'data, S>, + epoch: u64, + post_fee_amount: u64, +) -> u64 { + let fee = if let Ok(transfer_fee_config) = account_state.get_extension::() { + let transfer_fee = transfer_fee_config.get_epoch_fee(epoch); + if u16::from(transfer_fee.transfer_fee_basis_points) == MAX_FEE_BASIS_POINTS { + u64::from(transfer_fee.maximum_fee) + } else { + transfer_fee_config + .calculate_inverse_epoch_fee(epoch, post_fee_amount) + .unwrap() + } + } else { + 0 + }; + fee +} + +/// Calculate the fee for input amount +pub fn get_transfer_fee<'data, S: BaseState>( + account_state: &StateWithExtensionsMut<'data, S>, + epoch: u64, + pre_fee_amount: u64, +) -> u64 { + let fee = if let Ok(transfer_fee_config) = account_state.get_extension::() { + transfer_fee_config + .calculate_epoch_fee(epoch, pre_fee_amount) + .unwrap() + } else { + 0 + }; + fee +} + +pub fn get_account_extensions<'data, S: BaseState>( + account_state: &StateWithExtensionsMut<'data, S>, +) -> Vec { + let mut extensions: Vec = Vec::new(); + let extension_types = account_state.get_extension_types().unwrap(); + println!("extension_types:{:?}", extension_types); + for extension_type in extension_types { + match extension_type { + ExtensionType::ConfidentialTransferAccount => { + let extension = account_state + .get_extension::() + .unwrap(); + extensions.push(ExtensionStruct::ConfidentialTransferAccount(*extension)); + } + ExtensionType::ConfidentialTransferMint => { + let extension = account_state + .get_extension::() + .unwrap(); + extensions.push(ExtensionStruct::ConfidentialTransferMint(*extension)); + } + ExtensionType::CpiGuard => { + let extension = account_state.get_extension::().unwrap(); + extensions.push(ExtensionStruct::CpiGuard(*extension)); + } + ExtensionType::DefaultAccountState => { + let extension = account_state + .get_extension::() + .unwrap(); + extensions.push(ExtensionStruct::DefaultAccountState(*extension)); + } + ExtensionType::ImmutableOwner => { + let extension = account_state.get_extension::().unwrap(); + extensions.push(ExtensionStruct::ImmutableOwner(*extension)); + } + ExtensionType::InterestBearingConfig => { + let extension = account_state + .get_extension::() + .unwrap(); + extensions.push(ExtensionStruct::InterestBearingConfig(*extension)); + } + ExtensionType::MemoTransfer => { + let extension = account_state.get_extension::().unwrap(); + extensions.push(ExtensionStruct::MemoTransfer(*extension)); + } + ExtensionType::MintCloseAuthority => { + let extension = account_state.get_extension::().unwrap(); + extensions.push(ExtensionStruct::MintCloseAuthority(*extension)); + } + ExtensionType::NonTransferable => { + let extension = account_state.get_extension::().unwrap(); + extensions.push(ExtensionStruct::NonTransferable(*extension)); + } + ExtensionType::NonTransferableAccount => { + let extension = account_state + .get_extension::() + .unwrap(); + extensions.push(ExtensionStruct::NonTransferableAccount(*extension)); + } + ExtensionType::PermanentDelegate => { + let extension = account_state.get_extension::().unwrap(); + extensions.push(ExtensionStruct::PermanentDelegate(*extension)); + } + ExtensionType::TransferFeeConfig => { + let extension = account_state.get_extension::().unwrap(); + extensions.push(ExtensionStruct::TransferFeeConfig(*extension)); + } + ExtensionType::TransferFeeAmount => { + let extension = account_state.get_extension::().unwrap(); + extensions.push(ExtensionStruct::TransferFeeAmount(*extension)); + } + _ => { + println!("unkonwn extension:{:#?}", extension_type); + } + } + } + extensions +} + pub const Q_RATIO: f64 = 1.0001; pub fn tick_to_price(tick: i32) -> f64 { @@ -96,10 +314,11 @@ pub fn get_out_put_amount_and_remaining_accounts( is_base_input: bool, pool_config: &AmmConfig, pool_state: &PoolState, + tickarray_bitmap_extension: &TickArrayBitmapExtension, tick_arrays: &mut VecDeque, ) -> Result<(u64, VecDeque), &'static str> { let (is_pool_current_tick_array, current_vaild_tick_array_start_index) = pool_state - .get_first_initialized_tick_array(zero_for_one) + .get_first_initialized_tick_array(&Some(*tickarray_bitmap_extension), zero_for_one) .unwrap(); let (amount_calculated, tick_array_start_index_vec) = swap_compute( @@ -111,6 +330,7 @@ pub fn get_out_put_amount_and_remaining_accounts( current_vaild_tick_array_start_index, sqrt_price_limit_x64.unwrap_or(0), pool_state, + tickarray_bitmap_extension, tick_arrays, )?; println!("tick_array_start_index:{:?}", tick_array_start_index_vec); @@ -127,6 +347,7 @@ fn swap_compute( current_vaild_tick_array_start_index: i32, sqrt_price_limit_x64: u128, pool_state: &PoolState, + tickarray_bitmap_extension: &TickArrayBitmapExtension, tick_arrays: &mut VecDeque, ) -> Result<(u64, VecDeque), &'static str> { if amount_specified == 0 { @@ -203,16 +424,19 @@ fn swap_compute( } }; if !next_initialized_tick.is_initialized() { - let current_vaild_tick_array_start_index = - tick_array_bit_map::next_initialized_tick_array_start_index( - U1024(pool_state.tick_array_bitmap), + let current_vaild_tick_array_start_index = pool_state + .next_initialized_tick_array_start_index( + &Some(*tickarray_bitmap_extension), current_vaild_tick_array_start_index, - pool_state.tick_spacing.into(), zero_for_one, ) .unwrap(); tick_array_current = tick_arrays.pop_front().unwrap(); - if tick_array_current.start_tick_index != current_vaild_tick_array_start_index { + if current_vaild_tick_array_start_index.is_none() { + return Result::Err("tick array start tick index out of range limit"); + } + if tick_array_current.start_tick_index != current_vaild_tick_array_start_index.unwrap() + { return Result::Err("tick array start tick index does not match"); } tick_array_start_index_vec.push_back(tick_array_current.start_tick_index); diff --git a/client/src/main.rs b/client/src/main.rs index e75c9a3b..56ee06b4 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,9 +1,9 @@ #![allow(dead_code)] use anchor_client::{Client, Cluster}; use anchor_lang::prelude::AccountMeta; -use anchor_lang::AnchorDeserialize; use anyhow::{format_err, Result}; use arrayref::array_ref; +use clap::Parser; use configparser::ini::Ini; use rand::rngs::OsRng; use solana_account_decoder::{ @@ -12,7 +12,7 @@ use solana_account_decoder::{ }; use solana_client::{ rpc_client::RpcClient, - rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, + rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcTransactionConfig}, rpc_filter::{Memcmp, RpcFilterType}, rpc_request::TokenAccountsFilter, }; @@ -21,9 +21,10 @@ use solana_sdk::{ compute_budget::ComputeBudgetInstruction, program_pack::Pack, pubkey::Pubkey, - signature::{Keypair, Signer}, + signature::{Keypair, Signature, Signer}, transaction::Transaction, }; +use solana_transaction_status::UiTransactionEncoding; use std::path::Path; use std::rc::Rc; use std::str::FromStr; @@ -31,28 +32,37 @@ use std::{collections::VecDeque, convert::identity, mem::size_of}; mod instructions; use instructions::amm_instructions::*; +use instructions::events_instructions_parse::*; use instructions::rpc::*; use instructions::token_instructions::*; use instructions::utils::*; use raydium_amm_v3::{ - libraries::{fixed_point_64, liquidity_math, tick_array_bit_map, tick_math}, - states::{PoolState, TickArrayState}, + libraries::{fixed_point_64, liquidity_math, tick_math}, + states::{PoolState, TickArrayBitmapExtension, TickArrayState, POOL_TICK_ARRAY_BITMAP_SEED}, }; use spl_associated_token_account::get_associated_token_address; +use spl_token_2022::{ + extension::StateWithExtensionsMut, + state::Mint, + state::{Account, AccountState}, +}; +use spl_token_client::token::ExtensionInitializationParams; use crate::instructions::utils; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq)] pub struct ClientConfig { http_url: String, ws_url: String, payer_path: String, admin_path: String, raydium_v3_program: Pubkey, + slippage: f64, amm_config_key: Pubkey, mint0: Option, mint1: Option, pool_id_account: Option, + tickarray_bitmap_extension: Option, amm_config_index: u16, } @@ -91,6 +101,7 @@ fn load_cfg(client_config: &String) -> Result { panic!("raydium_v3_program must not be empty"); } let raydium_v3_program = Pubkey::from_str(&raydium_v3_program_str).unwrap(); + let slippage = config.getfloat("Global", "slippage").unwrap().unwrap(); let mut mint0 = None; let mint0_str = config.get("Pool", "mint0").unwrap(); @@ -133,6 +144,20 @@ fn load_cfg(client_config: &String) -> Result { } else { None }; + let tickarray_bitmap_extension = if pool_id_account != None { + Some( + Pubkey::find_program_address( + &[ + POOL_TICK_ARRAY_BITMAP_SEED.as_bytes(), + pool_id_account.unwrap().to_bytes().as_ref(), + ], + &raydium_v3_program, + ) + .0, + ) + } else { + None + }; Ok(ClientConfig { http_url, @@ -140,10 +165,12 @@ fn load_cfg(client_config: &String) -> Result { payer_path, admin_path, raydium_v3_program, + slippage, amm_config_key, mint0, mint1, pool_id_account, + tickarray_bitmap_extension, amm_config_index, }) } @@ -163,10 +190,11 @@ fn load_cur_and_next_five_tick_array( rpc_client: &RpcClient, pool_config: &ClientConfig, pool_state: &PoolState, + tickarray_bitmap_extension: &TickArrayBitmapExtension, zero_for_one: bool, ) -> VecDeque { let (_, mut current_vaild_tick_array_start_index) = pool_state - .get_first_initialized_tick_array(zero_for_one) + .get_first_initialized_tick_array(&Some(*tickarray_bitmap_extension), zero_for_one) .unwrap(); let mut tick_array_keys = Vec::new(); tick_array_keys.push( @@ -182,12 +210,13 @@ fn load_cur_and_next_five_tick_array( ); let mut max_array_size = 5; while max_array_size != 0 { - let next_tick_array_index = tick_array_bit_map::next_initialized_tick_array_start_index( - raydium_amm_v3::libraries::U1024(pool_state.tick_array_bitmap), - current_vaild_tick_array_start_index, - pool_state.tick_spacing.into(), - zero_for_one, - ); + let next_tick_array_index = pool_state + .next_initialized_tick_array_start_index( + &Some(*tickarray_bitmap_extension), + current_vaild_tick_array_start_index, + zero_for_one, + ) + .unwrap(); if next_tick_array_index.is_none() { break; } @@ -285,10 +314,201 @@ fn get_nft_account_and_position_by_owner( (nft_account, user_position_account) } +#[derive(Debug, Parser)] +pub struct Opts { + #[clap(subcommand)] + pub command: CommandsName, +} +#[derive(Debug, Parser)] +pub enum CommandsName { + NewMint { + #[arg(short, long)] + decimals: u8, + authority: Option, + #[arg(short, long)] + token_2022: bool, + #[arg(short, long)] + enable_freeze: bool, + #[arg(short, long)] + enable_close: bool, + #[arg(short, long)] + enable_non_transferable: bool, + #[arg(short, long)] + enable_permanent_delegate: bool, + rate_bps: Option, + default_account_state: Option, + transfer_fee: Option>, + confidential_transfer_auto_approve: Option, + }, + NewToken { + mint: Pubkey, + authority: Pubkey, + #[arg(short, long)] + not_ata: bool, + }, + MintTo { + mint: Pubkey, + to_token: Pubkey, + amount: u64, + }, + CreateConfig { + config_index: u16, + tick_spacing: u16, + trade_fee_rate: u32, + protocol_fee_rate: u32, + fund_fee_rate: u32, + }, + UpdateConfig { + config_index: u16, + param: u8, + value: u32, + remaining: Option, + }, + CreateOperation, + UpdateOperation { + param: u8, + keys: Vec, + }, + CreatePool { + config_index: u16, + price: f64, + mint0: Pubkey, + mint1: Pubkey, + #[arg(short, long, default_value_t = 0)] + open_time: u64, + }, + InitReward { + open_time: u64, + end_time: u64, + emissions: f64, + reward_mint: Pubkey, + }, + SetRewardParams { + index: u8, + open_time: u64, + end_time: u64, + emissions: f64, + reward_mint: Pubkey, + }, + TransferRewardOwner { + pool_id: Pubkey, + new_owner: Pubkey, + }, + OpenPosition { + tick_lower_price: f64, + tick_upper_price: f64, + #[arg(short, long)] + is_base_0: bool, + input_amount: u64, + #[arg(short, long)] + with_metadata: bool, + }, + IncreaseLiquidity { + tick_lower_price: f64, + tick_upper_price: f64, + #[arg(short, long)] + is_base_0: bool, + imput_amount: u64, + }, + DecreaseLiquidity { + tick_lower_index: i32, + tick_upper_index: i32, + liquidity: Option, + #[arg(short, long)] + simulate: bool, + }, + Swap { + input_token: Pubkey, + output_token: Pubkey, + #[arg(short, long)] + base_in: bool, + amount: u64, + limit_price: Option, + }, + SwapV2 { + input_token: Pubkey, + output_token: Pubkey, + #[arg(short, long)] + base_in: bool, + amount: u64, + limit_price: Option, + }, + PPositionByOwner { + user_wallet: Pubkey, + }, + PTickState { + tick: i32, + pool_id: Option, + }, + CompareKey { + key0: Pubkey, + key1: Pubkey, + }, + PMint { + mint: Pubkey, + }, + PToken { + token: Pubkey, + }, + POperation, + PConfig { + config_index: u16, + }, + PriceToTick { + price: f64, + }, + TickToPrice { + tick: i32, + }, + TickWithSpacing { + tick: i32, + tick_spacing: u16, + }, + TickArraryStartIndex { + tick: i32, + tick_spacing: u16, + }, + LiquidityToAmounts { + tick_lower: i32, + tick_upper: i32, + liquidity: i128, + }, + PPersonalPositionByPool { + pool_id: Option, + }, + PProtocolPositionByPool { + pool_id: Option, + }, + PTickArrayByPool { + pool_id: Option, + }, + PPool { + pool_id: Option, + }, + PBitmapExtension { + bitmap_extension: Option, + }, + PProtocol { + protocol_id: Pubkey, + }, + PPersonal { + personal_id: Pubkey, + }, + DecodeInstruction { + instr_hex_data: String, + }, + DecodeEvent { + log_event: String, + }, + DecodeTxLog { + tx_id: String, + }, +} +// #[cfg(not(feature = "async"))] fn main() -> Result<()> { println!("Starting..."); let client_config = "client_config.ini"; - let mut pool_config = load_cfg(&client_config.to_string()).unwrap(); + let pool_config = load_cfg(&client_config.to_string()).unwrap(); // Admin and cluster params. let payer = read_keypair_file(&pool_config.payer_path)?; let admin = read_keypair_file(&pool_config.admin_path)?; @@ -300,1740 +520,1653 @@ fn main() -> Result<()> { let url = Cluster::Custom(anchor_config.http_url, anchor_config.ws_url); let wallet = read_keypair_file(&pool_config.payer_path)?; let anchor_client = Client::new(url, Rc::new(wallet)); - loop { - println!("input command:"); - let mut line = String::new(); - std::io::stdin().read_line(&mut line).unwrap(); - let v: Vec<&str> = line.trim().split(' ').collect(); - match &v[0][..] { - "mint0" => { - let keypair_path = "KeyPairs/mint0_keypair.json"; - if !path_is_exist(keypair_path) { - if v.len() == 2 { - let decimals = v[1].parse::().unwrap(); - let mint0 = Keypair::generate(&mut OsRng); - let create_and_init_instr = create_and_init_mint_instr( - &pool_config.clone(), - &mint0.pubkey(), - &payer.pubkey(), - decimals as u8, - )?; - // send - let signers = vec![&payer, &mint0]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &create_and_init_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); + let program = anchor_client.program(pool_config.raydium_v3_program)?; - write_keypair_file(&mint0, keypair_path).unwrap(); - println!("mint0: {}", &mint0.pubkey()); - pool_config.mint0 = Some(mint0.pubkey()); - } else { - println!("invalid command: [mint0 decimals]"); - } - } else { - let mint0 = read_keypair_file(keypair_path).unwrap(); - println!("mint0: {}", &mint0.pubkey()); - pool_config.mint0 = Some(mint0.pubkey()); - } + let opts = Opts::parse(); + match opts.command { + CommandsName::NewMint { + authority, + decimals, + token_2022, + enable_freeze, + enable_close, + enable_non_transferable, + enable_permanent_delegate, + rate_bps, + default_account_state, + transfer_fee, + confidential_transfer_auto_approve, + } => { + let token_program = if token_2022 { + spl_token_2022::id() + } else { + spl_token::id() + }; + let authority = if let Some(key) = authority { + key + } else { + payer.pubkey() + }; + let freeze_authority = if enable_freeze { Some(authority) } else { None }; + let mut extensions = vec![]; + if enable_close { + extensions.push(ExtensionInitializationParams::MintCloseAuthority { + close_authority: Some(authority), + }); } - "mint1" => { - let keypair_path = "KeyPairs/mint1_keypair.json"; - if !path_is_exist(keypair_path) { - if v.len() == 2 { - let decimals = v[1].parse::().unwrap(); - let mint1 = Keypair::generate(&mut OsRng); - let create_and_init_instr = create_and_init_mint_instr( - &pool_config.clone(), - &mint1.pubkey(), - &payer.pubkey(), - decimals as u8, - )?; - - // send - let signers = vec![&payer, &mint1]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &create_and_init_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); - - write_keypair_file(&mint1, keypair_path).unwrap(); - println!("mint1: {}", &mint1.pubkey()); - pool_config.mint1 = Some(mint1.pubkey()); - } else { - println!("invalid command: [mint1 decimals]"); - } - } else { - let mint1 = read_keypair_file(keypair_path).unwrap(); - println!("mint1: {}", &mint1.pubkey()); - pool_config.mint1 = Some(mint1.pubkey()); - } + if enable_permanent_delegate { + extensions.push(ExtensionInitializationParams::PermanentDelegate { + delegate: authority, + }); } - "create_ata_token" => { - if v.len() == 3 { - let mint = Pubkey::from_str(&v[1]).unwrap(); - let owner = Pubkey::from_str(&v[2]).unwrap(); - let create_ata_instr = - create_ata_token_account_instr(&pool_config.clone(), &mint, &owner)?; - // send - let signers = vec![&payer]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &create_ata_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); - } else { - println!("invalid command: [create_ata_token mint owner]"); - } + if let Some(rate_bps) = rate_bps { + extensions.push(ExtensionInitializationParams::InterestBearingConfig { + rate_authority: Some(authority), + rate: rate_bps, + }) } - "ptoken" => { - if v.len() == 2 { - let token = Pubkey::from_str(&v[1]).unwrap(); - let cfg = pool_config.clone(); - let client = RpcClient::new(cfg.http_url.to_string()); - let token_data = &mut client.get_account_data(&token)?; - println!("token_data:{:?}", token_data); - } else { - println!("invalid command: [ptoken token]"); + if let Some(state) = default_account_state { + assert!( + enable_freeze, + "Token requires a freeze authority to default to frozen accounts" + ); + let account_state; + match state.as_str() { + "Uninitialized" => account_state = AccountState::Uninitialized, + "Initialized" => account_state = AccountState::Initialized, + "Frozen" => account_state = AccountState::Frozen, + _ => panic!("error default_account_state[Uninitialized, Initialized, Frozen]"), } + extensions.push(ExtensionInitializationParams::DefaultAccountState { + state: account_state, + }) } - "mint_to" => { - if v.len() == 4 { - let mint = Pubkey::from_str(&v[1]).unwrap(); - let to_token = Pubkey::from_str(&v[2]).unwrap(); - let amount = v[3].parse::().unwrap(); - let mint_to_instr = spl_token_mint_to_instr( - &pool_config.clone(), - &mint, - &to_token, - amount, - &payer, - )?; - // send - let signers = vec![&payer]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &mint_to_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); - } else { - println!("invalid command: [mint_to mint to_token amount]"); - } + if let Some(transfer_fee_value) = transfer_fee { + let transfer_fee_basis_points = transfer_fee_value[0] as u16; + let maximum_fee = transfer_fee_value[1]; + extensions.push(ExtensionInitializationParams::TransferFeeConfig { + transfer_fee_config_authority: Some(authority), + withdraw_withheld_authority: Some(authority), + transfer_fee_basis_points, + maximum_fee, + }); } - "create_config" | "ccfg" => { - if v.len() == 6 { - let config_index = v[1].parse::().unwrap(); - let tick_spacing = v[2].parse::().unwrap(); - let trade_fee_rate = v[3].parse::().unwrap(); - let protocol_fee_rate = v[4].parse::().unwrap(); - let fund_fee_rate = v[5].parse::().unwrap(); - let create_instr = create_amm_config_instr( - &pool_config.clone(), - config_index, - tick_spacing, - trade_fee_rate, - protocol_fee_rate, - fund_fee_rate, - )?; - // send - let signers = vec![&payer, &admin]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &create_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); - } else { - println!("invalid command: [ccfg index tick_spacing trade_fee_rate protocol_fee_rate fund_fee_rate]"); + if enable_non_transferable { + extensions.push(ExtensionInitializationParams::NonTransferable); + } + if let Some(auto_approve) = confidential_transfer_auto_approve { + extensions.push(ExtensionInitializationParams::ConfidentialTransferMint { + authority: Some(authority), + auto_approve_new_accounts: auto_approve, + auditor_elgamal_pubkey: None, + }); + } + + let mint = Keypair::generate(&mut OsRng); + let create_and_init_instr = create_and_init_mint_instr( + &pool_config.clone(), + token_program, + &mint.pubkey(), + &authority, + freeze_authority.as_ref(), + extensions, + decimals as u8, + )?; + // send + let signers = vec![&payer, &mint]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &create_and_init_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } + CommandsName::NewToken { + mint, + authority, + not_ata, + } => { + let mut signers = vec![&payer]; + let auxiliary_token_keypair = Keypair::generate(&mut OsRng); + let create_ata_instr = if not_ata { + signers.push(&auxiliary_token_keypair); + create_and_init_auxiliary_token( + &pool_config.clone(), + &auxiliary_token_keypair.pubkey(), + &mint, + &authority, + )? + } else { + let mint_account = rpc_client.get_account(&mint)?; + create_ata_token_account_instr( + &pool_config.clone(), + mint_account.owner, + &mint, + &authority, + )? + }; + // send + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &create_ata_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } + CommandsName::MintTo { + mint, + to_token, + amount, + } => { + let mint_account = rpc_client.get_account(&mint)?; + let mint_to_instr = spl_token_mint_to_instr( + &pool_config.clone(), + mint_account.owner, + &mint, + &to_token, + amount, + &payer, + )?; + // send + let signers = vec![&payer]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &mint_to_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } + CommandsName::CreateConfig { + config_index, + tick_spacing, + trade_fee_rate, + protocol_fee_rate, + fund_fee_rate, + } => { + let create_instr = create_amm_config_instr( + &pool_config.clone(), + config_index, + tick_spacing, + trade_fee_rate, + protocol_fee_rate, + fund_fee_rate, + )?; + // send + let signers = vec![&payer, &admin]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &create_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } + CommandsName::UpdateConfig { + config_index, + param, + value, + remaining, + } => { + let mut remaing_accounts = Vec::new(); + let mut update_value = 0; + let match_param = Some(param); + match match_param { + Some(0) => update_value = value, + Some(1) => update_value = value, + Some(2) => update_value = value, + Some(3) => { + let remaining_key = remaining.unwrap(); + remaing_accounts.push(AccountMeta::new_readonly(remaining_key, false)); } + Some(4) => { + let remaining_key = remaining.unwrap(); + remaing_accounts.push(AccountMeta::new_readonly(remaining_key, false)); + } + _ => panic!("error input"), } - "create_operation" => { - if v.len() == 1 { - let create_instr = create_operation_account_instr(&pool_config.clone())?; - // send - let signers = vec![&payer, &admin]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &create_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); - } else { - println!("invalid command: [create_operation]"); + let (amm_config_key, __bump) = Pubkey::find_program_address( + &[ + raydium_amm_v3::states::AMM_CONFIG_SEED.as_bytes(), + &config_index.to_be_bytes(), + ], + &pool_config.raydium_v3_program, + ); + let update_amm_config_instr = update_amm_config_instr( + &pool_config.clone(), + amm_config_key, + remaing_accounts, + param, + update_value, + )?; + // send + let signers = vec![&payer, &admin]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &update_amm_config_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } + CommandsName::CreateOperation => { + let create_instr = create_operation_account_instr(&pool_config.clone())?; + // send + let signers = vec![&payer, &admin]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &create_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } + CommandsName::UpdateOperation { param, keys } => { + let create_instr = update_operation_account_instr(&pool_config.clone(), param, keys)?; + // send + let signers = vec![&payer, &admin]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &create_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } + CommandsName::CreatePool { + config_index, + price, + mint0, + mint1, + open_time, + } => { + let mut price = price; + let mut mint0 = mint0; + let mut mint1 = mint1; + if mint0 > mint1 { + std::mem::swap(&mut mint0, &mut mint1); + price = 1.0 / price; + } + println!("mint0:{}, mint1:{}, price:{}", mint0, mint1, price); + let load_pubkeys = vec![mint0, mint1]; + let rsps = rpc_client.get_multiple_accounts(&load_pubkeys)?; + let mint0_owner = rsps[0].clone().unwrap().owner; + let mint1_owner = rsps[1].clone().unwrap().owner; + let mint0_account = + spl_token::state::Mint::unpack(&rsps[0].as_ref().unwrap().data).unwrap(); + let mint1_account = + spl_token::state::Mint::unpack(&rsps[1].as_ref().unwrap().data).unwrap(); + let sqrt_price_x64 = + price_to_sqrt_price_x64(price, mint0_account.decimals, mint1_account.decimals); + let (amm_config_key, __bump) = Pubkey::find_program_address( + &[ + raydium_amm_v3::states::AMM_CONFIG_SEED.as_bytes(), + &config_index.to_be_bytes(), + ], + &pool_config.raydium_v3_program, + ); + let tick = tick_math::get_tick_at_sqrt_price(sqrt_price_x64).unwrap(); + println!( + "tick:{}, price:{}, sqrt_price_x64:{}, amm_config_key:{}", + tick, price, sqrt_price_x64, amm_config_key + ); + let observation_account = Keypair::generate(&mut OsRng); + let mut create_observation_instr = create_account_rent_exmpt_instr( + &pool_config.clone(), + &observation_account.pubkey(), + pool_config.raydium_v3_program, + raydium_amm_v3::states::ObservationState::LEN, + )?; + let create_pool_instr = create_pool_instr( + &pool_config.clone(), + amm_config_key, + observation_account.pubkey(), + mint0, + mint1, + mint0_owner, + mint1_owner, + pool_config.tickarray_bitmap_extension.unwrap(), + sqrt_price_x64, + open_time, + )?; + create_observation_instr.extend(create_pool_instr); + + // send + let signers = vec![&payer, &observation_account]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &create_observation_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } + CommandsName::InitReward { + open_time, + end_time, + emissions, + reward_mint, + } => { + let mint_account = rpc_client.get_account(&reward_mint)?; + let emissions_per_second_x64 = (emissions * fixed_point_64::Q64 as f64) as u128; + let program = anchor_client.program(pool_config.raydium_v3_program)?; + println!("{}", pool_config.pool_id_account.unwrap()); + let pool_account: raydium_amm_v3::states::PoolState = + program.account(pool_config.pool_id_account.unwrap())?; + let operator_account_key = Pubkey::find_program_address( + &[raydium_amm_v3::states::OPERATION_SEED.as_bytes()], + &program.id(), + ) + .0; + + let reward_token_vault = Pubkey::find_program_address( + &[ + raydium_amm_v3::states::POOL_REWARD_VAULT_SEED.as_bytes(), + pool_config.pool_id_account.unwrap().to_bytes().as_ref(), + reward_mint.to_bytes().as_ref(), + ], + &program.id(), + ) + .0; + let user_reward_token = get_associated_token_address(&admin.pubkey(), &reward_mint); + let create_instr = initialize_reward_instr( + &pool_config.clone(), + pool_config.pool_id_account.unwrap(), + pool_account.amm_config, + operator_account_key, + reward_mint, + reward_token_vault, + user_reward_token, + mint_account.owner, + open_time, + end_time, + emissions_per_second_x64, + )?; + // send + let signers = vec![&payer, &admin]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &create_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } + CommandsName::SetRewardParams { + index, + open_time, + end_time, + emissions, + reward_mint, + } => { + let emissions_per_second_x64 = (emissions * fixed_point_64::Q64 as f64) as u128; + + let program = anchor_client.program(pool_config.raydium_v3_program)?; + println!("{}", pool_config.pool_id_account.unwrap()); + let pool_account: raydium_amm_v3::states::PoolState = + program.account(pool_config.pool_id_account.unwrap())?; + let operator_account_key = Pubkey::find_program_address( + &[raydium_amm_v3::states::OPERATION_SEED.as_bytes()], + &program.id(), + ) + .0; + + let reward_token_vault = Pubkey::find_program_address( + &[ + raydium_amm_v3::states::POOL_REWARD_VAULT_SEED.as_bytes(), + pool_config.pool_id_account.unwrap().to_bytes().as_ref(), + reward_mint.to_bytes().as_ref(), + ], + &program.id(), + ) + .0; + let user_reward_token = get_associated_token_address(&admin.pubkey(), &reward_mint); + let create_instr = set_reward_params_instr( + &pool_config.clone(), + pool_account.amm_config, + pool_config.pool_id_account.unwrap(), + reward_token_vault, + user_reward_token, + operator_account_key, + index, + open_time, + end_time, + emissions_per_second_x64, + )?; + // send + let signers = vec![&payer, &admin]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &create_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } + CommandsName::TransferRewardOwner { pool_id, new_owner } => { + let transfer_reward_owner_instrs = + transfer_reward_owner(&pool_config.clone(), pool_id, new_owner).unwrap(); + // send + let signers = vec![&payer, &admin]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &transfer_reward_owner_instrs, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } + CommandsName::OpenPosition { + tick_lower_price, + tick_upper_price, + is_base_0, + input_amount, + with_metadata, + } => { + // load pool to get observation + let pool: raydium_amm_v3::states::PoolState = + program.account(pool_config.pool_id_account.unwrap())?; + + let tick_lower_price_x64 = price_to_sqrt_price_x64( + tick_lower_price, + pool.mint_decimals_0, + pool.mint_decimals_1, + ); + let tick_upper_price_x64 = price_to_sqrt_price_x64( + tick_upper_price, + pool.mint_decimals_0, + pool.mint_decimals_1, + ); + let tick_lower_index = tick_with_spacing( + tick_math::get_tick_at_sqrt_price(tick_lower_price_x64)?, + pool.tick_spacing.into(), + ); + let tick_upper_index = tick_with_spacing( + tick_math::get_tick_at_sqrt_price(tick_upper_price_x64)?, + pool.tick_spacing.into(), + ); + println!( + "tick_lower_index:{}, tick_upper_index:{}", + tick_lower_index, tick_upper_index + ); + let tick_lower_price_x64 = tick_math::get_sqrt_price_at_tick(tick_lower_index)?; + let tick_upper_price_x64 = tick_math::get_sqrt_price_at_tick(tick_upper_index)?; + let liquidity = if is_base_0 { + liquidity_math::get_liquidity_from_single_amount_0( + pool.sqrt_price_x64, + tick_lower_price_x64, + tick_upper_price_x64, + input_amount, + ) + } else { + liquidity_math::get_liquidity_from_single_amount_1( + pool.sqrt_price_x64, + tick_lower_price_x64, + tick_upper_price_x64, + input_amount, + ) + }; + let (amount_0, amount_1) = liquidity_math::get_delta_amounts_signed( + pool.tick_current, + pool.sqrt_price_x64, + tick_lower_index, + tick_upper_index, + liquidity as i128, + )?; + println!( + "amount_0:{}, amount_1:{}, liquidity:{}", + amount_0, amount_1, liquidity + ); + // calc with slippage + let amount_0_with_slippage = + amount_with_slippage(amount_0 as u64, pool_config.slippage, true); + let amount_1_with_slippage = + amount_with_slippage(amount_1 as u64, pool_config.slippage, true); + // calc with transfer_fee + let transfer_fee = get_pool_mints_inverse_fee( + &rpc_client, + pool.token_mint_0, + pool.token_mint_1, + amount_0_with_slippage, + amount_1_with_slippage, + ); + println!( + "transfer_fee_0:{}, transfer_fee_1:{}", + transfer_fee.0.transfer_fee, transfer_fee.1.transfer_fee + ); + let amount_0_max = (amount_0_with_slippage as u64) + .checked_add(transfer_fee.0.transfer_fee) + .unwrap(); + let amount_1_max = (amount_1_with_slippage as u64) + .checked_add(transfer_fee.1.transfer_fee) + .unwrap(); + + let tick_array_lower_start_index = + raydium_amm_v3::states::TickArrayState::get_array_start_index( + tick_lower_index, + pool.tick_spacing.into(), + ); + let tick_array_upper_start_index = + raydium_amm_v3::states::TickArrayState::get_array_start_index( + tick_upper_index, + pool.tick_spacing.into(), + ); + // load position + let (_nft_tokens, positions) = get_nft_account_and_position_by_owner( + &rpc_client, + &payer.pubkey(), + &pool_config.raydium_v3_program, + ); + let rsps = rpc_client.get_multiple_accounts(&positions)?; + let mut user_positions = Vec::new(); + for rsp in rsps { + match rsp { + None => continue, + Some(rsp) => { + let position = deserialize_anchor_account::< + raydium_amm_v3::states::PersonalPositionState, + >(&rsp)?; + user_positions.push(position); + } } } - "update_operation" => { - let param = v[1].parse::().unwrap(); - let mut keys = Vec::new(); - for i in 2..v.len() { - keys.push(Pubkey::from_str(&v[i]).unwrap()); + let mut find_position = raydium_amm_v3::states::PersonalPositionState::default(); + for position in user_positions { + if position.pool_id == pool_config.pool_id_account.unwrap() + && position.tick_lower_index == tick_lower_index + && position.tick_upper_index == tick_upper_index + { + find_position = position.clone(); } - let create_instr = - update_operation_account_instr(&pool_config.clone(), param, keys)?; + } + if find_position.nft_mint == Pubkey::default() { + // personal position not exist + // new nft mint + let nft_mint = Keypair::generate(&mut OsRng); + let mut remaining_accounts = Vec::new(); + remaining_accounts.push(AccountMeta::new( + pool_config.tickarray_bitmap_extension.unwrap(), + false, + )); + + let mut instructions = Vec::new(); + let request_inits_instr = + ComputeBudgetInstruction::set_compute_unit_limit(1400_000u32); + instructions.push(request_inits_instr); + let open_position_instr = open_position_instr( + &pool_config.clone(), + pool_config.pool_id_account.unwrap(), + pool.token_vault_0, + pool.token_vault_1, + pool.token_mint_0, + pool.token_mint_1, + nft_mint.pubkey(), + payer.pubkey(), + spl_associated_token_account::get_associated_token_address( + &payer.pubkey(), + &pool_config.mint0.unwrap(), + ), + spl_associated_token_account::get_associated_token_address( + &payer.pubkey(), + &pool_config.mint1.unwrap(), + ), + remaining_accounts, + liquidity, + amount_0_max, + amount_1_max, + tick_lower_index, + tick_upper_index, + tick_array_lower_start_index, + tick_array_upper_start_index, + with_metadata, + )?; + instructions.extend(open_position_instr); // send - let signers = vec![&payer, &admin]; + let signers = vec![&payer, &nft_mint]; let recent_hash = rpc_client.get_latest_blockhash()?; let txn = Transaction::new_signed_with_payer( - &create_instr, + &instructions, Some(&payer.pubkey()), &signers, recent_hash, ); let signature = send_txn(&rpc_client, &txn, true)?; println!("{}", signature); + } else { + // personal position exist + println!("personal position exist:{:?}", find_position); } - "poperation" => { - if v.len() == 1 { - let program = anchor_client.program(pool_config.raydium_v3_program); - let (operation_account_key, __bump) = Pubkey::find_program_address( - &[raydium_amm_v3::states::OPERATION_SEED.as_bytes()], - &program.id(), - ); - println!("{}", operation_account_key); - let operation_account: raydium_amm_v3::states::OperationState = - program.account(operation_account_key)?; - println!("{:#?}", operation_account); - } else { - println!("invalid command: [poperation]"); + } + CommandsName::IncreaseLiquidity { + tick_lower_price, + tick_upper_price, + is_base_0, + imput_amount, + } => { + // load pool to get observation + let pool: raydium_amm_v3::states::PoolState = + program.account(pool_config.pool_id_account.unwrap())?; + + // load position + let (_nft_tokens, positions) = get_nft_account_and_position_by_owner( + &rpc_client, + &payer.pubkey(), + &pool_config.raydium_v3_program, + ); + let rsps = rpc_client.get_multiple_accounts(&positions)?; + let mut user_positions = Vec::new(); + for rsp in rsps { + match rsp { + None => continue, + Some(rsp) => { + let position = deserialize_anchor_account::< + raydium_amm_v3::states::PersonalPositionState, + >(&rsp)?; + user_positions.push(position); + } } } - "pcfg" => { - if v.len() == 2 { - let config_index = v[1].parse::().unwrap(); - let program = anchor_client.program(pool_config.raydium_v3_program); - let (amm_config_key, __bump) = Pubkey::find_program_address( - &[ - raydium_amm_v3::states::AMM_CONFIG_SEED.as_bytes(), - &config_index.to_be_bytes(), - ], - &program.id(), - ); - println!("{}", amm_config_key); - let amm_config_account: raydium_amm_v3::states::AmmConfig = - program.account(amm_config_key)?; - println!("{:#?}", amm_config_account); - } else { - println!("invalid command: [pcfg config_index]"); + + let tick_lower_price_x64 = price_to_sqrt_price_x64( + tick_lower_price, + pool.mint_decimals_0, + pool.mint_decimals_1, + ); + let tick_upper_price_x64 = price_to_sqrt_price_x64( + tick_upper_price, + pool.mint_decimals_0, + pool.mint_decimals_1, + ); + let tick_lower_index = tick_with_spacing( + tick_math::get_tick_at_sqrt_price(tick_lower_price_x64)?, + pool.tick_spacing.into(), + ); + let tick_upper_index = tick_with_spacing( + tick_math::get_tick_at_sqrt_price(tick_upper_price_x64)?, + pool.tick_spacing.into(), + ); + println!( + "tick_lower_index:{}, tick_upper_index:{}", + tick_lower_index, tick_upper_index + ); + let tick_lower_price_x64 = tick_math::get_sqrt_price_at_tick(tick_lower_index)?; + let tick_upper_price_x64 = tick_math::get_sqrt_price_at_tick(tick_upper_index)?; + let liquidity = if is_base_0 { + liquidity_math::get_liquidity_from_single_amount_0( + pool.sqrt_price_x64, + tick_lower_price_x64, + tick_upper_price_x64, + imput_amount, + ) + } else { + liquidity_math::get_liquidity_from_single_amount_1( + pool.sqrt_price_x64, + tick_lower_price_x64, + tick_upper_price_x64, + imput_amount, + ) + }; + let (amount_0, amount_1) = liquidity_math::get_delta_amounts_signed( + pool.tick_current, + pool.sqrt_price_x64, + tick_lower_index, + tick_upper_index, + liquidity as i128, + )?; + println!( + "amount_0:{}, amount_1:{}, liquidity:{}", + amount_0, amount_1, liquidity + ); + // calc with slippage + let amount_0_with_slippage = + amount_with_slippage(amount_0 as u64, pool_config.slippage, true); + let amount_1_with_slippage = + amount_with_slippage(amount_1 as u64, pool_config.slippage, true); + // calc with transfer_fee + let transfer_fee = get_pool_mints_inverse_fee( + &rpc_client, + pool.token_mint_0, + pool.token_mint_1, + amount_0_with_slippage, + amount_1_with_slippage, + ); + println!( + "transfer_fee_0:{}, transfer_fee_1:{}", + transfer_fee.0.transfer_fee, transfer_fee.1.transfer_fee + ); + let amount_0_max = (amount_0_with_slippage as u64) + .checked_add(transfer_fee.0.transfer_fee) + .unwrap(); + let amount_1_max = (amount_1_with_slippage as u64) + .checked_add(transfer_fee.1.transfer_fee) + .unwrap(); + + let tick_array_lower_start_index = + raydium_amm_v3::states::TickArrayState::get_array_start_index( + tick_lower_index, + pool.tick_spacing.into(), + ); + let tick_array_upper_start_index = + raydium_amm_v3::states::TickArrayState::get_array_start_index( + tick_upper_index, + pool.tick_spacing.into(), + ); + let mut find_position = raydium_amm_v3::states::PersonalPositionState::default(); + for position in user_positions { + if position.pool_id == pool_config.pool_id_account.unwrap() + && position.tick_lower_index == tick_lower_index + && position.tick_upper_index == tick_upper_index + { + find_position = position.clone(); } } - "update_amm_cfg" => { - if v.len() == 4 { - let config_index = v[1].parse::().unwrap(); - let param = v[2].parse::().unwrap(); - let mut remaing_accounts = Vec::new(); - let mut value = 0; - let match_param = Some(param); - match match_param { - Some(0) => value = v[3].parse::().unwrap(), - Some(1) => value = v[3].parse::().unwrap(), - Some(2) => value = v[3].parse::().unwrap(), - Some(3) => { - remaing_accounts.push(AccountMeta::new_readonly( - Pubkey::from_str(&v[3]).unwrap(), - false, - )); - } - Some(4) => { - remaing_accounts.push(AccountMeta::new_readonly( - Pubkey::from_str(&v[3]).unwrap(), - false, - )); - } - _ => panic!("error input"), - } - let (amm_config_key, __bump) = Pubkey::find_program_address( - &[ - raydium_amm_v3::states::AMM_CONFIG_SEED.as_bytes(), - &config_index.to_be_bytes(), - ], - &pool_config.raydium_v3_program, - ); - let update_amm_config_instr = update_amm_config_instr( - &pool_config.clone(), - amm_config_key, - remaing_accounts, - param, - value, - )?; - // send - let signers = vec![&payer, &admin]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &update_amm_config_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); - } else { - println!("invalid command: [set_new_cfg_owner config_index new_owner]"); - } + if find_position.nft_mint != Pubkey::default() + && find_position.pool_id == pool_config.pool_id_account.unwrap() + { + // personal position exist + let mut remaining_accounts = Vec::new(); + remaining_accounts.push(AccountMeta::new_readonly( + pool_config.tickarray_bitmap_extension.unwrap(), + false, + )); + + let increase_instr = increase_liquidity_instr( + &pool_config.clone(), + pool_config.pool_id_account.unwrap(), + pool.token_vault_0, + pool.token_vault_1, + pool.token_mint_0, + pool.token_mint_1, + find_position.nft_mint, + spl_associated_token_account::get_associated_token_address( + &payer.pubkey(), + &pool_config.mint0.unwrap(), + ), + spl_associated_token_account::get_associated_token_address( + &payer.pubkey(), + &pool_config.mint1.unwrap(), + ), + remaining_accounts, + liquidity, + amount_0_max, + amount_1_max, + tick_lower_index, + tick_upper_index, + tick_array_lower_start_index, + tick_array_upper_start_index, + )?; + // send + let signers = vec![&payer]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &increase_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } else { + // personal position not exist + println!("personal position exist:{:?}", find_position); } - "cmp_key" => { - if v.len() == 3 { - let mut token_mint_0 = Pubkey::from_str(&v[1]).unwrap(); - let mut token_mint_1 = Pubkey::from_str(&v[2]).unwrap(); - if token_mint_0 > token_mint_1 { - std::mem::swap(&mut token_mint_0, &mut token_mint_1); + } + CommandsName::DecreaseLiquidity { + tick_lower_index, + tick_upper_index, + liquidity, + simulate, + } => { + // load pool to get observation + let pool: raydium_amm_v3::states::PoolState = + program.account(pool_config.pool_id_account.unwrap())?; + + let tick_array_lower_start_index = + raydium_amm_v3::states::TickArrayState::get_array_start_index( + tick_lower_index, + pool.tick_spacing.into(), + ); + let tick_array_upper_start_index = + raydium_amm_v3::states::TickArrayState::get_array_start_index( + tick_upper_index, + pool.tick_spacing.into(), + ); + // load position + let (_nft_tokens, positions) = get_nft_account_and_position_by_owner( + &rpc_client, + &payer.pubkey(), + &pool_config.raydium_v3_program, + ); + let rsps = rpc_client.get_multiple_accounts(&positions)?; + let mut user_positions = Vec::new(); + for rsp in rsps { + match rsp { + None => continue, + Some(rsp) => { + let position = deserialize_anchor_account::< + raydium_amm_v3::states::PersonalPositionState, + >(&rsp)?; + user_positions.push(position); } - println!("mint0:{}, mint1:{}", token_mint_0, token_mint_1); - } else { - println!("cmp_key mint mint"); } } - "price_to_tick" => { - if v.len() == 2 { - let price = v[1].parse::().unwrap(); - let tick = price_to_tick(price); - println!("price:{}, tick:{}", price, tick); - } else { - println!("price_to_tick price"); + let mut find_position = raydium_amm_v3::states::PersonalPositionState::default(); + for position in user_positions { + if position.pool_id == pool_config.pool_id_account.unwrap() + && position.tick_lower_index == tick_lower_index + && position.tick_upper_index == tick_upper_index + { + find_position = position.clone(); + println!("liquidity:{:?}", find_position); } } - "tick_to_price" => { - if v.len() == 2 { - let tick = v[1].parse::().unwrap(); - let price = tick_to_price(tick); - println!("price:{}, tick:{}", price, tick); - } else { - println!("tick_to_price tick"); + if find_position.nft_mint != Pubkey::default() + && find_position.pool_id == pool_config.pool_id_account.unwrap() + { + let mut reward_vault_with_user_vault: Vec<(Pubkey, Pubkey)> = Vec::new(); + for item in pool.reward_infos.into_iter() { + if item.token_mint != Pubkey::default() { + reward_vault_with_user_vault.push(( + item.token_vault, + get_associated_token_address(&payer.pubkey(), &item.token_mint), + )); + } } - } - "tick_with_spacing" => { - if v.len() == 2 { - let tick = v[1].parse::().unwrap(); - let tick_spacing = v[2].parse::().unwrap(); - let tick_with_spacing = tick_with_spacing(tick, tick_spacing); - println!("tick:{}, tick_with_spacing:{}", tick, tick_with_spacing); + let liquidity = if let Some(liquidity) = liquidity { + liquidity } else { - println!("tick_with_spacing tick tick_spacing"); - } - } - "tick_array_start_index" => { - if v.len() == 2 { - let tick = v[1].parse::().unwrap(); - let tick_spacing = v[2].parse::().unwrap(); - let tick_array_start_index = - raydium_amm_v3::states::TickArrayState::get_array_start_index( - tick, - tick_spacing, - ); - println!( - "tick:{}, tick_array_start_index:{}", - tick, tick_array_start_index - ); - } else { - println!("tick_array_start_index tick tick_spacing"); - } - } - "liquidity_to_amounts" => { - let program = anchor_client.program(pool_config.raydium_v3_program); - println!("{}", pool_config.pool_id_account.unwrap()); - let pool_account: raydium_amm_v3::states::PoolState = - program.account(pool_config.pool_id_account.unwrap())?; - if v.len() == 4 { - let tick_upper = v[1].parse::().unwrap(); - let tick_lower = v[2].parse::().unwrap(); - let liquidity = v[3].parse::().unwrap(); - let amounts = raydium_amm_v3::libraries::get_delta_amounts_signed( - pool_account.tick_current, - pool_account.sqrt_price_x64, - tick_lower, - tick_upper, - liquidity, - )?; - println!("amount_0:{}, amount_1:{}", amounts.0, amounts.1); - } - } - "create_pool" | "cpool" => { - if v.len() == 6 { - let config_index = v[1].parse::().unwrap(); - let mut price = v[2].parse::().unwrap(); - let mut mint0 = Pubkey::from_str(&v[3]).unwrap(); - let mut mint1 = Pubkey::from_str(&v[4]).unwrap(); - let open_time = v[5].parse::().unwrap(); - if mint0 > mint1 { - std::mem::swap(&mut mint0, &mut mint1); - price = 1.0 / price; - } - println!("mint0:{}, mint1:{}, price:{}", mint0, mint1, price); - let load_pubkeys = vec![mint0, mint1]; - let rsps = rpc_client.get_multiple_accounts(&load_pubkeys)?; - let mint0_account = - spl_token::state::Mint::unpack(&rsps[0].as_ref().unwrap().data).unwrap(); - let mint1_account = - spl_token::state::Mint::unpack(&rsps[1].as_ref().unwrap().data).unwrap(); - let sqrt_price_x64 = price_to_sqrt_price_x64( - price, - mint0_account.decimals, - mint1_account.decimals, - ); - let (amm_config_key, __bump) = Pubkey::find_program_address( - &[ - raydium_amm_v3::states::AMM_CONFIG_SEED.as_bytes(), - &config_index.to_be_bytes(), - ], - &pool_config.raydium_v3_program, - ); - let tick = tick_math::get_tick_at_sqrt_price(sqrt_price_x64).unwrap(); - println!( - "tick:{}, price:{}, sqrt_price_x64:{}, amm_config_key:{}", - tick, price, sqrt_price_x64, amm_config_key - ); - let observation_account = Keypair::generate(&mut OsRng); - let mut create_observation_instr = create_account_rent_exmpt_instr( + find_position.liquidity + }; + let (amount_0_int, amount_1_int) = liquidity_math::get_delta_amounts_signed( + pool.tick_current, + pool.sqrt_price_x64, + tick_lower_index, + tick_upper_index, + -(liquidity as i128), + )?; + let amount_0_with_slippage = amount_with_slippage( + u64::try_from(-amount_0_int).unwrap(), + pool_config.slippage, + false, + ); + let amount_1_with_slippage = amount_with_slippage( + u64::try_from(-amount_1_int).unwrap(), + pool_config.slippage, + false, + ); + let transfer_fee = get_pool_mints_transfer_fee( + &rpc_client, + pool.token_mint_0, + pool.token_mint_1, + amount_0_with_slippage, + amount_1_with_slippage, + ); + let amount_0_min = amount_0_with_slippage + .checked_sub(transfer_fee.0.transfer_fee) + .unwrap(); + let amount_1_min = amount_1_with_slippage + .checked_sub(transfer_fee.1.transfer_fee) + .unwrap(); + + let mut remaining_accounts = Vec::new(); + remaining_accounts.push(AccountMeta::new( + pool_config.tickarray_bitmap_extension.unwrap(), + false, + )); + + let mut accounts = reward_vault_with_user_vault + .into_iter() + .map(|item| AccountMeta::new(item.0, false)) + .collect(); + remaining_accounts.append(&mut accounts); + // personal position exist + let mut decrease_instr = decrease_liquidity_instr( + &pool_config.clone(), + pool_config.pool_id_account.unwrap(), + pool.token_vault_0, + pool.token_vault_1, + pool.token_mint_0, + pool.token_mint_1, + find_position.nft_mint, + spl_associated_token_account::get_associated_token_address( + &payer.pubkey(), + &pool_config.mint0.unwrap(), + ), + spl_associated_token_account::get_associated_token_address( + &payer.pubkey(), + &pool_config.mint1.unwrap(), + ), + remaining_accounts, + liquidity, + amount_0_min, + amount_1_min, + tick_lower_index, + tick_upper_index, + tick_array_lower_start_index, + tick_array_upper_start_index, + )?; + if liquidity == find_position.liquidity { + let close_position_instr = close_personal_position_instr( &pool_config.clone(), - &observation_account.pubkey(), - pool_config.raydium_v3_program, - raydium_amm_v3::states::ObservationState::LEN, + find_position.nft_mint, )?; - let create_pool_instr = create_pool_instr( - &pool_config.clone(), - amm_config_key, - observation_account.pubkey(), - mint0, - mint1, - sqrt_price_x64, - open_time, + decrease_instr.extend(close_position_instr); + } + // send + let signers = vec![&payer]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &decrease_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + if simulate { + let ret = simulate_transaction( + &rpc_client, + &txn, + true, + CommitmentConfig::confirmed(), )?; - create_observation_instr.extend(create_pool_instr); - - // send - let signers = vec![&payer, &observation_account]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &create_observation_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); + println!("{:#?}", ret); + } else { let signature = send_txn(&rpc_client, &txn, true)?; println!("{}", signature); - } else { - println!("invalid command: [create_pool config_index tick_spacing]"); } + } else { + // personal position not exist + println!("personal position exist:{:?}", find_position); } - "p_all_personal_position_by_pool" => { - println!("pool_id:{}", pool_config.pool_id_account.unwrap()); - let position_accounts_by_pool = rpc_client.get_program_accounts_with_config( - &pool_config.raydium_v3_program, - RpcProgramAccountsConfig { - filters: Some(vec![ - RpcFilterType::Memcmp(Memcmp::new_base58_encoded( - 8 + 1 + size_of::(), - &pool_config.pool_id_account.unwrap().to_bytes(), - )), - RpcFilterType::DataSize( - raydium_amm_v3::states::PersonalPositionState::LEN as u64, - ), - ]), - account_config: RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::Base64), - ..RpcAccountInfoConfig::default() - }, - with_context: Some(false), - }, + } + CommandsName::Swap { + input_token, + output_token, + base_in, + amount, + limit_price, + } => { + // load mult account + let load_accounts = vec![ + input_token, + output_token, + pool_config.amm_config_key, + pool_config.pool_id_account.unwrap(), + pool_config.tickarray_bitmap_extension.unwrap(), + ]; + let rsps = rpc_client.get_multiple_accounts(&load_accounts)?; + let [user_input_account, user_output_account, amm_config_account, pool_account, tickarray_bitmap_extension_account] = + array_ref![rsps, 0, 5]; + let user_input_state = + spl_token::state::Account::unpack(&user_input_account.as_ref().unwrap().data) + .unwrap(); + let user_output_state = + spl_token::state::Account::unpack(&user_output_account.as_ref().unwrap().data) + .unwrap(); + let amm_config_state = deserialize_anchor_account::( + amm_config_account.as_ref().unwrap(), + )?; + let pool_state = deserialize_anchor_account::( + pool_account.as_ref().unwrap(), + )?; + let tickarray_bitmap_extension = + deserialize_anchor_account::( + tickarray_bitmap_extension_account.as_ref().unwrap(), )?; + let zero_for_one = user_input_state.mint == pool_state.token_mint_0 + && user_output_state.mint == pool_state.token_mint_1; + // load tick_arrays + let mut tick_arrays = load_cur_and_next_five_tick_array( + &rpc_client, + &pool_config, + &pool_state, + &tickarray_bitmap_extension, + zero_for_one, + ); - let mut total_fees_owed_0 = 0; - let mut total_fees_owed_1 = 0; - let mut total_reward_owed = 0; - for position in position_accounts_by_pool { - let personal_position = deserialize_anchor_account::< - raydium_amm_v3::states::PersonalPositionState, - >(&position.1)?; - if personal_position.pool_id == pool_config.pool_id_account.unwrap() { - println!( - "personal_position:{}, lower:{}, upper:{}, liquidity:{}, token_fees_owed_0:{}, token_fees_owed_1:{}, reward_amount_owed:{}, fee_growth_inside:{}, fee_growth_inside_1:{}, reward_inside:{}", - position.0, - personal_position.tick_lower_index, - personal_position.tick_upper_index, - personal_position.liquidity, - personal_position.token_fees_owed_0, - personal_position.token_fees_owed_1, - personal_position.reward_infos[0].reward_amount_owed, - personal_position.fee_growth_inside_0_last_x64, - personal_position.fee_growth_inside_1_last_x64, - personal_position.reward_infos[0].growth_inside_last_x64, - ); - total_fees_owed_0 += personal_position.token_fees_owed_0; - total_fees_owed_1 += personal_position.token_fees_owed_1; - total_reward_owed += personal_position.reward_infos[0].reward_amount_owed; - } - } - println!( - "total_fees_owed_0:{}, total_fees_owed_1:{}, total_reward_owed:{}", - total_fees_owed_0, total_fees_owed_1, total_reward_owed + let mut sqrt_price_limit_x64 = None; + if limit_price.is_some() { + let sqrt_price_x64 = price_to_sqrt_price_x64( + limit_price.unwrap(), + pool_state.mint_decimals_0, + pool_state.mint_decimals_1, ); + sqrt_price_limit_x64 = Some(sqrt_price_x64); } - "p_all_protocol_position_by_pool" => { - let position_accounts_by_pool = rpc_client.get_program_accounts_with_config( - &pool_config.raydium_v3_program, - RpcProgramAccountsConfig { - filters: Some(vec![ - RpcFilterType::Memcmp(Memcmp::new_base58_encoded( - 8 + 1, - &pool_config.pool_id_account.unwrap().to_bytes(), - )), - RpcFilterType::DataSize( - raydium_amm_v3::states::ProtocolPositionState::LEN as u64, - ), - ]), - account_config: RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::Base64Zstd), - ..RpcAccountInfoConfig::default() - }, - with_context: Some(false), - }, - )?; - for position in position_accounts_by_pool { - let protocol_position = deserialize_anchor_account::< - raydium_amm_v3::states::ProtocolPositionState, - >(&position.1)?; - if protocol_position.pool_id == pool_config.pool_id_account.unwrap() { - println!( - "protocol_position:{} lower_index:{}, upper_index:{}", - position.0, - protocol_position.tick_lower_index, - protocol_position.tick_upper_index, - ); - } - } + let (mut other_amount_threshold, mut tick_array_indexs) = + utils::get_out_put_amount_and_remaining_accounts( + amount, + sqrt_price_limit_x64, + zero_for_one, + base_in, + &amm_config_state, + &pool_state, + &tickarray_bitmap_extension, + &mut tick_arrays, + ) + .unwrap(); + if base_in { + // min out + other_amount_threshold = + amount_with_slippage(other_amount_threshold, pool_config.slippage, false); + } else { + // max in + other_amount_threshold = + amount_with_slippage(other_amount_threshold, pool_config.slippage, true); } - "p_all_tick_array_by_pool" => { - let tick_arrays_by_pool = rpc_client.get_program_accounts_with_config( - &pool_config.raydium_v3_program, - RpcProgramAccountsConfig { - filters: Some(vec![ - RpcFilterType::Memcmp(Memcmp::new_base58_encoded( - 8, - &pool_config.pool_id_account.unwrap().to_bytes(), - )), - RpcFilterType::DataSize( - raydium_amm_v3::states::TickArrayState::LEN as u64, - ), - ]), - account_config: RpcAccountInfoConfig { - encoding: Some(UiAccountEncoding::Base64Zstd), - ..RpcAccountInfoConfig::default() - }, - with_context: Some(false), - }, - )?; - for tick_array in tick_arrays_by_pool { - let tick_array_state = deserialize_anchor_account::< - raydium_amm_v3::states::TickArrayState, - >(&tick_array.1)?; - if tick_array_state.pool_id == pool_config.pool_id_account.unwrap() { - println!( - "tick_array:{}, {}, {}", - tick_array.0, - identity(tick_array_state.start_tick_index), - identity(tick_array_state.initialized_tick_count) - ); - } - } - } - "init_reward" => { - if v.len() == 5 { - let open_time = v[1].parse::().unwrap(); - let end_time = v[2].parse::().unwrap(); - // emissions_per_second is mul 10^^decimals - let emissions_per_second = v[3].parse::().unwrap(); - let reward_token_mint = Pubkey::from_str(&v[4]).unwrap(); - - let emissions_per_second_x64 = - (emissions_per_second * fixed_point_64::Q64 as f64) as u128; - - let program = anchor_client.program(pool_config.raydium_v3_program); - println!("{}", pool_config.pool_id_account.unwrap()); - let pool_account: raydium_amm_v3::states::PoolState = - program.account(pool_config.pool_id_account.unwrap())?; - let operator_account_key = Pubkey::find_program_address( - &[raydium_amm_v3::states::OPERATION_SEED.as_bytes()], - &program.id(), - ) - .0; - - let reward_token_vault = Pubkey::find_program_address( - &[ - raydium_amm_v3::states::POOL_REWARD_VAULT_SEED.as_bytes(), - pool_config.pool_id_account.unwrap().to_bytes().as_ref(), - reward_token_mint.to_bytes().as_ref(), - ], - &program.id(), + let current_or_next_tick_array_key = Pubkey::find_program_address( + &[ + raydium_amm_v3::states::TICK_ARRAY_SEED.as_bytes(), + pool_config.pool_id_account.unwrap().to_bytes().as_ref(), + &tick_array_indexs.pop_front().unwrap().to_be_bytes(), + ], + &pool_config.raydium_v3_program, + ) + .0; + let mut remaining_accounts = Vec::new(); + remaining_accounts.push(AccountMeta::new_readonly( + pool_config.tickarray_bitmap_extension.unwrap(), + false, + )); + let mut accounts = tick_array_indexs + .into_iter() + .map(|index| { + AccountMeta::new( + Pubkey::find_program_address( + &[ + raydium_amm_v3::states::TICK_ARRAY_SEED.as_bytes(), + pool_config.pool_id_account.unwrap().to_bytes().as_ref(), + &index.to_be_bytes(), + ], + &pool_config.raydium_v3_program, + ) + .0, + false, ) - .0; - let user_reward_token = - get_associated_token_address(&admin.pubkey(), &reward_token_mint); - let create_instr = initialize_reward_instr( - &pool_config.clone(), - pool_config.pool_id_account.unwrap(), - pool_account.amm_config, - operator_account_key, - reward_token_mint, - reward_token_vault, - user_reward_token, - open_time, - end_time, - emissions_per_second_x64, - )?; - // send - let signers = vec![&payer, &admin]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &create_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); + }) + .collect(); + remaining_accounts.append(&mut accounts); + let swap_instr = swap_instr( + &pool_config.clone(), + pool_state.amm_config, + pool_config.pool_id_account.unwrap(), + if zero_for_one { + pool_state.token_vault_0 } else { - println!("invalid command: [init_reward open_time, end_time, emissions_per_second_x64, reward_token_mint]"); - } - } - "set_reward_params" => { - if v.len() == 6 { - let index = v[1].parse::().unwrap(); - let open_time = v[2].parse::().unwrap(); - let end_time = v[3].parse::().unwrap(); - // emissions_per_second is mul 10^^decimals - let emissions_per_second = v[4].parse::().unwrap(); - let reward_token_mint = Pubkey::from_str(&v[5]).unwrap(); - let emissions_per_second_x64 = - (emissions_per_second * fixed_point_64::Q64 as f64) as u128; + pool_state.token_vault_1 + }, + if zero_for_one { + pool_state.token_vault_1 + } else { + pool_state.token_vault_0 + }, + pool_state.observation_key, + input_token, + output_token, + current_or_next_tick_array_key, + remaining_accounts, + amount, + other_amount_threshold, + sqrt_price_limit_x64, + base_in, + ) + .unwrap(); + // send + let signers = vec![&payer]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &swap_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } + CommandsName::SwapV2 { + input_token, + output_token, + base_in, + amount, + limit_price, + } => { + // load mult account + let load_accounts = vec![ + input_token, + output_token, + pool_config.amm_config_key, + pool_config.pool_id_account.unwrap(), + pool_config.tickarray_bitmap_extension.unwrap(), + pool_config.mint0.unwrap(), + pool_config.mint1.unwrap(), + ]; + let rsps = rpc_client.get_multiple_accounts(&load_accounts)?; + let epoch = rpc_client.get_epoch_info().unwrap().epoch; + let [user_input_account, user_output_account, amm_config_account, pool_account, tickarray_bitmap_extension_account, mint0_account, mint1_account] = + array_ref![rsps, 0, 7]; - let program = anchor_client.program(pool_config.raydium_v3_program); - println!("{}", pool_config.pool_id_account.unwrap()); - let pool_account: raydium_amm_v3::states::PoolState = - program.account(pool_config.pool_id_account.unwrap())?; - let operator_account_key = Pubkey::find_program_address( - &[raydium_amm_v3::states::OPERATION_SEED.as_bytes()], - &program.id(), - ) - .0; + let mut user_input_token_data = user_input_account.clone().unwrap().data; + let user_input_state = + StateWithExtensionsMut::::unpack(&mut user_input_token_data)?; + let mut user_output_token_data = user_output_account.clone().unwrap().data; + let user_output_state = + StateWithExtensionsMut::::unpack(&mut user_output_token_data)?; + let mut mint0_data = mint0_account.clone().unwrap().data; + let mint0_state = StateWithExtensionsMut::::unpack(&mut mint0_data)?; + let mut mint1_data = mint1_account.clone().unwrap().data; + let mint1_state = StateWithExtensionsMut::::unpack(&mut mint1_data)?; + let amm_config_state = deserialize_anchor_account::( + amm_config_account.as_ref().unwrap(), + )?; + let pool_state = deserialize_anchor_account::( + pool_account.as_ref().unwrap(), + )?; + let tickarray_bitmap_extension = + deserialize_anchor_account::( + tickarray_bitmap_extension_account.as_ref().unwrap(), + )?; + let zero_for_one = user_input_state.base.mint == pool_state.token_mint_0 + && user_output_state.base.mint == pool_state.token_mint_1; - let reward_token_vault = Pubkey::find_program_address( - &[ - raydium_amm_v3::states::POOL_REWARD_VAULT_SEED.as_bytes(), - pool_config.pool_id_account.unwrap().to_bytes().as_ref(), - reward_token_mint.to_bytes().as_ref(), - ], - &program.id(), - ) - .0; - let user_reward_token = - get_associated_token_address(&admin.pubkey(), &reward_token_mint); - let create_instr = set_reward_params_instr( - &pool_config.clone(), - pool_account.amm_config, - pool_config.pool_id_account.unwrap(), - reward_token_vault, - user_reward_token, - operator_account_key, - index, - open_time, - end_time, - emissions_per_second_x64, - )?; - // send - let signers = vec![&payer, &admin]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &create_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); + let transfer_fee = if base_in { + if zero_for_one { + get_transfer_fee(&mint0_state, epoch, amount) } else { - println!("invalid command: [set_reward_params index, open_time, end_time, emissions_per_second_x64, reward_token_mint]"); + get_transfer_fee(&mint1_state, epoch, amount) } + } else { + 0 + }; + let amount_specified = amount.checked_sub(transfer_fee).unwrap(); + // load tick_arrays + let mut tick_arrays = load_cur_and_next_five_tick_array( + &rpc_client, + &pool_config, + &pool_state, + &tickarray_bitmap_extension, + zero_for_one, + ); + + let mut sqrt_price_limit_x64 = None; + if limit_price.is_some() { + let sqrt_price_x64 = price_to_sqrt_price_x64( + limit_price.unwrap(), + pool_state.mint_decimals_0, + pool_state.mint_decimals_1, + ); + sqrt_price_limit_x64 = Some(sqrt_price_x64); } - "ppool" => { - let program = anchor_client.program(pool_config.raydium_v3_program); - let pool_id = if v.len() == 2 { - Pubkey::from_str(&v[1]).unwrap() + + let (mut other_amount_threshold, tick_array_indexs) = + utils::get_out_put_amount_and_remaining_accounts( + amount_specified, + sqrt_price_limit_x64, + zero_for_one, + base_in, + &amm_config_state, + &pool_state, + &tickarray_bitmap_extension, + &mut tick_arrays, + ) + .unwrap(); + if base_in { + // calc mint out amount with slippage + other_amount_threshold = + amount_with_slippage(other_amount_threshold, pool_config.slippage, false); + } else { + // calc max in with slippage + other_amount_threshold = + amount_with_slippage(other_amount_threshold, pool_config.slippage, true); + // calc max in with transfer_fee + let transfer_fee = if zero_for_one { + get_transfer_inverse_fee(&mint0_state, epoch, other_amount_threshold) } else { - pool_config.pool_id_account.unwrap() + get_transfer_inverse_fee(&mint1_state, epoch, other_amount_threshold) }; - println!("{}", pool_id); - let pool_account: raydium_amm_v3::states::PoolState = program.account(pool_id)?; - println!("{:#?}", pool_account); - } - "pprotocol" => { - if v.len() == 2 { - let protocol_key = Pubkey::from_str(&v[1]).unwrap(); - let program = anchor_client.program(pool_config.raydium_v3_program); - let protocol_account: raydium_amm_v3::states::ProtocolPositionState = - program.account(protocol_key)?; - println!("{:#?}", protocol_account); - } - } - "ppersonal" => { - if v.len() == 2 { - let personal_key = Pubkey::from_str(&v[1]).unwrap(); - let program = anchor_client.program(pool_config.raydium_v3_program); - let personal_account: raydium_amm_v3::states::PersonalPositionState = - program.account(personal_key)?; - println!("{:#?}", personal_account); - } + other_amount_threshold += transfer_fee; } - "open_position" | "open" => { - if v.len() == 5 { - let tick_lower_price = v[1].parse::().unwrap(); - let tick_upper_price = v[2].parse::().unwrap(); - let is_base_0 = v[3].parse::().unwrap(); - let imput_amount = v[4].parse::().unwrap(); - - // load pool to get observation - let program = anchor_client.program(pool_config.raydium_v3_program); - let pool: raydium_amm_v3::states::PoolState = - program.account(pool_config.pool_id_account.unwrap())?; - let tick_lower_price_x64 = price_to_sqrt_price_x64( - tick_lower_price, - pool.mint_decimals_0, - pool.mint_decimals_1, - ); - let tick_upper_price_x64 = price_to_sqrt_price_x64( - tick_upper_price, - pool.mint_decimals_0, - pool.mint_decimals_1, - ); - let tick_lower_index = tick_with_spacing( - tick_math::get_tick_at_sqrt_price(tick_lower_price_x64)?, - pool.tick_spacing.into(), - ); - let tick_upper_index = tick_with_spacing( - tick_math::get_tick_at_sqrt_price(tick_upper_price_x64)?, - pool.tick_spacing.into(), - ); - println!( - "tick_lower_index:{}, tick_upper_index:{}", - tick_lower_index, tick_upper_index - ); - let tick_lower_price_x64 = tick_math::get_sqrt_price_at_tick(tick_lower_index)?; - let tick_upper_price_x64 = tick_math::get_sqrt_price_at_tick(tick_upper_index)?; - let liquidity = if is_base_0 { - liquidity_math::get_liquidity_from_single_amount_0( - pool.sqrt_price_x64, - tick_lower_price_x64, - tick_upper_price_x64, - imput_amount, - ) - } else { - liquidity_math::get_liquidity_from_single_amount_1( - pool.sqrt_price_x64, - tick_lower_price_x64, - tick_upper_price_x64, - imput_amount, + let mut remaining_accounts = Vec::new(); + remaining_accounts.push(AccountMeta::new_readonly( + pool_config.tickarray_bitmap_extension.unwrap(), + false, + )); + let mut accounts = tick_array_indexs + .into_iter() + .map(|index| { + AccountMeta::new( + Pubkey::find_program_address( + &[ + raydium_amm_v3::states::TICK_ARRAY_SEED.as_bytes(), + pool_config.pool_id_account.unwrap().to_bytes().as_ref(), + &index.to_be_bytes(), + ], + &pool_config.raydium_v3_program, ) - }; - let (amount_0, amount_1) = liquidity_math::get_delta_amounts_signed( - pool.tick_current, - pool.sqrt_price_x64, - tick_lower_index, - tick_upper_index, - liquidity as i128, - )?; - println!( - "amount_0:{}, amount_1:{}, liquidity:{}", - amount_0, amount_1, liquidity - ); - let amount_0_max = amount_0 as u64; - let amount_1_max = amount_1 as u64; - - let tick_array_lower_start_index = - raydium_amm_v3::states::TickArrayState::get_array_start_index( - tick_lower_index, - pool.tick_spacing.into(), - ); - let tick_array_upper_start_index = - raydium_amm_v3::states::TickArrayState::get_array_start_index( - tick_upper_index, - pool.tick_spacing.into(), - ); - // load position - let (_nft_tokens, positions) = get_nft_account_and_position_by_owner( - &rpc_client, - &payer.pubkey(), - &pool_config.raydium_v3_program, - ); - let rsps = rpc_client.get_multiple_accounts(&positions)?; - let mut user_positions = Vec::new(); - for rsp in rsps { - match rsp { - None => continue, - Some(rsp) => { - let position = deserialize_anchor_account::< - raydium_amm_v3::states::PersonalPositionState, - >(&rsp)?; - user_positions.push(position); - } - } - } - let mut find_position = - raydium_amm_v3::states::PersonalPositionState::default(); - for position in user_positions { - if position.pool_id == pool_config.pool_id_account.unwrap() - && position.tick_lower_index == tick_lower_index - && position.tick_upper_index == tick_upper_index - { - find_position = position.clone(); - } - } - if find_position.nft_mint == Pubkey::default() { - // personal position not exist - // new nft mint - let nft_mint = Keypair::generate(&mut OsRng); - let mut instructions = Vec::new(); - let request_inits_instr = - ComputeBudgetInstruction::set_compute_unit_limit(1400_000u32); - instructions.push(request_inits_instr); - let open_position_instr = open_position_instr( - &pool_config.clone(), - pool_config.pool_id_account.unwrap(), - pool.token_vault_0, - pool.token_vault_1, - nft_mint.pubkey(), - payer.pubkey(), - spl_associated_token_account::get_associated_token_address( - &payer.pubkey(), - &pool_config.mint0.unwrap(), - ), - spl_associated_token_account::get_associated_token_address( - &payer.pubkey(), - &pool_config.mint1.unwrap(), - ), - liquidity, - amount_0_max, - amount_1_max, - tick_lower_index, - tick_upper_index, - tick_array_lower_start_index, - tick_array_upper_start_index, - )?; - instructions.extend(open_position_instr); - // send - let signers = vec![&payer, &nft_mint]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &instructions, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); - } else { - // personal position exist - println!("personal position exist:{:?}", find_position); - } + .0, + false, + ) + }) + .collect(); + remaining_accounts.append(&mut accounts); + let swap_instr = swap_v2_instr( + &pool_config.clone(), + pool_state.amm_config, + pool_config.pool_id_account.unwrap(), + if zero_for_one { + pool_state.token_vault_0 } else { - println!("invalid command: [open_position tick_lower_price tick_upper_price is_base_0 imput_amount]"); - } - } - "pall_position_by_owner" => { - if v.len() == 2 { - let user_wallet = Pubkey::from_str(&v[1]).unwrap(); - let program = anchor_client.program(pool_config.raydium_v3_program); - // load position - let (_nft_tokens, positions) = get_nft_account_and_position_by_owner( - &rpc_client, - &user_wallet, - &pool_config.raydium_v3_program, - ); - let rsps = rpc_client.get_multiple_accounts(&positions)?; - let mut user_positions = Vec::new(); - for rsp in rsps { - match rsp { - None => continue, - Some(rsp) => { - let position = deserialize_anchor_account::< - raydium_amm_v3::states::PersonalPositionState, - >(&rsp)?; - let (personal_position_key, __bump) = Pubkey::find_program_address( - &[ - raydium_amm_v3::states::POSITION_SEED.as_bytes(), - position.nft_mint.to_bytes().as_ref(), - ], - &program.id(), - ); - println!("id:{}, lower:{}, upper:{}, liquidity:{}, fees_owed_0:{}, fees_owed_1:{}, fee_growth_inside_0:{}, fee_growth_inside_1:{}", personal_position_key, position.tick_lower_index, position.tick_upper_index, position.liquidity, position.token_fees_owed_0, position.token_fees_owed_1, position.fee_growth_inside_0_last_x64, position.fee_growth_inside_1_last_x64); - user_positions.push(position); - } - } - } - } - } - "increase_liquidity" => { - if v.len() == 5 { - let tick_lower_price = v[1].parse::().unwrap(); - let tick_upper_price = v[2].parse::().unwrap(); - let is_base_0 = v[3].parse::().unwrap(); - let imput_amount = v[4].parse::().unwrap(); - - // load pool to get observation - let program = anchor_client.program(pool_config.raydium_v3_program); - let pool: raydium_amm_v3::states::PoolState = - program.account(pool_config.pool_id_account.unwrap())?; - - // load position - let (_nft_tokens, positions) = get_nft_account_and_position_by_owner( - &rpc_client, - &payer.pubkey(), - &pool_config.raydium_v3_program, - ); - let rsps = rpc_client.get_multiple_accounts(&positions)?; - let mut user_positions = Vec::new(); - for rsp in rsps { - match rsp { - None => continue, - Some(rsp) => { - let position = deserialize_anchor_account::< - raydium_amm_v3::states::PersonalPositionState, - >(&rsp)?; - user_positions.push(position); - } - } - } - - let tick_lower_price_x64 = price_to_sqrt_price_x64( - tick_lower_price, - pool.mint_decimals_0, - pool.mint_decimals_1, - ); - let tick_upper_price_x64 = price_to_sqrt_price_x64( - tick_upper_price, - pool.mint_decimals_0, - pool.mint_decimals_1, - ); - let tick_lower_index = tick_with_spacing( - tick_math::get_tick_at_sqrt_price(tick_lower_price_x64)?, - pool.tick_spacing.into(), - ); - let tick_upper_index = tick_with_spacing( - tick_math::get_tick_at_sqrt_price(tick_upper_price_x64)?, - pool.tick_spacing.into(), - ); - println!( - "tick_lower_index:{}, tick_upper_index:{}", - tick_lower_index, tick_upper_index - ); - let tick_lower_price_x64 = tick_math::get_sqrt_price_at_tick(tick_lower_index)?; - let tick_upper_price_x64 = tick_math::get_sqrt_price_at_tick(tick_upper_index)?; - let liquidity = if is_base_0 { - liquidity_math::get_liquidity_from_single_amount_0( - pool.sqrt_price_x64, - tick_lower_price_x64, - tick_upper_price_x64, - imput_amount, - ) - } else { - liquidity_math::get_liquidity_from_single_amount_1( - pool.sqrt_price_x64, - tick_lower_price_x64, - tick_upper_price_x64, - imput_amount, - ) - }; - let (amount_0, amount_1) = liquidity_math::get_delta_amounts_signed( - pool.tick_current, - pool.sqrt_price_x64, - tick_lower_index, - tick_upper_index, - liquidity as i128, - )?; - println!( - "amount_0:{}, amount_1:{}, liquidity:{}", - amount_0, amount_1, liquidity - ); - let amount_0_max = amount_0 as u64; - let amount_1_max = amount_1 as u64; - - let tick_array_lower_start_index = - raydium_amm_v3::states::TickArrayState::get_array_start_index( - tick_lower_index, - pool.tick_spacing.into(), - ); - let tick_array_upper_start_index = - raydium_amm_v3::states::TickArrayState::get_array_start_index( - tick_upper_index, - pool.tick_spacing.into(), - ); - let mut find_position = - raydium_amm_v3::states::PersonalPositionState::default(); - for position in user_positions { - if position.pool_id == pool_config.pool_id_account.unwrap() - && position.tick_lower_index == tick_lower_index - && position.tick_upper_index == tick_upper_index - { - find_position = position.clone(); - } - } - if find_position.nft_mint != Pubkey::default() - && find_position.pool_id == pool_config.pool_id_account.unwrap() - { - // personal position exist - let increase_instr = increase_liquidity_instr( - &pool_config.clone(), - pool_config.pool_id_account.unwrap(), - pool.token_vault_0, - pool.token_vault_1, - find_position.nft_mint, - spl_associated_token_account::get_associated_token_address( - &payer.pubkey(), - &pool_config.mint0.unwrap(), - ), - spl_associated_token_account::get_associated_token_address( - &payer.pubkey(), - &pool_config.mint1.unwrap(), - ), - liquidity, - amount_0_max, - amount_1_max, - tick_lower_index, - tick_upper_index, - tick_array_lower_start_index, - tick_array_upper_start_index, - )?; - // send - let signers = vec![&payer]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &increase_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); - } else { - // personal position not exist - println!("personal position exist:{:?}", find_position); - } + pool_state.token_vault_1 + }, + if zero_for_one { + pool_state.token_vault_1 } else { - println!("invalid command: [increase_liquidity tick_lower_price tick_upper_price is_base_0 imput_amount]"); - } - } - "decrease_liquidity" => { - if v.len() == 7 { - let tick_lower_index = v[1].parse::().unwrap(); - let tick_upper_index = v[2].parse::().unwrap(); - let liquidity = v[3].parse::().unwrap(); - let amount_0_min = v[4].parse::().unwrap(); - let amount_1_min = v[5].parse::().unwrap(); - let simulate = v[6].parse::().unwrap(); - - // load pool to get observation - let program = anchor_client.program(pool_config.raydium_v3_program); - let pool: raydium_amm_v3::states::PoolState = - program.account(pool_config.pool_id_account.unwrap())?; - - let tick_array_lower_start_index = - raydium_amm_v3::states::TickArrayState::get_array_start_index( - tick_lower_index, - pool.tick_spacing.into(), - ); - let tick_array_upper_start_index = - raydium_amm_v3::states::TickArrayState::get_array_start_index( - tick_upper_index, - pool.tick_spacing.into(), - ); - // load position - let (_nft_tokens, positions) = get_nft_account_and_position_by_owner( - &rpc_client, - &payer.pubkey(), - &pool_config.raydium_v3_program, - ); - let rsps = rpc_client.get_multiple_accounts(&positions)?; - let mut user_positions = Vec::new(); - for rsp in rsps { - match rsp { - None => continue, - Some(rsp) => { - let position = deserialize_anchor_account::< - raydium_amm_v3::states::PersonalPositionState, - >(&rsp)?; - user_positions.push(position); - } - } - } - let mut find_position = - raydium_amm_v3::states::PersonalPositionState::default(); - for position in user_positions { - if position.pool_id == pool_config.pool_id_account.unwrap() - && position.tick_lower_index == tick_lower_index - && position.tick_upper_index == tick_upper_index - { - find_position = position.clone(); - println!("liquidity:{:?}", find_position); - } - } - if find_position.nft_mint != Pubkey::default() - && find_position.pool_id == pool_config.pool_id_account.unwrap() - { - let mut reward_vault_with_user_vault: Vec<(Pubkey, Pubkey)> = Vec::new(); - for item in pool.reward_infos.into_iter() { - if item.token_mint != Pubkey::default() { - reward_vault_with_user_vault.push(( - item.token_vault, - get_associated_token_address(&payer.pubkey(), &item.token_mint), - )); - } - } - let remaining_accounts = reward_vault_with_user_vault - .into_iter() - .map(|item| AccountMeta::new(item.0, false)) - .collect(); - // personal position exist - let mut decrease_instr = decrease_liquidity_instr( - &pool_config.clone(), - pool_config.pool_id_account.unwrap(), - pool.token_vault_0, - pool.token_vault_1, - find_position.nft_mint, - spl_associated_token_account::get_associated_token_address( - &payer.pubkey(), - &pool_config.mint0.unwrap(), - ), - spl_associated_token_account::get_associated_token_address( - &payer.pubkey(), - &pool_config.mint1.unwrap(), - ), - remaining_accounts, - liquidity, - amount_0_min, - amount_1_min, - tick_lower_index, - tick_upper_index, - tick_array_lower_start_index, - tick_array_upper_start_index, - )?; - if liquidity == find_position.liquidity { - let close_position_instr = close_personal_position_instr( - &pool_config.clone(), - find_position.nft_mint, - )?; - decrease_instr.extend(close_position_instr); - } - // send - let signers = vec![&payer]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &decrease_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, + pool_state.token_vault_0 + }, + pool_state.observation_key, + input_token, + output_token, + if zero_for_one { + pool_state.token_mint_0 + } else { + pool_state.token_mint_1 + }, + if zero_for_one { + pool_state.token_mint_0 + } else { + pool_state.token_mint_1 + }, + remaining_accounts, + amount, + other_amount_threshold, + sqrt_price_limit_x64, + base_in, + ) + .unwrap(); + // send + let signers = vec![&payer]; + let recent_hash = rpc_client.get_latest_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &swap_instr, + Some(&payer.pubkey()), + &signers, + recent_hash, + ); + let signature = send_txn(&rpc_client, &txn, true)?; + println!("{}", signature); + } + CommandsName::PPositionByOwner { user_wallet } => { + // load position + let (_nft_tokens, positions) = get_nft_account_and_position_by_owner( + &rpc_client, + &user_wallet, + &pool_config.raydium_v3_program, + ); + let rsps = rpc_client.get_multiple_accounts(&positions)?; + let mut user_positions = Vec::new(); + for rsp in rsps { + match rsp { + None => continue, + Some(rsp) => { + let position = deserialize_anchor_account::< + raydium_amm_v3::states::PersonalPositionState, + >(&rsp)?; + let (personal_position_key, __bump) = Pubkey::find_program_address( + &[ + raydium_amm_v3::states::POSITION_SEED.as_bytes(), + position.nft_mint.to_bytes().as_ref(), + ], + &program.id(), ); - if simulate { - let ret = simulate_transaction( - &rpc_client, - &txn, - true, - CommitmentConfig::confirmed(), - )?; - println!("{:#?}", ret); - } else { - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); - } - } else { - // personal position not exist - println!("personal position exist:{:?}", find_position); + println!("id:{}, lower:{}, upper:{}, liquidity:{}, fees_owed_0:{}, fees_owed_1:{}, fee_growth_inside_0:{}, fee_growth_inside_1:{}", personal_position_key, position.tick_lower_index, position.tick_upper_index, position.liquidity, position.token_fees_owed_0, position.token_fees_owed_1, position.fee_growth_inside_0_last_x64, position.fee_growth_inside_1_last_x64); + user_positions.push(position); } - } else { - println!("invalid command: [decrease_liquidity tick_lower_index tick_upper_index liquidity amount_0_min amount_1_min, simulate]"); } } - "ptick_state" => { - if v.len() == 2 { - let tick = v[1].parse::().unwrap(); - // load pool to get tick_spacing - let program = anchor_client.program(pool_config.raydium_v3_program); - let pool: raydium_amm_v3::states::PoolState = - program.account(pool_config.pool_id_account.unwrap())?; + } + CommandsName::PTickState { tick, pool_id } => { + let pool_id = if let Some(pool_id) = pool_id { + pool_id + } else { + pool_config.pool_id_account.unwrap() + }; + println!("pool_id:{}", pool_id); + let pool: raydium_amm_v3::states::PoolState = program.account(pool_id)?; - let tick_array_start_index = - raydium_amm_v3::states::TickArrayState::get_array_start_index( - tick, - pool.tick_spacing.into(), - ); - let program = anchor_client.program(pool_config.raydium_v3_program); - let (tick_array_key, __bump) = Pubkey::find_program_address( - &[ - raydium_amm_v3::states::TICK_ARRAY_SEED.as_bytes(), - pool_config.pool_id_account.unwrap().to_bytes().as_ref(), - &tick_array_start_index.to_be_bytes(), - ], - &program.id(), - ); - let mut tick_array_account: raydium_amm_v3::states::TickArrayState = - program.account(tick_array_key)?; - let tick_state = tick_array_account - .get_tick_state_mut(tick, pool.tick_spacing.into()) - .unwrap(); - println!("{:?}", tick_state); - } + let tick_array_start_index = + raydium_amm_v3::states::TickArrayState::get_array_start_index( + tick, + pool.tick_spacing.into(), + ); + let program = anchor_client.program(pool_config.raydium_v3_program)?; + let (tick_array_key, __bump) = Pubkey::find_program_address( + &[ + raydium_amm_v3::states::TICK_ARRAY_SEED.as_bytes(), + pool_id.to_bytes().as_ref(), + &tick_array_start_index.to_be_bytes(), + ], + &program.id(), + ); + let mut tick_array_account: raydium_amm_v3::states::TickArrayState = + program.account(tick_array_key)?; + let tick_state = tick_array_account + .get_tick_state_mut(tick, pool.tick_spacing.into()) + .unwrap(); + println!("{:?}", tick_state); + } + CommandsName::CompareKey { key0, key1 } => { + let mut token_mint_0 = key0; + let mut token_mint_1 = key1; + if token_mint_0 > token_mint_1 { + std::mem::swap(&mut token_mint_0, &mut token_mint_1); } - "swap_base_in" => { - if v.len() == 4 || v.len() == 5 { - let user_input_token = Pubkey::from_str(&v[1]).unwrap(); - let user_output_token = Pubkey::from_str(&v[2]).unwrap(); - let amount_in = v[3].parse::().unwrap(); - let mut limit_price = None; - if v.len() == 5 { - limit_price = Some(v[4].parse::().unwrap()); - } - let is_base_input = true; - - // load mult account - let load_accounts = vec![ - user_input_token, - user_output_token, - pool_config.amm_config_key, - pool_config.pool_id_account.unwrap(), - ]; - let rsps = rpc_client.get_multiple_accounts(&load_accounts)?; - let [user_input_account, user_output_account, amm_config_account, pool_account] = - array_ref![rsps, 0, 4]; - let user_input_state = spl_token::state::Account::unpack( - &user_input_account.as_ref().unwrap().data, - ) - .unwrap(); - let user_output_state = spl_token::state::Account::unpack( - &user_output_account.as_ref().unwrap().data, - ) - .unwrap(); - let amm_config_state = - deserialize_anchor_account::( - amm_config_account.as_ref().unwrap(), - )?; - let pool_state = deserialize_anchor_account::( - pool_account.as_ref().unwrap(), - )?; - let zero_for_one = user_input_state.mint == pool_state.token_mint_0 - && user_output_state.mint == pool_state.token_mint_1; - // load tick_arrays - let mut tick_arrays = load_cur_and_next_five_tick_array( - &rpc_client, - &pool_config, - &pool_state, - zero_for_one, - ); - - let mut sqrt_price_limit_x64 = None; - if limit_price.is_some() { - let sqrt_price_x64 = price_to_sqrt_price_x64( - limit_price.unwrap(), - pool_state.mint_decimals_0, - pool_state.mint_decimals_1, - ); - sqrt_price_limit_x64 = Some(sqrt_price_x64); - } - - let (other_amount_threshold, mut tick_array_indexs) = - utils::get_out_put_amount_and_remaining_accounts( - amount_in, - sqrt_price_limit_x64, - zero_for_one, - is_base_input, - &amm_config_state, - &pool_state, - &mut tick_arrays, - ) - .unwrap(); + println!("mint0:{}, mint1:{}", token_mint_0, token_mint_1); + } + CommandsName::PMint { mint } => { + let mint_data = &mut rpc_client.get_account_data(&mint)?; + let mint_state = StateWithExtensionsMut::::unpack(mint_data)?; + println!("mint_state:{:?}", mint_state); + let extensions = get_account_extensions(&mint_state); + println!("mint_extensions:{:#?}", extensions); + } + CommandsName::PToken { token } => { + let token_data = &mut rpc_client.get_account_data(&token)?; + let token_state = StateWithExtensionsMut::::unpack(token_data)?; + println!("token_state:{:?}", token_state); + let extensions = get_account_extensions(&token_state); + println!("token_extensions:{:#?}", extensions); + } + CommandsName::POperation => { + let (operation_account_key, __bump) = Pubkey::find_program_address( + &[raydium_amm_v3::states::OPERATION_SEED.as_bytes()], + &program.id(), + ); + println!("{}", operation_account_key); + let operation_account: raydium_amm_v3::states::OperationState = + program.account(operation_account_key)?; + println!("{:#?}", operation_account); + } + CommandsName::PConfig { config_index } => { + let (amm_config_key, __bump) = Pubkey::find_program_address( + &[ + raydium_amm_v3::states::AMM_CONFIG_SEED.as_bytes(), + &config_index.to_be_bytes(), + ], + &program.id(), + ); + println!("{}", amm_config_key); + let amm_config_account: raydium_amm_v3::states::AmmConfig = + program.account(amm_config_key)?; + println!("{:#?}", amm_config_account); + } + CommandsName::PriceToTick { price } => { + println!("price:{}, tick:{}", price, price_to_tick(price)); + } + CommandsName::TickToPrice { tick } => { + println!("tick:{}, price:{}", tick, tick_to_price(tick)); + } + CommandsName::TickWithSpacing { tick, tick_spacing } => { + println!( + "tick:{}, tick_spacing:{}, tick_with_spacing:{}", + tick, + tick_spacing, + tick_with_spacing(tick, tick_spacing as i32) + ); + } + CommandsName::TickArraryStartIndex { tick, tick_spacing } => { + println!( + "tick:{}, tick_spacing:{},tick_array_start_index:{}", + tick, + tick_spacing, + raydium_amm_v3::states::TickArrayState::get_array_start_index(tick, tick_spacing,) + ); + } + CommandsName::LiquidityToAmounts { + tick_lower, + tick_upper, + liquidity, + } => { + let pool_account: raydium_amm_v3::states::PoolState = + program.account(pool_config.pool_id_account.unwrap())?; + let amounts = raydium_amm_v3::libraries::get_delta_amounts_signed( + pool_account.tick_current, + pool_account.sqrt_price_x64, + tick_lower, + tick_upper, + liquidity, + )?; + println!("amount_0:{}, amount_1:{}", amounts.0, amounts.1); + } + CommandsName::PPersonalPositionByPool { pool_id } => { + let pool_id = if let Some(pool_id) = pool_id { + pool_id + } else { + pool_config.pool_id_account.unwrap() + }; + println!("pool_id:{}", pool_id); + let position_accounts_by_pool = rpc_client.get_program_accounts_with_config( + &pool_config.raydium_v3_program, + RpcProgramAccountsConfig { + filters: Some(vec![ + RpcFilterType::Memcmp(Memcmp::new_base58_encoded( + 8 + 1 + size_of::(), + &pool_id.to_bytes(), + )), + RpcFilterType::DataSize( + raydium_amm_v3::states::PersonalPositionState::LEN as u64, + ), + ]), + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64), + ..RpcAccountInfoConfig::default() + }, + with_context: Some(false), + }, + )?; - let current_or_next_tick_array_key = Pubkey::find_program_address( - &[ - raydium_amm_v3::states::TICK_ARRAY_SEED.as_bytes(), - pool_config.pool_id_account.unwrap().to_bytes().as_ref(), - &tick_array_indexs.pop_front().unwrap().to_be_bytes(), - ], - &pool_config.raydium_v3_program, - ) - .0; - let remaining_accounts = tick_array_indexs - .into_iter() - .map(|index| { - AccountMeta::new( - Pubkey::find_program_address( - &[ - raydium_amm_v3::states::TICK_ARRAY_SEED.as_bytes(), - pool_config.pool_id_account.unwrap().to_bytes().as_ref(), - &index.to_be_bytes(), - ], - &pool_config.raydium_v3_program, - ) - .0, - false, - ) - }) - .collect(); - let swap_instr = swap_instr( - &pool_config.clone(), - pool_state.amm_config, - pool_config.pool_id_account.unwrap(), - if zero_for_one { - pool_state.token_vault_0 - } else { - pool_state.token_vault_1 - }, - if zero_for_one { - pool_state.token_vault_1 - } else { - pool_state.token_vault_0 - }, - pool_state.observation_key, - user_input_token, - user_output_token, - current_or_next_tick_array_key, - remaining_accounts, - amount_in, - other_amount_threshold, - sqrt_price_limit_x64, - is_base_input, - ) - .unwrap(); - // send - let signers = vec![&payer]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &swap_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, + let mut total_fees_owed_0 = 0; + let mut total_fees_owed_1 = 0; + let mut total_reward_owed = 0; + for position in position_accounts_by_pool { + let personal_position = deserialize_anchor_account::< + raydium_amm_v3::states::PersonalPositionState, + >(&position.1)?; + if personal_position.pool_id == pool_id { + println!( + "personal_position:{}, lower:{}, upper:{}, liquidity:{}, token_fees_owed_0:{}, token_fees_owed_1:{}, reward_amount_owed:{}, fee_growth_inside:{}, fee_growth_inside_1:{}, reward_inside:{}", + position.0, + personal_position.tick_lower_index, + personal_position.tick_upper_index, + personal_position.liquidity, + personal_position.token_fees_owed_0, + personal_position.token_fees_owed_1, + personal_position.reward_infos[0].reward_amount_owed, + personal_position.fee_growth_inside_0_last_x64, + personal_position.fee_growth_inside_1_last_x64, + personal_position.reward_infos[0].growth_inside_last_x64, ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); + total_fees_owed_0 += personal_position.token_fees_owed_0; + total_fees_owed_1 += personal_position.token_fees_owed_1; + total_reward_owed += personal_position.reward_infos[0].reward_amount_owed; } } - "swap_base_out" => { - if v.len() == 4 || v.len() == 5 { - let user_input_token = Pubkey::from_str(&v[1]).unwrap(); - let user_output_token = Pubkey::from_str(&v[2]).unwrap(); - let amount_in = v[3].parse::().unwrap(); - let mut limit_price = None; - if v.len() == 5 { - limit_price = Some(v[4].parse::().unwrap()); - } - let is_base_input = false; - - // load mult account - let load_accounts = vec![ - user_input_token, - user_output_token, - pool_config.amm_config_key, - pool_config.pool_id_account.unwrap(), - ]; - let rsps = rpc_client.get_multiple_accounts(&load_accounts)?; - let [user_input_account, user_output_account, amm_config_account, pool_account] = - array_ref![rsps, 0, 4]; - let user_input_state = spl_token::state::Account::unpack( - &user_input_account.as_ref().unwrap().data, - ) - .unwrap(); - let user_output_state = spl_token::state::Account::unpack( - &user_output_account.as_ref().unwrap().data, - ) - .unwrap(); - let amm_config_state = - deserialize_anchor_account::( - amm_config_account.as_ref().unwrap(), - )?; - let pool_state = deserialize_anchor_account::( - pool_account.as_ref().unwrap(), - )?; - let zero_for_one = user_input_state.mint == pool_state.token_mint_0 - && user_output_state.mint == pool_state.token_mint_1; - // load tick_arrays - let mut tick_arrays = load_cur_and_next_five_tick_array( - &rpc_client, - &pool_config, - &pool_state, - zero_for_one, - ); - - let mut sqrt_price_limit_x64 = None; - if limit_price.is_some() { - let sqrt_price_x64 = price_to_sqrt_price_x64( - limit_price.unwrap(), - pool_state.mint_decimals_0, - pool_state.mint_decimals_1, - ); - sqrt_price_limit_x64 = Some(sqrt_price_x64); - } - - let (other_amount_threshold, mut tick_array_indexs) = - utils::get_out_put_amount_and_remaining_accounts( - amount_in, - sqrt_price_limit_x64, - zero_for_one, - is_base_input, - &amm_config_state, - &pool_state, - &mut tick_arrays, - ) - .unwrap(); + println!( + "total_fees_owed_0:{}, total_fees_owed_1:{}, total_reward_owed:{}", + total_fees_owed_0, total_fees_owed_1, total_reward_owed + ); + } + CommandsName::PProtocolPositionByPool { pool_id } => { + let pool_id = if let Some(pool_id) = pool_id { + pool_id + } else { + pool_config.pool_id_account.unwrap() + }; + println!("pool_id:{}", pool_id); + let position_accounts_by_pool = rpc_client.get_program_accounts_with_config( + &pool_config.raydium_v3_program, + RpcProgramAccountsConfig { + filters: Some(vec![ + RpcFilterType::Memcmp(Memcmp::new_base58_encoded( + 8 + 1, + &pool_id.to_bytes(), + )), + RpcFilterType::DataSize( + raydium_amm_v3::states::ProtocolPositionState::LEN as u64, + ), + ]), + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64Zstd), + ..RpcAccountInfoConfig::default() + }, + with_context: Some(false), + }, + )?; - let current_or_next_tick_array_key = Pubkey::find_program_address( - &[ - raydium_amm_v3::states::TICK_ARRAY_SEED.as_bytes(), - pool_config.pool_id_account.unwrap().to_bytes().as_ref(), - &tick_array_indexs.pop_front().unwrap().to_be_bytes(), - ], - &pool_config.raydium_v3_program, - ) - .0; - let remaining_accounts = tick_array_indexs - .into_iter() - .map(|index| { - AccountMeta::new( - Pubkey::find_program_address( - &[ - raydium_amm_v3::states::TICK_ARRAY_SEED.as_bytes(), - pool_config.pool_id_account.unwrap().to_bytes().as_ref(), - &index.to_be_bytes(), - ], - &pool_config.raydium_v3_program, - ) - .0, - false, - ) - }) - .collect(); - let swap_instr = swap_instr( - &pool_config.clone(), - pool_state.amm_config, - pool_config.pool_id_account.unwrap(), - if zero_for_one { - pool_state.token_vault_0 - } else { - pool_state.token_vault_1 - }, - if zero_for_one { - pool_state.token_vault_1 - } else { - pool_state.token_vault_0 - }, - pool_state.observation_key, - user_input_token, - user_output_token, - current_or_next_tick_array_key, - remaining_accounts, - amount_in, - other_amount_threshold, - sqrt_price_limit_x64, - is_base_input, - ) - .unwrap(); - // send - let signers = vec![&payer]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &swap_instr, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); - } - } - "tick_to_x64" => { - if v.len() == 2 { - let tick = v[1].parse::().unwrap(); - let sqrt_price_x64 = tick_math::get_sqrt_price_at_tick(tick)?; - let sqrt_price_f = (sqrt_price_x64 >> fixed_point_64::RESOLUTION) as f64 - + (sqrt_price_x64 % fixed_point_64::Q64) as f64 - / fixed_point_64::Q64 as f64; - println!("{}-{}", sqrt_price_x64, sqrt_price_f * sqrt_price_f); - } - } - "sqrt_price_x64_to_tick" => { - if v.len() == 2 { - let sqrt_price_x64 = v[1].parse::().unwrap(); - let tick = tick_math::get_tick_at_sqrt_price(sqrt_price_x64)?; - println!("sqrt_price_x64:{}, tick:{}", sqrt_price_x64, tick); - } - } - "x64_to_f" => { - if v.len() == 2 { - let x_64 = v[1].parse::().unwrap(); - let f = (x_64 >> fixed_point_64::RESOLUTION) as f64 - + (x_64 % fixed_point_64::Q64) as f64 / fixed_point_64::Q64 as f64; - println!("float:{}", f); - } - } - "sqrt_price_x64_to_tick_by_self" => { - if v.len() == 2 { - let sqrt_price_x64 = v[1].parse::().unwrap(); - let sqrt_price_f = (sqrt_price_x64 >> fixed_point_64::RESOLUTION) as f64 - + (sqrt_price_x64 % fixed_point_64::Q64) as f64 - / fixed_point_64::Q64 as f64; - let tick = (sqrt_price_f * sqrt_price_f).log(Q_RATIO) as i32; + for position in position_accounts_by_pool { + let protocol_position = deserialize_anchor_account::< + raydium_amm_v3::states::ProtocolPositionState, + >(&position.1)?; + if protocol_position.pool_id == pool_id { println!( - "tick:{}, sqrt_price_f:{}, price:{}", - tick, - sqrt_price_f, - sqrt_price_f * sqrt_price_f + "protocol_position:{} lower_index:{}, upper_index:{}", + position.0, + protocol_position.tick_lower_index, + protocol_position.tick_upper_index, ); } } - "f_price_to_tick" => { - if v.len() == 5 { - let price = v[1].parse::().unwrap(); - let mint_decimals_0 = v[2].parse::().unwrap(); - let mint_decimals_1 = v[3].parse::().unwrap(); - let tick_spacing = v[4].parse::().unwrap(); - let tick_price_x64 = - price_to_sqrt_price_x64(price, mint_decimals_0, mint_decimals_1); - let tick_index = tick_with_spacing( - tick_math::get_tick_at_sqrt_price(tick_price_x64)?, - tick_spacing.into(), - ); - println!("tick_index:{}", tick_index); - } else { - println!("f_price_to_tick price mint_decimals_0 mint_decimals_1 tick_spacing") - } - } - "tick_test" => { - if v.len() == 2 { - let min = v[1].parse::().unwrap(); - let price = (2.0 as f64).powi(min); - let tick = price.log(Q_RATIO) as i32; - println!("tick:{}, price:{}", tick, price); - - let price = (2.0 as f64).powi(min / 2); - let price_x64 = price * fixed_point_64::Q64 as f64; - println!("price_x64:{}", price_x64); - } - } - "decode_instruction" => { - if v.len() == 2 { - let instr_data = v[1]; - let data = hex::decode(instr_data)?; - let mut ix_data: &[u8] = &data; - let mut sighash: [u8; 8] = [0; 8]; - sighash.copy_from_slice(&ix_data[..8]); - ix_data = ix_data.get(8..).unwrap(); + } + CommandsName::PTickArrayByPool { pool_id } => { + let pool_id = if let Some(pool_id) = pool_id { + pool_id + } else { + pool_config.pool_id_account.unwrap() + }; + println!("pool_id:{}", pool_id); + let tick_arrays_by_pool = rpc_client.get_program_accounts_with_config( + &pool_config.raydium_v3_program, + RpcProgramAccountsConfig { + filters: Some(vec![ + RpcFilterType::Memcmp(Memcmp::new_base58_encoded(8, &pool_id.to_bytes())), + RpcFilterType::DataSize(raydium_amm_v3::states::TickArrayState::LEN as u64), + ]), + account_config: RpcAccountInfoConfig { + encoding: Some(UiAccountEncoding::Base64Zstd), + ..RpcAccountInfoConfig::default() + }, + with_context: Some(false), + }, + )?; - match sighash { - [135, 128, 47, 77, 15, 152, 240, 49] => { - let ix = raydium_amm_v3::instruction::OpenPosition::deserialize( - &mut &ix_data[..], - ) - .map_err(|_| { - anchor_lang::error::ErrorCode::InstructionDidNotDeserialize - }) - .unwrap(); - let raydium_amm_v3::instruction::OpenPosition { - tick_lower_index, - tick_upper_index, - tick_array_lower_start_index, - tick_array_upper_start_index, - liquidity, - amount_0_max, - amount_1_max, - } = ix; - println!("tick_lower_index:{}, tick_upper_index:{}, tick_array_lower_start_index:{}, tick_array_upper_start_index:{}, liquidity:{}, amount_0_max{}, amount_1_max{}", tick_lower_index, tick_upper_index, tick_array_lower_start_index, tick_array_upper_start_index, liquidity, amount_0_max, amount_1_max); - } - [46, 156, 243, 118, 13, 205, 251, 178] => { - let ix = raydium_amm_v3::instruction::IncreaseLiquidity::deserialize( - &mut &ix_data[..], - ) - .map_err(|_| { - anchor_lang::error::ErrorCode::InstructionDidNotDeserialize - }) - .unwrap(); - let raydium_amm_v3::instruction::IncreaseLiquidity { - liquidity, - amount_0_max, - amount_1_max, - } = ix; - println!( - "liquidity:{}, amount_0_max:{}, amount_1_max:{}", - liquidity, amount_0_max, amount_1_max - ); - } - [160, 38, 208, 111, 104, 91, 44, 1] => { - let ix = raydium_amm_v3::instruction::DecreaseLiquidity::deserialize( - &mut &ix_data[..], - ) - .map_err(|_| { - anchor_lang::error::ErrorCode::InstructionDidNotDeserialize - }) - .unwrap(); - let raydium_amm_v3::instruction::DecreaseLiquidity { - liquidity, - amount_0_min, - amount_1_min, - } = ix; - println!( - "liquidity:{}, amount_0_min:{}, amount_1_min:{}", - liquidity, amount_0_min, amount_1_min - ); - } - [248, 198, 158, 145, 225, 117, 135, 200] => { - let ix = - raydium_amm_v3::instruction::Swap::deserialize(&mut &ix_data[..]) - .map_err(|_| { - anchor_lang::error::ErrorCode::InstructionDidNotDeserialize - }) - .unwrap(); - let raydium_amm_v3::instruction::Swap { - amount, - other_amount_threshold, - sqrt_price_limit_x64, - is_base_input, - } = ix; - println!( - "amount:{}, other_amount_threshold:{}, sqrt_price_limit_x64:{}, is_base_input:{}", - amount, other_amount_threshold, sqrt_price_limit_x64, is_base_input - ); - } - [95, 135, 192, 196, 242, 129, 230, 68] => { - let ix = raydium_amm_v3::instruction::InitializeReward::deserialize( - &mut &ix_data[..], - ) - .map_err(|_| { - anchor_lang::error::ErrorCode::InstructionDidNotDeserialize - }) - .unwrap(); - let raydium_amm_v3::instructions::InitializeRewardParam { - open_time, - end_time, - emissions_per_second_x64, - } = ix.param; - println!( - "open_time:{}, end_time:{}, emissions_per_second_x64:{}", - open_time, end_time, emissions_per_second_x64 - ); - } - _ => { - println!("Not decode yet"); - } - } - } - } - "decode_log_event" => { - if v.len() == 2 { - let log_event = v[1]; - let borsh_bytes = match anchor_lang::__private::base64::decode(&log_event) { - Ok(borsh_bytes) => borsh_bytes, - _ => { - println!("Could not base64 decode log: {}", log_event); - return Ok(()); - } - }; - let mut slice: &[u8] = &borsh_bytes[..]; - let disc: [u8; 8] = { - let mut disc = [0; 8]; - disc.copy_from_slice(&borsh_bytes[..8]); - slice = &slice[8..]; - disc - }; - match disc { - [64, 198, 205, 232, 38, 8, 113, 226] => { - let log = - raydium_amm_v3::states::SwapEvent::deserialize(&mut &slice[..]) - .map_err(|_| { - anchor_lang::error::ErrorCode::InstructionDidNotDeserialize - }) - .unwrap(); - let raydium_amm_v3::states::SwapEvent { - pool_state, - sender, - token_account_0, - token_account_1, - amount_0, - amount_1, - zero_for_one, - sqrt_price_x64, - liquidity, - tick, - } = log; - let pool_f_price = sqrt_price_x64_to_price(sqrt_price_x64, 9, 6); - println!("pool_state:{}, sender:{}, token_account_0:{}, token_account_1:{}, amount_0:{}, amount_1:{}, zero_for_one:{}, sqrt_price_x64:{}, pool_f_price:{}, liquidity:{}, tick:{}", pool_state, sender, token_account_0, token_account_1, amount_0, amount_1, zero_for_one, sqrt_price_x64, pool_f_price, liquidity, tick); - } - [58, 222, 86, 58, 68, 50, 85, 56] => { - let log = raydium_amm_v3::states::DecreaseLiquidityEvent::deserialize( - &mut &slice[..], - ) - .map_err(|_| { - anchor_lang::error::ErrorCode::InstructionDidNotDeserialize - }) - .unwrap(); - let raydium_amm_v3::states::DecreaseLiquidityEvent { - position_nft_mint, - liquidity, - decrease_amount_0, - decrease_amount_1, - fee_amount_0, - fee_amount_1, - reward_amounts, - } = log; - println!("position_nft_mint:{}, liquidity:{}, decrease_amount_0:{}, decrease_amount_1:{}, fee_amount_0:{}, fee_amount_1:{}, reward_amounts:{:?}", position_nft_mint, liquidity, decrease_amount_0, decrease_amount_1,fee_amount_0,fee_amount_1,reward_amounts); - } - _ => { - println!("Not decode yet"); - } - } + for tick_array in tick_arrays_by_pool { + let tick_array_state = deserialize_anchor_account::< + raydium_amm_v3::states::TickArrayState, + >(&tick_array.1)?; + if tick_array_state.pool_id == pool_id { + println!( + "tick_array:{}, {}, {}", + tick_array.0, + identity(tick_array_state.start_tick_index), + identity(tick_array_state.initialized_tick_count) + ); } } - "transfer_reward_owner" => { - if v.len() != 3 { - panic!("invalild args") - } - let pool_id = Pubkey::from_str(&v[1]).unwrap(); - let new_owner = Pubkey::from_str(&v[2]).unwrap(); - let transfer_reward_owner_instrs = - transfer_reward_owner(&pool_config.clone(), pool_id, new_owner).unwrap(); - // send - let signers = vec![&payer, &admin]; - let recent_hash = rpc_client.get_latest_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &transfer_reward_owner_instrs, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let signature = send_txn(&rpc_client, &txn, true)?; - println!("{}", signature); - } - _ => { - println!("command not exist"); - } + } + CommandsName::PPool { pool_id } => { + let pool_id = if let Some(pool_id) = pool_id { + pool_id + } else { + pool_config.pool_id_account.unwrap() + }; + println!("pool_id:{}", pool_id); + let pool_account: raydium_amm_v3::states::PoolState = program.account(pool_id)?; + println!("{:#?}", pool_account); + } + CommandsName::PBitmapExtension { bitmap_extension } => { + let bitmap_extension = if let Some(bitmap_extension) = bitmap_extension { + bitmap_extension + } else { + pool_config.tickarray_bitmap_extension.unwrap() + }; + println!("bitmap_extension:{}", bitmap_extension); + let bitmap_extension_account: raydium_amm_v3::states::TickArrayBitmapExtension = + program.account(bitmap_extension)?; + println!("{:#?}", bitmap_extension_account); + } + CommandsName::PProtocol { protocol_id } => { + let protocol_account: raydium_amm_v3::states::ProtocolPositionState = + program.account(protocol_id)?; + println!("{:#?}", protocol_account); + } + CommandsName::PPersonal { personal_id } => { + let personal_account: raydium_amm_v3::states::PersonalPositionState = + program.account(personal_id)?; + println!("{:#?}", personal_account); + } + CommandsName::DecodeInstruction { instr_hex_data } => { + handle_program_instruction(&instr_hex_data, InstructionDecodeType::BaseHex)?; + } + CommandsName::DecodeEvent { log_event } => { + handle_program_log( + &pool_config.raydium_v3_program.to_string(), + &log_event, + false, + )?; + } + CommandsName::DecodeTxLog { tx_id } => { + let signature = Signature::from_str(&tx_id)?; + let tx = rpc_client.get_transaction_with_config( + &signature, + RpcTransactionConfig { + encoding: Some(UiTransactionEncoding::Json), + commitment: Some(CommitmentConfig::confirmed()), + max_supported_transaction_version: Some(0), + }, + )?; + let transaction = tx.transaction; + // get meta + let meta = if transaction.meta.is_some() { + transaction.meta + } else { + None + }; + // get encoded_transaction + let encoded_transaction = transaction.transaction; + // decode instruction data + parse_program_instruction( + &pool_config.raydium_v3_program.to_string(), + encoded_transaction, + meta.clone(), + )?; + // decode logs + parse_program_event(&pool_config.raydium_v3_program.to_string(), meta.clone())?; } } + + Ok(()) } diff --git a/client_config.ini b/client_config.ini index 2ae9ef5d..9a4d6338 100644 --- a/client_config.ini +++ b/client_config.ini @@ -3,7 +3,8 @@ http_url = https://api.devnet.solana.com ws_url = wss://api.devnet.solana.com/ payer_path = id.json admin_path = adMCyoCgfkg7bQiJ9aBJ59H3BXLY3r5LNLfPpQfMzBe.json -raydium_v3_program = DEVeYuwvQnhz1roDpSwqmnWtoKTeYftM7Qt7gFPMF3tj +raydium_v3_program = devi51mZmdwUJGU9hjN27vEz64Gps7uUefqxg27EAtH +slippage = 0.01 [Pool] mint0 = 2SiSpNowr7zUv5ZJHuzHszskQNaskWsNukhivCtuVLHo diff --git a/programs/amm/Cargo.toml b/programs/amm/Cargo.toml index 807dec67..2e4e18fd 100644 --- a/programs/amm/Cargo.toml +++ b/programs/amm/Cargo.toml @@ -15,6 +15,7 @@ no-entrypoint = [] no-idl = [] cpi = ["no-entrypoint"] default = [] +client = [] no-log-ix-name = [] init_if_needed = [] enable-log = [] @@ -25,7 +26,8 @@ paramset = [] anchor-lang = { version = "0.28.0", features = ["init-if-needed"] } anchor-spl = "0.28.0" solana-program = "<1.17.0" -uint = "0.9.5" +spl-memo = "4.0.0" +uint = "0.9.1" mpl-token-metadata = { version = "^1.11.0", features = ["no-entrypoint"] } bytemuck = { version = "1.4.0", features = ["derive", "min_const_generics"] } arrayref = { version = "0.3.6" } diff --git a/programs/amm/src/instructions/collect_remaining_rewards.rs b/programs/amm/src/instructions/collect_remaining_rewards.rs index f27911e7..69691724 100644 --- a/programs/amm/src/instructions/collect_remaining_rewards.rs +++ b/programs/amm/src/instructions/collect_remaining_rewards.rs @@ -7,6 +7,9 @@ use anchor_spl::{ token_interface::{Mint, Token2022, TokenAccount}, }; +/// Memo msg for collect remaining +pub const COLLECT_REMAINING_MEMO_MSG: &'static [u8] = b"raydium_collect_remaining"; + #[derive(Accounts)] pub struct CollectRemainingRewards<'info> { /// The founder who init reward info in berfore @@ -31,9 +34,9 @@ pub struct CollectRemainingRewards<'info> { /// memo program /// CHECK: - // #[account( - // address = spl_memo::id() - // )] + #[account( + address = spl_memo::id() + )] pub memo_program: UncheckedAccount<'info>, } @@ -41,6 +44,10 @@ pub fn collect_remaining_rewards( ctx: Context, reward_index: u8, ) -> Result<()> { + // invoke_memo_instruction( + // COLLECT_REMAINING_MEMO_MSG, + // ctx.accounts.memo_program.to_account_info(), + // )?; let amount_remaining = get_remaining_reward_amount( &ctx.accounts.pool_state, &ctx.accounts.reward_token_vault, diff --git a/programs/amm/src/instructions/decrease_liquidity.rs b/programs/amm/src/instructions/decrease_liquidity.rs index 2bb35f48..8b350dc6 100644 --- a/programs/amm/src/instructions/decrease_liquidity.rs +++ b/programs/amm/src/instructions/decrease_liquidity.rs @@ -2,14 +2,15 @@ use super::calculate_latest_token_fees; use super::modify_position; use crate::error::ErrorCode; use crate::states::*; -use crate::util; -use crate::util::transfer_from_pool_vault_to_user; +use crate::util::{self, transfer_from_pool_vault_to_user}; use anchor_lang::prelude::*; use anchor_spl::token::Token; use anchor_spl::token_interface::Mint; use anchor_spl::token_interface::{Token2022, TokenAccount}; use std::cell::RefMut; +/// Memo msg for decrease liquidity +pub const DECREASE_MEMO_MSG: &'static [u8] = b"raydium_decrease"; #[derive(Accounts)] pub struct DecreaseLiquidity<'info> { /// The position owner or delegated authority @@ -166,9 +167,9 @@ pub struct DecreaseLiquidityV2<'info> { /// memo program /// CHECK: - // #[account( - // address = spl_memo::id() - // )] + #[account( + address = spl_memo::id() + )] pub memo_program: UncheckedAccount<'info>, /// The mint of token vault 0 @@ -251,13 +252,17 @@ pub fn decrease_liquidity<'a, 'b, 'c, 'info>( amount_0_min: u64, amount_1_min: u64, ) -> Result<()> { + // if accounts.memo_program.is_some() { + // let memp_program = accounts.memo_program.as_ref().unwrap().to_account_info(); + // invoke_memo_instruction(DECREASE_MEMO_MSG, memp_program)?; + // } assert!(liquidity <= accounts.personal_position.liquidity); let liquidity_before; let pool_sqrt_price_x64; let pool_tick_current; - let tickarray_bitmap_extension; + let mut tickarray_bitmap_extension = None; - let remaining_accounts_iter = &mut remaining_accounts.iter(); + let remaining_collect_accounts = &mut Vec::new(); { let pool_state = accounts.pool_state.load()?; if !pool_state.get_status_by_bit(PoolStatusBitIndex::DecreaseLiquidity) @@ -275,16 +280,22 @@ pub fn decrease_liquidity<'a, 'b, 'c, 'info>( accounts.tick_array_upper.load()?.start_tick_index, ]); - tickarray_bitmap_extension = if use_tickarray_bitmap_extension { - let tickarray_bitmap_extension_info = remaining_accounts_iter.next().unwrap(); - require_keys_eq!( - tickarray_bitmap_extension_info.key(), - TickArrayBitmapExtension::key(pool_state.key()) + for account_info in remaining_accounts.into_iter() { + if account_info + .key() + .eq(&TickArrayBitmapExtension::key(pool_state.key())) + { + tickarray_bitmap_extension = Some(account_info); + continue; + } + remaining_collect_accounts.push(account_info); + } + if use_tickarray_bitmap_extension { + require!( + tickarray_bitmap_extension.is_some(), + ErrorCode::MissingTickArrayBitmapExtensionAccount ); - Some(tickarray_bitmap_extension_info) - } else { - None - }; + } } let (decrease_amount_0, latest_fees_owed_0, decrease_amount_1, latest_fees_owed_1) = @@ -376,7 +387,7 @@ pub fn decrease_liquidity<'a, 'b, 'c, 'info>( let personal_position = &mut accounts.personal_position; let reward_amounts = collect_rewards( &accounts.pool_state, - remaining_accounts_iter.as_slice(), + remaining_collect_accounts.as_slice(), accounts.token_program.clone(), token_2022_program_opt.clone(), personal_position, @@ -560,7 +571,7 @@ pub fn burn_liquidity<'b, 'info>( pub fn collect_rewards<'a, 'b, 'c, 'info>( pool_state_loader: &AccountLoader<'info, PoolState>, - remaining_accounts: &[AccountInfo<'info>], + remaining_accounts: &[&AccountInfo<'info>], token_program: Program<'info, Token>, token_program_2022: Option>, personal_position_state: &mut PersonalPositionState, @@ -587,14 +598,14 @@ pub fn collect_rewards<'a, 'b, 'c, 'info>( let mut remaining_accounts = remaining_accounts.iter(); for i in 0..remaining_accounts_len / reward_group_account_num { let reward_token_vault = - InterfaceAccount::::try_from(&remaining_accounts.next().unwrap())?; + InterfaceAccount::::try_from(remaining_accounts.next().unwrap())?; let recipient_token_account = - InterfaceAccount::::try_from(&remaining_accounts.next().unwrap())?; + InterfaceAccount::::try_from(remaining_accounts.next().unwrap())?; let mut reward_vault_mint: Option> = None; if need_reward_mint { reward_vault_mint = Some(InterfaceAccount::::try_from( - &remaining_accounts.next().unwrap(), + remaining_accounts.next().unwrap(), )?); } require_keys_eq!(reward_token_vault.mint, recipient_token_account.mint); @@ -648,7 +659,7 @@ pub fn collect_rewards<'a, 'b, 'c, 'info>( fn check_required_accounts_length( pool_state_loader: &AccountLoader, - remaining_accounts: &[AccountInfo], + remaining_accounts: &[&AccountInfo], reward_group_account_num: usize, ) -> Result<()> { let pool_state = pool_state_loader.load()?; diff --git a/programs/amm/src/instructions/swap_v2.rs b/programs/amm/src/instructions/swap_v2.rs index f0cb94c2..6e51341e 100644 --- a/programs/amm/src/instructions/swap_v2.rs +++ b/programs/amm/src/instructions/swap_v2.rs @@ -9,6 +9,9 @@ use crate::{states::*, util}; use anchor_lang::prelude::*; use anchor_spl::token::Token; use anchor_spl::token_interface::{Mint, Token2022, TokenAccount}; + +/// Memo msg for swap +pub const SWAP_MEMO_MSG: &'static [u8] = b"raydium_swap"; #[derive(Accounts)] pub struct SwapSingleV2<'info> { /// The user performing the swap @@ -49,9 +52,9 @@ pub struct SwapSingleV2<'info> { pub token_program_2022: Program<'info, Token2022>, /// CHECK: - // #[account( - // address = spl_memo::id() - // )] + #[account( + address = spl_memo::id() + )] pub memo_program: UncheckedAccount<'info>, /// The mint of token vault 0 @@ -80,6 +83,8 @@ pub fn exact_internal_v2<'info>( sqrt_price_limit_x64: u128, is_base_input: bool, ) -> Result { + // invoke_memo_instruction(SWAP_MEMO_MSG, ctx.memo_program.to_account_info())?; + let block_timestamp = solana_program::clock::Clock::get()?.unix_timestamp as u64; let amount_0; @@ -116,7 +121,7 @@ pub fn exact_internal_v2<'info>( let mut tickarray_bitmap_extension = None; let tick_array_states = &mut VecDeque::new(); - + for account_info in remaining_accounts.into_iter() { if account_info .key() diff --git a/programs/amm/src/lib.rs b/programs/amm/src/lib.rs index 90587de8..1a7fa2c6 100644 --- a/programs/amm/src/lib.rs +++ b/programs/amm/src/lib.rs @@ -5,6 +5,7 @@ pub mod states; pub mod util; use anchor_lang::prelude::*; +use core as core_; use instructions::*; use states::*; use util::access_control::*; diff --git a/programs/amm/src/libraries/big_num.rs b/programs/amm/src/libraries/big_num.rs index 2e0f3d11..6057b92d 100644 --- a/programs/amm/src/libraries/big_num.rs +++ b/programs/amm/src/libraries/big_num.rs @@ -2,7 +2,6 @@ ///! U128 is more efficient that u128 ///! https://github.com/solana-labs/solana/issues/19549 use uint::construct_uint; - construct_uint! { pub struct U128(2); } @@ -11,231 +10,336 @@ construct_uint! { pub struct U256(4); } -construct_uint! { - pub struct U512(8); -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, PartialOrd, Eq, Ord)] -pub struct U1024(pub [u64; 16]); -const N_WORDS: usize = 16; -impl U1024 { - /// Whether this is zero. - #[inline] - pub const fn is_zero(&self) -> bool { - let mut i = 0; - while i < 16 { - if self.0[i] != 0 { - return false; - } else { - i += 1; +#[macro_export] +macro_rules! construct_bignum { + ( $(#[$attr:meta])* $visibility:vis struct $name:ident ( $n_words:tt ); ) => { + $crate::construct_bignum! { @construct $(#[$attr])* $visibility struct $name ($n_words); } + impl $crate::core_::convert::From for $name { + fn from(value: u128) -> $name { + let mut ret = [0; $n_words]; + ret[0] = value as u64; + ret[1] = (value >> 64) as u64; + $name(ret) } } - return true; - } - - /// Zero (additive identity) of this type. - #[inline] - pub const fn zero() -> Self { - Self([0; N_WORDS]) - } - - /// One (multiplicative identity) of this type. - #[inline] - pub const fn one() -> Self { - let mut words = [0; N_WORDS]; - words[0] = 1u64; - Self(words) - } - - /// The maximum value which can be inhabited by this type. - #[inline] - pub fn max_value() -> Self { - let mut result = [0; N_WORDS]; - for i in 0..N_WORDS { - result[i] = u64::max_value(); + + impl $crate::core_::convert::From for $name { + fn from(value: i128) -> $name { + match value >= 0 { + true => From::from(value as u128), + false => { panic!("Unsigned integer can't be created from negative value"); } + } + } } - U1024(result) - } - - /// Conversion to usize with overflow checking - /// - /// # Panics - /// - /// Panics if the number is larger than usize::max_value(). - #[inline] - pub fn as_usize(&self) -> usize { - let arr = self.0; - if !self.fits_word() || arr[0] > u64::try_from(usize::max_value()).unwrap() { - panic!("Integer overflow when casting to usize") + + impl $name { + /// Low 2 words (u128) + #[inline] + pub const fn low_u128(&self) -> u128 { + let &$name(ref arr) = self; + ((arr[1] as u128) << 64) + arr[0] as u128 + } + + /// Conversion to u128 with overflow checking + /// + /// # Panics + /// + /// Panics if the number is larger than 2^128. + #[inline] + pub fn as_u128(&self) -> u128 { + let &$name(ref arr) = self; + for i in 2..$n_words { + if arr[i] != 0 { + panic!("Integer overflow when casting to u128") + } + + } + self.low_u128() + } } - arr[0] as usize - } - - // Whether this fits u64. - #[inline] - fn fits_word(&self) -> bool { - let arr = self.0; - for i in 1..N_WORDS { - if arr[i] != 0 { - return false; + + impl $crate::core_::convert::TryFrom<$name> for u128 { + type Error = &'static str; + + #[inline] + fn try_from(u: $name) -> $crate::core_::result::Result { + let $name(arr) = u; + for i in 2..$n_words { + if arr[i] != 0 { + return Err("integer overflow when casting to u128"); + } + } + Ok(((arr[1] as u128) << 64) + arr[0] as u128) } } - return true; - } - - /// Returns the number of leading zeros in the binary representation of self. - pub fn leading_zeros(&self) -> u32 { - let mut r = 0; - for i in 0..N_WORDS { - let w = self.0[N_WORDS - i - 1]; - if w == 0 { - r += 64; - } else { - r += w.leading_zeros(); - break; + + impl $crate::core_::convert::TryFrom<$name> for i128 { + type Error = &'static str; + + #[inline] + fn try_from(u: $name) -> $crate::core_::result::Result { + let err_str = "integer overflow when casting to i128"; + let i = u128::try_from(u).map_err(|_| err_str)?; + if i > i128::max_value() as u128 { + Err(err_str) + } else { + Ok(i as i128) + } } } - r - } - - /// Returns the number of trailing zeros in the binary representation of self. - pub fn trailing_zeros(&self) -> u32 { - let mut r = 0; - for i in 0..N_WORDS { - let w = self.0[i]; - if w == 0 { - r += 64; - } else { - r += w.trailing_zeros(); - break; + }; + + ( @construct $(#[$attr:meta])* $visibility:vis struct $name:ident ( $n_words:tt ); ) => { + /// Little-endian large integer type + #[repr(C)] + $(#[$attr])* + #[derive(Copy, Clone, Eq, PartialEq, Hash)] + $visibility struct $name (pub [u64; $n_words]); + + /// Get a reference to the underlying little-endian words. + impl AsRef<[u64]> for $name { + #[inline] + fn as_ref(&self) -> &[u64] { + &self.0 + } + } + + impl<'a> From<&'a $name> for $name { + fn from(x: &'a $name) -> $name { + *x + } + } + + impl $name { + /// Maximum value. + pub const MAX: $name = $name([u64::max_value(); $n_words]); + + /// Conversion to usize with overflow checking + /// + /// # Panics + /// + /// Panics if the number is larger than usize::max_value(). + #[inline] + pub fn as_usize(&self) -> usize { + let &$name(ref arr) = self; + if !self.fits_word() || arr[0] > usize::max_value() as u64 { + panic!("Integer overflow when casting to usize") + } + arr[0] as usize + } + + /// Whether this is zero. + #[inline] + pub const fn is_zero(&self) -> bool { + let &$name(ref arr) = self; + let mut i = 0; + while i < $n_words { if arr[i] != 0 { return false; } else { i += 1; } } + return true; + } + + // Whether this fits u64. + #[inline] + fn fits_word(&self) -> bool { + let &$name(ref arr) = self; + for i in 1..$n_words { if arr[i] != 0 { return false; } } + return true; + } + + /// Return if specific bit is set. + /// + /// # Panics + /// + /// Panics if `index` exceeds the bit width of the number. + #[inline] + pub const fn bit(&self, index: usize) -> bool { + let &$name(ref arr) = self; + arr[index / 64] & (1 << (index % 64)) != 0 + } + + /// Returns the number of leading zeros in the binary representation of self. + pub fn leading_zeros(&self) -> u32 { + let mut r = 0; + for i in 0..$n_words { + let w = self.0[$n_words - i - 1]; + if w == 0 { + r += 64; + } else { + r += w.leading_zeros(); + break; + } + } + r + } + + /// Returns the number of trailing zeros in the binary representation of self. + pub fn trailing_zeros(&self) -> u32 { + let mut r = 0; + for i in 0..$n_words { + let w = self.0[i]; + if w == 0 { + r += 64; + } else { + r += w.trailing_zeros(); + break; + } + } + r + } + + /// Zero (additive identity) of this type. + #[inline] + pub const fn zero() -> Self { + Self([0; $n_words]) + } + + /// One (multiplicative identity) of this type. + #[inline] + pub const fn one() -> Self { + let mut words = [0; $n_words]; + words[0] = 1u64; + Self(words) + } + + /// The maximum value which can be inhabited by this type. + #[inline] + pub const fn max_value() -> Self { + Self::MAX + } + } + + impl $crate::core_::default::Default for $name { + fn default() -> Self { + $name::zero() } } - r - } - - /// Return if specific bit is set. - /// - /// # Panics - /// - /// Panics if `index` exceeds the bit width of the number. - #[inline] - pub const fn bit(&self, index: usize) -> bool { - self.0[index / 64] & (1 << (index % 64)) != 0 - } -} -impl core::default::Default for U1024 { - fn default() -> Self { - U1024::zero() - } -} + impl $crate::core_::ops::BitAnd<$name> for $name { + type Output = $name; -impl core::ops::BitAnd for U1024 { - type Output = U1024; - fn bitand(self, other: U1024) -> U1024 { - let arr1 = self.0; - let arr2 = other.0; - let mut ret = [0u64; N_WORDS]; - for i in 0..N_WORDS { - ret[i] = arr1[i] & arr2[i]; + #[inline] + fn bitand(self, other: $name) -> $name { + let $name(ref arr1) = self; + let $name(ref arr2) = other; + let mut ret = [0u64; $n_words]; + for i in 0..$n_words { + ret[i] = arr1[i] & arr2[i]; + } + $name(ret) + } } - U1024(ret) - } -} -impl core::ops::BitXor for U1024 { - type Output = U1024; - fn bitxor(self, other: U1024) -> U1024 { - let arr1 = self.0; - let arr2 = other.0; - let mut ret = [0u64; N_WORDS]; - for i in 0..N_WORDS { - ret[i] = arr1[i] ^ arr2[i]; + impl $crate::core_::ops::BitOr<$name> for $name { + type Output = $name; + + #[inline] + fn bitor(self, other: $name) -> $name { + let $name(ref arr1) = self; + let $name(ref arr2) = other; + let mut ret = [0u64; $n_words]; + for i in 0..$n_words { + ret[i] = arr1[i] | arr2[i]; + } + $name(ret) + } } - U1024(ret) - } -} -impl core::ops::BitOr for U1024 { - type Output = U1024; - fn bitor(self, other: U1024) -> U1024 { - let arr1 = self.0; - let arr2 = other.0; - let mut ret = [0u64; N_WORDS]; - for i in 0..N_WORDS { - ret[i] = arr1[i] | arr2[i]; + impl $crate::core_::ops::BitXor<$name> for $name { + type Output = $name; + + #[inline] + fn bitxor(self, other: $name) -> $name { + let $name(ref arr1) = self; + let $name(ref arr2) = other; + let mut ret = [0u64; $n_words]; + for i in 0..$n_words { + ret[i] = arr1[i] ^ arr2[i]; + } + $name(ret) + } } - U1024(ret) - } -} -impl core::ops::Not for U1024 { - type Output = U1024; - fn not(self) -> U1024 { - let arr = self.0; - let mut ret = [0u64; N_WORDS]; - for i in 0..N_WORDS { - ret[i] = !arr[i]; + impl $crate::core_::ops::Not for $name { + type Output = $name; + + #[inline] + fn not(self) -> $name { + let $name(ref arr) = self; + let mut ret = [0u64; $n_words]; + for i in 0..$n_words { + ret[i] = !arr[i]; + } + $name(ret) + } } - U1024(ret) - } -} -impl core::ops::Shl for U1024 { - type Output = U1024; - - fn shl(self, shift: usize) -> U1024 { - let original = self.0; - let mut ret = [0u64; N_WORDS]; - let word_shift = shift / 64; - let bit_shift = shift % 64; - - // shift - for i in word_shift..N_WORDS { - ret[i] = original[i - word_shift] << bit_shift; + + impl $crate::core_::ops::Shl for $name { + type Output = $name; + + fn shl(self, shift: usize) -> $name { + let $name(ref original) = self; + let mut ret = [0u64; $n_words]; + let word_shift = shift / 64; + let bit_shift = shift % 64; + + // shift + for i in word_shift..$n_words { + ret[i] = original[i - word_shift] << bit_shift; + } + // carry + if bit_shift > 0 { + for i in word_shift+1..$n_words { + ret[i] += original[i - 1 - word_shift] >> (64 - bit_shift); + } + } + $name(ret) + } } - // carry - if bit_shift > 0 { - for i in word_shift + 1..N_WORDS { - ret[i] += original[i - 1 - word_shift] >> (64 - bit_shift); + + impl<'a> $crate::core_::ops::Shl for &'a $name { + type Output = $name; + fn shl(self, shift: usize) -> $name { + *self << shift } } - U1024(ret) - } -} -impl<'a> core::ops::Shl for &'a U1024 { - type Output = U1024; - fn shl(self, shift: usize) -> U1024 { - *self << shift - } -} -impl core::ops::Shr for U1024 { - type Output = U1024; + impl $crate::core_::ops::Shr for $name { + type Output = $name; + + fn shr(self, shift: usize) -> $name { + let $name(ref original) = self; + let mut ret = [0u64; $n_words]; + let word_shift = shift / 64; + let bit_shift = shift % 64; - fn shr(self, shift: usize) -> U1024 { - let original = self.0; - let mut ret = [0u64; N_WORDS]; - let word_shift = shift / 64; - let bit_shift = shift % 64; + // shift + for i in word_shift..$n_words { + ret[i - word_shift] = original[i] >> bit_shift; + } - // shift - for i in word_shift..N_WORDS { - ret[i - word_shift] = original[i] >> bit_shift; + // Carry + if bit_shift > 0 { + for i in word_shift+1..$n_words { + ret[i - word_shift - 1] += original[i] << (64 - bit_shift); + } + } + + $name(ret) + } } - // Carry - if bit_shift > 0 { - for i in word_shift + 1..N_WORDS { - ret[i - word_shift - 1] += original[i] << (64 - bit_shift); + impl<'a> $crate::core_::ops::Shr for &'a $name { + type Output = $name; + fn shr(self, shift: usize) -> $name { + *self >> shift } } + }; +} - U1024(ret) - } +construct_bignum! { + pub struct U512(8); } -impl<'a> core::ops::Shr for &'a U1024 { - type Output = U1024; - fn shr(self, shift: usize) -> U1024 { - *self >> shift - } + +construct_bignum! { + pub struct U1024(16); } diff --git a/programs/amm/src/states/tick_array.rs b/programs/amm/src/states/tick_array.rs index 75a73c4d..ba3d66b9 100644 --- a/programs/amm/src/states/tick_array.rs +++ b/programs/amm/src/states/tick_array.rs @@ -622,7 +622,7 @@ pub mod tick_array_test { use std::convert::identity; #[test] - fn get_arrary_start_index_test() { + fn get_array_start_index_test() { assert_eq!(TickArrayState::get_array_start_index(120, 3), 0); assert_eq!(TickArrayState::get_array_start_index(1002, 30), 0); assert_eq!(TickArrayState::get_array_start_index(-120, 3), -180); @@ -690,8 +690,8 @@ pub mod tick_array_test { tick_array_ref .borrow() .get_tick_offset_in_array(1105, tick_spacing) - .unwrap_err(), - error!(anchor_lang::error::ErrorCode::RequireEqViolated) + .unwrap(), + 36 ); // (1108-960) / tick_spacing assert_eq!( diff --git a/programs/amm/src/states/tickarray_bitmap_extension.rs b/programs/amm/src/states/tickarray_bitmap_extension.rs index cfc563ca..5ceb77ec 100644 --- a/programs/amm/src/states/tickarray_bitmap_extension.rs +++ b/programs/amm/src/states/tickarray_bitmap_extension.rs @@ -115,7 +115,7 @@ impl TickArrayBitmapExtension { let tick_array_offset_in_bitmap = Self::tick_array_offset_in_bitmap(tick_array_start_index, tick_spacing); let tick_array_bitmap = U512(tick_array_bitmap); - let mask = U512::one() << tick_array_offset_in_bitmap; + let mask = U512::one() << tick_array_offset_in_bitmap.try_into().unwrap(); if tick_array_start_index < 0 { self.negative_tick_array_bitmap[offset as usize] = tick_array_bitmap.bitxor(mask).0; } else { @@ -174,7 +174,9 @@ impl TickArrayBitmapExtension { // tick from upper to lower // find from highter bits to lower bits let offset_bit_map = U512(tickarray_bitmap) - << (TICK_ARRAY_BITMAP_SIZE - 1 - tick_array_offset_in_bitmap); + << (TICK_ARRAY_BITMAP_SIZE - 1 - tick_array_offset_in_bitmap) + .try_into() + .unwrap(); let next_bit = if offset_bit_map.is_zero() { None @@ -193,7 +195,8 @@ impl TickArrayBitmapExtension { } else { // tick from lower to upper // find from lower bits to highter bits - let offset_bit_map = U512(tickarray_bitmap) >> tick_array_offset_in_bitmap; + let offset_bit_map = + U512(tickarray_bitmap) >> tick_array_offset_in_bitmap.try_into().unwrap(); let next_bit = if offset_bit_map.is_zero() { None @@ -488,12 +491,14 @@ pub mod tick_array_bitmap_extension_test { .unwrap(); assert!(next == tick_spacing * TICK_ARRAY_SIZE * 1000); - let next = tick_array_bitmap_extension.next_initialized_tick_array_from_one_bitmap( - tick_spacing * TICK_ARRAY_SIZE * 7393, - tick_spacing as u16, - false, - ); - assert!(next.is_err()); + let next = tick_array_bitmap_extension + .next_initialized_tick_array_from_one_bitmap( + tick_spacing * TICK_ARRAY_SIZE * 7393, + tick_spacing as u16, + false, + ) + .unwrap(); + assert!(next.0 == false); // zero_for_one. let (_, next) = tick_array_bitmap_extension @@ -583,11 +588,13 @@ pub mod tick_array_bitmap_extension_test { assert!(next == -tick_spacing * TICK_ARRAY_SIZE * 1000); // zero_for_one, last tickarray start index is too little, not reach the extension boundary value. - let next = tick_array_bitmap_extension.next_initialized_tick_array_from_one_bitmap( - -tick_spacing * TICK_ARRAY_SIZE * 7394, - tick_spacing as u16, - true, - ); - assert!(next.is_err()); + let next = tick_array_bitmap_extension + .next_initialized_tick_array_from_one_bitmap( + -tick_spacing * TICK_ARRAY_SIZE * 7394, + tick_spacing as u16, + true, + ) + .unwrap(); + assert!(next.0 == false); } } diff --git a/programs/amm/src/util/token.rs b/programs/amm/src/util/token.rs index 5a582bff..51716c6c 100644 --- a/programs/amm/src/util/token.rs +++ b/programs/amm/src/util/token.rs @@ -15,6 +15,15 @@ use anchor_spl::{ token_interface::{Mint, TokenAccount}, }; +pub fn invoke_memo_instruction<'info>( + memo_msg: &[u8], + memo_program: AccountInfo<'info>, +) -> solana_program::entrypoint::ProgramResult { + let ix = spl_memo::build_memo(memo_msg, &Vec::new()); + let accounts = vec![memo_program]; + solana_program::program::invoke(&ix, &accounts[..]) +} + pub fn transfer_from_user_to_pool_vault<'info>( signer: &Signer<'info>, from: &InterfaceAccount<'info, TokenAccount>,