From 542ad5f30afea5160c305c7e87fdbf3b03eb0886 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Wed, 18 Dec 2024 03:07:08 +0900 Subject: [PATCH 01/33] Add position test --- rust-sdk/whirlpool/src/position.rs | 80 ++++++++++ rust-sdk/whirlpool/src/tests/program.rs | 188 ++++++++++++++++++++++-- 2 files changed, 257 insertions(+), 11 deletions(-) diff --git a/rust-sdk/whirlpool/src/position.rs b/rust-sdk/whirlpool/src/position.rs index 0e4c67517..a71b65c8d 100644 --- a/rust-sdk/whirlpool/src/position.rs +++ b/rust-sdk/whirlpool/src/position.rs @@ -8,6 +8,7 @@ use orca_whirlpools_core::POSITION_BUNDLE_SIZE; use solana_client::nonblocking::rpc_client::RpcClient; use solana_sdk::account::Account; use solana_sdk::pubkey::Pubkey; +use solana_sdk::signer::Signer; use crate::{get_token_accounts_for_owner, ParsedTokenAccount}; @@ -295,3 +296,82 @@ pub async fn fetch_positions_in_whirlpool( let filters = vec![PositionFilter::Whirlpool(whirlpool)]; fetch_all_position_with_filter(rpc, filters).await } + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{ + setup_ata_with_amount, setup_mint_with_decimals, setup_position, setup_whirlpool, + RpcContext, + }; + use serial_test::serial; + use solana_program_test::tokio; + use std::error::Error; + + #[tokio::test] + #[serial] + #[ignore = "Skipped until solana-bankrun supports gpa"] + async fn test_fetch_positions_for_owner_no_positions() -> Result<(), Box> { + let ctx = RpcContext::new().await; + let owner = ctx.signer.pubkey(); + let positions = fetch_positions_for_owner(&ctx.rpc, owner).await?; + assert!( + positions.is_empty(), + "No positions should exist for a new owner" + ); + Ok(()) + } + + #[tokio::test] + #[serial] + #[ignore = "Skipped until solana-bankrun supports gpa"] + async fn test_fetch_positions_for_owner_with_position() -> Result<(), Box> { + let ctx = RpcContext::new().await; + let mint_a = setup_mint_with_decimals(&ctx, 9).await?; + let mint_b = setup_mint_with_decimals(&ctx, 9).await?; + setup_ata_with_amount(&ctx, mint_a, 1_000_000_000).await?; + setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; + + let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; + let position_pubkey = setup_position(whirlpool).await?; + + let owner = ctx.signer.pubkey(); + let positions = fetch_positions_for_owner(&ctx.rpc, owner).await?; + assert_eq!( + positions.len(), + 1, + "Should have one position after setup_position" + ); + + match &positions[0] { + PositionOrBundle::Position(pos) => { + assert_eq!(pos.address, position_pubkey); + } + _ => panic!("Expected a single position, but found a bundle"), + } + + Ok(()) + } + + #[tokio::test] + #[serial] + #[ignore = "Skipped until solana-bankrun supports gpa"] + async fn test_fetch_positions_in_whirlpool() -> Result<(), Box> { + let ctx = RpcContext::new().await; + let mint_a = setup_mint_with_decimals(&ctx, 9).await?; + let mint_b = setup_mint_with_decimals(&ctx, 9).await?; + setup_ata_with_amount(&ctx, mint_a, 1_000_000_000).await?; + setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; + let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; + let _position_pubkey = setup_position(whirlpool).await?; + + let positions = fetch_positions_in_whirlpool(&ctx.rpc, whirlpool).await?; + assert_eq!( + positions.len(), + 1, + "Should find one position in this whirlpool" + ); + + Ok(()) + } +} diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index 81d735c44..145efd81e 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -1,17 +1,32 @@ -use solana_sdk::{pubkey::Pubkey, signer::Signer, system_program}; -use std::error::Error; - use orca_whirlpools_client::{ - get_fee_tier_address, get_token_badge_address, get_whirlpool_address, InitializePoolV2, - InitializePoolV2InstructionArgs, + get_bundled_position_address, get_fee_tier_address, get_position_address, + get_position_bundle_address, get_token_badge_address, get_whirlpool_address, InitializePoolV2, + InitializePoolV2InstructionArgs, InitializePositionBundle, OpenBundledPosition, + OpenBundledPositionInstructionArgs, OpenPosition, OpenPositionInstructionArgs, }; use orca_whirlpools_core::tick_index_to_sqrt_price; +use solana_program::program_pack::Pack; use solana_program::sysvar::rent::ID as RENT_PROGRAM_ID; +use solana_sdk::{ + pubkey::Pubkey, + signer::{keypair::Keypair, Signer}, + system_instruction, system_program, +}; +use spl_associated_token_account::{ + get_associated_token_address, get_associated_token_address_with_program_id, + instruction::create_associated_token_account, +}; +use spl_token::{state::Mint, ID as TOKEN_PROGRAM_ID}; +use spl_token_2022::{state::Mint as Token2022Mint, ID as TOKEN_2022_PROGRAM_ID}; +use std::error::Error; use crate::WHIRLPOOLS_CONFIG_ADDRESS; use super::rpc::RpcContext; +use crate::tests::token::{setup_ata, setup_mint_with_decimals}; +use crate::tests::token_extensions::setup_mint_te; + pub async fn setup_whirlpool( ctx: &RpcContext, token_a: Pubkey, @@ -60,14 +75,165 @@ pub async fn setup_whirlpool( Ok(whirlpool) } -pub async fn setup_position(_whirlpool: Pubkey) -> Result> { - todo!() +pub async fn setup_position(whirlpool: Pubkey) -> Result> { + let ctx = RpcContext::new().await; + + // Use token utility functions + let position_mint = setup_mint_with_decimals(&ctx, 0).await?; + let position_token_account = setup_ata(&ctx, position_mint).await?; + + let (position_pubkey, position_bump) = get_position_address(&position_mint)?; + + let open_position_ix = OpenPosition { + funder: ctx.signer.pubkey(), + owner: ctx.signer.pubkey(), + position: position_pubkey, + position_mint, + position_token_account, + whirlpool, + token_program: TOKEN_PROGRAM_ID, + system_program: system_program::id(), + associated_token_program: spl_associated_token_account::id(), + rent: RENT_PROGRAM_ID, + } + .instruction(OpenPositionInstructionArgs { + tick_lower_index: -128, + tick_upper_index: 128, + position_bump, + }); + + ctx.send_transaction(vec![open_position_ix]).await?; + + Ok(position_pubkey) } -pub async fn setup_te_position(_whirlpool: Pubkey) -> Result> { - todo!() +pub async fn setup_te_position(whirlpool: Pubkey) -> Result> { + let ctx = RpcContext::new().await; + + let position_mint = Keypair::new(); + let lamports = ctx + .rpc + .get_minimum_balance_for_rent_exemption(Token2022Mint::LEN) + .await?; + + let create_mint_ix = system_instruction::create_account( + &ctx.signer.pubkey(), + &position_mint.pubkey(), + lamports, + Token2022Mint::LEN as u64, + &TOKEN_2022_PROGRAM_ID, + ); + + let init_mint_ix = spl_token_2022::instruction::initialize_mint( + &TOKEN_2022_PROGRAM_ID, + &position_mint.pubkey(), + &ctx.signer.pubkey(), + None, + 0, + )?; + + let position_token_account = + get_associated_token_address(&ctx.signer.pubkey(), &position_mint.pubkey()); + + let create_ata_ix = create_associated_token_account( + &ctx.signer.pubkey(), + &ctx.signer.pubkey(), + &position_mint.pubkey(), + &TOKEN_2022_PROGRAM_ID, + ); + + let (position_pubkey, position_bump) = get_position_address(&position_mint.pubkey())?; + + let tick_lower_index: i32 = -128; + let tick_upper_index: i32 = 128; + + let open_position_ix = OpenPosition { + funder: ctx.signer.pubkey(), + owner: ctx.signer.pubkey(), + position: position_pubkey, + position_mint: position_mint.pubkey(), + position_token_account, + whirlpool, + token_program: TOKEN_2022_PROGRAM_ID, + system_program: system_program::id(), + associated_token_program: spl_associated_token_account::id(), + rent: RENT_PROGRAM_ID, + } + .instruction(OpenPositionInstructionArgs { + tick_lower_index, + tick_upper_index, + position_bump, + }); + + ctx.send_transaction_with_signers( + vec![ + create_mint_ix, + init_mint_ix, + create_ata_ix, + open_position_ix, + ], + vec![&position_mint], + ) + .await?; + + Ok(position_pubkey) } -pub async fn setup_position_bundle(_whirlpool: Pubkey) -> Result> { - todo!() +/// Creates a Position Bundle and initializes the specified number of bundled positions +/// Calls `OpenBundledPosition` for each position specified in `bundle_positions` +pub async fn setup_position_bundle( + whirlpool: Pubkey, + bundle_positions: Option>, +) -> Result> { + let ctx = RpcContext::new().await; + + // Use token utility functions + let position_bundle_mint = setup_mint_with_decimals(&ctx, 0).await?; + let position_bundle_token_account = setup_ata(&ctx, position_bundle_mint).await?; + + let (position_bundle_address, _) = get_position_bundle_address(&position_bundle_mint)?; + + let open_bundle_ix = InitializePositionBundle { + funder: ctx.signer.pubkey(), + position_bundle: position_bundle_address, + position_bundle_mint, + position_bundle_token_account, + position_bundle_owner: ctx.signer.pubkey(), + token_program: TOKEN_PROGRAM_ID, + system_program: system_program::id(), + associated_token_program: spl_associated_token_account::id(), + rent: RENT_PROGRAM_ID, + } + .instruction(); + + ctx.send_transaction(vec![open_bundle_ix]).await?; + + // Initialize bundled positions + if let Some(positions) = bundle_positions { + for (i, _) in positions.iter().enumerate() { + let bundle_index = i as u16; + let (bundled_position_address, _) = + get_bundled_position_address(&position_bundle_mint, bundle_index as u8)?; + + let open_bundled_ix = OpenBundledPosition { + funder: ctx.signer.pubkey(), + bundled_position: bundled_position_address, + position_bundle: position_bundle_address, + position_bundle_authority: ctx.signer.pubkey(), + position_bundle_token_account, + whirlpool, + system_program: system_program::id(), + rent: RENT_PROGRAM_ID, + } + .instruction(OpenBundledPositionInstructionArgs { + tick_lower_index: -128, + tick_upper_index: 128, + bundle_index, + }); + + ctx.send_transaction(vec![open_bundled_ix]).await?; + } + } + + Ok(position_bundle_address) } From faf362a5f21cfe706680a8922285d837a15f5e30 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 19 Dec 2024 12:49:02 +0900 Subject: [PATCH 02/33] Initial commit for position test --- rust-sdk/whirlpool/src/increase_liquidity.rs | 81 ++++++++- rust-sdk/whirlpool/src/pool.rs | 4 +- rust-sdk/whirlpool/src/position.rs | 12 +- rust-sdk/whirlpool/src/tests/program.rs | 176 +++++++++++++++---- 4 files changed, 236 insertions(+), 37 deletions(-) diff --git a/rust-sdk/whirlpool/src/increase_liquidity.rs b/rust-sdk/whirlpool/src/increase_liquidity.rs index 538c17df3..9eba7654a 100644 --- a/rust-sdk/whirlpool/src/increase_liquidity.rs +++ b/rust-sdk/whirlpool/src/increase_liquidity.rs @@ -18,6 +18,7 @@ use solana_sdk::program_pack::Pack; use solana_sdk::signer::Signer; use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signature::Keypair}; use spl_associated_token_account::get_associated_token_address_with_program_id; +use spl_token::state::Account as TokenAccount; use spl_token_2022::state::Mint; use crate::{get_rent, SPLASH_POOL_TICK_SPACING}; @@ -25,7 +26,6 @@ use crate::{ token::{get_current_transfer_fee, prepare_token_accounts_instructions, TokenAccountStrategy}, FUNDER, SLIPPAGE_TOLERANCE_BPS, }; - // TODO: support transfer hooks fn get_increase_liquidity_quote( @@ -695,3 +695,82 @@ pub async fn open_position_instructions( ) .await } + +#[cfg(test)] +mod tests { + use serial_test::serial; + use solana_program_test::tokio; + use solana_sdk::pubkey::Pubkey; + use spl_token::state::Account as TokenAccount; + use std::collections::HashMap; + + use crate::{ + tests::{ + setup_ata_te, setup_ata_with_amount, setup_config_and_fee_tiers, setup_mint_te, + setup_mint_te_fee, setup_mint_with_decimals, setup_position, setup_te_position, + setup_whirlpool, RpcContext, SetupAtaConfig, + }, + DEFAULT_FUNDER, WHIRLPOOLS_CONFIG_ADDRESS, + }; + + use super::*; + + // Helper functions + async fn fetch_token(rpc: &RpcClient, address: Pubkey) -> Result> { + let account = rpc.get_account(&address).await?; + TokenAccount::unpack(&account.data).map_err(|e| e.into()) + } + + async fn fetch_position(rpc: &RpcClient, address: Pubkey) -> Result> { + let account = rpc.get_account(&address).await?; + Position::from_bytes(&account.data).map_err(|e| e.into()) + } + + const TEST_SLIPPAGE_TOLERANCE: u16 = 100; // 1% + + #[tokio::test] + #[serial] + async fn test_increase_liquidity() -> Result<(), Box> { + let ctx = RpcContext::new().await; + + // setup and initialize + setup_config_and_fee_tiers(&ctx).await?; + let mint_a_pubkey = setup_mint_with_decimals(&ctx, 9).await?; + let mint_b_pubkey = setup_mint_with_decimals(&ctx, 9).await?; + let token_balance: u64 = 1_000_000; + setup_ata_with_amount(&ctx, mint_a_pubkey, token_balance).await?; + setup_ata_with_amount(&ctx, mint_b_pubkey, token_balance).await?; + + // setup pool and position + let tick_spacing = 64; + let pool_pubkey = + setup_whirlpool(&ctx, ctx.config, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; + + let position_mint = setup_position( + &ctx, + pool_pubkey, + Some((-100, 100)), + Some(ctx.signer.pubkey()), + ) + .await?; + + // test increase liquidity + let param = IncreaseLiquidityParam::Liquidity(10_000); + let increase_ix = increase_liquidity_instructions( + &ctx.rpc, + position_mint, + param, + Some(TEST_SLIPPAGE_TOLERANCE), + Some(ctx.signer.pubkey()), + ) + .await?; + + // send transaction + let signers: Vec<&Keypair> = increase_ix.additional_signers.iter().collect(); + + ctx.send_transaction_with_signers(increase_ix.instructions, signers) + .await?; + + Ok(()) + } +} diff --git a/rust-sdk/whirlpool/src/pool.rs b/rust-sdk/whirlpool/src/pool.rs index ae461ed8a..1b4d6d1ac 100644 --- a/rust-sdk/whirlpool/src/pool.rs +++ b/rust-sdk/whirlpool/src/pool.rs @@ -408,9 +408,9 @@ mod tests { setup_ata_with_amount(&ctx, mint_b, 500_000_000_000).await?; // Setup all pools - let concentrated_pool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; + let concentrated_pool = setup_whirlpool(&ctx, ctx.config, mint_a, mint_b, 64).await?; let splash_pool = - setup_whirlpool(&ctx, mint_a, mint_b, SPLASH_POOL_TICK_SPACING).await?; + setup_whirlpool(&ctx, ctx.config, mint_a, mint_b, SPLASH_POOL_TICK_SPACING).await?; Ok(Self { ctx, diff --git a/rust-sdk/whirlpool/src/position.rs b/rust-sdk/whirlpool/src/position.rs index a71b65c8d..460d7fba5 100644 --- a/rust-sdk/whirlpool/src/position.rs +++ b/rust-sdk/whirlpool/src/position.rs @@ -308,6 +308,8 @@ mod tests { use solana_program_test::tokio; use std::error::Error; + const DEFAULT_TICK_RANGE: (i32, i32) = (-100, 100); + #[tokio::test] #[serial] #[ignore = "Skipped until solana-bankrun supports gpa"] @@ -332,8 +334,9 @@ mod tests { setup_ata_with_amount(&ctx, mint_a, 1_000_000_000).await?; setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; - let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; - let position_pubkey = setup_position(whirlpool).await?; + let whirlpool = setup_whirlpool(&ctx, ctx.config, mint_a, mint_b, 64).await?; + let position_pubkey = + setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; let owner = ctx.signer.pubkey(); let positions = fetch_positions_for_owner(&ctx.rpc, owner).await?; @@ -362,8 +365,9 @@ mod tests { let mint_b = setup_mint_with_decimals(&ctx, 9).await?; setup_ata_with_amount(&ctx, mint_a, 1_000_000_000).await?; setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; - let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; - let _position_pubkey = setup_position(whirlpool).await?; + let whirlpool = setup_whirlpool(&ctx, ctx.config, mint_a, mint_b, 64).await?; + let _position_pubkey = + setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; let positions = fetch_positions_in_whirlpool(&ctx.rpc, whirlpool).await?; assert_eq!( diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index 145efd81e..d82272459 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -1,10 +1,14 @@ use orca_whirlpools_client::{ get_bundled_position_address, get_fee_tier_address, get_position_address, - get_position_bundle_address, get_token_badge_address, get_whirlpool_address, InitializePoolV2, - InitializePoolV2InstructionArgs, InitializePositionBundle, OpenBundledPosition, - OpenBundledPositionInstructionArgs, OpenPosition, OpenPositionInstructionArgs, + get_position_bundle_address, get_tick_array_address, get_token_badge_address, + get_whirlpool_address, InitializePoolV2, InitializePoolV2InstructionArgs, + InitializePositionBundle, InitializeTickArray, InitializeTickArrayInstructionArgs, + OpenBundledPosition, OpenBundledPositionInstructionArgs, OpenPosition, + OpenPositionInstructionArgs, Whirlpool, +}; +use orca_whirlpools_core::{ + get_initializable_tick_index, get_tick_array_start_tick_index, tick_index_to_sqrt_price, }; -use orca_whirlpools_core::tick_index_to_sqrt_price; use solana_program::program_pack::Pack; use solana_program::sysvar::rent::ID as RENT_PROGRAM_ID; use solana_sdk::{ @@ -13,27 +17,30 @@ use solana_sdk::{ system_instruction, system_program, }; use spl_associated_token_account::{ - get_associated_token_address, get_associated_token_address_with_program_id, - instruction::create_associated_token_account, + get_associated_token_address, instruction::create_associated_token_account, }; -use spl_token::{state::Mint, ID as TOKEN_PROGRAM_ID}; +use spl_token::instruction::initialize_mint2; +use spl_token::ID as TOKEN_PROGRAM_ID; use spl_token_2022::{state::Mint as Token2022Mint, ID as TOKEN_2022_PROGRAM_ID}; use std::error::Error; +use crate::tests::token::{setup_ata, setup_mint_with_decimals}; use crate::WHIRLPOOLS_CONFIG_ADDRESS; use super::rpc::RpcContext; -use crate::tests::token::{setup_ata, setup_mint_with_decimals}; use crate::tests::token_extensions::setup_mint_te; +use solana_program::system_instruction::create_account; +use spl_token::state::Mint; + pub async fn setup_whirlpool( ctx: &RpcContext, + config: Pubkey, token_a: Pubkey, token_b: Pubkey, tick_spacing: u16, ) -> Result> { - let config = *WHIRLPOOLS_CONFIG_ADDRESS.try_lock()?; let fee_tier = get_fee_tier_address(&config, tick_spacing)?.0; let whirlpool = get_whirlpool_address(&config, &token_a, &token_b, tick_spacing)?.0; let token_badge_a = get_token_badge_address(&config, &token_a)?.0; @@ -75,20 +82,74 @@ pub async fn setup_whirlpool( Ok(whirlpool) } -pub async fn setup_position(whirlpool: Pubkey) -> Result> { - let ctx = RpcContext::new().await; +pub async fn setup_position( + ctx: &RpcContext, + whirlpool: Pubkey, + tick_range: Option<(i32, i32)>, + owner: Option, +) -> Result> { + let owner = owner.unwrap_or_else(|| ctx.signer.pubkey()); + let whirlpool_data = ctx.rpc.get_account(&whirlpool).await?; + let whirlpool_account = Whirlpool::from_bytes(&whirlpool_data.data)?; - // Use token utility functions - let position_mint = setup_mint_with_decimals(&ctx, 0).await?; - let position_token_account = setup_ata(&ctx, position_mint).await?; + let (tick_lower, tick_upper) = tick_range.unwrap_or((-100, 100)); - let (position_pubkey, position_bump) = get_position_address(&position_mint)?; + let lower_tick_index = get_initializable_tick_index( + tick_lower - (tick_lower % whirlpool_account.tick_spacing as i32), + whirlpool_account.tick_spacing, + None, + ); + let upper_tick_index = get_initializable_tick_index( + tick_upper - (tick_upper % whirlpool_account.tick_spacing as i32), + whirlpool_account.tick_spacing, + None, + ); + + // Initialize tick arrays if needed + let tick_arrays = [ + get_tick_array_start_tick_index(lower_tick_index, whirlpool_account.tick_spacing), + get_tick_array_start_tick_index(upper_tick_index, whirlpool_account.tick_spacing), + ]; + + for start_tick in tick_arrays.iter() { + let (tick_array_address, _) = get_tick_array_address(&whirlpool, *start_tick)?; + + let account_result = ctx.rpc.get_account(&tick_array_address).await; + let needs_init = match account_result { + Ok(account) => account.data.is_empty(), + Err(_) => true, + }; + + if needs_init { + let init_tick_array_ix = InitializeTickArray { + whirlpool, + funder: ctx.signer.pubkey(), + tick_array: tick_array_address, + system_program: system_program::id(), + } + .instruction(InitializeTickArrayInstructionArgs { + start_tick_index: *start_tick, + }); + + ctx.send_transaction(vec![init_tick_array_ix]).await?; + } + } + // Create position mint + let position_mint = Keypair::new(); + + // Calculate position PDA + let (position_pubkey, position_bump) = get_position_address(&position_mint.pubkey())?; + + // Calculate position token account + let position_token_account = get_associated_token_address(&owner, &position_mint.pubkey()); + + // Create OpenPosition instruction let open_position_ix = OpenPosition { funder: ctx.signer.pubkey(), - owner: ctx.signer.pubkey(), + owner: owner, position: position_pubkey, - position_mint, + position_mint: position_mint.pubkey(), position_token_account, whirlpool, token_program: TOKEN_PROGRAM_ID, @@ -97,19 +158,66 @@ pub async fn setup_position(whirlpool: Pubkey) -> Result> rent: RENT_PROGRAM_ID, } .instruction(OpenPositionInstructionArgs { - tick_lower_index: -128, - tick_upper_index: 128, + tick_lower_index: lower_tick_index, + tick_upper_index: upper_tick_index, position_bump, }); - ctx.send_transaction(vec![open_position_ix]).await?; + ctx.send_transaction_with_signers(vec![open_position_ix], vec![&position_mint]) + .await?; - Ok(position_pubkey) + Ok(position_mint.pubkey()) } +pub async fn setup_te_position( + ctx: &RpcContext, + whirlpool: Pubkey, + tick_range: Option<(i32, i32)>, + owner: Option, +) -> Result> { + let owner = owner.unwrap_or_else(|| ctx.signer.pubkey()); + let whirlpool_data = ctx.rpc.get_account(&whirlpool).await?; + let whirlpool_account = Whirlpool::from_bytes(&whirlpool_data.data)?; + + // Get tick range + let (tick_lower, tick_upper) = tick_range.unwrap_or((-100, 100)); + + // Get initializable tick indexes + let lower_tick_index = + get_initializable_tick_index(tick_lower, whirlpool_account.tick_spacing, None); + let upper_tick_index = + get_initializable_tick_index(tick_upper, whirlpool_account.tick_spacing, None); + + // Initialize tick arrays if needed (재사용) + let tick_arrays = [ + get_tick_array_start_tick_index(lower_tick_index, whirlpool_account.tick_spacing), + get_tick_array_start_tick_index(upper_tick_index, whirlpool_account.tick_spacing), + ]; + + for start_tick in tick_arrays.iter() { + let (tick_array_address, _) = get_tick_array_address(&whirlpool, *start_tick)?; + + let account_result = ctx.rpc.get_account(&tick_array_address).await; + let needs_init = match account_result { + Ok(account) => account.data.is_empty(), + Err(_) => true, + }; + + if needs_init { + let init_tick_array_ix = InitializeTickArray { + whirlpool, + funder: ctx.signer.pubkey(), + tick_array: tick_array_address, + system_program: system_program::id(), + } + .instruction(InitializeTickArrayInstructionArgs { + start_tick_index: *start_tick, + }); -pub async fn setup_te_position(whirlpool: Pubkey) -> Result> { - let ctx = RpcContext::new().await; + ctx.send_transaction(vec![init_tick_array_ix]).await?; + } + } + // Create Token-2022 position let position_mint = Keypair::new(); let lamports = ctx .rpc @@ -144,12 +252,9 @@ pub async fn setup_te_position(whirlpool: Pubkey) -> Result Result Result Result<(), Box> { + // Set funder first + crate::set_funder(ctx.signer.pubkey()); + + // Then setup config using ctx.config + crate::set_whirlpools_config_address(crate::WhirlpoolsConfigInput::SolanaDevnet)?; + + Ok(()) +} From d2682234ac99e3e1eac467fae38bd38f9cfc59ec Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 19 Dec 2024 12:54:11 +0900 Subject: [PATCH 03/33] Update rpc --- rust-sdk/whirlpool/src/tests/rpc.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rust-sdk/whirlpool/src/tests/rpc.rs b/rust-sdk/whirlpool/src/tests/rpc.rs index a00fa9dc4..54690daff 100644 --- a/rust-sdk/whirlpool/src/tests/rpc.rs +++ b/rust-sdk/whirlpool/src/tests/rpc.rs @@ -40,6 +40,7 @@ use crate::{SPLASH_POOL_TICK_SPACING, WHIRLPOOLS_CONFIG_ADDRESS}; pub struct RpcContext { pub rpc: RpcClient, pub signer: Keypair, + pub config: Pubkey, keypairs: Vec, keypair_index: AtomicUsize, } @@ -62,6 +63,7 @@ impl RpcContext { ); let config = *WHIRLPOOLS_CONFIG_ADDRESS.lock().unwrap(); + test.add_account( config, Account { @@ -149,6 +151,7 @@ impl RpcContext { Self { rpc, signer, + config, keypairs, keypair_index: AtomicUsize::new(0), } From 901ed91a430a0811a092dd543e9b91720b3a2dc9 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 19 Dec 2024 12:58:03 +0900 Subject: [PATCH 04/33] Update format --- rust-sdk/whirlpool/src/increase_liquidity.rs | 2 +- rust-sdk/whirlpool/src/tests/program.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust-sdk/whirlpool/src/increase_liquidity.rs b/rust-sdk/whirlpool/src/increase_liquidity.rs index 9eba7654a..ea2fa6ecb 100644 --- a/rust-sdk/whirlpool/src/increase_liquidity.rs +++ b/rust-sdk/whirlpool/src/increase_liquidity.rs @@ -18,7 +18,6 @@ use solana_sdk::program_pack::Pack; use solana_sdk::signer::Signer; use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signature::Keypair}; use spl_associated_token_account::get_associated_token_address_with_program_id; -use spl_token::state::Account as TokenAccount; use spl_token_2022::state::Mint; use crate::{get_rent, SPLASH_POOL_TICK_SPACING}; @@ -26,6 +25,7 @@ use crate::{ token::{get_current_transfer_fee, prepare_token_accounts_instructions, TokenAccountStrategy}, FUNDER, SLIPPAGE_TOLERANCE_BPS, }; + // TODO: support transfer hooks fn get_increase_liquidity_quote( diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index d82272459..aab9ea20b 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -278,7 +278,7 @@ pub async fn setup_te_position( create_ata_ix, open_position_ix, ], - vec![&position_mint], // position_mint는 keypair signer가 필요합니다 + vec![&position_mint], ) .await?; From 476885d5fac0dfc899f8b94523ca9270a102188d Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 19 Dec 2024 23:43:07 +0900 Subject: [PATCH 05/33] Add decrease liquidity test --- rust-sdk/whirlpool/src/decrease_liquidity.rs | 295 ++++++++++++++++++- rust-sdk/whirlpool/src/tests/program.rs | 75 +++-- 2 files changed, 334 insertions(+), 36 deletions(-) diff --git a/rust-sdk/whirlpool/src/decrease_liquidity.rs b/rust-sdk/whirlpool/src/decrease_liquidity.rs index 15a2c99fb..5d743c1eb 100644 --- a/rust-sdk/whirlpool/src/decrease_liquidity.rs +++ b/rust-sdk/whirlpool/src/decrease_liquidity.rs @@ -20,6 +20,7 @@ use orca_whirlpools_core::{ use solana_client::nonblocking::rpc_client::RpcClient; use solana_sdk::{account::Account, instruction::Instruction, pubkey::Pubkey, signature::Keypair}; use spl_associated_token_account::get_associated_token_address_with_program_id; +use solana_sdk::program_pack::Pack; use crate::{ token::{get_current_transfer_fee, prepare_token_accounts_instructions, TokenAccountStrategy}, @@ -226,20 +227,20 @@ pub async fn decrease_liquidity_instructions( instructions.push( DecreaseLiquidityV2 { whirlpool: position.whirlpool, - token_program_a: mint_a_info.owner, - token_program_b: mint_b_info.owner, - memo_program: spl_memo::ID, position_authority: authority, position: position_address, position_token_account: position_token_account_address, - token_mint_a: pool.token_mint_a, - token_mint_b: pool.token_mint_b, token_owner_account_a: *token_owner_account_a, token_owner_account_b: *token_owner_account_b, token_vault_a: pool.token_vault_a, token_vault_b: pool.token_vault_b, + token_mint_a: pool.token_mint_a, + token_mint_b: pool.token_mint_b, + token_program_a: mint_a_info.owner, + token_program_b: mint_b_info.owner, tick_array_lower: lower_tick_array_address, tick_array_upper: upper_tick_array_address, + memo_program: spl_memo::ID, } .instruction(DecreaseLiquidityV2InstructionArgs { liquidity_amount: quote.liquidity_delta, @@ -516,20 +517,20 @@ pub async fn close_position_instructions( instructions.push( DecreaseLiquidityV2 { whirlpool: position.whirlpool, - token_program_a: mint_a_info.owner, - token_program_b: mint_b_info.owner, - memo_program: spl_memo::ID, position_authority: authority, position: position_address, position_token_account: position_token_account_address, - token_mint_a: pool.token_mint_a, - token_mint_b: pool.token_mint_b, token_owner_account_a: *token_owner_account_a, token_owner_account_b: *token_owner_account_b, token_vault_a: pool.token_vault_a, token_vault_b: pool.token_vault_b, + token_mint_a: pool.token_mint_a, + token_mint_b: pool.token_mint_b, + token_program_a: mint_a_info.owner, + token_program_b: mint_b_info.owner, tick_array_lower: lower_tick_array_address, tick_array_upper: upper_tick_array_address, + memo_program: spl_memo::ID, } .instruction(DecreaseLiquidityV2InstructionArgs { liquidity_amount: quote.liquidity_delta, @@ -581,8 +582,8 @@ pub async fn close_position_instructions( position: position_address, position_token_account: position_token_account_address, reward_owner_account: *reward_owner, - reward_vault: pool.reward_infos[i].vault, reward_mint: pool.reward_infos[i].mint, + reward_vault: pool.reward_infos[i].vault, reward_token_program: reward_info.owner, memo_program: spl_memo::ID, } @@ -635,3 +636,275 @@ pub async fn close_position_instructions( rewards_quote, }) } + +#[cfg(test)] +mod tests { + use super::*; + + // Helper functions + async fn fetch_token(rpc: &RpcClient, address: Pubkey) -> Result> { + let account = rpc.get_account(&address).await?; + TokenAccount::unpack(&account.data).map_err(|e| e.into()) + } + + async fn fetch_position(rpc: &RpcClient, address: Pubkey) -> Result> { + let account = rpc.get_account(&address).await?; + Position::from_bytes(&account.data).map_err(|e| e.into()) + } + + use crate::tests::{ + setup_ata_te, setup_ata_with_amount, setup_config_and_fee_tiers, + setup_mint_te_fee, setup_mint_with_decimals, setup_position, setup_te_position, + setup_whirlpool, RpcContext, SetupAtaConfig, + }; + use solana_sdk::signature::{Keypair, Signer}; + use solana_sdk::pubkey::Pubkey; + use spl_associated_token_account::get_associated_token_address_with_program_id; + use spl_token::state::Account as TokenAccount; + use serial_test::serial; + use std::error::Error; + + const TEST_SLIPPAGE_TOLERANCE: u16 = 100; // 1% + + #[tokio::test] + #[serial] + async fn test_decrease_liquidity() -> Result<(), Box> { + let ctx = RpcContext::new().await; + + // Setup and initialize + setup_config_and_fee_tiers(&ctx).await?; + let mint_a_pubkey = setup_mint_with_decimals(&ctx, 9).await?; + let mint_b_pubkey = setup_mint_with_decimals(&ctx, 9).await?; + let token_balance: u64 = 1_000_000_000; + setup_ata_with_amount(&ctx, mint_a_pubkey, token_balance).await?; + setup_ata_with_amount(&ctx, mint_b_pubkey, token_balance).await?; + + // Setup pool and position + let tick_spacing = 64; + let pool_pubkey = + setup_whirlpool(&ctx, ctx.config, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; + + let position_mint = setup_position( + &ctx, + pool_pubkey, + Some((-128, 128)), + Some(ctx.signer.pubkey()), + ) + .await?; + + // Increase liquidity first to have liquidity to decrease + let increase_param = crate::increase_liquidity::IncreaseLiquidityParam::Liquidity(100_000); + let increase_ix = crate::increase_liquidity::increase_liquidity_instructions( + &ctx.rpc, + position_mint, + increase_param, + Some(TEST_SLIPPAGE_TOLERANCE), + Some(ctx.signer.pubkey()), + ) + .await?; + + ctx.send_transaction_with_signers(increase_ix.instructions, vec![]) + .await?; + + // Test Decrease Liquidity + let decrease_param = DecreaseLiquidityParam::Liquidity(50_000); + let decrease_ix = decrease_liquidity_instructions( + &ctx.rpc, + position_mint, + decrease_param, + Some(TEST_SLIPPAGE_TOLERANCE), + Some(ctx.signer.pubkey()), + ) + .await?; + + // Send transaction + ctx.send_transaction_with_signers(decrease_ix.instructions, vec![]) + .await?; + + // Fetch the updated position and check liquidity + let position_address = get_position_address(&position_mint)?.0; + let updated_position = fetch_position(&ctx.rpc, position_address).await?; + + assert_eq!(updated_position.liquidity, 50_000); + + Ok(()) + } + + // Additional test cases based on decreaseLiquidity.test.ts + + #[tokio::test] + #[serial] + async fn test_decrease_liquidity_to_zero() -> Result<(), Box> { + let ctx = RpcContext::new().await; + + // Setup and initialize + setup_config_and_fee_tiers(&ctx).await?; + let mint_a_pubkey = setup_mint_with_decimals(&ctx, 9).await?; + let mint_b_pubkey = setup_mint_with_decimals(&ctx, 9).await?; + let token_balance: u64 = 1_000_000_000; + setup_ata_with_amount(&ctx, mint_a_pubkey, token_balance).await?; + setup_ata_with_amount(&ctx, mint_b_pubkey, token_balance).await?; + + // Setup pool and position + let tick_spacing = 64; + let pool_pubkey = + setup_whirlpool(&ctx, ctx.config, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; + + let position_mint = setup_position( + &ctx, + pool_pubkey, + Some((-128, 128)), + Some(ctx.signer.pubkey()), + ) + .await?; + + // Increase liquidity first to have liquidity to decrease + let increase_param = crate::increase_liquidity::IncreaseLiquidityParam::Liquidity(100_000); + let increase_ix = crate::increase_liquidity::increase_liquidity_instructions( + &ctx.rpc, + position_mint, + increase_param, + Some(TEST_SLIPPAGE_TOLERANCE), + Some(ctx.signer.pubkey()), + ) + .await?; + + ctx.send_transaction_with_signers(increase_ix.instructions, vec![]) + .await?; + + // Decrease liquidity to zero + let decrease_param = DecreaseLiquidityParam::Liquidity(100_000); + let decrease_ix = decrease_liquidity_instructions( + &ctx.rpc, + position_mint, + decrease_param, + Some(TEST_SLIPPAGE_TOLERANCE), + Some(ctx.signer.pubkey()), + ) + .await?; + + // Send transaction + ctx.send_transaction_with_signers(decrease_ix.instructions, vec![]) + .await?; + + // Fetch the updated position and check liquidity + let position_address = get_position_address(&position_mint)?.0; + let updated_position = fetch_position(&ctx.rpc, position_address).await?; + + assert_eq!(updated_position.liquidity, 0); + + Ok(()) + } + + #[tokio::test] + #[serial] + async fn test_decrease_liquidity_with_transfer_fee() -> Result<(), Box> { + println!("Starting test_decrease_liquidity_with_transfer_fee"); + let ctx = RpcContext::new().await; + + // Setup and initialize mints with transfer fees + println!("Setting up mints with transfer fees"); + let mint_a_pubkey = setup_mint_te_fee(&ctx).await?; + let mint_b_pubkey = setup_mint_te_fee(&ctx).await?; + let token_balance: u64 = 1_000_000_000; + + // Setup token accounts + println!("Setting up token accounts for mint A"); + setup_ata_te( + &ctx, + mint_a_pubkey, + Some(SetupAtaConfig { + amount: Some(token_balance), + ..Default::default() + }), + ) + .await?; + + println!("Setting up token accounts for mint B"); + setup_ata_te( + &ctx, + mint_b_pubkey, + Some(SetupAtaConfig { + amount: Some(token_balance), + ..Default::default() + }), + ) + .await?; + + // Setup pool and position + println!("Setting up whirlpool and position"); + let tick_spacing = 64; + let pool_pubkey = + setup_whirlpool(&ctx, ctx.config, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; + println!("Whirlpool pubkey: {}", pool_pubkey); + let position_mint = setup_te_position( + &ctx, + pool_pubkey, + Some((-128, 128)), + Some(ctx.signer.pubkey()), + ) + .await?; + + // Increase liquidity first to have liquidity to decrease + println!("Increasing initial liquidity by 100,000"); + let increase_param = crate::increase_liquidity::IncreaseLiquidityParam::Liquidity(100_000); + let increase_ix = crate::increase_liquidity::increase_liquidity_instructions( + &ctx.rpc, + position_mint, + increase_param, + Some(TEST_SLIPPAGE_TOLERANCE), + Some(ctx.signer.pubkey()), + ) + .await?; + + ctx.send_transaction_with_signers(increase_ix.instructions, vec![]) + .await?; + + // Decrease liquidity + println!("Decreasing liquidity by 50,000"); + let decrease_param = DecreaseLiquidityParam::Liquidity(50_000); + let decrease_ix = decrease_liquidity_instructions( + &ctx.rpc, + position_mint, + decrease_param, + Some(TEST_SLIPPAGE_TOLERANCE), + Some(ctx.signer.pubkey()), + ) + .await?; + + // Send transaction + println!("Sending decrease liquidity transaction"); + ctx.send_transaction_with_signers(decrease_ix.instructions, vec![]) + .await?; + + // Fetch the token accounts and check balances + println!("Fetching token accounts to verify balances"); + + // Fetch token accounts + let owner_token_account_a = get_associated_token_address_with_program_id( + &ctx.signer.pubkey(), + &mint_a_pubkey, + &spl_token_2022::ID, + ); + let owner_token_account_b = get_associated_token_address_with_program_id( + &ctx.signer.pubkey(), + &mint_b_pubkey, + &spl_token_2022::ID, + ); + + let token_account_a = fetch_token(&ctx.rpc, owner_token_account_a).await?; + let token_account_b = fetch_token(&ctx.rpc, owner_token_account_b).await?; + + println!("Token A account balance: {}", token_account_a.amount); + println!("Token B account balance: {}", token_account_b.amount); + + // Expected amounts with transfer fees applied + // Since decrease_liquidity returns collected tokens, need to calculate fees + + // For simplicity, we'll assume the collected tokens are correct + // In a real test, you would calculate expected balances based on the fees and amounts + println!("Test completed successfully"); + + Ok(()) + } +} diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index aab9ea20b..d12abafd4 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -18,8 +18,8 @@ use solana_sdk::{ }; use spl_associated_token_account::{ get_associated_token_address, instruction::create_associated_token_account, + get_associated_token_address_with_program_id, }; -use spl_token::instruction::initialize_mint2; use spl_token::ID as TOKEN_PROGRAM_ID; use spl_token_2022::{state::Mint as Token2022Mint, ID as TOKEN_2022_PROGRAM_ID}; use std::error::Error; @@ -43,31 +43,41 @@ pub async fn setup_whirlpool( ) -> Result> { let fee_tier = get_fee_tier_address(&config, tick_spacing)?.0; let whirlpool = get_whirlpool_address(&config, &token_a, &token_b, tick_spacing)?.0; - let token_badge_a = get_token_badge_address(&config, &token_a)?.0; - let token_badge_b = get_token_badge_address(&config, &token_b)?.0; + let (token_badge_a, _) = get_token_badge_address(&config, &token_a)?; + let (token_badge_b, _) = get_token_badge_address(&config, &token_b)?; let vault_a = ctx.get_next_keypair(); let vault_b = ctx.get_next_keypair(); - let mint_a_info = ctx.rpc.get_account(&token_a).await?; - let mint_b_info = ctx.rpc.get_account(&token_b).await?; + let mint_infos = ctx + .rpc + .get_multiple_accounts(&[token_a, token_b]) + .await?; + let mint_a_info = mint_infos[0] + .as_ref() + .ok_or("Token A mint info not found")?; + let mint_b_info = mint_infos[1] + .as_ref() + .ok_or("Token B mint info not found")?; + + let token_program_a = mint_a_info.owner; + let token_program_b = mint_b_info.owner; - // Default initial price of 1.0 let sqrt_price = tick_index_to_sqrt_price(0); let instructions = vec![InitializePoolV2 { - whirlpool, - fee_tier, + whirlpools_config: config, token_mint_a: token_a, token_mint_b: token_b, - whirlpools_config: config, + token_badge_a: token_badge_a, + token_badge_b: token_badge_b, funder: ctx.signer.pubkey(), + whirlpool, token_vault_a: vault_a.pubkey(), token_vault_b: vault_b.pubkey(), - token_badge_a, - token_badge_b, - token_program_a: mint_a_info.owner, - token_program_b: mint_b_info.owner, + fee_tier: fee_tier, + token_program_a: token_program_a, + token_program_b: token_program_b, system_program: system_program::id(), rent: RENT_PROGRAM_ID, } @@ -174,27 +184,31 @@ pub async fn setup_te_position( tick_range: Option<(i32, i32)>, owner: Option, ) -> Result> { + println!("Starting setup_te_position"); let owner = owner.unwrap_or_else(|| ctx.signer.pubkey()); let whirlpool_data = ctx.rpc.get_account(&whirlpool).await?; let whirlpool_account = Whirlpool::from_bytes(&whirlpool_data.data)?; // Get tick range - let (tick_lower, tick_upper) = tick_range.unwrap_or((-100, 100)); + let (tick_lower, tick_upper) = tick_range.unwrap_or((-128, 128)); + println!("Tick range: lower={}, upper={}", tick_lower, tick_upper); - // Get initializable tick indexes - let lower_tick_index = - get_initializable_tick_index(tick_lower, whirlpool_account.tick_spacing, None); - let upper_tick_index = - get_initializable_tick_index(tick_upper, whirlpool_account.tick_spacing, None); + // Tick Index를 Tick Spacing에 맞게 정렬 + let tick_spacing = whirlpool_account.tick_spacing as i32; + let tick_lower_aligned = (tick_lower / tick_spacing) * tick_spacing; + let tick_upper_aligned = (tick_upper / tick_spacing) * tick_spacing; + println!("Aligned ticks: lower={}, upper={}", tick_lower_aligned, tick_upper_aligned); - // Initialize tick arrays if needed (재사용) + // Initialize tick arrays if needed let tick_arrays = [ - get_tick_array_start_tick_index(lower_tick_index, whirlpool_account.tick_spacing), - get_tick_array_start_tick_index(upper_tick_index, whirlpool_account.tick_spacing), + get_tick_array_start_tick_index(tick_lower_aligned, whirlpool_account.tick_spacing), + get_tick_array_start_tick_index(tick_upper_aligned, whirlpool_account.tick_spacing), ]; + println!("Tick array start indices: {:?}", tick_arrays); for start_tick in tick_arrays.iter() { let (tick_array_address, _) = get_tick_array_address(&whirlpool, *start_tick)?; + println!("Processing tick array at index {}", start_tick); let account_result = ctx.rpc.get_account(&tick_array_address).await; let needs_init = match account_result { @@ -203,6 +217,7 @@ pub async fn setup_te_position( }; if needs_init { + println!("Initializing tick array at index {}", start_tick); let init_tick_array_ix = InitializeTickArray { whirlpool, funder: ctx.signer.pubkey(), @@ -214,10 +229,14 @@ pub async fn setup_te_position( }); ctx.send_transaction(vec![init_tick_array_ix]).await?; + println!("Tick array initialized successfully"); + } else { + println!("Tick array already initialized"); } } // Create Token-2022 position + println!("Creating Token-2022 position"); let position_mint = Keypair::new(); let lamports = ctx .rpc @@ -241,7 +260,11 @@ pub async fn setup_te_position( )?; let position_token_account = - get_associated_token_address(&ctx.signer.pubkey(), &position_mint.pubkey()); + get_associated_token_address_with_program_id( + &ctx.signer.pubkey(), + &position_mint.pubkey(), + &TOKEN_2022_PROGRAM_ID, + ); let create_ata_ix = create_associated_token_account( &ctx.signer.pubkey(), @@ -251,6 +274,7 @@ pub async fn setup_te_position( ); let (position_pubkey, position_bump) = get_position_address(&position_mint.pubkey())?; + println!("Position PDA: {}", position_pubkey); let open_position_ix = OpenPosition { funder: ctx.signer.pubkey(), @@ -265,8 +289,8 @@ pub async fn setup_te_position( rent: RENT_PROGRAM_ID, } .instruction(OpenPositionInstructionArgs { - tick_lower_index: lower_tick_index, - tick_upper_index: upper_tick_index, + tick_lower_index: tick_lower_aligned, + tick_upper_index: tick_upper_aligned, position_bump, }); @@ -281,6 +305,7 @@ pub async fn setup_te_position( vec![&position_mint], ) .await?; + println!("Transaction completed successfully"); Ok(position_pubkey) } From 208a7ca72da16ed9d8401552d6bfe836a0c076c2 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 19 Dec 2024 23:44:19 +0900 Subject: [PATCH 06/33] fmt --- rust-sdk/whirlpool/src/decrease_liquidity.rs | 124 +------------------ rust-sdk/whirlpool/src/tests/program.rs | 25 ++-- 2 files changed, 18 insertions(+), 131 deletions(-) diff --git a/rust-sdk/whirlpool/src/decrease_liquidity.rs b/rust-sdk/whirlpool/src/decrease_liquidity.rs index 5d743c1eb..c07d59985 100644 --- a/rust-sdk/whirlpool/src/decrease_liquidity.rs +++ b/rust-sdk/whirlpool/src/decrease_liquidity.rs @@ -18,9 +18,9 @@ use orca_whirlpools_core::{ get_tick_index_in_array, CollectFeesQuote, CollectRewardsQuote, DecreaseLiquidityQuote, }; use solana_client::nonblocking::rpc_client::RpcClient; +use solana_sdk::program_pack::Pack; use solana_sdk::{account::Account, instruction::Instruction, pubkey::Pubkey, signature::Keypair}; use spl_associated_token_account::get_associated_token_address_with_program_id; -use solana_sdk::program_pack::Pack; use crate::{ token::{get_current_transfer_fee, prepare_token_accounts_instructions, TokenAccountStrategy}, @@ -653,15 +653,15 @@ mod tests { } use crate::tests::{ - setup_ata_te, setup_ata_with_amount, setup_config_and_fee_tiers, - setup_mint_te_fee, setup_mint_with_decimals, setup_position, setup_te_position, - setup_whirlpool, RpcContext, SetupAtaConfig, + setup_ata_te, setup_ata_with_amount, setup_config_and_fee_tiers, setup_mint_te_fee, + setup_mint_with_decimals, setup_position, setup_te_position, setup_whirlpool, RpcContext, + SetupAtaConfig, }; - use solana_sdk::signature::{Keypair, Signer}; + use serial_test::serial; use solana_sdk::pubkey::Pubkey; + use solana_sdk::signature::{Keypair, Signer}; use spl_associated_token_account::get_associated_token_address_with_program_id; use spl_token::state::Account as TokenAccount; - use serial_test::serial; use std::error::Error; const TEST_SLIPPAGE_TOLERANCE: u16 = 100; // 1% @@ -795,116 +795,4 @@ mod tests { Ok(()) } - - #[tokio::test] - #[serial] - async fn test_decrease_liquidity_with_transfer_fee() -> Result<(), Box> { - println!("Starting test_decrease_liquidity_with_transfer_fee"); - let ctx = RpcContext::new().await; - - // Setup and initialize mints with transfer fees - println!("Setting up mints with transfer fees"); - let mint_a_pubkey = setup_mint_te_fee(&ctx).await?; - let mint_b_pubkey = setup_mint_te_fee(&ctx).await?; - let token_balance: u64 = 1_000_000_000; - - // Setup token accounts - println!("Setting up token accounts for mint A"); - setup_ata_te( - &ctx, - mint_a_pubkey, - Some(SetupAtaConfig { - amount: Some(token_balance), - ..Default::default() - }), - ) - .await?; - - println!("Setting up token accounts for mint B"); - setup_ata_te( - &ctx, - mint_b_pubkey, - Some(SetupAtaConfig { - amount: Some(token_balance), - ..Default::default() - }), - ) - .await?; - - // Setup pool and position - println!("Setting up whirlpool and position"); - let tick_spacing = 64; - let pool_pubkey = - setup_whirlpool(&ctx, ctx.config, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; - println!("Whirlpool pubkey: {}", pool_pubkey); - let position_mint = setup_te_position( - &ctx, - pool_pubkey, - Some((-128, 128)), - Some(ctx.signer.pubkey()), - ) - .await?; - - // Increase liquidity first to have liquidity to decrease - println!("Increasing initial liquidity by 100,000"); - let increase_param = crate::increase_liquidity::IncreaseLiquidityParam::Liquidity(100_000); - let increase_ix = crate::increase_liquidity::increase_liquidity_instructions( - &ctx.rpc, - position_mint, - increase_param, - Some(TEST_SLIPPAGE_TOLERANCE), - Some(ctx.signer.pubkey()), - ) - .await?; - - ctx.send_transaction_with_signers(increase_ix.instructions, vec![]) - .await?; - - // Decrease liquidity - println!("Decreasing liquidity by 50,000"); - let decrease_param = DecreaseLiquidityParam::Liquidity(50_000); - let decrease_ix = decrease_liquidity_instructions( - &ctx.rpc, - position_mint, - decrease_param, - Some(TEST_SLIPPAGE_TOLERANCE), - Some(ctx.signer.pubkey()), - ) - .await?; - - // Send transaction - println!("Sending decrease liquidity transaction"); - ctx.send_transaction_with_signers(decrease_ix.instructions, vec![]) - .await?; - - // Fetch the token accounts and check balances - println!("Fetching token accounts to verify balances"); - - // Fetch token accounts - let owner_token_account_a = get_associated_token_address_with_program_id( - &ctx.signer.pubkey(), - &mint_a_pubkey, - &spl_token_2022::ID, - ); - let owner_token_account_b = get_associated_token_address_with_program_id( - &ctx.signer.pubkey(), - &mint_b_pubkey, - &spl_token_2022::ID, - ); - - let token_account_a = fetch_token(&ctx.rpc, owner_token_account_a).await?; - let token_account_b = fetch_token(&ctx.rpc, owner_token_account_b).await?; - - println!("Token A account balance: {}", token_account_a.amount); - println!("Token B account balance: {}", token_account_b.amount); - - // Expected amounts with transfer fees applied - // Since decrease_liquidity returns collected tokens, need to calculate fees - - // For simplicity, we'll assume the collected tokens are correct - // In a real test, you would calculate expected balances based on the fees and amounts - println!("Test completed successfully"); - - Ok(()) - } } diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index d12abafd4..15f3cdee4 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -17,8 +17,8 @@ use solana_sdk::{ system_instruction, system_program, }; use spl_associated_token_account::{ - get_associated_token_address, instruction::create_associated_token_account, - get_associated_token_address_with_program_id, + get_associated_token_address, get_associated_token_address_with_program_id, + instruction::create_associated_token_account, }; use spl_token::ID as TOKEN_PROGRAM_ID; use spl_token_2022::{state::Mint as Token2022Mint, ID as TOKEN_2022_PROGRAM_ID}; @@ -49,10 +49,7 @@ pub async fn setup_whirlpool( let vault_a = ctx.get_next_keypair(); let vault_b = ctx.get_next_keypair(); - let mint_infos = ctx - .rpc - .get_multiple_accounts(&[token_a, token_b]) - .await?; + let mint_infos = ctx.rpc.get_multiple_accounts(&[token_a, token_b]).await?; let mint_a_info = mint_infos[0] .as_ref() .ok_or("Token A mint info not found")?; @@ -197,7 +194,10 @@ pub async fn setup_te_position( let tick_spacing = whirlpool_account.tick_spacing as i32; let tick_lower_aligned = (tick_lower / tick_spacing) * tick_spacing; let tick_upper_aligned = (tick_upper / tick_spacing) * tick_spacing; - println!("Aligned ticks: lower={}, upper={}", tick_lower_aligned, tick_upper_aligned); + println!( + "Aligned ticks: lower={}, upper={}", + tick_lower_aligned, tick_upper_aligned + ); // Initialize tick arrays if needed let tick_arrays = [ @@ -259,12 +259,11 @@ pub async fn setup_te_position( 0, )?; - let position_token_account = - get_associated_token_address_with_program_id( - &ctx.signer.pubkey(), - &position_mint.pubkey(), - &TOKEN_2022_PROGRAM_ID, - ); + let position_token_account = get_associated_token_address_with_program_id( + &ctx.signer.pubkey(), + &position_mint.pubkey(), + &TOKEN_2022_PROGRAM_ID, + ); let create_ata_ix = create_associated_token_account( &ctx.signer.pubkey(), From 7a8934df752e6ecef2d5d7203e3cef08b3f4ebd3 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 9 Jan 2025 04:41:02 +0900 Subject: [PATCH 07/33] Address comments --- rust-sdk/whirlpool/src/position.rs | 24 ++++++++++++++++-------- rust-sdk/whirlpool/src/tests/program.rs | 10 +++------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/rust-sdk/whirlpool/src/position.rs b/rust-sdk/whirlpool/src/position.rs index a71b65c8d..7788824b8 100644 --- a/rust-sdk/whirlpool/src/position.rs +++ b/rust-sdk/whirlpool/src/position.rs @@ -335,12 +335,16 @@ mod tests { let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; let position_pubkey = setup_position(whirlpool).await?; + let te_mint = setup_mint_with_decimals(&ctx, 9).await?; + setup_ata_with_amount(&ctx, te_mint, 1_000_000_000).await?; + let te_position_pubkey = setup_position(whirlpool).await?; + let owner = ctx.signer.pubkey(); let positions = fetch_positions_for_owner(&ctx.rpc, owner).await?; - assert_eq!( - positions.len(), - 1, - "Should have one position after setup_position" + + assert!( + positions.len() >= 2, + "Did not fetch all positions for the owner (expected two or more)" ); match &positions[0] { @@ -362,14 +366,18 @@ mod tests { let mint_b = setup_mint_with_decimals(&ctx, 9).await?; setup_ata_with_amount(&ctx, mint_a, 1_000_000_000).await?; setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; + let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; let _position_pubkey = setup_position(whirlpool).await?; + let te_mint = setup_mint_with_decimals(&ctx, 9).await?; + setup_ata_with_amount(&ctx, te_mint, 1_000_000_000).await?; + let _te_position_pubkey = setup_position(whirlpool).await?; + let positions = fetch_positions_in_whirlpool(&ctx.rpc, whirlpool).await?; - assert_eq!( - positions.len(), - 1, - "Should find one position in this whirlpool" + assert!( + positions.len() >= 2, + "Should find multiple positions in this whirlpool, including te_position" ); Ok(()) diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index 145efd81e..1dbdfc3f9 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -78,18 +78,14 @@ pub async fn setup_whirlpool( pub async fn setup_position(whirlpool: Pubkey) -> Result> { let ctx = RpcContext::new().await; - // Use token utility functions - let position_mint = setup_mint_with_decimals(&ctx, 0).await?; - let position_token_account = setup_ata(&ctx, position_mint).await?; - - let (position_pubkey, position_bump) = get_position_address(&position_mint)?; + let (position_pubkey, position_bump) = get_position_address(&Pubkey::new_unique())?; let open_position_ix = OpenPosition { funder: ctx.signer.pubkey(), owner: ctx.signer.pubkey(), position: position_pubkey, - position_mint, - position_token_account, + position_mint: Pubkey::new_unique(), + position_token_account: Pubkey::default(), whirlpool, token_program: TOKEN_PROGRAM_ID, system_program: system_program::id(), From 217552a83012200bbdc7eb6d2a5bd20f1667bdb8 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 9 Jan 2025 06:32:37 +0900 Subject: [PATCH 08/33] Remove print Remove unused config function --- rust-sdk/whirlpool/src/increase_liquidity.rs | 3 +-- rust-sdk/whirlpool/src/tests/program.rs | 10 ---------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/rust-sdk/whirlpool/src/increase_liquidity.rs b/rust-sdk/whirlpool/src/increase_liquidity.rs index ea2fa6ecb..3a2c05bfd 100644 --- a/rust-sdk/whirlpool/src/increase_liquidity.rs +++ b/rust-sdk/whirlpool/src/increase_liquidity.rs @@ -706,7 +706,7 @@ mod tests { use crate::{ tests::{ - setup_ata_te, setup_ata_with_amount, setup_config_and_fee_tiers, setup_mint_te, + setup_ata_te, setup_ata_with_amount, setup_mint_te, setup_mint_te_fee, setup_mint_with_decimals, setup_position, setup_te_position, setup_whirlpool, RpcContext, SetupAtaConfig, }, @@ -734,7 +734,6 @@ mod tests { let ctx = RpcContext::new().await; // setup and initialize - setup_config_and_fee_tiers(&ctx).await?; let mint_a_pubkey = setup_mint_with_decimals(&ctx, 9).await?; let mint_b_pubkey = setup_mint_with_decimals(&ctx, 9).await?; let token_balance: u64 = 1_000_000; diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index aab9ea20b..92fa43323 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -343,13 +343,3 @@ pub async fn setup_position_bundle( Ok(position_bundle_address) } - -pub async fn setup_config_and_fee_tiers(ctx: &RpcContext) -> Result<(), Box> { - // Set funder first - crate::set_funder(ctx.signer.pubkey()); - - // Then setup config using ctx.config - crate::set_whirlpools_config_address(crate::WhirlpoolsConfigInput::SolanaDevnet)?; - - Ok(()) -} From ecefd3354e3f1f44a1fd9f9606d19e4520b9b66d Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 9 Jan 2025 20:31:10 +0900 Subject: [PATCH 09/33] Update comments --- rust-sdk/whirlpool/src/decrease_liquidity.rs | 1 - rust-sdk/whirlpool/src/tests/program.rs | 20 -------------------- 2 files changed, 21 deletions(-) diff --git a/rust-sdk/whirlpool/src/decrease_liquidity.rs b/rust-sdk/whirlpool/src/decrease_liquidity.rs index c07d59985..6371bb0d5 100644 --- a/rust-sdk/whirlpool/src/decrease_liquidity.rs +++ b/rust-sdk/whirlpool/src/decrease_liquidity.rs @@ -730,7 +730,6 @@ mod tests { Ok(()) } - // Additional test cases based on decreaseLiquidity.test.ts #[tokio::test] #[serial] diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index 15f3cdee4..8818523a8 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -181,34 +181,23 @@ pub async fn setup_te_position( tick_range: Option<(i32, i32)>, owner: Option, ) -> Result> { - println!("Starting setup_te_position"); let owner = owner.unwrap_or_else(|| ctx.signer.pubkey()); let whirlpool_data = ctx.rpc.get_account(&whirlpool).await?; let whirlpool_account = Whirlpool::from_bytes(&whirlpool_data.data)?; - // Get tick range let (tick_lower, tick_upper) = tick_range.unwrap_or((-128, 128)); - println!("Tick range: lower={}, upper={}", tick_lower, tick_upper); - // Tick Index를 Tick Spacing에 맞게 정렬 let tick_spacing = whirlpool_account.tick_spacing as i32; let tick_lower_aligned = (tick_lower / tick_spacing) * tick_spacing; let tick_upper_aligned = (tick_upper / tick_spacing) * tick_spacing; - println!( - "Aligned ticks: lower={}, upper={}", - tick_lower_aligned, tick_upper_aligned - ); - // Initialize tick arrays if needed let tick_arrays = [ get_tick_array_start_tick_index(tick_lower_aligned, whirlpool_account.tick_spacing), get_tick_array_start_tick_index(tick_upper_aligned, whirlpool_account.tick_spacing), ]; - println!("Tick array start indices: {:?}", tick_arrays); for start_tick in tick_arrays.iter() { let (tick_array_address, _) = get_tick_array_address(&whirlpool, *start_tick)?; - println!("Processing tick array at index {}", start_tick); let account_result = ctx.rpc.get_account(&tick_array_address).await; let needs_init = match account_result { @@ -217,7 +206,6 @@ pub async fn setup_te_position( }; if needs_init { - println!("Initializing tick array at index {}", start_tick); let init_tick_array_ix = InitializeTickArray { whirlpool, funder: ctx.signer.pubkey(), @@ -229,14 +217,9 @@ pub async fn setup_te_position( }); ctx.send_transaction(vec![init_tick_array_ix]).await?; - println!("Tick array initialized successfully"); - } else { - println!("Tick array already initialized"); } } - // Create Token-2022 position - println!("Creating Token-2022 position"); let position_mint = Keypair::new(); let lamports = ctx .rpc @@ -273,7 +256,6 @@ pub async fn setup_te_position( ); let (position_pubkey, position_bump) = get_position_address(&position_mint.pubkey())?; - println!("Position PDA: {}", position_pubkey); let open_position_ix = OpenPosition { funder: ctx.signer.pubkey(), @@ -293,7 +275,6 @@ pub async fn setup_te_position( position_bump, }); - println!("Sending transaction with instructions..."); ctx.send_transaction_with_signers( vec![ create_mint_ix, @@ -304,7 +285,6 @@ pub async fn setup_te_position( vec![&position_mint], ) .await?; - println!("Transaction completed successfully"); Ok(position_pubkey) } From 1cad741d6658dea3654f4d1804569853d8e3e0ea Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 9 Jan 2025 20:31:29 +0900 Subject: [PATCH 10/33] Fix fmt --- rust-sdk/whirlpool/src/decrease_liquidity.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust-sdk/whirlpool/src/decrease_liquidity.rs b/rust-sdk/whirlpool/src/decrease_liquidity.rs index 6371bb0d5..ddf15cce3 100644 --- a/rust-sdk/whirlpool/src/decrease_liquidity.rs +++ b/rust-sdk/whirlpool/src/decrease_liquidity.rs @@ -730,7 +730,6 @@ mod tests { Ok(()) } - #[tokio::test] #[serial] async fn test_decrease_liquidity_to_zero() -> Result<(), Box> { From 74fe0b3f3507afe622d9a3ffbcf096ea65f55d02 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 9 Jan 2025 21:34:52 +0900 Subject: [PATCH 11/33] Cleanup unused variables and wrong test --- rust-sdk/whirlpool/src/increase_liquidity.rs | 9 ++++----- rust-sdk/whirlpool/src/pool.rs | 4 ++-- rust-sdk/whirlpool/src/position.rs | 10 ++++++---- rust-sdk/whirlpool/src/tests/program.rs | 3 +-- rust-sdk/whirlpool/src/tests/rpc.rs | 2 -- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/rust-sdk/whirlpool/src/increase_liquidity.rs b/rust-sdk/whirlpool/src/increase_liquidity.rs index 3a2c05bfd..37b54139f 100644 --- a/rust-sdk/whirlpool/src/increase_liquidity.rs +++ b/rust-sdk/whirlpool/src/increase_liquidity.rs @@ -706,9 +706,9 @@ mod tests { use crate::{ tests::{ - setup_ata_te, setup_ata_with_amount, setup_mint_te, - setup_mint_te_fee, setup_mint_with_decimals, setup_position, setup_te_position, - setup_whirlpool, RpcContext, SetupAtaConfig, + setup_ata_te, setup_ata_with_amount, setup_mint_te, setup_mint_te_fee, + setup_mint_with_decimals, setup_position, setup_te_position, setup_whirlpool, + RpcContext, SetupAtaConfig, }, DEFAULT_FUNDER, WHIRLPOOLS_CONFIG_ADDRESS, }; @@ -742,8 +742,7 @@ mod tests { // setup pool and position let tick_spacing = 64; - let pool_pubkey = - setup_whirlpool(&ctx, ctx.config, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; + let pool_pubkey = setup_whirlpool(&ctx, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; let position_mint = setup_position( &ctx, diff --git a/rust-sdk/whirlpool/src/pool.rs b/rust-sdk/whirlpool/src/pool.rs index 1b4d6d1ac..ae461ed8a 100644 --- a/rust-sdk/whirlpool/src/pool.rs +++ b/rust-sdk/whirlpool/src/pool.rs @@ -408,9 +408,9 @@ mod tests { setup_ata_with_amount(&ctx, mint_b, 500_000_000_000).await?; // Setup all pools - let concentrated_pool = setup_whirlpool(&ctx, ctx.config, mint_a, mint_b, 64).await?; + let concentrated_pool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; let splash_pool = - setup_whirlpool(&ctx, ctx.config, mint_a, mint_b, SPLASH_POOL_TICK_SPACING).await?; + setup_whirlpool(&ctx, mint_a, mint_b, SPLASH_POOL_TICK_SPACING).await?; Ok(Self { ctx, diff --git a/rust-sdk/whirlpool/src/position.rs b/rust-sdk/whirlpool/src/position.rs index e4b72273e..4c4c596ae 100644 --- a/rust-sdk/whirlpool/src/position.rs +++ b/rust-sdk/whirlpool/src/position.rs @@ -334,13 +334,14 @@ mod tests { setup_ata_with_amount(&ctx, mint_a, 1_000_000_000).await?; setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; - let whirlpool = setup_whirlpool(&ctx, ctx.config, mint_a, mint_b, 64).await?; + let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; let position_pubkey = setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; let te_mint = setup_mint_with_decimals(&ctx, 9).await?; setup_ata_with_amount(&ctx, te_mint, 1_000_000_000).await?; - let te_position_pubkey = setup_position(whirlpool).await?; + let _te_position_pubkey = + setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; let owner = ctx.signer.pubkey(); let positions = fetch_positions_for_owner(&ctx.rpc, owner).await?; @@ -369,13 +370,14 @@ mod tests { let mint_b = setup_mint_with_decimals(&ctx, 9).await?; setup_ata_with_amount(&ctx, mint_a, 1_000_000_000).await?; setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; - let whirlpool = setup_whirlpool(&ctx, ctx.config, mint_a, mint_b, 64).await?; + let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; let _position_pubkey = setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; let te_mint = setup_mint_with_decimals(&ctx, 9).await?; setup_ata_with_amount(&ctx, te_mint, 1_000_000_000).await?; - let _te_position_pubkey = setup_position(whirlpool).await?; + let _te_position_pubkey = + setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; let positions = fetch_positions_in_whirlpool(&ctx.rpc, whirlpool).await?; assert!( diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index becbba1a5..516cae3b8 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -36,11 +36,11 @@ use spl_token::state::Mint; pub async fn setup_whirlpool( ctx: &RpcContext, - config: Pubkey, token_a: Pubkey, token_b: Pubkey, tick_spacing: u16, ) -> Result> { + let config = *WHIRLPOOLS_CONFIG_ADDRESS.try_lock()?; let fee_tier = get_fee_tier_address(&config, tick_spacing)?.0; let whirlpool = get_whirlpool_address(&config, &token_a, &token_b, tick_spacing)?.0; let token_badge_a = get_token_badge_address(&config, &token_a)?.0; @@ -269,7 +269,6 @@ pub async fn setup_te_position( position_bump, }); - println!("Sending transaction with instructions..."); ctx.send_transaction_with_signers( vec![ create_mint_ix, diff --git a/rust-sdk/whirlpool/src/tests/rpc.rs b/rust-sdk/whirlpool/src/tests/rpc.rs index 54690daff..32e2d0997 100644 --- a/rust-sdk/whirlpool/src/tests/rpc.rs +++ b/rust-sdk/whirlpool/src/tests/rpc.rs @@ -40,7 +40,6 @@ use crate::{SPLASH_POOL_TICK_SPACING, WHIRLPOOLS_CONFIG_ADDRESS}; pub struct RpcContext { pub rpc: RpcClient, pub signer: Keypair, - pub config: Pubkey, keypairs: Vec, keypair_index: AtomicUsize, } @@ -151,7 +150,6 @@ impl RpcContext { Self { rpc, signer, - config, keypairs, keypair_index: AtomicUsize::new(0), } From 45c753e79f2ece94776d9184807ac47445c6befb Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 9 Jan 2025 21:42:35 +0900 Subject: [PATCH 12/33] Cleanup spacing --- rust-sdk/whirlpool/src/tests/rpc.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust-sdk/whirlpool/src/tests/rpc.rs b/rust-sdk/whirlpool/src/tests/rpc.rs index 32e2d0997..a00fa9dc4 100644 --- a/rust-sdk/whirlpool/src/tests/rpc.rs +++ b/rust-sdk/whirlpool/src/tests/rpc.rs @@ -62,7 +62,6 @@ impl RpcContext { ); let config = *WHIRLPOOLS_CONFIG_ADDRESS.lock().unwrap(); - test.add_account( config, Account { From 50ef0341dc7d52a6d416fb20792ddf76cc8c4320 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 9 Jan 2025 22:53:14 +0900 Subject: [PATCH 13/33] Update comments --- rust-sdk/whirlpool/src/tests/program.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index 516cae3b8..5c4f63a97 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -186,7 +186,6 @@ pub async fn setup_te_position( let upper_tick_index = get_initializable_tick_index(tick_upper, whirlpool_account.tick_spacing, None); - // Initialize tick arrays if needed (재사용) let tick_arrays = [ get_tick_array_start_tick_index(lower_tick_index, whirlpool_account.tick_spacing), get_tick_array_start_tick_index(upper_tick_index, whirlpool_account.tick_spacing), From 34ee972d5338fa48c9fa165c73d998f4bb7be51d Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 9 Jan 2025 23:06:13 +0900 Subject: [PATCH 14/33] Cleanup repo --- rust-sdk/whirlpool/src/decrease_liquidity.rs | 8 +-- rust-sdk/whirlpool/src/increase_liquidity.rs | 11 ++-- rust-sdk/whirlpool/src/pool.rs | 6 +- rust-sdk/whirlpool/src/position.rs | 12 ++-- rust-sdk/whirlpool/src/tests/program.rs | 66 +++++++++----------- 5 files changed, 48 insertions(+), 55 deletions(-) diff --git a/rust-sdk/whirlpool/src/decrease_liquidity.rs b/rust-sdk/whirlpool/src/decrease_liquidity.rs index ddf15cce3..2f5826653 100644 --- a/rust-sdk/whirlpool/src/decrease_liquidity.rs +++ b/rust-sdk/whirlpool/src/decrease_liquidity.rs @@ -653,7 +653,7 @@ mod tests { } use crate::tests::{ - setup_ata_te, setup_ata_with_amount, setup_config_and_fee_tiers, setup_mint_te_fee, + setup_ata_te, setup_ata_with_amount, setup_mint_te_fee, setup_mint_with_decimals, setup_position, setup_te_position, setup_whirlpool, RpcContext, SetupAtaConfig, }; @@ -672,7 +672,6 @@ mod tests { let ctx = RpcContext::new().await; // Setup and initialize - setup_config_and_fee_tiers(&ctx).await?; let mint_a_pubkey = setup_mint_with_decimals(&ctx, 9).await?; let mint_b_pubkey = setup_mint_with_decimals(&ctx, 9).await?; let token_balance: u64 = 1_000_000_000; @@ -682,7 +681,7 @@ mod tests { // Setup pool and position let tick_spacing = 64; let pool_pubkey = - setup_whirlpool(&ctx, ctx.config, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; + setup_whirlpool(&ctx, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; let position_mint = setup_position( &ctx, @@ -736,7 +735,6 @@ mod tests { let ctx = RpcContext::new().await; // Setup and initialize - setup_config_and_fee_tiers(&ctx).await?; let mint_a_pubkey = setup_mint_with_decimals(&ctx, 9).await?; let mint_b_pubkey = setup_mint_with_decimals(&ctx, 9).await?; let token_balance: u64 = 1_000_000_000; @@ -746,7 +744,7 @@ mod tests { // Setup pool and position let tick_spacing = 64; let pool_pubkey = - setup_whirlpool(&ctx, ctx.config, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; + setup_whirlpool(&ctx, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; let position_mint = setup_position( &ctx, diff --git a/rust-sdk/whirlpool/src/increase_liquidity.rs b/rust-sdk/whirlpool/src/increase_liquidity.rs index 3a2c05bfd..0c1c0846c 100644 --- a/rust-sdk/whirlpool/src/increase_liquidity.rs +++ b/rust-sdk/whirlpool/src/increase_liquidity.rs @@ -706,9 +706,9 @@ mod tests { use crate::{ tests::{ - setup_ata_te, setup_ata_with_amount, setup_mint_te, - setup_mint_te_fee, setup_mint_with_decimals, setup_position, setup_te_position, - setup_whirlpool, RpcContext, SetupAtaConfig, + setup_ata_te, setup_ata_with_amount, setup_mint_te, setup_mint_te_fee, + setup_mint_with_decimals, setup_position, setup_te_position, setup_whirlpool, + RpcContext, SetupAtaConfig, }, DEFAULT_FUNDER, WHIRLPOOLS_CONFIG_ADDRESS, }; @@ -742,8 +742,7 @@ mod tests { // setup pool and position let tick_spacing = 64; - let pool_pubkey = - setup_whirlpool(&ctx, ctx.config, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; + let pool_pubkey = setup_whirlpool(&ctx, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; let position_mint = setup_position( &ctx, @@ -772,4 +771,4 @@ mod tests { Ok(()) } -} +} \ No newline at end of file diff --git a/rust-sdk/whirlpool/src/pool.rs b/rust-sdk/whirlpool/src/pool.rs index 1b4d6d1ac..bdf17dad0 100644 --- a/rust-sdk/whirlpool/src/pool.rs +++ b/rust-sdk/whirlpool/src/pool.rs @@ -408,9 +408,9 @@ mod tests { setup_ata_with_amount(&ctx, mint_b, 500_000_000_000).await?; // Setup all pools - let concentrated_pool = setup_whirlpool(&ctx, ctx.config, mint_a, mint_b, 64).await?; + let concentrated_pool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; let splash_pool = - setup_whirlpool(&ctx, ctx.config, mint_a, mint_b, SPLASH_POOL_TICK_SPACING).await?; + setup_whirlpool(&ctx, mint_a, mint_b, SPLASH_POOL_TICK_SPACING).await?; Ok(Self { ctx, @@ -545,4 +545,4 @@ mod tests { assert_eq!(pool.fee_rate, 1000); } } -} +} \ No newline at end of file diff --git a/rust-sdk/whirlpool/src/position.rs b/rust-sdk/whirlpool/src/position.rs index e4b72273e..ee7cc127b 100644 --- a/rust-sdk/whirlpool/src/position.rs +++ b/rust-sdk/whirlpool/src/position.rs @@ -334,13 +334,14 @@ mod tests { setup_ata_with_amount(&ctx, mint_a, 1_000_000_000).await?; setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; - let whirlpool = setup_whirlpool(&ctx, ctx.config, mint_a, mint_b, 64).await?; + let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; let position_pubkey = setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; let te_mint = setup_mint_with_decimals(&ctx, 9).await?; setup_ata_with_amount(&ctx, te_mint, 1_000_000_000).await?; - let te_position_pubkey = setup_position(whirlpool).await?; + let _te_position_pubkey = + setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; let owner = ctx.signer.pubkey(); let positions = fetch_positions_for_owner(&ctx.rpc, owner).await?; @@ -369,13 +370,14 @@ mod tests { let mint_b = setup_mint_with_decimals(&ctx, 9).await?; setup_ata_with_amount(&ctx, mint_a, 1_000_000_000).await?; setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; - let whirlpool = setup_whirlpool(&ctx, ctx.config, mint_a, mint_b, 64).await?; + let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; let _position_pubkey = setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; let te_mint = setup_mint_with_decimals(&ctx, 9).await?; setup_ata_with_amount(&ctx, te_mint, 1_000_000_000).await?; - let _te_position_pubkey = setup_position(whirlpool).await?; + let _te_position_pubkey = + setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; let positions = fetch_positions_in_whirlpool(&ctx.rpc, whirlpool).await?; assert!( @@ -385,4 +387,4 @@ mod tests { Ok(()) } -} +} \ No newline at end of file diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index a502364c2..77439a2c3 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -17,9 +17,9 @@ use solana_sdk::{ system_instruction, system_program, }; use spl_associated_token_account::{ - get_associated_token_address, get_associated_token_address_with_program_id, - instruction::create_associated_token_account, + get_associated_token_address, instruction::create_associated_token_account, }; +use spl_token::instruction::initialize_mint2; use spl_token::ID as TOKEN_PROGRAM_ID; use spl_token_2022::{state::Mint as Token2022Mint, ID as TOKEN_2022_PROGRAM_ID}; use std::error::Error; @@ -36,45 +36,38 @@ use spl_token::state::Mint; pub async fn setup_whirlpool( ctx: &RpcContext, - config: Pubkey, token_a: Pubkey, token_b: Pubkey, tick_spacing: u16, ) -> Result> { + let config = *WHIRLPOOLS_CONFIG_ADDRESS.try_lock()?; let fee_tier = get_fee_tier_address(&config, tick_spacing)?.0; let whirlpool = get_whirlpool_address(&config, &token_a, &token_b, tick_spacing)?.0; - let (token_badge_a, _) = get_token_badge_address(&config, &token_a)?; - let (token_badge_b, _) = get_token_badge_address(&config, &token_b)?; + let token_badge_a = get_token_badge_address(&config, &token_a)?.0; + let token_badge_b = get_token_badge_address(&config, &token_b)?.0; let vault_a = ctx.get_next_keypair(); let vault_b = ctx.get_next_keypair(); - let mint_infos = ctx.rpc.get_multiple_accounts(&[token_a, token_b]).await?; - let mint_a_info = mint_infos[0] - .as_ref() - .ok_or("Token A mint info not found")?; - let mint_b_info = mint_infos[1] - .as_ref() - .ok_or("Token B mint info not found")?; - - let token_program_a = mint_a_info.owner; - let token_program_b = mint_b_info.owner; + let mint_a_info = ctx.rpc.get_account(&token_a).await?; + let mint_b_info = ctx.rpc.get_account(&token_b).await?; + // Default initial price of 1.0 let sqrt_price = tick_index_to_sqrt_price(0); let instructions = vec![InitializePoolV2 { - whirlpools_config: config, + whirlpool, + fee_tier, token_mint_a: token_a, token_mint_b: token_b, - token_badge_a: token_badge_a, - token_badge_b: token_badge_b, + whirlpools_config: config, funder: ctx.signer.pubkey(), - whirlpool, token_vault_a: vault_a.pubkey(), token_vault_b: vault_b.pubkey(), - fee_tier: fee_tier, - token_program_a: token_program_a, - token_program_b: token_program_b, + token_badge_a, + token_badge_b, + token_program_a: mint_a_info.owner, + token_program_b: mint_b_info.owner, system_program: system_program::id(), rent: RENT_PROGRAM_ID, } @@ -184,15 +177,18 @@ pub async fn setup_te_position( let whirlpool_data = ctx.rpc.get_account(&whirlpool).await?; let whirlpool_account = Whirlpool::from_bytes(&whirlpool_data.data)?; - let (tick_lower, tick_upper) = tick_range.unwrap_or((-128, 128)); + // Get tick range + let (tick_lower, tick_upper) = tick_range.unwrap_or((-100, 100)); - let tick_spacing = whirlpool_account.tick_spacing as i32; - let tick_lower_aligned = (tick_lower / tick_spacing) * tick_spacing; - let tick_upper_aligned = (tick_upper / tick_spacing) * tick_spacing; + // Get initializable tick indexes + let lower_tick_index = + get_initializable_tick_index(tick_lower, whirlpool_account.tick_spacing, None); + let upper_tick_index = + get_initializable_tick_index(tick_upper, whirlpool_account.tick_spacing, None); let tick_arrays = [ - get_tick_array_start_tick_index(tick_lower_aligned, whirlpool_account.tick_spacing), - get_tick_array_start_tick_index(tick_upper_aligned, whirlpool_account.tick_spacing), + get_tick_array_start_tick_index(lower_tick_index, whirlpool_account.tick_spacing), + get_tick_array_start_tick_index(upper_tick_index, whirlpool_account.tick_spacing), ]; for start_tick in tick_arrays.iter() { @@ -219,6 +215,7 @@ pub async fn setup_te_position( } } + // Create Token-2022 position let position_mint = Keypair::new(); let lamports = ctx .rpc @@ -241,11 +238,8 @@ pub async fn setup_te_position( 0, )?; - let position_token_account = get_associated_token_address_with_program_id( - &ctx.signer.pubkey(), - &position_mint.pubkey(), - &TOKEN_2022_PROGRAM_ID, - ); + let position_token_account = + get_associated_token_address(&ctx.signer.pubkey(), &position_mint.pubkey()); let create_ata_ix = create_associated_token_account( &ctx.signer.pubkey(), @@ -269,8 +263,8 @@ pub async fn setup_te_position( rent: RENT_PROGRAM_ID, } .instruction(OpenPositionInstructionArgs { - tick_lower_index: tick_lower_aligned, - tick_upper_index: tick_upper_aligned, + tick_lower_index: lower_tick_index, + tick_upper_index: upper_tick_index, position_bump, }); @@ -345,4 +339,4 @@ pub async fn setup_position_bundle( } Ok(position_bundle_address) -} +} \ No newline at end of file From e422a86f06795d301da4c76e70b990e0ab2ed3a2 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 9 Jan 2025 23:32:16 +0900 Subject: [PATCH 15/33] Cleanup --- rust-sdk/whirlpool/src/decrease_liquidity.rs | 44 +++++++++----------- rust-sdk/whirlpool/src/increase_liquidity.rs | 2 +- rust-sdk/whirlpool/src/pool.rs | 2 +- rust-sdk/whirlpool/src/position.rs | 2 +- rust-sdk/whirlpool/src/tests/program.rs | 2 +- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/rust-sdk/whirlpool/src/decrease_liquidity.rs b/rust-sdk/whirlpool/src/decrease_liquidity.rs index 2f5826653..dcaac4473 100644 --- a/rust-sdk/whirlpool/src/decrease_liquidity.rs +++ b/rust-sdk/whirlpool/src/decrease_liquidity.rs @@ -1,9 +1,3 @@ -use std::{ - collections::HashSet, - error::Error, - time::{SystemTime, UNIX_EPOCH}, -}; - use orca_whirlpools_client::{ get_position_address, get_tick_array_address, Position, TickArray, Whirlpool, }; @@ -21,6 +15,11 @@ use solana_client::nonblocking::rpc_client::RpcClient; use solana_sdk::program_pack::Pack; use solana_sdk::{account::Account, instruction::Instruction, pubkey::Pubkey, signature::Keypair}; use spl_associated_token_account::get_associated_token_address_with_program_id; +use std::{ + collections::HashSet, + error::Error, + time::{SystemTime, UNIX_EPOCH}, +}; use crate::{ token::{get_current_transfer_fee, prepare_token_accounts_instructions, TokenAccountStrategy}, @@ -227,20 +226,20 @@ pub async fn decrease_liquidity_instructions( instructions.push( DecreaseLiquidityV2 { whirlpool: position.whirlpool, + token_program_a: mint_a_info.owner, + token_program_b: mint_b_info.owner, + memo_program: spl_memo::ID, position_authority: authority, position: position_address, position_token_account: position_token_account_address, + token_mint_a: pool.token_mint_a, + token_mint_b: pool.token_mint_b, token_owner_account_a: *token_owner_account_a, token_owner_account_b: *token_owner_account_b, token_vault_a: pool.token_vault_a, token_vault_b: pool.token_vault_b, - token_mint_a: pool.token_mint_a, - token_mint_b: pool.token_mint_b, - token_program_a: mint_a_info.owner, - token_program_b: mint_b_info.owner, tick_array_lower: lower_tick_array_address, tick_array_upper: upper_tick_array_address, - memo_program: spl_memo::ID, } .instruction(DecreaseLiquidityV2InstructionArgs { liquidity_amount: quote.liquidity_delta, @@ -517,20 +516,20 @@ pub async fn close_position_instructions( instructions.push( DecreaseLiquidityV2 { whirlpool: position.whirlpool, + token_program_a: mint_a_info.owner, + token_program_b: mint_b_info.owner, + memo_program: spl_memo::ID, position_authority: authority, position: position_address, position_token_account: position_token_account_address, + token_mint_a: pool.token_mint_a, + token_mint_b: pool.token_mint_b, token_owner_account_a: *token_owner_account_a, token_owner_account_b: *token_owner_account_b, token_vault_a: pool.token_vault_a, token_vault_b: pool.token_vault_b, - token_mint_a: pool.token_mint_a, - token_mint_b: pool.token_mint_b, - token_program_a: mint_a_info.owner, - token_program_b: mint_b_info.owner, tick_array_lower: lower_tick_array_address, tick_array_upper: upper_tick_array_address, - memo_program: spl_memo::ID, } .instruction(DecreaseLiquidityV2InstructionArgs { liquidity_amount: quote.liquidity_delta, @@ -582,8 +581,8 @@ pub async fn close_position_instructions( position: position_address, position_token_account: position_token_account_address, reward_owner_account: *reward_owner, - reward_mint: pool.reward_infos[i].mint, reward_vault: pool.reward_infos[i].vault, + reward_mint: pool.reward_infos[i].mint, reward_token_program: reward_info.owner, memo_program: spl_memo::ID, } @@ -653,9 +652,8 @@ mod tests { } use crate::tests::{ - setup_ata_te, setup_ata_with_amount, setup_mint_te_fee, - setup_mint_with_decimals, setup_position, setup_te_position, setup_whirlpool, RpcContext, - SetupAtaConfig, + setup_ata_te, setup_ata_with_amount, setup_mint_te_fee, setup_mint_with_decimals, + setup_position, setup_te_position, setup_whirlpool, RpcContext, SetupAtaConfig, }; use serial_test::serial; use solana_sdk::pubkey::Pubkey; @@ -680,8 +678,7 @@ mod tests { // Setup pool and position let tick_spacing = 64; - let pool_pubkey = - setup_whirlpool(&ctx, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; + let pool_pubkey = setup_whirlpool(&ctx, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; let position_mint = setup_position( &ctx, @@ -743,8 +740,7 @@ mod tests { // Setup pool and position let tick_spacing = 64; - let pool_pubkey = - setup_whirlpool(&ctx, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; + let pool_pubkey = setup_whirlpool(&ctx, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; let position_mint = setup_position( &ctx, diff --git a/rust-sdk/whirlpool/src/increase_liquidity.rs b/rust-sdk/whirlpool/src/increase_liquidity.rs index 0c1c0846c..37b54139f 100644 --- a/rust-sdk/whirlpool/src/increase_liquidity.rs +++ b/rust-sdk/whirlpool/src/increase_liquidity.rs @@ -771,4 +771,4 @@ mod tests { Ok(()) } -} \ No newline at end of file +} diff --git a/rust-sdk/whirlpool/src/pool.rs b/rust-sdk/whirlpool/src/pool.rs index bdf17dad0..ae461ed8a 100644 --- a/rust-sdk/whirlpool/src/pool.rs +++ b/rust-sdk/whirlpool/src/pool.rs @@ -545,4 +545,4 @@ mod tests { assert_eq!(pool.fee_rate, 1000); } } -} \ No newline at end of file +} diff --git a/rust-sdk/whirlpool/src/position.rs b/rust-sdk/whirlpool/src/position.rs index ee7cc127b..4c4c596ae 100644 --- a/rust-sdk/whirlpool/src/position.rs +++ b/rust-sdk/whirlpool/src/position.rs @@ -387,4 +387,4 @@ mod tests { Ok(()) } -} \ No newline at end of file +} diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index 77439a2c3..5c4f63a97 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -339,4 +339,4 @@ pub async fn setup_position_bundle( } Ok(position_bundle_address) -} \ No newline at end of file +} From a32d25e5316c6e5b49f9b88ac29e0a969ac99c28 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Fri, 10 Jan 2025 05:18:32 +0900 Subject: [PATCH 16/33] Update increase liquidity test to actually check tokens --- .github/dependabot.yml | 3 - rust-sdk/whirlpool/src/increase_liquidity.rs | 77 ++++++++++++++++++++ 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e4e7d052b..4481c937f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,6 @@ updates: directory: "/" schedule: interval: "daily" - labels: [ ] groups: docusaurus: applies-to: version-updates @@ -13,7 +12,6 @@ updates: directory: "/" schedule: interval: "daily" - labels: [ ] groups: program-dependencies: applies-to: version-updates @@ -22,4 +20,3 @@ updates: directory: "/" schedule: interval: "daily" - labels: [ ] diff --git a/rust-sdk/whirlpool/src/increase_liquidity.rs b/rust-sdk/whirlpool/src/increase_liquidity.rs index 37b54139f..5e585293a 100644 --- a/rust-sdk/whirlpool/src/increase_liquidity.rs +++ b/rust-sdk/whirlpool/src/increase_liquidity.rs @@ -726,6 +726,13 @@ mod tests { Position::from_bytes(&account.data).map_err(|e| e.into()) } + async fn get_token_balance(rpc: &RpcClient, address: Pubkey) -> Result> { + // For an SPL account that we already know, just fetch + unpack + let account_data = rpc.get_account(&address).await?; + let token_account = spl_token::state::Account::unpack(&account_data.data)?; + Ok(token_account.amount) + } + const TEST_SLIPPAGE_TOLERANCE: u16 = 100; // 1% #[tokio::test] @@ -771,4 +778,74 @@ mod tests { Ok(()) } + + #[tokio::test] + async fn test_increase_liquidity_with_quote_verification() -> Result<(), Box> { + let ctx = RpcContext::new().await; + + let mint_a_pubkey = setup_mint_with_decimals(&ctx, 9).await?; + let mint_b_pubkey = setup_mint_with_decimals(&ctx, 9).await?; + + let token_balance: u64 = 1_000_000; + let user_ata_a = setup_ata_with_amount(&ctx, mint_a_pubkey, token_balance).await?; + let user_ata_b = setup_ata_with_amount(&ctx, mint_b_pubkey, token_balance).await?; + + let pool_pubkey = setup_whirlpool(&ctx, mint_a_pubkey, mint_b_pubkey, 64).await?; + let position_mint = setup_position( + &ctx, + pool_pubkey, + Some((-128, 128)), // or any range + Some(ctx.signer.pubkey()), + ) + .await?; + + let param = IncreaseLiquidityParam::TokenA(100_000); + let increase_ix = increase_liquidity_instructions( + &ctx.rpc, + position_mint, + param.clone(), + Some(100), // 1% slippage + Some(ctx.signer.pubkey()), + ) + .await?; + + let quote = &increase_ix.quote; + + let before_a = get_token_balance(&ctx.rpc, user_ata_a).await?; + let before_b = get_token_balance(&ctx.rpc, user_ata_b).await?; + + let signers: Vec<&Keypair> = increase_ix.additional_signers.iter().collect(); + ctx.send_transaction_with_signers(increase_ix.instructions, signers) + .await?; + + let after_a = get_token_balance(&ctx.rpc, user_ata_a).await?; + let after_b = get_token_balance(&ctx.rpc, user_ata_b).await?; + let used_a = before_a.saturating_sub(after_a); + let used_b = before_b.saturating_sub(after_b); + + assert!( + used_a >= quote.token_est_a && used_a <= quote.token_max_a, + "token A usage out of expected range: used={} estimate={}..{}", + used_a, + quote.token_est_a, + quote.token_max_a + ); + assert!( + used_b >= quote.token_est_b && used_b <= quote.token_max_b, + "token B usage out of expected range: used={} estimate={}..{}", + used_b, + quote.token_est_b, + quote.token_max_b + ); + + let position_pubkey = get_position_address(&position_mint).unwrap().0; + let position_data = fetch_position(&ctx.rpc, position_pubkey).await?; + + assert_eq!( + position_data.liquidity, quote.liquidity_delta, + "Position liquidity does not match the quote" + ); + + Ok(()) + } } From 77947f6740dbfead71a88855ae6bdd5844aa81da Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Fri, 10 Jan 2025 06:06:18 +0900 Subject: [PATCH 17/33] Update test to check te and bundle --- rust-sdk/whirlpool/src/position.rs | 40 +++++++++++++++++++----------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/rust-sdk/whirlpool/src/position.rs b/rust-sdk/whirlpool/src/position.rs index 7788824b8..c4480c949 100644 --- a/rust-sdk/whirlpool/src/position.rs +++ b/rust-sdk/whirlpool/src/position.rs @@ -303,11 +303,15 @@ mod tests { use crate::tests::{ setup_ata_with_amount, setup_mint_with_decimals, setup_position, setup_whirlpool, RpcContext, + setup_te_position, + setup_position_bundle, }; use serial_test::serial; use solana_program_test::tokio; use std::error::Error; + const DEFAULT_TICK_RANGE: (i32, i32) = (-100, 100); + #[tokio::test] #[serial] #[ignore = "Skipped until solana-bankrun supports gpa"] @@ -333,25 +337,29 @@ mod tests { setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; - let position_pubkey = setup_position(whirlpool).await?; + let normal_position_pubkey = setup_position(whirlpool).await?; + + // 1) Add a te_position (uses token-2022) + let te_position_pubkey = setup_te_position(whirlpool).await?; - let te_mint = setup_mint_with_decimals(&ctx, 9).await?; - setup_ata_with_amount(&ctx, te_mint, 1_000_000_000).await?; - let te_position_pubkey = setup_position(whirlpool).await?; + // 2) Add a position bundle, optionally with multiple bundled positions + let position_bundle_pubkey = setup_position_bundle(whirlpool, Some(vec![(), ()])).await?; let owner = ctx.signer.pubkey(); let positions = fetch_positions_for_owner(&ctx.rpc, owner).await?; + // Expect at least 3: normal, te_position, and a bundle assert!( - positions.len() >= 2, - "Did not fetch all positions for the owner (expected two or more)" + positions.len() >= 3, + "Did not find all positions for the owner (expected normal, te_position, bundle)" ); + // Existing checks remain... match &positions[0] { PositionOrBundle::Position(pos) => { - assert_eq!(pos.address, position_pubkey); + assert_eq!(pos.address, normal_position_pubkey); } - _ => panic!("Expected a single position, but found a bundle"), + _ => panic!("Expected a single position, but found a bundle!"), } Ok(()) @@ -368,16 +376,20 @@ mod tests { setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; - let _position_pubkey = setup_position(whirlpool).await?; + let _normal_position_pubkey = setup_position(whirlpool).await?; - let te_mint = setup_mint_with_decimals(&ctx, 9).await?; - setup_ata_with_amount(&ctx, te_mint, 1_000_000_000).await?; - let _te_position_pubkey = setup_position(whirlpool).await?; + // 1) te_position + let _te_position_pubkey = setup_te_position(whirlpool).await?; + + // 2) position bundle + let _position_bundle_pubkey = setup_position_bundle(whirlpool, Some(vec![(), ()])).await?; let positions = fetch_positions_in_whirlpool(&ctx.rpc, whirlpool).await?; + + // Expect at least 3: normal + te_position + bundle assert!( - positions.len() >= 2, - "Should find multiple positions in this whirlpool, including te_position" + positions.len() >= 3, + "Should find multiple positions in this whirlpool, including te_position & bundle" ); Ok(()) From 1440df32cee01586561b35ff1a10281e7f44aeb1 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Fri, 10 Jan 2025 06:09:53 +0900 Subject: [PATCH 18/33] Update dependabot --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c8e93d5e9..f45680415 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -23,4 +23,4 @@ updates: directory: "/" schedule: interval: "daily" - labels: [ ] + labels: [ ] \ No newline at end of file From 24abe078a38375fa935bd5d29937c108411a2a7d Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Fri, 10 Jan 2025 06:11:23 +0900 Subject: [PATCH 19/33] Fix dependabot space --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f45680415..cc2bdde7d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -23,4 +23,4 @@ updates: directory: "/" schedule: interval: "daily" - labels: [ ] \ No newline at end of file + labels: [ ] \ No newline at end of file From a847a803a3f9439d154795cbc280aebbb7d52645 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Fri, 10 Jan 2025 06:16:37 +0900 Subject: [PATCH 20/33] revert dependabot --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cc2bdde7d..f45680415 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -23,4 +23,4 @@ updates: directory: "/" schedule: interval: "daily" - labels: [ ] \ No newline at end of file + labels: [ ] \ No newline at end of file From 57f9e6e2318ecdd35c54d5b18e7f5a6a341c823e Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Fri, 10 Jan 2025 06:25:40 +0900 Subject: [PATCH 21/33] FMT --- .github/dependabot.yml | 2 +- rust-sdk/whirlpool/src/position.rs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f45680415..c8e93d5e9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -23,4 +23,4 @@ updates: directory: "/" schedule: interval: "daily" - labels: [ ] \ No newline at end of file + labels: [ ] diff --git a/rust-sdk/whirlpool/src/position.rs b/rust-sdk/whirlpool/src/position.rs index c4480c949..8e12171b3 100644 --- a/rust-sdk/whirlpool/src/position.rs +++ b/rust-sdk/whirlpool/src/position.rs @@ -301,10 +301,8 @@ pub async fn fetch_positions_in_whirlpool( mod tests { use super::*; use crate::tests::{ - setup_ata_with_amount, setup_mint_with_decimals, setup_position, setup_whirlpool, - RpcContext, - setup_te_position, - setup_position_bundle, + setup_ata_with_amount, setup_mint_with_decimals, setup_position, setup_position_bundle, + setup_te_position, setup_whirlpool, RpcContext, }; use serial_test::serial; use solana_program_test::tokio; From 83973ed8dedf6eb8a9eb996f53817579aed746cc Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Fri, 10 Jan 2025 06:46:05 +0900 Subject: [PATCH 22/33] Update fmt --- .github/dependabot.yml | 3 +++ rust-sdk/whirlpool/src/position.rs | 23 +++-------------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f0d56cb7d..c8e93d5e9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,6 +4,7 @@ updates: directory: "/" schedule: interval: "daily" + labels: [ ] groups: docusaurus: applies-to: version-updates @@ -13,6 +14,7 @@ updates: directory: "/" schedule: interval: "daily" + labels: [ ] groups: program-dependencies: applies-to: version-updates @@ -21,3 +23,4 @@ updates: directory: "/" schedule: interval: "daily" + labels: [ ] diff --git a/rust-sdk/whirlpool/src/position.rs b/rust-sdk/whirlpool/src/position.rs index a09449791..90244f249 100644 --- a/rust-sdk/whirlpool/src/position.rs +++ b/rust-sdk/whirlpool/src/position.rs @@ -310,6 +310,8 @@ mod tests { const DEFAULT_TICK_RANGE: (i32, i32) = (-100, 100); + const DEFAULT_TICK_RANGE: (i32, i32) = (-100, 100); + #[tokio::test] #[serial] #[ignore = "Skipped until solana-bankrun supports gpa"] @@ -335,15 +337,6 @@ mod tests { setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; -<<<<<<< HEAD - let position_pubkey = - setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; - - let te_mint = setup_mint_with_decimals(&ctx, 9).await?; - setup_ata_with_amount(&ctx, te_mint, 1_000_000_000).await?; - let _te_position_pubkey = - setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; -======= let normal_position_pubkey = setup_position(whirlpool).await?; // 1) Add a te_position (uses token-2022) @@ -351,7 +344,6 @@ mod tests { // 2) Add a position bundle, optionally with multiple bundled positions let position_bundle_pubkey = setup_position_bundle(whirlpool, Some(vec![(), ()])).await?; ->>>>>>> cdefdca0e4db43aa95691f4163eb73e48ded6006 let owner = ctx.signer.pubkey(); let positions = fetch_positions_for_owner(&ctx.rpc, owner).await?; @@ -382,16 +374,8 @@ mod tests { let mint_b = setup_mint_with_decimals(&ctx, 9).await?; setup_ata_with_amount(&ctx, mint_a, 1_000_000_000).await?; setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; + let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; -<<<<<<< HEAD - let _position_pubkey = - setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; - - let te_mint = setup_mint_with_decimals(&ctx, 9).await?; - setup_ata_with_amount(&ctx, te_mint, 1_000_000_000).await?; - let _te_position_pubkey = - setup_position(&ctx, whirlpool, Some(DEFAULT_TICK_RANGE), None).await?; -======= let _normal_position_pubkey = setup_position(whirlpool).await?; // 1) te_position @@ -399,7 +383,6 @@ mod tests { // 2) position bundle let _position_bundle_pubkey = setup_position_bundle(whirlpool, Some(vec![(), ()])).await?; ->>>>>>> cdefdca0e4db43aa95691f4163eb73e48ded6006 let positions = fetch_positions_in_whirlpool(&ctx.rpc, whirlpool).await?; From f329cddbf9ccd3b78a314ff12444ecef0163d67c Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Sat, 11 Jan 2025 04:50:20 +0900 Subject: [PATCH 23/33] Update not to create mint, instead use instructions --- rust-sdk/whirlpool/src/tests/program.rs | 99 ++++++++----------------- 1 file changed, 31 insertions(+), 68 deletions(-) diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index 1dbdfc3f9..39f972394 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -78,16 +78,18 @@ pub async fn setup_whirlpool( pub async fn setup_position(whirlpool: Pubkey) -> Result> { let ctx = RpcContext::new().await; - let (position_pubkey, position_bump) = get_position_address(&Pubkey::new_unique())?; + let position_mint = ctx.get_next_keypair(); + + let (position_pubkey, position_bump) = get_position_address(&position_mint.pubkey())?; let open_position_ix = OpenPosition { funder: ctx.signer.pubkey(), owner: ctx.signer.pubkey(), position: position_pubkey, - position_mint: Pubkey::new_unique(), - position_token_account: Pubkey::default(), + position_mint: position_mint.pubkey(), + position_token_account: Pubkey::default(), // instruction will create whirlpool, - token_program: TOKEN_PROGRAM_ID, + token_program: TOKEN_PROGRAM_ID, // or TOKEN_2022_PROGRAM_ID if needed system_program: system_program::id(), associated_token_program: spl_associated_token_account::id(), rent: RENT_PROGRAM_ID, @@ -98,7 +100,8 @@ pub async fn setup_position(whirlpool: Pubkey) -> Result> position_bump, }); - ctx.send_transaction(vec![open_position_ix]).await?; + ctx.send_transaction_with_signers(vec![open_position_ix], vec![&position_mint]) + .await?; Ok(position_pubkey) } @@ -106,94 +109,54 @@ pub async fn setup_position(whirlpool: Pubkey) -> Result> pub async fn setup_te_position(whirlpool: Pubkey) -> Result> { let ctx = RpcContext::new().await; - let position_mint = Keypair::new(); - let lamports = ctx - .rpc - .get_minimum_balance_for_rent_exemption(Token2022Mint::LEN) - .await?; - - let create_mint_ix = system_instruction::create_account( - &ctx.signer.pubkey(), - &position_mint.pubkey(), - lamports, - Token2022Mint::LEN as u64, - &TOKEN_2022_PROGRAM_ID, - ); - - let init_mint_ix = spl_token_2022::instruction::initialize_mint( - &TOKEN_2022_PROGRAM_ID, - &position_mint.pubkey(), - &ctx.signer.pubkey(), - None, - 0, - )?; - - let position_token_account = - get_associated_token_address(&ctx.signer.pubkey(), &position_mint.pubkey()); - - let create_ata_ix = create_associated_token_account( - &ctx.signer.pubkey(), - &ctx.signer.pubkey(), - &position_mint.pubkey(), - &TOKEN_2022_PROGRAM_ID, - ); + // 1) Keypair for the TE position mint + let te_position_mint = ctx.get_next_keypair(); - let (position_pubkey, position_bump) = get_position_address(&position_mint.pubkey())?; - - let tick_lower_index: i32 = -128; - let tick_upper_index: i32 = 128; + // 2) Derive the position PDA + let (position_pubkey, position_bump) = get_position_address(&te_position_mint.pubkey())?; + // 3) Build an OpenPosition instruction with token_program = TOKEN_2022_PROGRAM_ID let open_position_ix = OpenPosition { funder: ctx.signer.pubkey(), owner: ctx.signer.pubkey(), position: position_pubkey, - position_mint: position_mint.pubkey(), - position_token_account, + position_mint: te_position_mint.pubkey(), + position_token_account: Pubkey::default(), whirlpool, - token_program: TOKEN_2022_PROGRAM_ID, + token_program: TOKEN_2022_PROGRAM_ID, // TE uses token-2022 system_program: system_program::id(), associated_token_program: spl_associated_token_account::id(), rent: RENT_PROGRAM_ID, } .instruction(OpenPositionInstructionArgs { - tick_lower_index, - tick_upper_index, + tick_lower_index: -128, + tick_upper_index: 128, position_bump, }); - ctx.send_transaction_with_signers( - vec![ - create_mint_ix, - init_mint_ix, - create_ata_ix, - open_position_ix, - ], - vec![&position_mint], - ) - .await?; + // 4) The instruction itself will create the mint & ATA + ctx.send_transaction_with_signers(vec![open_position_ix], vec![&te_position_mint]) + .await?; + // 5) Return the position PDA Ok(position_pubkey) } -/// Creates a Position Bundle and initializes the specified number of bundled positions -/// Calls `OpenBundledPosition` for each position specified in `bundle_positions` pub async fn setup_position_bundle( whirlpool: Pubkey, bundle_positions: Option>, ) -> Result> { let ctx = RpcContext::new().await; - // Use token utility functions - let position_bundle_mint = setup_mint_with_decimals(&ctx, 0).await?; - let position_bundle_token_account = setup_ata(&ctx, position_bundle_mint).await?; - - let (position_bundle_address, _) = get_position_bundle_address(&position_bundle_mint)?; + let position_bundle_mint = ctx.get_next_keypair(); + let (position_bundle_address, _bundle_bump) = + get_position_bundle_address(&position_bundle_mint.pubkey())?; let open_bundle_ix = InitializePositionBundle { funder: ctx.signer.pubkey(), position_bundle: position_bundle_address, - position_bundle_mint, - position_bundle_token_account, + position_bundle_mint: position_bundle_mint.pubkey(), + position_bundle_token_account: Pubkey::default(), position_bundle_owner: ctx.signer.pubkey(), token_program: TOKEN_PROGRAM_ID, system_program: system_program::id(), @@ -202,21 +165,21 @@ pub async fn setup_position_bundle( } .instruction(); - ctx.send_transaction(vec![open_bundle_ix]).await?; + ctx.send_transaction_with_signers(vec![open_bundle_ix], vec![&position_bundle_mint]) + .await?; - // Initialize bundled positions if let Some(positions) = bundle_positions { for (i, _) in positions.iter().enumerate() { let bundle_index = i as u16; let (bundled_position_address, _) = - get_bundled_position_address(&position_bundle_mint, bundle_index as u8)?; + get_bundled_position_address(&position_bundle_mint.pubkey(), bundle_index as u8)?; let open_bundled_ix = OpenBundledPosition { funder: ctx.signer.pubkey(), bundled_position: bundled_position_address, position_bundle: position_bundle_address, position_bundle_authority: ctx.signer.pubkey(), - position_bundle_token_account, + position_bundle_token_account: Pubkey::default(), whirlpool, system_program: system_program::id(), rent: RENT_PROGRAM_ID, From aa459483aacd3dd76cb9f1e5dc237ec29f533a1d Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Mon, 13 Jan 2025 18:43:51 +0900 Subject: [PATCH 24/33] Add multiple pool tests and fix bugs in the program.rs --- rust-sdk/whirlpool/src/increase_liquidity.rs | 202 +++++++++++-------- rust-sdk/whirlpool/src/position.rs | 10 +- rust-sdk/whirlpool/src/tests/program.rs | 126 +++++++----- 3 files changed, 201 insertions(+), 137 deletions(-) diff --git a/rust-sdk/whirlpool/src/increase_liquidity.rs b/rust-sdk/whirlpool/src/increase_liquidity.rs index 5e585293a..8799f28d0 100644 --- a/rust-sdk/whirlpool/src/increase_liquidity.rs +++ b/rust-sdk/whirlpool/src/increase_liquidity.rs @@ -698,12 +698,6 @@ pub async fn open_position_instructions( #[cfg(test)] mod tests { - use serial_test::serial; - use solana_program_test::tokio; - use solana_sdk::pubkey::Pubkey; - use spl_token::state::Account as TokenAccount; - use std::collections::HashMap; - use crate::{ tests::{ setup_ata_te, setup_ata_with_amount, setup_mint_te, setup_mint_te_fee, @@ -712,6 +706,15 @@ mod tests { }, DEFAULT_FUNDER, WHIRLPOOLS_CONFIG_ADDRESS, }; + use serial_test::serial; + use solana_program_test::tokio; + use solana_sdk::pubkey::Pubkey; + use spl_token::state::Account as TokenAccount; + use spl_token_2022::{ + extension::StateWithExtensionsOwned, state::Account as TokenAccount2022, + ID as TOKEN_2022_PROGRAM_ID, + }; + use std::collections::HashMap; use super::*; @@ -727,122 +730,159 @@ mod tests { } async fn get_token_balance(rpc: &RpcClient, address: Pubkey) -> Result> { - // For an SPL account that we already know, just fetch + unpack let account_data = rpc.get_account(&address).await?; - let token_account = spl_token::state::Account::unpack(&account_data.data)?; - Ok(token_account.amount) + + if account_data.owner == TOKEN_2022_PROGRAM_ID { + let state = StateWithExtensionsOwned::::unpack(account_data.data)?; + Ok(state.base.amount) + } else { + let token_account = TokenAccount::unpack(&account_data.data)?; + Ok(token_account.amount) + } } const TEST_SLIPPAGE_TOLERANCE: u16 = 100; // 1% + const TEST_INCREASE_LIQUIDITY_PARAM: u64 = 100_000; #[tokio::test] - #[serial] - async fn test_increase_liquidity() -> Result<(), Box> { + async fn test_increase_liquidity_with_different_pools() -> Result<(), Box> { let ctx = RpcContext::new().await; - // setup and initialize - let mint_a_pubkey = setup_mint_with_decimals(&ctx, 9).await?; - let mint_b_pubkey = setup_mint_with_decimals(&ctx, 9).await?; - let token_balance: u64 = 1_000_000; - setup_ata_with_amount(&ctx, mint_a_pubkey, token_balance).await?; - setup_ata_with_amount(&ctx, mint_b_pubkey, token_balance).await?; - - // setup pool and position - let tick_spacing = 64; - let pool_pubkey = setup_whirlpool(&ctx, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; + // Setup mints with different characteristics + let mint_a = setup_mint_with_decimals(&ctx, 9).await?; + let mint_b = setup_mint_with_decimals(&ctx, 9).await?; + let te_mint = setup_mint_te(&ctx, &[]).await?; + let te_fee_mint = setup_mint_te_fee(&ctx).await?; // 1% fee - let position_mint = setup_position( + // Setup token accounts with initial balances + let token_balance: u64 = 1_000_000; + let user_ata_a = setup_ata_with_amount(&ctx, mint_a, token_balance).await?; + let user_ata_b = setup_ata_with_amount(&ctx, mint_b, token_balance).await?; + let user_ata_te = setup_ata_te( &ctx, - pool_pubkey, - Some((-100, 100)), - Some(ctx.signer.pubkey()), + te_mint, + Some(SetupAtaConfig { + amount: Some(token_balance), + }), ) .await?; - - // test increase liquidity - let param = IncreaseLiquidityParam::Liquidity(10_000); - let increase_ix = increase_liquidity_instructions( - &ctx.rpc, - position_mint, - param, - Some(TEST_SLIPPAGE_TOLERANCE), - Some(ctx.signer.pubkey()), + let user_ata_tefee = setup_ata_te( + &ctx, + te_fee_mint, + Some(SetupAtaConfig { + amount: Some(token_balance), + }), ) .await?; - // send transaction - let signers: Vec<&Keypair> = increase_ix.additional_signers.iter().collect(); - - ctx.send_transaction_with_signers(increase_ix.instructions, signers) + // Setup different pool types + let pool_ab = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; + let pool_a_te = setup_whirlpool(&ctx, mint_a, te_mint, 64).await?; + let pool_a_tefee = setup_whirlpool(&ctx, mint_a, te_fee_mint, 64).await?; + + // Test cases with different position ranges + let position_ranges = vec![(-128, 128), (-64, 64), (-32, 32)]; + + // Test regular pool (A/B) + for range in &position_ranges { + let position_mint = + setup_position(&ctx, pool_ab, Some(*range), Some(ctx.signer.pubkey())).await?; + let increase_ix = increase_liquidity_instructions( + &ctx.rpc, + position_mint, + IncreaseLiquidityParam::TokenA(TEST_INCREASE_LIQUIDITY_PARAM), + Some(TEST_SLIPPAGE_TOLERANCE), + Some(ctx.signer.pubkey()), + ) .await?; - Ok(()) - } - - #[tokio::test] - async fn test_increase_liquidity_with_quote_verification() -> Result<(), Box> { - let ctx = RpcContext::new().await; - - let mint_a_pubkey = setup_mint_with_decimals(&ctx, 9).await?; - let mint_b_pubkey = setup_mint_with_decimals(&ctx, 9).await?; + verify_increase_liquidity(&ctx, &increase_ix, user_ata_a, user_ata_b, position_mint) + .await?; + } - let token_balance: u64 = 1_000_000; - let user_ata_a = setup_ata_with_amount(&ctx, mint_a_pubkey, token_balance).await?; - let user_ata_b = setup_ata_with_amount(&ctx, mint_b_pubkey, token_balance).await?; + // Test pool with transfer hook token (A/TE) + for range in &position_ranges { + let position_mint = + setup_position(&ctx, pool_a_te, Some(*range), Some(ctx.signer.pubkey())).await?; + let increase_ix = increase_liquidity_instructions( + &ctx.rpc, + position_mint, + IncreaseLiquidityParam::TokenA(TEST_INCREASE_LIQUIDITY_PARAM), + Some(TEST_SLIPPAGE_TOLERANCE), + Some(ctx.signer.pubkey()), + ) + .await?; - let pool_pubkey = setup_whirlpool(&ctx, mint_a_pubkey, mint_b_pubkey, 64).await?; - let position_mint = setup_position( - &ctx, - pool_pubkey, - Some((-128, 128)), // or any range - Some(ctx.signer.pubkey()), - ) - .await?; + verify_increase_liquidity(&ctx, &increase_ix, user_ata_a, user_ata_te, position_mint) + .await?; + } - let param = IncreaseLiquidityParam::TokenA(100_000); - let increase_ix = increase_liquidity_instructions( - &ctx.rpc, - position_mint, - param.clone(), - Some(100), // 1% slippage - Some(ctx.signer.pubkey()), - ) - .await?; + // Test pool with transfer fee token (A/TEFee) + for range in &position_ranges { + let position_mint = + setup_position(&ctx, pool_a_tefee, Some(*range), Some(ctx.signer.pubkey())).await?; + let increase_ix = increase_liquidity_instructions( + &ctx.rpc, + position_mint, + IncreaseLiquidityParam::TokenA(TEST_INCREASE_LIQUIDITY_PARAM), + Some(TEST_SLIPPAGE_TOLERANCE), + Some(ctx.signer.pubkey()), + ) + .await?; - let quote = &increase_ix.quote; + verify_increase_liquidity( + &ctx, + &increase_ix, + user_ata_a, + user_ata_tefee, + position_mint, + ) + .await?; + } - let before_a = get_token_balance(&ctx.rpc, user_ata_a).await?; - let before_b = get_token_balance(&ctx.rpc, user_ata_b).await?; + Ok(()) + } + async fn verify_increase_liquidity( + ctx: &RpcContext, + increase_ix: &IncreaseLiquidityInstruction, + token_a_account: Pubkey, + token_b_account: Pubkey, + position_mint: Pubkey, + ) -> Result<(), Box> { + let before_a = get_token_balance(&ctx.rpc, token_a_account).await?; + let before_b = get_token_balance(&ctx.rpc, token_b_account).await?; let signers: Vec<&Keypair> = increase_ix.additional_signers.iter().collect(); - ctx.send_transaction_with_signers(increase_ix.instructions, signers) + let tx = ctx + .send_transaction_with_signers(increase_ix.instructions.clone(), signers) .await?; - let after_a = get_token_balance(&ctx.rpc, user_ata_a).await?; - let after_b = get_token_balance(&ctx.rpc, user_ata_b).await?; + let after_a = get_token_balance(&ctx.rpc, token_a_account).await?; + let after_b = get_token_balance(&ctx.rpc, token_b_account).await?; let used_a = before_a.saturating_sub(after_a); let used_b = before_b.saturating_sub(after_b); + // Verify token usage is within expected ranges assert!( - used_a >= quote.token_est_a && used_a <= quote.token_max_a, + used_a >= increase_ix.quote.token_est_a && used_a <= increase_ix.quote.token_max_a, "token A usage out of expected range: used={} estimate={}..{}", used_a, - quote.token_est_a, - quote.token_max_a + increase_ix.quote.token_est_a, + increase_ix.quote.token_max_a ); assert!( - used_b >= quote.token_est_b && used_b <= quote.token_max_b, + used_b >= increase_ix.quote.token_est_b && used_b <= increase_ix.quote.token_max_b, "token B usage out of expected range: used={} estimate={}..{}", used_b, - quote.token_est_b, - quote.token_max_b + increase_ix.quote.token_est_b, + increase_ix.quote.token_max_b ); + // Verify position liquidity let position_pubkey = get_position_address(&position_mint).unwrap().0; let position_data = fetch_position(&ctx.rpc, position_pubkey).await?; - assert_eq!( - position_data.liquidity, quote.liquidity_delta, + position_data.liquidity, increase_ix.quote.liquidity_delta, "Position liquidity does not match the quote" ); diff --git a/rust-sdk/whirlpool/src/position.rs b/rust-sdk/whirlpool/src/position.rs index 90244f249..4094463f6 100644 --- a/rust-sdk/whirlpool/src/position.rs +++ b/rust-sdk/whirlpool/src/position.rs @@ -310,8 +310,6 @@ mod tests { const DEFAULT_TICK_RANGE: (i32, i32) = (-100, 100); - const DEFAULT_TICK_RANGE: (i32, i32) = (-100, 100); - #[tokio::test] #[serial] #[ignore = "Skipped until solana-bankrun supports gpa"] @@ -337,10 +335,10 @@ mod tests { setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; - let normal_position_pubkey = setup_position(whirlpool).await?; + let normal_position_pubkey = setup_position(&ctx, whirlpool, None, None).await?; // 1) Add a te_position (uses token-2022) - let te_position_pubkey = setup_te_position(whirlpool).await?; + let te_position_pubkey = setup_te_position(&ctx, whirlpool, None, None).await?; // 2) Add a position bundle, optionally with multiple bundled positions let position_bundle_pubkey = setup_position_bundle(whirlpool, Some(vec![(), ()])).await?; @@ -376,10 +374,10 @@ mod tests { setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; let whirlpool = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; - let _normal_position_pubkey = setup_position(whirlpool).await?; + let _normal_position_pubkey = setup_position(&ctx, whirlpool, None, None).await?; // 1) te_position - let _te_position_pubkey = setup_te_position(whirlpool).await?; + let _te_position_pubkey = setup_te_position(&ctx, whirlpool, None, None).await?; // 2) position bundle let _position_bundle_pubkey = setup_position_bundle(whirlpool, Some(vec![(), ()])).await?; diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index 08f3e527b..f113eb354 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -31,9 +31,6 @@ use super::rpc::RpcContext; use crate::tests::token_extensions::setup_mint_te; -use solana_program::system_instruction::create_account; -use spl_token::state::Mint; - pub async fn setup_whirlpool( ctx: &RpcContext, token_a: Pubkey, @@ -88,56 +85,86 @@ pub async fn setup_position( tick_range: Option<(i32, i32)>, owner: Option, ) -> Result> { - let owner = owner.unwrap_or_else(|| ctx.signer.pubkey()); + let position_mint = ctx.get_next_keypair(); + let whirlpool_data = ctx.rpc.get_account(&whirlpool).await?; let whirlpool_account = Whirlpool::from_bytes(&whirlpool_data.data)?; let (tick_lower, tick_upper) = tick_range.unwrap_or((-100, 100)); - let lower_tick_index = get_initializable_tick_index( - tick_lower - (tick_lower % whirlpool_account.tick_spacing as i32), - whirlpool_account.tick_spacing, - None, - ); - let upper_tick_index = get_initializable_tick_index( - tick_upper - (tick_upper % whirlpool_account.tick_spacing as i32), - whirlpool_account.tick_spacing, - None, - ); - - let position_mint = ctx.get_next_keypair(); + let lower_tick_index = + get_initializable_tick_index(tick_lower, whirlpool_account.tick_spacing, Some(false)); + let upper_tick_index = + get_initializable_tick_index(tick_upper, whirlpool_account.tick_spacing, Some(true)); - let (position_pubkey, position_bump) = get_position_address(&position_mint.pubkey())?; + let lower_tick_array_start = + get_tick_array_start_tick_index(lower_tick_index, whirlpool_account.tick_spacing); + let upper_tick_array_start = + get_tick_array_start_tick_index(upper_tick_index, whirlpool_account.tick_spacing); - // Create position mint - let position_mint = Keypair::new(); + let (lower_tick_array_addr, _) = get_tick_array_address(&whirlpool, lower_tick_array_start)?; + let (upper_tick_array_addr, _) = get_tick_array_address(&whirlpool, upper_tick_array_start)?; - // Calculate position PDA - let (position_pubkey, position_bump) = get_position_address(&position_mint.pubkey())?; + let mut instructions = vec![]; - // Calculate position token account - let position_token_account = get_associated_token_address(&owner, &position_mint.pubkey()); + let lower_tick_array_account = ctx.rpc.get_account(&lower_tick_array_addr).await; + if lower_tick_array_account.is_err() { + instructions.push( + InitializeTickArray { + whirlpool, + funder: ctx.signer.pubkey(), + tick_array: lower_tick_array_addr, + system_program: system_program::id(), + } + .instruction(InitializeTickArrayInstructionArgs { + start_tick_index: lower_tick_array_start, + }), + ); + } - // Create OpenPosition instruction - let open_position_ix = OpenPosition { - funder: ctx.signer.pubkey(), - owner: owner, - position: position_pubkey, - position_mint: position_mint.pubkey(), - position_token_account: Pubkey::default(), // instruction will create - whirlpool, - token_program: TOKEN_PROGRAM_ID, // or TOKEN_2022_PROGRAM_ID if needed - system_program: system_program::id(), - associated_token_program: spl_associated_token_account::id(), - rent: RENT_PROGRAM_ID, + if upper_tick_array_start != lower_tick_array_start { + let upper_tick_array_account = ctx.rpc.get_account(&upper_tick_array_addr).await; + if upper_tick_array_account.is_err() { + instructions.push( + InitializeTickArray { + whirlpool, + funder: ctx.signer.pubkey(), + tick_array: upper_tick_array_addr, + system_program: system_program::id(), + } + .instruction(InitializeTickArrayInstructionArgs { + start_tick_index: upper_tick_array_start, + }), + ); + } } - .instruction(OpenPositionInstructionArgs { - tick_lower_index: lower_tick_index, - tick_upper_index: upper_tick_index, - position_bump, - }); - ctx.send_transaction_with_signers(vec![open_position_ix], vec![&position_mint]) + let (position_pubkey, position_bump) = get_position_address(&position_mint.pubkey())?; + let owner_pubkey = owner.unwrap_or(ctx.signer.pubkey()); + let position_token_account = + get_associated_token_address(&owner_pubkey, &position_mint.pubkey()); + + instructions.push( + OpenPosition { + funder: ctx.signer.pubkey(), + owner: owner_pubkey, + position: position_pubkey, + position_mint: position_mint.pubkey(), + position_token_account, + whirlpool, + token_program: TOKEN_PROGRAM_ID, + system_program: system_program::id(), + associated_token_program: spl_associated_token_account::id(), + rent: RENT_PROGRAM_ID, + } + .instruction(OpenPositionInstructionArgs { + tick_lower_index: lower_tick_index, + tick_upper_index: upper_tick_index, + position_bump, + }), + ); + + ctx.send_transaction_with_signers(instructions, vec![&position_mint]) .await?; Ok(position_mint.pubkey()) @@ -190,21 +217,21 @@ pub async fn setup_te_position( } } - // 1) Keypair for the TE position mint let te_position_mint = ctx.get_next_keypair(); - // 2) Derive the position PDA let (position_pubkey, position_bump) = get_position_address(&te_position_mint.pubkey())?; - // 3) Build an OpenPosition instruction with token_program = TOKEN_2022_PROGRAM_ID + let te_position_token_account = + get_associated_token_address(&owner, &te_position_mint.pubkey()); + let open_position_ix = OpenPosition { funder: ctx.signer.pubkey(), - owner: owner, + owner, position: position_pubkey, position_mint: te_position_mint.pubkey(), - position_token_account: Pubkey::default(), + position_token_account: te_position_token_account, whirlpool, - token_program: TOKEN_2022_PROGRAM_ID, // TE uses token-2022 + token_program: TOKEN_PROGRAM_ID, system_program: system_program::id(), associated_token_program: spl_associated_token_account::id(), rent: RENT_PROGRAM_ID, @@ -215,11 +242,10 @@ pub async fn setup_te_position( position_bump, }); - // 4) The instruction itself will create the mint & ATA - ctx.send_transaction_with_signers(vec![open_position_ix], vec![&te_position_mint]) + let tx_result = ctx + .send_transaction_with_signers(vec![open_position_ix], vec![&te_position_mint]) .await?; - // 5) Return the position PDA Ok(position_pubkey) } From e0128f5a2cfe98614fccf7cad23696a5abf63765 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Mon, 13 Jan 2025 19:39:02 +0900 Subject: [PATCH 25/33] Update full combo of tests --- rust-sdk/whirlpool/src/increase_liquidity.rs | 274 ++++++++++--------- 1 file changed, 141 insertions(+), 133 deletions(-) diff --git a/rust-sdk/whirlpool/src/increase_liquidity.rs b/rust-sdk/whirlpool/src/increase_liquidity.rs index 8799f28d0..4d6bfee2c 100644 --- a/rust-sdk/whirlpool/src/increase_liquidity.rs +++ b/rust-sdk/whirlpool/src/increase_liquidity.rs @@ -698,32 +698,37 @@ pub async fn open_position_instructions( #[cfg(test)] mod tests { - use crate::{ - tests::{ - setup_ata_te, setup_ata_with_amount, setup_mint_te, setup_mint_te_fee, - setup_mint_with_decimals, setup_position, setup_te_position, setup_whirlpool, - RpcContext, SetupAtaConfig, - }, - DEFAULT_FUNDER, WHIRLPOOLS_CONFIG_ADDRESS, - }; + use std::collections::HashMap; + use std::error::Error; + use std::time::Duration; + + use orca_whirlpools_client::{get_position_address, Position}; use serial_test::serial; use solana_program_test::tokio; - use solana_sdk::pubkey::Pubkey; + use solana_sdk::{ + program_pack::Pack, + pubkey::Pubkey, + signer::{keypair::Keypair, Signer}, + }; use spl_token::state::Account as TokenAccount; use spl_token_2022::{ - extension::StateWithExtensionsOwned, state::Account as TokenAccount2022, + extension::{ExtensionType, StateWithExtensionsOwned}, + state::Account as TokenAccount2022, ID as TOKEN_2022_PROGRAM_ID, }; - use std::collections::HashMap; - - use super::*; - // Helper functions - async fn fetch_token(rpc: &RpcClient, address: Pubkey) -> Result> { - let account = rpc.get_account(&address).await?; - TokenAccount::unpack(&account.data).map_err(|e| e.into()) - } + use crate::{ + increase_liquidity_instructions, + tests::{ + setup_ata_te, setup_ata_with_amount, setup_mint_te, setup_mint_te_fee, + setup_mint_with_decimals, setup_position, setup_te_position, setup_whirlpool, + RpcContext, SetupAtaConfig, + }, + IncreaseLiquidityParam, + }; + // SPL Token 2022 or SPL Token + use solana_client::nonblocking::rpc_client::RpcClient; async fn fetch_position(rpc: &RpcClient, address: Pubkey) -> Result> { let account = rpc.get_account(&address).await?; Position::from_bytes(&account.data).map_err(|e| e.into()) @@ -733,34 +738,87 @@ mod tests { let account_data = rpc.get_account(&address).await?; if account_data.owner == TOKEN_2022_PROGRAM_ID { + // Token-2022 + (possibly) extension let state = StateWithExtensionsOwned::::unpack(account_data.data)?; Ok(state.base.amount) } else { + // SPL Token let token_account = TokenAccount::unpack(&account_data.data)?; Ok(token_account.amount) } } - const TEST_SLIPPAGE_TOLERANCE: u16 = 100; // 1% - const TEST_INCREASE_LIQUIDITY_PARAM: u64 = 100_000; + async fn verify_increase_liquidity( + ctx: &RpcContext, + increase_ix: &crate::IncreaseLiquidityInstruction, + token_a_account: Pubkey, + token_b_account: Pubkey, + position_mint: Pubkey, + ) -> Result<(), Box> { + let before_a = get_token_balance(&ctx.rpc, token_a_account).await?; + let before_b = get_token_balance(&ctx.rpc, token_b_account).await?; + + let signers: Vec<&Keypair> = increase_ix.additional_signers.iter().collect(); + ctx.send_transaction_with_signers(increase_ix.instructions.clone(), signers) + .await?; + + let after_a = get_token_balance(&ctx.rpc, token_a_account).await?; + let after_b = get_token_balance(&ctx.rpc, token_b_account).await?; + let used_a = before_a.saturating_sub(after_a); + let used_b = before_b.saturating_sub(after_b); + + let quote = &increase_ix.quote; + assert!( + used_a >= quote.token_est_a && used_a <= quote.token_max_a, + "Token A usage out of range: used={}, est={}..{}", + used_a, + quote.token_est_a, + quote.token_max_a + ); + assert!( + used_b >= quote.token_est_b && used_b <= quote.token_max_b, + "Token B usage out of range: used={}, est={}..{}", + used_b, + quote.token_est_b, + quote.token_max_b + ); + + let position_pubkey = get_position_address(&position_mint)?.0; + let position_data = fetch_position(&ctx.rpc, position_pubkey).await?; + assert_eq!( + position_data.liquidity, quote.liquidity_delta, + "Position liquidity mismatch! expected={}, got={}", + quote.liquidity_delta, position_data.liquidity + ); + + Ok(()) + } #[tokio::test] - async fn test_increase_liquidity_with_different_pools() -> Result<(), Box> { + #[serial] + async fn test_increase_liquidity_multiple_combos() -> Result<(), Box> { let ctx = RpcContext::new().await; - // Setup mints with different characteristics let mint_a = setup_mint_with_decimals(&ctx, 9).await?; let mint_b = setup_mint_with_decimals(&ctx, 9).await?; - let te_mint = setup_mint_te(&ctx, &[]).await?; - let te_fee_mint = setup_mint_te_fee(&ctx).await?; // 1% fee + let mint_te_a = setup_mint_te(&ctx, &[]).await?; // Token-2022 + let mint_te_b = setup_mint_te(&ctx, &[]).await?; // Token-2022 + let mint_te_fee = setup_mint_te_fee(&ctx).await?; // Token-2022 + transfer-fee - // Setup token accounts with initial balances let token_balance: u64 = 1_000_000; let user_ata_a = setup_ata_with_amount(&ctx, mint_a, token_balance).await?; let user_ata_b = setup_ata_with_amount(&ctx, mint_b, token_balance).await?; - let user_ata_te = setup_ata_te( + let user_ata_te_a = setup_ata_te( + &ctx, + mint_te_a, + Some(SetupAtaConfig { + amount: Some(token_balance), + }), + ) + .await?; + let user_ata_te_b = setup_ata_te( &ctx, - te_mint, + mint_te_b, Some(SetupAtaConfig { amount: Some(token_balance), }), @@ -768,124 +826,74 @@ mod tests { .await?; let user_ata_tefee = setup_ata_te( &ctx, - te_fee_mint, + mint_te_fee, Some(SetupAtaConfig { amount: Some(token_balance), }), ) .await?; - // Setup different pool types - let pool_ab = setup_whirlpool(&ctx, mint_a, mint_b, 64).await?; - let pool_a_te = setup_whirlpool(&ctx, mint_a, te_mint, 64).await?; - let pool_a_tefee = setup_whirlpool(&ctx, mint_a, te_fee_mint, 64).await?; - - // Test cases with different position ranges - let position_ranges = vec![(-128, 128), (-64, 64), (-32, 32)]; - - // Test regular pool (A/B) - for range in &position_ranges { - let position_mint = - setup_position(&ctx, pool_ab, Some(*range), Some(ctx.signer.pubkey())).await?; - let increase_ix = increase_liquidity_instructions( - &ctx.rpc, - position_mint, - IncreaseLiquidityParam::TokenA(TEST_INCREASE_LIQUIDITY_PARAM), - Some(TEST_SLIPPAGE_TOLERANCE), - Some(ctx.signer.pubkey()), - ) - .await?; - - verify_increase_liquidity(&ctx, &increase_ix, user_ata_a, user_ata_b, position_mint) + let mut minted: HashMap<&str, Pubkey> = HashMap::new(); + minted.insert("A", mint_a); + minted.insert("B", mint_b); + minted.insert("TEA", mint_te_a); + minted.insert("TEB", mint_te_b); + minted.insert("TEFee", mint_te_fee); + + let mut user_atas: HashMap<&str, Pubkey> = HashMap::new(); + user_atas.insert("A", user_ata_a); + user_atas.insert("B", user_ata_b); + user_atas.insert("TEA", user_ata_te_a); + user_atas.insert("TEB", user_ata_te_b); + user_atas.insert("TEFee", user_ata_tefee); + + let pool_combos = vec![ + ("A-B", ("A", "B")), + ("A-TEA", ("A", "TEA")), + ("TEA-TEB", ("TEA", "TEB")), + ("A-TEFee", ("A", "TEFee")), + ]; + + let position_ranges = vec![ + ("equally centered", (-100, 100)), + ("one sided A", (-100, -1)), + ("one sided B", (1, 100)), + ]; + + let tick_spacing = 64; + + for (pool_name, (mint_a_key, mint_b_key)) in &pool_combos { + let pubkey_a = *minted.get(mint_a_key).unwrap(); + let pubkey_b = *minted.get(mint_b_key).unwrap(); + + let pool_pubkey = setup_whirlpool(&ctx, pubkey_a, pubkey_b, tick_spacing).await?; + + for (range_name, (lower, upper)) in &position_ranges { + let position_mint = + setup_position(&ctx, pool_pubkey, Some((*lower, *upper)), None).await?; + + let param = IncreaseLiquidityParam::Liquidity(10_000); + let increase_ix = increase_liquidity_instructions( + &ctx.rpc, + position_mint, + param, + Some(100), // 1% slippage + Some(ctx.signer.pubkey()), + ) .await?; - } - - // Test pool with transfer hook token (A/TE) - for range in &position_ranges { - let position_mint = - setup_position(&ctx, pool_a_te, Some(*range), Some(ctx.signer.pubkey())).await?; - let increase_ix = increase_liquidity_instructions( - &ctx.rpc, - position_mint, - IncreaseLiquidityParam::TokenA(TEST_INCREASE_LIQUIDITY_PARAM), - Some(TEST_SLIPPAGE_TOLERANCE), - Some(ctx.signer.pubkey()), - ) - .await?; - verify_increase_liquidity(&ctx, &increase_ix, user_ata_a, user_ata_te, position_mint) - .await?; - } + let ata_a = *user_atas.get(mint_a_key).unwrap(); + let ata_b = *user_atas.get(mint_b_key).unwrap(); - // Test pool with transfer fee token (A/TEFee) - for range in &position_ranges { - let position_mint = - setup_position(&ctx, pool_a_tefee, Some(*range), Some(ctx.signer.pubkey())).await?; - let increase_ix = increase_liquidity_instructions( - &ctx.rpc, - position_mint, - IncreaseLiquidityParam::TokenA(TEST_INCREASE_LIQUIDITY_PARAM), - Some(TEST_SLIPPAGE_TOLERANCE), - Some(ctx.signer.pubkey()), - ) - .await?; + verify_increase_liquidity(&ctx, &increase_ix, ata_a, ata_b, position_mint).await?; - verify_increase_liquidity( - &ctx, - &increase_ix, - user_ata_a, - user_ata_tefee, - position_mint, - ) - .await?; + println!( + "[combo={}, range={}] position={} => used token A/B within quote, liquidity ok", + pool_name, range_name, position_mint + ); + } } Ok(()) } - - async fn verify_increase_liquidity( - ctx: &RpcContext, - increase_ix: &IncreaseLiquidityInstruction, - token_a_account: Pubkey, - token_b_account: Pubkey, - position_mint: Pubkey, - ) -> Result<(), Box> { - let before_a = get_token_balance(&ctx.rpc, token_a_account).await?; - let before_b = get_token_balance(&ctx.rpc, token_b_account).await?; - let signers: Vec<&Keypair> = increase_ix.additional_signers.iter().collect(); - let tx = ctx - .send_transaction_with_signers(increase_ix.instructions.clone(), signers) - .await?; - - let after_a = get_token_balance(&ctx.rpc, token_a_account).await?; - let after_b = get_token_balance(&ctx.rpc, token_b_account).await?; - let used_a = before_a.saturating_sub(after_a); - let used_b = before_b.saturating_sub(after_b); - - // Verify token usage is within expected ranges - assert!( - used_a >= increase_ix.quote.token_est_a && used_a <= increase_ix.quote.token_max_a, - "token A usage out of expected range: used={} estimate={}..{}", - used_a, - increase_ix.quote.token_est_a, - increase_ix.quote.token_max_a - ); - assert!( - used_b >= increase_ix.quote.token_est_b && used_b <= increase_ix.quote.token_max_b, - "token B usage out of expected range: used={} estimate={}..{}", - used_b, - increase_ix.quote.token_est_b, - increase_ix.quote.token_max_b - ); - - // Verify position liquidity - let position_pubkey = get_position_address(&position_mint).unwrap().0; - let position_data = fetch_position(&ctx.rpc, position_pubkey).await?; - assert_eq!( - position_data.liquidity, increase_ix.quote.liquidity_delta, - "Position liquidity does not match the quote" - ); - - Ok(()) - } } From e15f60bba20b126b57c8c007c66ae7768c01e1bf Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Tue, 14 Jan 2025 01:23:45 +0900 Subject: [PATCH 26/33] Update multiple combo of tests Update program to handle multiple tick array when decrease --- rust-sdk/whirlpool/src/decrease_liquidity.rs | 294 +++++++++++-------- rust-sdk/whirlpool/src/tests/program.rs | 92 +++++- 2 files changed, 260 insertions(+), 126 deletions(-) diff --git a/rust-sdk/whirlpool/src/decrease_liquidity.rs b/rust-sdk/whirlpool/src/decrease_liquidity.rs index dcaac4473..59b18ba4e 100644 --- a/rust-sdk/whirlpool/src/decrease_liquidity.rs +++ b/rust-sdk/whirlpool/src/decrease_liquidity.rs @@ -638,152 +638,210 @@ pub async fn close_position_instructions( #[cfg(test)] mod tests { - use super::*; - - // Helper functions - async fn fetch_token(rpc: &RpcClient, address: Pubkey) -> Result> { - let account = rpc.get_account(&address).await?; - TokenAccount::unpack(&account.data).map_err(|e| e.into()) - } - - async fn fetch_position(rpc: &RpcClient, address: Pubkey) -> Result> { - let account = rpc.get_account(&address).await?; - Position::from_bytes(&account.data).map_err(|e| e.into()) - } - - use crate::tests::{ - setup_ata_te, setup_ata_with_amount, setup_mint_te_fee, setup_mint_with_decimals, - setup_position, setup_te_position, setup_whirlpool, RpcContext, SetupAtaConfig, - }; - use serial_test::serial; - use solana_sdk::pubkey::Pubkey; - use solana_sdk::signature::{Keypair, Signer}; - use spl_associated_token_account::get_associated_token_address_with_program_id; - use spl_token::state::Account as TokenAccount; + use std::collections::HashMap; use std::error::Error; - const TEST_SLIPPAGE_TOLERANCE: u16 = 100; // 1% - - #[tokio::test] - #[serial] - async fn test_decrease_liquidity() -> Result<(), Box> { - let ctx = RpcContext::new().await; - - // Setup and initialize - let mint_a_pubkey = setup_mint_with_decimals(&ctx, 9).await?; - let mint_b_pubkey = setup_mint_with_decimals(&ctx, 9).await?; - let token_balance: u64 = 1_000_000_000; - setup_ata_with_amount(&ctx, mint_a_pubkey, token_balance).await?; - setup_ata_with_amount(&ctx, mint_b_pubkey, token_balance).await?; - - // Setup pool and position - let tick_spacing = 64; - let pool_pubkey = setup_whirlpool(&ctx, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; - - let position_mint = setup_position( - &ctx, - pool_pubkey, - Some((-128, 128)), - Some(ctx.signer.pubkey()), - ) - .await?; + use serial_test::serial; + use solana_program_test::tokio; + use solana_sdk::{ + program_pack::Pack, + pubkey::Pubkey, + signer::{keypair::Keypair, Signer}, + }; - // Increase liquidity first to have liquidity to decrease - let increase_param = crate::increase_liquidity::IncreaseLiquidityParam::Liquidity(100_000); - let increase_ix = crate::increase_liquidity::increase_liquidity_instructions( - &ctx.rpc, - position_mint, - increase_param, - Some(TEST_SLIPPAGE_TOLERANCE), - Some(ctx.signer.pubkey()), - ) - .await?; + use solana_client::nonblocking::rpc_client::RpcClient; + use spl_token::state::Account as TokenAccount; + use spl_token_2022::{ + extension::StateWithExtensionsOwned, state::Account as TokenAccount2022, + ID as TOKEN_2022_PROGRAM_ID, + }; - ctx.send_transaction_with_signers(increase_ix.instructions, vec![]) - .await?; + use crate::{ + decrease_liquidity_instructions, increase_liquidity_instructions, + tests::{ + setup_ata_te, setup_ata_with_amount, setup_mint_te, setup_mint_te_fee, + setup_mint_with_decimals, setup_position, setup_whirlpool, RpcContext, SetupAtaConfig, + }, + DecreaseLiquidityParam, IncreaseLiquidityParam, + }; + use orca_whirlpools_client::{get_position_address, Position}; + + async fn get_token_balance(rpc: &RpcClient, address: Pubkey) -> Result> { + let account_data = rpc.get_account(&address).await?; + + if account_data.owner == TOKEN_2022_PROGRAM_ID { + // Token-2022 + (possibly) extension + let state = StateWithExtensionsOwned::::unpack(account_data.data)?; + Ok(state.base.amount) + } else { + // SPL Token + let token_account = TokenAccount::unpack(&account_data.data)?; + Ok(token_account.amount) + } + } - // Test Decrease Liquidity - let decrease_param = DecreaseLiquidityParam::Liquidity(50_000); - let decrease_ix = decrease_liquidity_instructions( - &ctx.rpc, - position_mint, - decrease_param, - Some(TEST_SLIPPAGE_TOLERANCE), - Some(ctx.signer.pubkey()), - ) - .await?; + async fn fetch_position( + rpc: &solana_client::nonblocking::rpc_client::RpcClient, + position_pubkey: Pubkey, + ) -> Result> { + let account = rpc.get_account(&position_pubkey).await?; + Ok(Position::from_bytes(&account.data)?) + } - // Send transaction - ctx.send_transaction_with_signers(decrease_ix.instructions, vec![]) + async fn verify_decrease_liquidity( + ctx: &RpcContext, + decrease_ix: &crate::DecreaseLiquidityInstruction, + token_a_account: Pubkey, + token_b_account: Pubkey, + position_mint: Pubkey, + ) -> Result<(), Box> { + let before_a = get_token_balance(&ctx.rpc, token_a_account).await?; + let before_b = get_token_balance(&ctx.rpc, token_b_account).await?; + + let signers: Vec<&Keypair> = decrease_ix.additional_signers.iter().collect(); + ctx.send_transaction_with_signers(decrease_ix.instructions.clone(), signers) .await?; - // Fetch the updated position and check liquidity - let position_address = get_position_address(&position_mint)?.0; - let updated_position = fetch_position(&ctx.rpc, position_address).await?; + let after_a = get_token_balance(&ctx.rpc, token_a_account).await?; + let after_b = get_token_balance(&ctx.rpc, token_b_account).await?; + let gained_a = after_a.saturating_sub(before_a); + let gained_b = after_b.saturating_sub(before_b); + + let quote = &decrease_ix.quote; + assert!( + gained_a >= quote.token_min_a && gained_a <= quote.token_est_a, + "Token A gain out of range: gained={}, expected={}..{}", + gained_a, + quote.token_min_a, + quote.token_est_a + ); + assert!( + gained_b >= quote.token_min_b && gained_b <= quote.token_est_b, + "Token B gain out of range: gained={}, expected={}..{}", + gained_b, + quote.token_min_b, + quote.token_est_b + ); - assert_eq!(updated_position.liquidity, 50_000); + let position_pubkey = get_position_address(&position_mint)?.0; + let position_data = fetch_position(&ctx.rpc, position_pubkey).await?; + assert_eq!( + position_data.liquidity, quote.liquidity_delta, + "Position liquidity mismatch! expected={}, got={}", + quote.liquidity_delta, position_data.liquidity + ); Ok(()) } #[tokio::test] #[serial] - async fn test_decrease_liquidity_to_zero() -> Result<(), Box> { + async fn test_decrease_liquidity_multiple_combos() -> Result<(), Box> { let ctx = RpcContext::new().await; - // Setup and initialize - let mint_a_pubkey = setup_mint_with_decimals(&ctx, 9).await?; - let mint_b_pubkey = setup_mint_with_decimals(&ctx, 9).await?; - let token_balance: u64 = 1_000_000_000; - setup_ata_with_amount(&ctx, mint_a_pubkey, token_balance).await?; - setup_ata_with_amount(&ctx, mint_b_pubkey, token_balance).await?; + let mint_a = setup_mint_with_decimals(&ctx, 9).await?; + let mint_b = setup_mint_with_decimals(&ctx, 9).await?; + let mint_te_a = setup_mint_te(&ctx, &[]).await?; + let mint_te_b = setup_mint_te(&ctx, &[]).await?; + let mint_te_fee = setup_mint_te_fee(&ctx).await?; - // Setup pool and position - let tick_spacing = 64; - let pool_pubkey = setup_whirlpool(&ctx, mint_a_pubkey, mint_b_pubkey, tick_spacing).await?; - - let position_mint = setup_position( + let token_balance = 1_000_000; + let user_ata_a = setup_ata_with_amount(&ctx, mint_a, token_balance).await?; + let user_ata_b = setup_ata_with_amount(&ctx, mint_b, token_balance).await?; + let user_ata_te_a = setup_ata_te( &ctx, - pool_pubkey, - Some((-128, 128)), - Some(ctx.signer.pubkey()), + mint_te_a, + Some(SetupAtaConfig { + amount: Some(token_balance), + }), ) .await?; - - // Increase liquidity first to have liquidity to decrease - let increase_param = crate::increase_liquidity::IncreaseLiquidityParam::Liquidity(100_000); - let increase_ix = crate::increase_liquidity::increase_liquidity_instructions( - &ctx.rpc, - position_mint, - increase_param, - Some(TEST_SLIPPAGE_TOLERANCE), - Some(ctx.signer.pubkey()), + let user_ata_te_b = setup_ata_te( + &ctx, + mint_te_b, + Some(SetupAtaConfig { + amount: Some(token_balance), + }), ) .await?; - - ctx.send_transaction_with_signers(increase_ix.instructions, vec![]) - .await?; - - // Decrease liquidity to zero - let decrease_param = DecreaseLiquidityParam::Liquidity(100_000); - let decrease_ix = decrease_liquidity_instructions( - &ctx.rpc, - position_mint, - decrease_param, - Some(TEST_SLIPPAGE_TOLERANCE), - Some(ctx.signer.pubkey()), + let user_ata_tefee = setup_ata_te( + &ctx, + mint_te_fee, + Some(SetupAtaConfig { + amount: Some(token_balance), + }), ) .await?; - // Send transaction - ctx.send_transaction_with_signers(decrease_ix.instructions, vec![]) - .await?; + let mut minted: HashMap<&str, Pubkey> = HashMap::new(); + minted.insert("A", mint_a); + minted.insert("B", mint_b); + minted.insert("TEA", mint_te_a); + minted.insert("TEB", mint_te_b); + minted.insert("TEFee", mint_te_fee); + + let mut user_atas: HashMap<&str, Pubkey> = HashMap::new(); + user_atas.insert("A", user_ata_a); + user_atas.insert("B", user_ata_b); + user_atas.insert("TEA", user_ata_te_a); + user_atas.insert("TEB", user_ata_te_b); + user_atas.insert("TEFee", user_ata_tefee); + + let pool_combos = vec![ + ("A-B", ("A", "B")), + ("A-TEA", ("A", "TEA")), + ("TEA-TEB", ("TEA", "TEB")), + ("A-TEFee", ("A", "TEFee")), + ]; + + let position_ranges = vec![ + ("equally centered", (-100, 100)), + ("one sided A", (-100, -1)), + ("one sided B", (1, 100)), + ]; - // Fetch the updated position and check liquidity - let position_address = get_position_address(&position_mint)?.0; - let updated_position = fetch_position(&ctx.rpc, position_address).await?; + let tick_spacing = 64; - assert_eq!(updated_position.liquidity, 0); + for (pool_name, (mint_a_key, mint_b_key)) in &pool_combos { + let pubkey_a = *minted.get(mint_a_key).unwrap(); + let pubkey_b = *minted.get(mint_b_key).unwrap(); + + let pool_pubkey = setup_whirlpool(&ctx, pubkey_a, pubkey_b, tick_spacing).await?; + + for (range_name, (lower, upper)) in &position_ranges { + let position_mint = + setup_position(&ctx, pool_pubkey, Some((*lower, *upper)), None).await?; + + let increase_ix = increase_liquidity_instructions( + &ctx.rpc, + position_mint, + IncreaseLiquidityParam::Liquidity(100_000), + Some(100), // 1% slippage + Some(ctx.signer.pubkey()), + ) + .await?; + ctx.send_transaction_with_signers(increase_ix.instructions, vec![]) + .await?; + + let decrease_ix = decrease_liquidity_instructions( + &ctx.rpc, + position_mint, + DecreaseLiquidityParam::Liquidity(50_000), + Some(100), // 1% slippage + Some(ctx.signer.pubkey()), + ) + .await?; + + let ata_a = *user_atas.get(mint_a_key).unwrap(); + let ata_b = *user_atas.get(mint_b_key).unwrap(); + verify_decrease_liquidity(&ctx, &decrease_ix, ata_a, ata_b, position_mint).await?; + + println!( + "[combo={}, range={}, pos={}] Decrease 50k => OK", + pool_name, range_name, position_mint + ); + } + } Ok(()) } diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index f113eb354..077759796 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -4,10 +4,12 @@ use orca_whirlpools_client::{ get_whirlpool_address, InitializePoolV2, InitializePoolV2InstructionArgs, InitializePositionBundle, InitializeTickArray, InitializeTickArrayInstructionArgs, OpenBundledPosition, OpenBundledPositionInstructionArgs, OpenPosition, - OpenPositionInstructionArgs, Whirlpool, + OpenPositionInstructionArgs, OpenPositionWithTokenExtensions, + OpenPositionWithTokenExtensionsInstructionArgs, Whirlpool, }; use orca_whirlpools_core::{ get_initializable_tick_index, get_tick_array_start_tick_index, tick_index_to_sqrt_price, + TICK_ARRAY_SIZE, }; use solana_program::program_pack::Pack; use solana_program::sysvar::rent::ID as RENT_PROGRAM_ID; @@ -31,6 +33,61 @@ use super::rpc::RpcContext; use crate::tests::token_extensions::setup_mint_te; +pub async fn init_tick_arrays_for_range( + ctx: &RpcContext, + whirlpool: Pubkey, + lower_tick_index: i32, + upper_tick_index: i32, + spacing: u16, +) -> Result<(), Box> { + let (low, high) = if lower_tick_index <= upper_tick_index { + (lower_tick_index, upper_tick_index) + } else { + (upper_tick_index, lower_tick_index) + }; + + let offset = (spacing as i32) * (TICK_ARRAY_SIZE as i32); + + let start_low = get_tick_array_start_tick_index(low, spacing); + let start_high = get_tick_array_start_tick_index(high, spacing); + + let (begin, end) = if start_low <= start_high { + (start_low, start_high) + } else { + (start_high, start_low) + }; + + let mut instructions = vec![]; + + let mut current = begin; + while current <= end { + let (tick_array_addr, _) = get_tick_array_address(&whirlpool, current)?; + + let account_result = ctx.rpc.get_account(&tick_array_addr).await; + if account_result.is_err() { + instructions.push( + InitializeTickArray { + whirlpool, + funder: ctx.signer.pubkey(), + tick_array: tick_array_addr, + system_program: system_program::id(), + } + .instruction(InitializeTickArrayInstructionArgs { + start_tick_index: current, + }), + ); + } + + current += offset; + } + + if !instructions.is_empty() { + ctx.send_transaction(instructions).await?; + } + + Ok(()) +} + pub async fn setup_whirlpool( ctx: &RpcContext, token_a: Pubkey, @@ -105,6 +162,15 @@ pub async fn setup_position( let (lower_tick_array_addr, _) = get_tick_array_address(&whirlpool, lower_tick_array_start)?; let (upper_tick_array_addr, _) = get_tick_array_address(&whirlpool, upper_tick_array_start)?; + init_tick_arrays_for_range( + ctx, + whirlpool, + tick_lower, + tick_upper, + whirlpool_account.tick_spacing, + ) + .await?; + let mut instructions = vec![]; let lower_tick_array_account = ctx.rpc.get_account(&lower_tick_array_addr).await; @@ -169,12 +235,14 @@ pub async fn setup_position( Ok(position_mint.pubkey()) } + pub async fn setup_te_position( ctx: &RpcContext, whirlpool: Pubkey, tick_range: Option<(i32, i32)>, owner: Option, ) -> Result> { + let metadata_update_auth = Pubkey::try_from("3axbTs2z5GBy6usVbNVoqEgZMng3vZvMnAoX29BFfwhr")?; let owner = owner.unwrap_or_else(|| ctx.signer.pubkey()); let whirlpool_data = ctx.rpc.get_account(&whirlpool).await?; let whirlpool_account = Whirlpool::from_bytes(&whirlpool_data.data)?; @@ -192,6 +260,14 @@ pub async fn setup_te_position( get_tick_array_start_tick_index(lower_tick_index, whirlpool_account.tick_spacing), get_tick_array_start_tick_index(upper_tick_index, whirlpool_account.tick_spacing), ]; + init_tick_arrays_for_range( + ctx, + whirlpool, + tick_lower, + tick_upper, + whirlpool_account.tick_spacing, + ) + .await?; for start_tick in tick_arrays.iter() { let (tick_array_address, _) = get_tick_array_address(&whirlpool, *start_tick)?; @@ -224,22 +300,22 @@ pub async fn setup_te_position( let te_position_token_account = get_associated_token_address(&owner, &te_position_mint.pubkey()); - let open_position_ix = OpenPosition { + let open_position_ix = OpenPositionWithTokenExtensions { funder: ctx.signer.pubkey(), owner, position: position_pubkey, position_mint: te_position_mint.pubkey(), position_token_account: te_position_token_account, whirlpool, - token_program: TOKEN_PROGRAM_ID, + token2022_program: TOKEN_2022_PROGRAM_ID, system_program: system_program::id(), associated_token_program: spl_associated_token_account::id(), - rent: RENT_PROGRAM_ID, + metadata_update_auth, } - .instruction(OpenPositionInstructionArgs { - tick_lower_index: -128, - tick_upper_index: 128, - position_bump, + .instruction(OpenPositionWithTokenExtensionsInstructionArgs { + tick_lower_index: lower_tick_index, + tick_upper_index: upper_tick_index, + with_token_metadata_extension: true, }); let tx_result = ctx From 7861e2e4bc8440e66e154dba63bf15389cf93575 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Tue, 14 Jan 2025 03:00:33 +0900 Subject: [PATCH 27/33] Update to use actual address in positions --- rust-sdk/whirlpool/src/tests/program.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index 39f972394..e395b9168 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -82,14 +82,20 @@ pub async fn setup_position(whirlpool: Pubkey) -> Result> let (position_pubkey, position_bump) = get_position_address(&position_mint.pubkey())?; + let position_token_account = get_associated_token_address_with_program_id( + &ctx.signer.pubkey(), + &position_mint.pubkey(), + &TOKEN_PROGRAM_ID, + ); + let open_position_ix = OpenPosition { funder: ctx.signer.pubkey(), owner: ctx.signer.pubkey(), position: position_pubkey, position_mint: position_mint.pubkey(), - position_token_account: Pubkey::default(), // instruction will create + position_token_account, whirlpool, - token_program: TOKEN_PROGRAM_ID, // or TOKEN_2022_PROGRAM_ID if needed + token_program: TOKEN_PROGRAM_ID, system_program: system_program::id(), associated_token_program: spl_associated_token_account::id(), rent: RENT_PROGRAM_ID, @@ -109,13 +115,16 @@ pub async fn setup_position(whirlpool: Pubkey) -> Result> pub async fn setup_te_position(whirlpool: Pubkey) -> Result> { let ctx = RpcContext::new().await; - // 1) Keypair for the TE position mint let te_position_mint = ctx.get_next_keypair(); - // 2) Derive the position PDA let (position_pubkey, position_bump) = get_position_address(&te_position_mint.pubkey())?; - // 3) Build an OpenPosition instruction with token_program = TOKEN_2022_PROGRAM_ID + let position_token_account = get_associated_token_address_with_program_id( + &ctx.signer.pubkey(), + &te_position_mint.pubkey(), + &TOKEN_2022_PROGRAM_ID, + ); + let open_position_ix = OpenPosition { funder: ctx.signer.pubkey(), owner: ctx.signer.pubkey(), @@ -123,7 +132,7 @@ pub async fn setup_te_position(whirlpool: Pubkey) -> Result Result Date: Tue, 14 Jan 2025 03:13:26 +0900 Subject: [PATCH 28/33] Fix to use real address for positions --- rust-sdk/whirlpool/src/tests/program.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/rust-sdk/whirlpool/src/tests/program.rs b/rust-sdk/whirlpool/src/tests/program.rs index 98e887767..f9fe47be3 100644 --- a/rust-sdk/whirlpool/src/tests/program.rs +++ b/rust-sdk/whirlpool/src/tests/program.rs @@ -17,7 +17,8 @@ use solana_sdk::{ system_instruction, system_program, }; use spl_associated_token_account::{ - get_associated_token_address, instruction::create_associated_token_account, + get_associated_token_address, get_associated_token_address_with_program_id, + instruction::create_associated_token_account, }; use spl_token::instruction::initialize_mint2; use spl_token::ID as TOKEN_PROGRAM_ID; @@ -147,8 +148,6 @@ pub async fn setup_position( &TOKEN_PROGRAM_ID, ); let owner_pubkey = owner.unwrap_or(ctx.signer.pubkey()); - let position_token_account = - get_associated_token_address(&owner_pubkey, &position_mint.pubkey()); let open_position_ix = OpenPosition { funder: ctx.signer.pubkey(), @@ -163,8 +162,8 @@ pub async fn setup_position( rent: RENT_PROGRAM_ID, } .instruction(OpenPositionInstructionArgs { - tick_lower_index: -128, - tick_upper_index: 128, + tick_lower_index: lower_tick_index, + tick_upper_index: upper_tick_index, position_bump, }); instructions.push( @@ -193,9 +192,6 @@ pub async fn setup_position( Ok(position_mint.pubkey()) } -pub async fn setup_te_position(whirlpool: Pubkey) -> Result> { - let ctx = RpcContext::new().await; - pub async fn setup_te_position( ctx: &RpcContext, whirlpool: Pubkey, @@ -265,20 +261,17 @@ pub async fn setup_te_position( position_token_account: te_position_token_account, whirlpool, token_program: TOKEN_2022_PROGRAM_ID, - token_program: TOKEN_PROGRAM_ID, system_program: system_program::id(), associated_token_program: spl_associated_token_account::id(), rent: RENT_PROGRAM_ID, } .instruction(OpenPositionInstructionArgs { - tick_lower_index: -128, - tick_upper_index: 128, + tick_lower_index: lower_tick_index, + tick_upper_index: upper_tick_index, position_bump, }); ctx.send_transaction_with_signers(vec![open_position_ix], vec![&te_position_mint]) - let tx_result = ctx - .send_transaction_with_signers(vec![open_position_ix], vec![&te_position_mint]) .await?; Ok(position_pubkey) From a5a04c644b1265984b27fb1bc791ef14f034f3d7 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Tue, 14 Jan 2025 04:43:44 +0900 Subject: [PATCH 29/33] Add harvest test --- rust-sdk/whirlpool/src/harvest.rs | 200 ++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/rust-sdk/whirlpool/src/harvest.rs b/rust-sdk/whirlpool/src/harvest.rs index b2527d447..b7d31dce5 100644 --- a/rust-sdk/whirlpool/src/harvest.rs +++ b/rust-sdk/whirlpool/src/harvest.rs @@ -325,3 +325,203 @@ pub async fn harvest_position_instructions( rewards_quote, }) } + +#[cfg(test)] +mod tests { + use orca_whirlpools_client::get_position_address; + use orca_whirlpools_client::Position; + use serial_test::serial; + use solana_client::nonblocking::rpc_client::RpcClient; + use solana_program_test::tokio; + use solana_sdk::{ + program_pack::Pack, + pubkey::Pubkey, + signer::{keypair::Keypair, Signer}, + }; + use std::collections::HashMap; + use std::error::Error; + + use crate::{ + harvest_position_instructions, increase_liquidity_instructions, swap_instructions, + tests::{ + setup_ata_te, setup_ata_with_amount, setup_mint_te, setup_mint_te_fee, + setup_mint_with_decimals, setup_position, setup_te_position, setup_whirlpool, + RpcContext, SetupAtaConfig, + }, + HarvestPositionInstruction, IncreaseLiquidityParam, SwapType, + }; + + use spl_token::state::Account as TokenAccount; + use spl_token_2022::{ + extension::StateWithExtensionsOwned, state::Account as TokenAccount2022, + ID as TOKEN_2022_PROGRAM_ID, + }; + + async fn fetch_position( + rpc: &solana_client::nonblocking::rpc_client::RpcClient, + position_pubkey: Pubkey, + ) -> Result> { + let account = rpc.get_account(&position_pubkey).await?; + Ok(Position::from_bytes(&account.data)?) + } + + async fn get_token_balance(rpc: &RpcClient, address: Pubkey) -> Result> { + let account_data = rpc.get_account(&address).await?; + + if account_data.owner == TOKEN_2022_PROGRAM_ID { + let state = StateWithExtensionsOwned::::unpack(account_data.data)?; + Ok(state.base.amount) + } else { + let token_account = TokenAccount::unpack(&account_data.data)?; + Ok(token_account.amount) + } + } + + async fn verify_harvest_position( + ctx: &RpcContext, + harvest_ix: &HarvestPositionInstruction, + ata_a: Pubkey, + ata_b: Pubkey, + + position_mint: Pubkey, + ) -> Result<(), Box> { + let before_a = get_token_balance(&ctx.rpc, ata_a).await?; + let before_b = get_token_balance(&ctx.rpc, ata_b).await?; + + let signers: Vec<&Keypair> = harvest_ix.additional_signers.iter().collect(); + ctx.send_transaction_with_signers(harvest_ix.instructions.clone(), signers) + .await?; + + let after_a = get_token_balance(&ctx.rpc, ata_a).await?; + let after_b = get_token_balance(&ctx.rpc, ata_b).await?; + let gained_a = after_a.saturating_sub(before_a); + let gained_b = after_b.saturating_sub(before_b); + + let fees_quote = &harvest_ix.fees_quote; + assert!( + gained_a >= fees_quote.fee_owed_a, + "Less token A than expected from harvest. got={}, expected={}", + gained_a, + fees_quote.fee_owed_a + ); + assert!( + gained_b >= fees_quote.fee_owed_b, + "Less token B than expected from harvest. got={}, expected={}", + gained_b, + fees_quote.fee_owed_b + ); + + let position_pubkey = get_position_address(&position_mint)?.0; + let position_data = fetch_position(&ctx.rpc, position_pubkey).await?; + + Ok(()) + } + + #[tokio::test] + #[serial] + async fn test_harvest_position_with_swap() -> Result<(), Box> { + let ctx = RpcContext::new().await; + + let mint_a = setup_mint_with_decimals(&ctx, 9).await?; + let mint_b = setup_mint_with_decimals(&ctx, 9).await?; + let mint_te_a = setup_mint_te(&ctx, &[]).await?; // Token-2022 + let mint_te_fee = setup_mint_te_fee(&ctx).await?; // Token-2022 + fee extension + + let user_ata_a = setup_ata_with_amount(&ctx, mint_a, 1_000_000).await?; + let user_ata_b = setup_ata_with_amount(&ctx, mint_b, 1_000_000).await?; + let user_ata_te_a = setup_ata_te( + &ctx, + mint_te_a, + Some(SetupAtaConfig { + amount: Some(1_000_000), + }), + ) + .await?; + let user_ata_te_fee = setup_ata_te( + &ctx, + mint_te_fee, + Some(SetupAtaConfig { + amount: Some(1_000_000), + }), + ) + .await?; + + let minted_map = HashMap::from([ + ("A", mint_a), + ("B", mint_b), + ("TEA", mint_te_a), + ("TEFee", mint_te_fee), + ]); + let user_atas_map = HashMap::from([ + ("A", user_ata_a), + ("B", user_ata_b), + ("TEA", user_ata_te_a), + ("TEFee", user_ata_te_fee), + ]); + + let pool_combos = vec![("A-B", ("A", "B")), ("A-TEA", ("A", "TEA"))]; + let position_ranges = vec![ + ("equally centered", (-100, 100)), + ("one sided A", (-100, -1)), + ]; + let tick_spacing = 64; + + for (pool_name, (mkey_a, mkey_b)) in &pool_combos { + let pubkey_a = minted_map[mkey_a]; + let pubkey_b = minted_map[mkey_b]; + let pool_pubkey = setup_whirlpool(&ctx, pubkey_a, pubkey_b, tick_spacing).await?; + + for (range_name, (lower, upper)) in &position_ranges { + let position_mint = + setup_position(&ctx, pool_pubkey, Some((*lower, *upper)), None).await?; + + let inc_liq_ix = increase_liquidity_instructions( + &ctx.rpc, + position_mint, + IncreaseLiquidityParam::Liquidity(50_000), + Some(100), + Some(ctx.signer.pubkey()), + ) + .await?; + ctx.send_transaction_with_signers(inc_liq_ix.instructions, vec![]) + .await?; + + let swap_a_10 = swap_instructions( + &ctx.rpc, + pool_pubkey, + 10, + pubkey_a, + SwapType::ExactIn, + Some(100), // 1% + Some(ctx.signer.pubkey()), + ) + .await?; + ctx.send_transaction_with_signers( + swap_a_10.instructions, + swap_a_10.additional_signers.iter().collect(), + ) + .await?; + + let harvest_ix = harvest_position_instructions( + &ctx.rpc, + position_mint, + Some(ctx.signer.pubkey()), + ) + .await?; + + // 검증 + let ata_a = user_atas_map[mkey_a]; + let ata_b = user_atas_map[mkey_b]; + + verify_harvest_position(&ctx, &harvest_ix, ata_a, ata_b, position_mint).await?; + + println!( + "[harvest w/ swap] pool={}, range={}, pos={} => fees & rewards harvested OK", + pool_name, range_name, position_mint + ); + } + } + + Ok(()) + } +} From 7ccb1206e339b0a3b743f43bce628f2ccb06206b Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Tue, 14 Jan 2025 05:35:17 +0900 Subject: [PATCH 30/33] Update comment --- rust-sdk/whirlpool/src/harvest.rs | 40 ++++++++++++++++++------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/rust-sdk/whirlpool/src/harvest.rs b/rust-sdk/whirlpool/src/harvest.rs index b7d31dce5..ca23ac58d 100644 --- a/rust-sdk/whirlpool/src/harvest.rs +++ b/rust-sdk/whirlpool/src/harvest.rs @@ -328,8 +328,7 @@ pub async fn harvest_position_instructions( #[cfg(test)] mod tests { - use orca_whirlpools_client::get_position_address; - use orca_whirlpools_client::Position; + use orca_whirlpools_client::{get_position_address, Position}; use serial_test::serial; use solana_client::nonblocking::rpc_client::RpcClient; use solana_program_test::tokio; @@ -338,23 +337,33 @@ mod tests { pubkey::Pubkey, signer::{keypair::Keypair, Signer}, }; - use std::collections::HashMap; - use std::error::Error; + use spl_token::state::Account as TokenAccount; + use spl_token_2022::{ + extension::StateWithExtensionsOwned, + state::Account as TokenAccount2022, + ID as TOKEN_2022_PROGRAM_ID, + }; + use std::{collections::HashMap, error::Error}; use crate::{ - harvest_position_instructions, increase_liquidity_instructions, swap_instructions, + harvest_position_instructions, + increase_liquidity_instructions, + swap_instructions, tests::{ - setup_ata_te, setup_ata_with_amount, setup_mint_te, setup_mint_te_fee, - setup_mint_with_decimals, setup_position, setup_te_position, setup_whirlpool, - RpcContext, SetupAtaConfig, + setup_ata_te, + setup_ata_with_amount, + setup_mint_te, + setup_mint_te_fee, + setup_mint_with_decimals, + setup_position, + setup_te_position, + setup_whirlpool, + RpcContext, + SetupAtaConfig, }, - HarvestPositionInstruction, IncreaseLiquidityParam, SwapType, - }; - - use spl_token::state::Account as TokenAccount; - use spl_token_2022::{ - extension::StateWithExtensionsOwned, state::Account as TokenAccount2022, - ID as TOKEN_2022_PROGRAM_ID, + HarvestPositionInstruction, + IncreaseLiquidityParam, + SwapType, }; async fn fetch_position( @@ -509,7 +518,6 @@ mod tests { ) .await?; - // 검증 let ata_a = user_atas_map[mkey_a]; let ata_b = user_atas_map[mkey_b]; From 453ce1e68aa345ba113afb383bfadab933f3688f Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Tue, 14 Jan 2025 22:22:19 +0900 Subject: [PATCH 31/33] Add swap test --- rust-sdk/whirlpool/src/swap.rs | 264 +++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) diff --git a/rust-sdk/whirlpool/src/swap.rs b/rust-sdk/whirlpool/src/swap.rs index 930d0cb5a..923255e48 100644 --- a/rust-sdk/whirlpool/src/swap.rs +++ b/rust-sdk/whirlpool/src/swap.rs @@ -323,3 +323,267 @@ pub async fn swap_instructions( additional_signers: token_accounts.additional_signers, }) } + +#[cfg(test)] +mod tests { + use serial_test::serial; + use solana_client::nonblocking::rpc_client::RpcClient; + use solana_sdk::{program_pack::Pack, pubkey::Pubkey, signer::Signer}; + use spl_token::state::Account as TokenAccount; + use spl_token_2022::state::Account as TokenAccount2022; + use spl_token_2022::{extension::StateWithExtensionsOwned, ID as TOKEN_2022_PROGRAM_ID}; + use std::error::Error; + + use crate::{ + create_concentrated_liquidity_pool_instructions, create_splash_pool_instructions, + increase_liquidity_instructions, open_full_range_position_instructions, swap_instructions, + tests::{setup_ata_with_amount, setup_mint_with_decimals, RpcContext}, + IncreaseLiquidityParam, SwapQuote, SwapType, + }; + + struct SwapTestContext { + pub ctx: RpcContext, + + pub mint_a: Pubkey, + pub mint_b: Pubkey, + pub ata_a: Pubkey, + pub ata_b: Pubkey, + } + + impl SwapTestContext { + pub async fn new() -> Result> { + let ctx = RpcContext::new().await; + + let mint_a = setup_mint_with_decimals(&ctx, 9).await?; + let mint_b = setup_mint_with_decimals(&ctx, 9).await?; + + let ata_a = setup_ata_with_amount(&ctx, mint_a, 1_000_000_000).await?; + let ata_b = setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; + + Ok(Self { + ctx, + mint_a, + mint_b, + ata_a, + ata_b, + }) + } + + async fn get_token_balance(&self, address: Pubkey) -> Result> { + let account_data = self.ctx.rpc.get_account(&address).await?; + if account_data.owner == TOKEN_2022_PROGRAM_ID { + let parsed = + StateWithExtensionsOwned::::unpack(account_data.data)?; + Ok(parsed.base.amount) + } else { + let parsed = TokenAccount::unpack(&account_data.data)?; + Ok(parsed.amount) + } + } + + pub async fn init_pool(&self, is_splash: bool) -> Result> { + if is_splash { + let pool = create_splash_pool_instructions( + &self.ctx.rpc, + self.mint_a, + self.mint_b, + None, + Some(self.ctx.signer.pubkey()), + ) + .await?; + self.ctx + .send_transaction_with_signers( + pool.instructions, + pool.additional_signers.iter().collect(), + ) + .await?; + Ok(pool.pool_address) + } else { + let cl_pool = create_concentrated_liquidity_pool_instructions( + &self.ctx.rpc, + self.mint_a, + self.mint_b, + 128, + None, + Some(self.ctx.signer.pubkey()), + ) + .await?; + self.ctx + .send_transaction_with_signers( + cl_pool.instructions, + cl_pool.additional_signers.iter().collect(), + ) + .await?; + Ok(cl_pool.pool_address) + } + } + + pub async fn open_position_with_liquidity( + &self, + pool_pubkey: Pubkey, + ) -> Result> { + let position = open_full_range_position_instructions( + &self.ctx.rpc, + pool_pubkey, + IncreaseLiquidityParam::Liquidity(50_000_000), + None, + Some(self.ctx.signer.pubkey()), + ) + .await?; + self.ctx + .send_transaction_with_signers( + position.instructions, + position.additional_signers.iter().collect(), + ) + .await?; + + Ok(position.position_mint) + } + + pub async fn do_swap( + &self, + pool_pubkey: Pubkey, + a_to_b: bool, + swap_type: SwapType, + amount: u64, + ) -> Result<(), Box> { + let specified_mint = if a_to_b { self.mint_a } else { self.mint_b }; + + let before_a = self.get_token_balance(self.ata_a).await?; + let before_b = self.get_token_balance(self.ata_b).await?; + + let swap_ix = swap_instructions( + &self.ctx.rpc, + pool_pubkey, + amount, + specified_mint, + swap_type.clone(), + Some(100), // 1% slippage + Some(self.ctx.signer.pubkey()), + ) + .await?; + + self.ctx + .send_transaction_with_signers( + swap_ix.instructions, + swap_ix.additional_signers.iter().collect(), + ) + .await?; + + let after_a = self.get_token_balance(self.ata_a).await?; + let after_b = self.get_token_balance(self.ata_b).await?; + + let used_a = before_a.saturating_sub(after_a); + let used_b = before_b.saturating_sub(after_b); + let gained_a = after_a.saturating_sub(before_a); + let gained_b = after_b.saturating_sub(before_b); + + match swap_ix.quote { + SwapQuote::ExactIn(q) => { + if a_to_b { + // used A, gained B + assert_eq!(used_a, q.token_in, "Used A mismatch"); + assert_eq!(gained_b, q.token_est_out, "Gained B mismatch"); + } else { + // used B, gained A + assert_eq!(used_b, q.token_in, "Used B mismatch"); + assert_eq!(gained_a, q.token_est_out, "Gained A mismatch"); + } + } + SwapQuote::ExactOut(q) => { + if a_to_b { + // gained B, used A + assert_eq!(gained_b, q.token_out, "Gained B mismatch"); + assert_eq!(used_a, q.token_est_in, "Used A mismatch"); + } else { + // gained A, used B + assert_eq!(gained_a, q.token_out, "Gained A mismatch"); + assert_eq!(used_b, q.token_est_in, "Used B mismatch"); + } + } + } + println!( + "swap result => a_to_b={}, used_a={}, used_b={}, gained_a={}, gained_b={}", + a_to_b, used_a, used_b, gained_a, gained_b + ); + + Ok(()) + } + } + + #[tokio::test] + async fn test_swap_for_multiple_pools() -> Result<(), Box> { + let stx = SwapTestContext::new().await?; + + let ctx = &stx.ctx; + + let mint_a = setup_mint_with_decimals(&ctx, 9).await?; + let mint_b = setup_mint_with_decimals(&ctx, 9).await?; + let ata_a = setup_ata_with_amount(&ctx, mint_a, 500_000_000).await?; + let ata_b = setup_ata_with_amount(&ctx, mint_b, 500_000_000).await?; + + let pool = create_concentrated_liquidity_pool_instructions( + &ctx.rpc, + mint_a, + mint_b, + 128, + None, + Some(ctx.signer.pubkey()), + ) + .await?; + ctx.send_transaction_with_signers( + pool.instructions, + pool.additional_signers.iter().collect(), + ) + .await?; + + let pool_pubkey = pool.pool_address; + + let position = open_full_range_position_instructions( + &ctx.rpc, + pool_pubkey, + IncreaseLiquidityParam::Liquidity(50_000_000), + None, + Some(ctx.signer.pubkey()), + ) + .await?; + ctx.send_transaction_with_signers( + position.instructions, + position.additional_signers.iter().collect(), + ) + .await?; + + let swap_ix = swap_instructions( + &ctx.rpc, + pool_pubkey, + 10_000, + mint_a, + SwapType::ExactIn, + Some(100), + Some(ctx.signer.pubkey()), + ) + .await?; + let before_a = stx.get_token_balance(ata_a).await?; + let before_b = stx.get_token_balance(ata_b).await?; + + ctx.send_transaction_with_signers( + swap_ix.instructions, + swap_ix.additional_signers.iter().collect(), + ) + .await?; + + let after_a = stx.get_token_balance(ata_a).await?; + let after_b = stx.get_token_balance(ata_b).await?; + let used_a = before_a.saturating_sub(after_a); + let gained_b = after_b.saturating_sub(before_b); + + if let SwapQuote::ExactIn(q) = swap_ix.quote { + assert_eq!(used_a, q.token_in, "Used A mismatch"); + assert_eq!(gained_b, q.token_est_out, "Gained B mismatch"); + } else { + panic!("Expected ExactIn quote"); + } + + Ok(()) + } +} From 7b59ccb960eb1c99d50c66daa6e5a11a0948cfbc Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Wed, 22 Jan 2025 03:42:06 +0900 Subject: [PATCH 32/33] Update swap to use rstest --- rust-sdk/whirlpool/src/swap.rs | 464 +++++++++++++++++---------------- 1 file changed, 241 insertions(+), 223 deletions(-) diff --git a/rust-sdk/whirlpool/src/swap.rs b/rust-sdk/whirlpool/src/swap.rs index 923255e48..14c362d33 100644 --- a/rust-sdk/whirlpool/src/swap.rs +++ b/rust-sdk/whirlpool/src/swap.rs @@ -326,264 +326,282 @@ pub async fn swap_instructions( #[cfg(test)] mod tests { + use std::collections::HashMap; + use std::error::Error; + + use rstest::rstest; use serial_test::serial; use solana_client::nonblocking::rpc_client::RpcClient; - use solana_sdk::{program_pack::Pack, pubkey::Pubkey, signer::Signer}; + use solana_program_test::tokio; + use solana_sdk::{ + program_pack::Pack, + pubkey::Pubkey, + signer::{keypair::Keypair, Signer}, + }; use spl_token::state::Account as TokenAccount; - use spl_token_2022::state::Account as TokenAccount2022; - use spl_token_2022::{extension::StateWithExtensionsOwned, ID as TOKEN_2022_PROGRAM_ID}; - use std::error::Error; + use spl_token_2022::{ + extension::StateWithExtensionsOwned, state::Account as TokenAccount2022, + ID as TOKEN_2022_PROGRAM_ID, + }; use crate::{ - create_concentrated_liquidity_pool_instructions, create_splash_pool_instructions, - increase_liquidity_instructions, open_full_range_position_instructions, swap_instructions, - tests::{setup_ata_with_amount, setup_mint_with_decimals, RpcContext}, - IncreaseLiquidityParam, SwapQuote, SwapType, + increase_liquidity_instructions, swap_instructions, + tests::{ + setup_ata_te, setup_ata_with_amount, setup_mint_te, setup_mint_te_fee, + setup_mint_with_decimals, setup_position, setup_whirlpool, RpcContext, SetupAtaConfig, + }, + IncreaseLiquidityParam, SwapInstructions, SwapQuote, SwapType, }; - struct SwapTestContext { - pub ctx: RpcContext, - - pub mint_a: Pubkey, - pub mint_b: Pubkey, - pub ata_a: Pubkey, - pub ata_b: Pubkey, + async fn get_token_balance(rpc: &RpcClient, address: Pubkey) -> Result> { + let account_data = rpc.get_account(&address).await?; + if account_data.owner == TOKEN_2022_PROGRAM_ID { + let parsed = StateWithExtensionsOwned::::unpack(account_data.data)?; + Ok(parsed.base.amount) + } else { + let parsed = TokenAccount::unpack(&account_data.data)?; + Ok(parsed.amount) + } } - impl SwapTestContext { - pub async fn new() -> Result> { - let ctx = RpcContext::new().await; + async fn setup_all_mints( + ctx: &RpcContext, + ) -> Result, Box> { + let mint_a = setup_mint_with_decimals(ctx, 9).await?; + let mint_b = setup_mint_with_decimals(ctx, 9).await?; + let mint_te_a = setup_mint_te(ctx, &[]).await?; + let mint_te_b = setup_mint_te(ctx, &[]).await?; + let mint_tefee = setup_mint_te_fee(ctx).await?; + + let mut out = HashMap::new(); + out.insert("A", mint_a); + out.insert("B", mint_b); + out.insert("TEA", mint_te_a); + out.insert("TEB", mint_te_b); + out.insert("TEFee", mint_tefee); + Ok(out) + } - let mint_a = setup_mint_with_decimals(&ctx, 9).await?; - let mint_b = setup_mint_with_decimals(&ctx, 9).await?; + async fn setup_all_atas( + ctx: &RpcContext, + minted: &HashMap<&str, Pubkey>, + ) -> Result, Box> { + // Give each user ATA a large balance + let token_balance = 1_000_000_000; + + let ata_a = setup_ata_with_amount(ctx, minted["A"], token_balance).await?; + let ata_b = setup_ata_with_amount(ctx, minted["B"], token_balance).await?; + let ata_te_a = setup_ata_te( + ctx, + minted["TEA"], + Some(SetupAtaConfig { + amount: Some(token_balance), + }), + ) + .await?; + let ata_te_b = setup_ata_te( + ctx, + minted["TEB"], + Some(SetupAtaConfig { + amount: Some(token_balance), + }), + ) + .await?; + let ata_tefee = setup_ata_te( + ctx, + minted["TEFee"], + Some(SetupAtaConfig { + amount: Some(token_balance), + }), + ) + .await?; - let ata_a = setup_ata_with_amount(&ctx, mint_a, 1_000_000_000).await?; - let ata_b = setup_ata_with_amount(&ctx, mint_b, 1_000_000_000).await?; + let mut out = HashMap::new(); + out.insert("A", ata_a); + out.insert("B", ata_b); + out.insert("TEA", ata_te_a); + out.insert("TEB", ata_te_b); + out.insert("TEFee", ata_tefee); + Ok(out) + } - Ok(Self { - ctx, - mint_a, - mint_b, - ata_a, - ata_b, - }) + fn parse_pool_name(pool: &str) -> (&'static str, &'static str) { + match pool { + "A-B" => ("A", "B"), + "A-TEA" => ("A", "TEA"), + "TEA-TEB" => ("TEA", "TEB"), + "A-TEFee" => ("A", "TEFee"), + _ => panic!("Unknown pool combo: {}", pool), } + } - async fn get_token_balance(&self, address: Pubkey) -> Result> { - let account_data = self.ctx.rpc.get_account(&address).await?; - if account_data.owner == TOKEN_2022_PROGRAM_ID { - let parsed = - StateWithExtensionsOwned::::unpack(account_data.data)?; - Ok(parsed.base.amount) - } else { - let parsed = TokenAccount::unpack(&account_data.data)?; - Ok(parsed.amount) + async fn verify_swap( + ctx: &RpcContext, + swap_ix: &SwapInstructions, + user_ata_for_finalA: Pubkey, + user_ata_for_finalB: Pubkey, + a_to_b: bool, + ) -> Result<(), Box> { + let before_a = get_token_balance(&ctx.rpc, user_ata_for_finalA).await?; + let before_b = get_token_balance(&ctx.rpc, user_ata_for_finalB).await?; + + // do swap + let signers: Vec<&Keypair> = swap_ix.additional_signers.iter().collect(); + ctx.send_transaction_with_signers(swap_ix.instructions.clone(), signers) + .await?; + + let after_a = get_token_balance(&ctx.rpc, user_ata_for_finalA).await?; + let after_b = get_token_balance(&ctx.rpc, user_ata_for_finalB).await?; + + let used_a = before_a.saturating_sub(after_a); + let used_b = before_b.saturating_sub(after_b); + let gained_a = after_a.saturating_sub(before_a); + let gained_b = after_b.saturating_sub(before_b); + + match &swap_ix.quote { + SwapQuote::ExactIn(q) => { + if a_to_b { + assert_eq!(used_a, q.token_in, "Used A mismatch"); + assert_eq!(gained_b, q.token_est_out, "Gained B mismatch"); + } else { + assert_eq!(used_b, q.token_in, "Used B mismatch"); + assert_eq!(gained_a, q.token_est_out, "Gained A mismatch"); + } + } + SwapQuote::ExactOut(q) => { + if a_to_b { + assert_eq!(gained_b, q.token_out, "Gained B mismatch"); + assert_eq!(used_a, q.token_est_in, "Used A mismatch"); + } else { + assert_eq!(gained_a, q.token_out, "Gained A mismatch"); + assert_eq!(used_b, q.token_est_in, "Used B mismatch"); + } } } + Ok(()) + } - pub async fn init_pool(&self, is_splash: bool) -> Result> { - if is_splash { - let pool = create_splash_pool_instructions( - &self.ctx.rpc, - self.mint_a, - self.mint_b, - None, - Some(self.ctx.signer.pubkey()), - ) - .await?; - self.ctx - .send_transaction_with_signers( - pool.instructions, - pool.additional_signers.iter().collect(), - ) - .await?; - Ok(pool.pool_address) + #[rstest] + #[rstest] + #[case("A-B", true, SwapType::ExactIn, 1000)] + #[case("A-B", true, SwapType::ExactOut, 500)] + #[case("A-B", false, SwapType::ExactIn, 200)] + #[case("A-B", false, SwapType::ExactOut, 100)] + #[case("A-TEA", true, SwapType::ExactIn, 1000)] + #[case("A-TEA", true, SwapType::ExactOut, 500)] + #[case("A-TEA", false, SwapType::ExactIn, 200)] + #[case("A-TEA", false, SwapType::ExactOut, 100)] + #[case("TEA-TEB", true, SwapType::ExactIn, 1000)] + #[case("TEA-TEB", true, SwapType::ExactOut, 500)] + #[case("TEA-TEB", false, SwapType::ExactIn, 200)] + #[case("TEA-TEB", false, SwapType::ExactOut, 100)] + #[case("A-TEFee", true, SwapType::ExactIn, 1000)] + #[case("A-TEFee", true, SwapType::ExactOut, 500)] + #[case("A-TEFee", false, SwapType::ExactIn, 200)] + #[case("A-TEFee", false, SwapType::ExactOut, 100)] + #[serial] + fn test_swap_scenarios( + #[case] pool_name: &str, + #[case] a_to_b: bool, + #[case] swap_type: SwapType, + #[case] amount: u64, + ) { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + let ctx = RpcContext::new().await; + + let minted = setup_all_mints(&ctx).await.unwrap(); + let user_atas = setup_all_atas(&ctx, &minted).await.unwrap(); + + let (mkey_a, mkey_b) = parse_pool_name(pool_name); + let pubkey_a = minted[mkey_a]; + let pubkey_b = minted[mkey_b]; + + let tick_spacing = 64; + let (final_a, final_b) = if pubkey_a < pubkey_b { + (pubkey_a, pubkey_b) } else { - let cl_pool = create_concentrated_liquidity_pool_instructions( - &self.ctx.rpc, - self.mint_a, - self.mint_b, - 128, - None, - Some(self.ctx.signer.pubkey()), - ) - .await?; - self.ctx - .send_transaction_with_signers( - cl_pool.instructions, - cl_pool.additional_signers.iter().collect(), - ) - .await?; - Ok(cl_pool.pool_address) - } - } + (pubkey_b, pubkey_a) + }; + + let pool_pubkey = setup_whirlpool(&ctx, final_a, final_b, tick_spacing) + .await + .unwrap(); - pub async fn open_position_with_liquidity( - &self, - pool_pubkey: Pubkey, - ) -> Result> { - let position = open_full_range_position_instructions( - &self.ctx.rpc, + let position_mint = setup_position( + &ctx, pool_pubkey, - IncreaseLiquidityParam::Liquidity(50_000_000), + Some((-192, 192)), // aligned to spacing=64 None, - Some(self.ctx.signer.pubkey()), ) - .await?; - self.ctx - .send_transaction_with_signers( - position.instructions, - position.additional_signers.iter().collect(), - ) - .await?; - - Ok(position.position_mint) - } - - pub async fn do_swap( - &self, - pool_pubkey: Pubkey, - a_to_b: bool, - swap_type: SwapType, - amount: u64, - ) -> Result<(), Box> { - let specified_mint = if a_to_b { self.mint_a } else { self.mint_b }; + .await + .unwrap(); - let before_a = self.get_token_balance(self.ata_a).await?; - let before_b = self.get_token_balance(self.ata_b).await?; - - let swap_ix = swap_instructions( - &self.ctx.rpc, - pool_pubkey, - amount, - specified_mint, - swap_type.clone(), + let liq_ix = increase_liquidity_instructions( + &ctx.rpc, + position_mint, + IncreaseLiquidityParam::Liquidity(1_000_000), Some(100), // 1% slippage - Some(self.ctx.signer.pubkey()), + Some(ctx.signer.pubkey()), ) - .await?; - - self.ctx - .send_transaction_with_signers( - swap_ix.instructions, - swap_ix.additional_signers.iter().collect(), - ) - .await?; - - let after_a = self.get_token_balance(self.ata_a).await?; - let after_b = self.get_token_balance(self.ata_b).await?; + .await + .unwrap(); + ctx.send_transaction_with_signers( + liq_ix.instructions, + liq_ix.additional_signers.iter().collect(), + ) + .await + .unwrap(); - let used_a = before_a.saturating_sub(after_a); - let used_b = before_b.saturating_sub(after_b); - let gained_a = after_a.saturating_sub(before_a); - let gained_b = after_b.saturating_sub(before_b); + let user_ata_for_finalA = if final_a == pubkey_a { + user_atas[mkey_a] + } else { + user_atas[mkey_b] + }; + let user_ata_for_finalB = if final_b == pubkey_b { + user_atas[mkey_b] + } else { + user_atas[mkey_a] + }; - match swap_ix.quote { - SwapQuote::ExactIn(q) => { + let token_for_this_call = match swap_type { + SwapType::ExactIn => { if a_to_b { - // used A, gained B - assert_eq!(used_a, q.token_in, "Used A mismatch"); - assert_eq!(gained_b, q.token_est_out, "Gained B mismatch"); + final_a } else { - // used B, gained A - assert_eq!(used_b, q.token_in, "Used B mismatch"); - assert_eq!(gained_a, q.token_est_out, "Gained A mismatch"); + final_b } } - SwapQuote::ExactOut(q) => { + SwapType::ExactOut => { if a_to_b { - // gained B, used A - assert_eq!(gained_b, q.token_out, "Gained B mismatch"); - assert_eq!(used_a, q.token_est_in, "Used A mismatch"); + final_b } else { - // gained A, used B - assert_eq!(gained_a, q.token_out, "Gained A mismatch"); - assert_eq!(used_b, q.token_est_in, "Used B mismatch"); + final_a } } - } - println!( - "swap result => a_to_b={}, used_a={}, used_b={}, gained_a={}, gained_b={}", - a_to_b, used_a, used_b, gained_a, gained_b - ); - - Ok(()) - } - } - - #[tokio::test] - async fn test_swap_for_multiple_pools() -> Result<(), Box> { - let stx = SwapTestContext::new().await?; - - let ctx = &stx.ctx; - - let mint_a = setup_mint_with_decimals(&ctx, 9).await?; - let mint_b = setup_mint_with_decimals(&ctx, 9).await?; - let ata_a = setup_ata_with_amount(&ctx, mint_a, 500_000_000).await?; - let ata_b = setup_ata_with_amount(&ctx, mint_b, 500_000_000).await?; - - let pool = create_concentrated_liquidity_pool_instructions( - &ctx.rpc, - mint_a, - mint_b, - 128, - None, - Some(ctx.signer.pubkey()), - ) - .await?; - ctx.send_transaction_with_signers( - pool.instructions, - pool.additional_signers.iter().collect(), - ) - .await?; - - let pool_pubkey = pool.pool_address; - - let position = open_full_range_position_instructions( - &ctx.rpc, - pool_pubkey, - IncreaseLiquidityParam::Liquidity(50_000_000), - None, - Some(ctx.signer.pubkey()), - ) - .await?; - ctx.send_transaction_with_signers( - position.instructions, - position.additional_signers.iter().collect(), - ) - .await?; - - let swap_ix = swap_instructions( - &ctx.rpc, - pool_pubkey, - 10_000, - mint_a, - SwapType::ExactIn, - Some(100), - Some(ctx.signer.pubkey()), - ) - .await?; - let before_a = stx.get_token_balance(ata_a).await?; - let before_b = stx.get_token_balance(ata_b).await?; - - ctx.send_transaction_with_signers( - swap_ix.instructions, - swap_ix.additional_signers.iter().collect(), - ) - .await?; + }; - let after_a = stx.get_token_balance(ata_a).await?; - let after_b = stx.get_token_balance(ata_b).await?; - let used_a = before_a.saturating_sub(after_a); - let gained_b = after_b.saturating_sub(before_b); - - if let SwapQuote::ExactIn(q) = swap_ix.quote { - assert_eq!(used_a, q.token_in, "Used A mismatch"); - assert_eq!(gained_b, q.token_est_out, "Gained B mismatch"); - } else { - panic!("Expected ExactIn quote"); - } - - Ok(()) + let swap_ix = swap_instructions( + &ctx.rpc, + pool_pubkey, + amount, + token_for_this_call, + swap_type.clone(), + Some(100), // slippage + Some(ctx.signer.pubkey()), + ) + .await + .unwrap(); + + verify_swap( + &ctx, + &swap_ix, + user_ata_for_finalA, + user_ata_for_finalB, + a_to_b, + ) + .await + .unwrap(); + }); } } From 5f57494e344820b01d3017c35f2068d5b650c68e Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Thu, 23 Jan 2025 01:13:15 +0900 Subject: [PATCH 33/33] Update serial to fix stuck test --- rust-sdk/whirlpool/src/increase_liquidity.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust-sdk/whirlpool/src/increase_liquidity.rs b/rust-sdk/whirlpool/src/increase_liquidity.rs index 5857ea118..134111b33 100644 --- a/rust-sdk/whirlpool/src/increase_liquidity.rs +++ b/rust-sdk/whirlpool/src/increase_liquidity.rs @@ -883,6 +883,7 @@ mod tests { #[case("A-TEFee", "equally centered", -100, 100)] #[case("A-TEFee", "one sided A", -100, -1)] #[case("A-TEFee", "one sided B", 1, 100)] + #[serial] fn test_increase_liquidity_cases( #[case] pool_name: &str, #[case] position_name: &str,