Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add increase liquidity test for rust sdk #605

Open
wants to merge 3 commits into
base: paul/rust-sdk-add-position-test
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions rust-sdk/whirlpool/src/increase_liquidity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TokenAccount, Box<dyn Error>> {
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<Position, Box<dyn Error>> {
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<dyn Error>> {
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?;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I would assert that the quote matches whatever happened on-chain (same way we have the test in ts sdk)

Ok(())
}
}
4 changes: 2 additions & 2 deletions rust-sdk/whirlpool/src/pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 8 additions & 4 deletions rust-sdk/whirlpool/src/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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?;
Expand Down Expand Up @@ -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!(
Expand Down
174 changes: 145 additions & 29 deletions rust-sdk/whirlpool/src/tests/program.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -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<Pubkey, Box<dyn Error>> {
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;
Expand Down Expand Up @@ -75,20 +82,74 @@ pub async fn setup_whirlpool(
Ok(whirlpool)
}

pub async fn setup_position(whirlpool: Pubkey) -> Result<Pubkey, Box<dyn Error>> {
let ctx = RpcContext::new().await;
pub async fn setup_position(
ctx: &RpcContext,
whirlpool: Pubkey,
tick_range: Option<(i32, i32)>,
owner: Option<Pubkey>,
) -> Result<Pubkey, Box<dyn Error>> {
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,
Expand All @@ -97,19 +158,66 @@ pub async fn setup_position(whirlpool: Pubkey) -> Result<Pubkey, Box<dyn Error>>
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<Pubkey>,
) -> Result<Pubkey, Box<dyn Error>> {
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<Pubkey, Box<dyn Error>> {
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
Expand Down Expand Up @@ -144,12 +252,9 @@ pub async fn setup_te_position(whirlpool: Pubkey) -> Result<Pubkey, Box<dyn Erro

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(),
owner: owner,
position: position_pubkey,
position_mint: position_mint.pubkey(),
position_token_account,
Expand All @@ -160,11 +265,12 @@ pub async fn setup_te_position(whirlpool: Pubkey) -> Result<Pubkey, Box<dyn Erro
rent: RENT_PROGRAM_ID,
}
.instruction(OpenPositionInstructionArgs {
tick_lower_index,
tick_upper_index,
tick_lower_index: lower_tick_index,
tick_upper_index: upper_tick_index,
position_bump,
});

println!("Sending transaction with instructions...");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This print can be removed?

ctx.send_transaction_with_signers(
vec![
create_mint_ix,
Expand Down Expand Up @@ -237,3 +343,13 @@ pub async fn setup_position_bundle(

Ok(position_bundle_address)
}

pub async fn setup_config_and_fee_tiers(ctx: &RpcContext) -> Result<(), Box<dyn Error>> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used anywhere? it is not actually setting up any fee tiers and/or configs

// Set funder first
crate::set_funder(ctx.signer.pubkey());

// Then setup config using ctx.config
crate::set_whirlpools_config_address(crate::WhirlpoolsConfigInput::SolanaDevnet)?;

Ok(())
}
Loading