Skip to content

Commit

Permalink
Merge branch 'main' into paul/add-swap-test-rust-sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
pauldragonfly authored Jan 16, 2025
2 parents 17b74fe + f0ea324 commit e3b4081
Show file tree
Hide file tree
Showing 10 changed files with 631 additions and 363 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ jobs:
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
id: idl
with:
name: idl
- name: Create Release Name
Expand All @@ -104,4 +105,4 @@ jobs:
name: ${{ steps.name.outputs.NAME }}
generateReleaseNotes: true
allowUpdates: true
artifacts: idl.zip
artifacts: ${{ steps.idl.outputs.download-path }}/idl.zip
2 changes: 1 addition & 1 deletion examples/ts-sdk/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"devDependencies": {
"@next/bundle-analyzer": "^15.1.4",
"@types/node": "^22.10.5",
"@types/node": "^22.10.7",
"@types/react": "^18.3.13",
"copy-webpack-plugin": "^12.0.2",
"typescript": "^5.7.3",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"clean": "nx reset && nx run-many --target clean --projects"
},
"devDependencies": {
"@types/node": "^22.10.5",
"@types/node": "^22.10.7",
"nx": "^20.2.2",
"rimraf": "^6.0.1",
"tsup": "^8.3.5",
Expand Down
2 changes: 1 addition & 1 deletion rust-sdk/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"devDependencies": {
"@codama/nodes-from-anchor": "^1.1.0",
"@codama/renderers-rust": "^1.0.5",
"@codama/renderers-rust": "^1.0.6",
"@orca-so/whirlpools-program": "*",
"@orca-so/whirlpools-rust-core": "*",
"codama": "^1.2.0"
Expand Down
1 change: 1 addition & 0 deletions rust-sdk/whirlpool/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ base64 = { version = "^0.20" }
toml = { version = "^0.7" }
tokio = { version = "^1.0", features = ["sync"] }
spl-pod = { version = "^0.1" }
rstest = "0.18.2"
258 changes: 158 additions & 100 deletions rust-sdk/whirlpool/src/decrease_liquidity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,15 +641,15 @@ 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_program_test::tokio;
use solana_sdk::{
program_pack::Pack,
pubkey::Pubkey,
signer::{keypair::Keypair, Signer},
};

use solana_client::nonblocking::rpc_client::RpcClient;
use spl_token::state::Account as TokenAccount;
use spl_token_2022::{
extension::StateWithExtensionsOwned, state::Account as TokenAccount2022,
Expand All @@ -668,20 +668,17 @@ mod tests {

async fn get_token_balance(rpc: &RpcClient, address: Pubkey) -> Result<u64, Box<dyn Error>> {
let account_data = rpc.get_account(&address).await?;

if account_data.owner == TOKEN_2022_PROGRAM_ID {
// Token-2022 + (possibly) extension
let state = StateWithExtensionsOwned::<TokenAccount2022>::unpack(account_data.data)?;
Ok(state.base.amount)
let parsed = StateWithExtensionsOwned::<TokenAccount2022>::unpack(account_data.data)?;
Ok(parsed.base.amount)
} else {
// SPL Token
let token_account = TokenAccount::unpack(&account_data.data)?;
Ok(token_account.amount)
let parsed = TokenAccount::unpack(&account_data.data)?;
Ok(parsed.amount)
}
}

async fn fetch_position(
rpc: &solana_client::nonblocking::rpc_client::RpcClient,
rpc: &RpcClient,
position_pubkey: Pubkey,
) -> Result<Position, Box<dyn Error>> {
let account = rpc.get_account(&position_pubkey).await?;
Expand All @@ -695,18 +692,22 @@ mod tests {
token_b_account: Pubkey,
position_mint: Pubkey,
) -> Result<(), Box<dyn Error>> {
// pre
let before_a = get_token_balance(&ctx.rpc, token_a_account).await?;
let before_b = get_token_balance(&ctx.rpc, token_b_account).await?;

// send
let signers: Vec<&Keypair> = decrease_ix.additional_signers.iter().collect();
ctx.send_transaction_with_signers(decrease_ix.instructions.clone(), signers)
.await?;

// post
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);

// check quote
let quote = &decrease_ix.quote;
assert!(
gained_a >= quote.token_min_a && gained_a <= quote.token_est_a,
Expand All @@ -723,6 +724,7 @@ mod tests {
quote.token_est_b
);

// confirm on-chain liquidity updated
let position_pubkey = get_position_address(&position_mint)?.0;
let position_data = fetch_position(&ctx.rpc, position_pubkey).await?;
assert_eq!(
Expand All @@ -734,115 +736,171 @@ mod tests {
Ok(())
}

#[tokio::test]
#[serial]
async fn test_decrease_liquidity_multiple_combos() -> Result<(), Box<dyn Error>> {
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?;
let mint_te_b = setup_mint_te(&ctx, &[]).await?;
let mint_te_fee = setup_mint_te_fee(&ctx).await?;
async fn setup_all_mints(
ctx: &RpcContext,
) -> Result<HashMap<&'static str, Pubkey>, Box<dyn Error>> {
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)
}

async fn setup_all_atas(
ctx: &RpcContext,
minted: &HashMap<&'static str, Pubkey>,
) -> Result<HashMap<&'static str, Pubkey>, Box<dyn Error>> {
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,
mint_te_a,
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 user_ata_te_b = setup_ata_te(
&ctx,
mint_te_b,
let ata_te_b = setup_ata_te(
ctx,
minted["TEB"],
Some(SetupAtaConfig {
amount: Some(token_balance),
}),
)
.await?;
let user_ata_tefee = setup_ata_te(
&ctx,
mint_te_fee,
let ata_tefee = setup_ata_te(
ctx,
minted["TEFee"],
Some(SetupAtaConfig {
amount: Some(token_balance),
}),
)
.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)),
];

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 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
);
}
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)
}

fn parse_pool_name(pool_name: &str) -> (&'static str, &'static str) {
match pool_name {
"A-B" => ("A", "B"),
"A-TEA" => ("A", "TEA"),
"TEA-TEB" => ("TEA", "TEB"),
"A-TEFee" => ("A", "TEFee"),
_ => panic!("Unknown combo: {}", pool_name),
}
}

Ok(())
#[rstest]
#[case("A-B", "equally centered", -100, 100)]
#[case("A-B", "one sided A", -100, -1)]
#[case("A-B", "one sided B", 1, 100)]
#[case("A-TEA", "equally centered", -100, 100)]
#[case("A-TEA", "one sided A", -100, -1)]
#[case("A-TEA", "one sided B", 1, 100)]
#[case("TEA-TEB","equally centered", -100, 100)]
#[case("TEA-TEB","one sided A", -100, -1)]
#[case("TEA-TEB", "one sided B", 1, 100)]
#[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_decrease_liquidity_cases(
#[case] pool_name: &str,
#[case] position_name: &str,
#[case] lower_tick: i32,
#[case] upper_tick: i32,
) {
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 swapped = pubkey_a > pubkey_b;
let (final_a, final_b) = if pubkey_a < pubkey_b {
(pubkey_a, pubkey_b)
} else {
(pubkey_b, pubkey_a)
};

let tick_spacing = 64;
let pool_pubkey = setup_whirlpool(&ctx, final_a, final_b, tick_spacing)
.await
.unwrap();

let position_mint =
setup_position(&ctx, pool_pubkey, Some((lower_tick, upper_tick)), None)
.await
.unwrap();

let inc_ix = increase_liquidity_instructions(
&ctx.rpc,
position_mint,
IncreaseLiquidityParam::Liquidity(100_000),
Some(100),
Some(ctx.signer.pubkey()),
)
.await
.unwrap();
ctx.send_transaction_with_signers(inc_ix.instructions, vec![])
.await
.unwrap();

let dec_ix = decrease_liquidity_instructions(
&ctx.rpc,
position_mint,
DecreaseLiquidityParam::Liquidity(50_000),
Some(100),
Some(ctx.signer.pubkey()),
)
.await
.unwrap();

let user_ata_for_token_a = if swapped {
user_atas[mkey_b]
} else {
user_atas[mkey_a]
};
let user_ata_for_token_b = if swapped {
user_atas[mkey_a]
} else {
user_atas[mkey_b]
};

verify_decrease_liquidity(
&ctx,
&dec_ix,
user_ata_for_token_a,
user_ata_for_token_b,
position_mint,
)
.await
.unwrap();

println!(
"[combo={}, range=({},{}), pos={}] => Decrease 50k => OK",
pool_name, lower_tick, upper_tick, position_mint
);
});
}
}
Loading

0 comments on commit e3b4081

Please sign in to comment.