diff --git a/rust-sdk/whirlpool/src/increase_liquidity.rs b/rust-sdk/whirlpool/src/increase_liquidity.rs index 538c17df..ea2fa6ec 100644 --- a/rust-sdk/whirlpool/src/increase_liquidity.rs +++ b/rust-sdk/whirlpool/src/increase_liquidity.rs @@ -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 ae461ed8..1b4d6d1a 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 a71b65c8..460d7fba 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 145efd81..aab9ea20 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<(), 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(()) +} diff --git a/rust-sdk/whirlpool/src/tests/rpc.rs b/rust-sdk/whirlpool/src/tests/rpc.rs index a00fa9dc..54690daf 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), }