diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 71476f539..069e2d78c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -93,6 +93,7 @@ jobs: steps: - name: Download artifacts uses: actions/download-artifact@v4 + id: idl with: name: idl - name: Create Release Name @@ -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 diff --git a/examples/ts-sdk/next/package.json b/examples/ts-sdk/next/package.json index bcef5d1ad..462565eb4 100644 --- a/examples/ts-sdk/next/package.json +++ b/examples/ts-sdk/next/package.json @@ -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", diff --git a/package.json b/package.json index 2b154fe57..4546033a4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/rust-sdk/client/package.json b/rust-sdk/client/package.json index 39f8f85b5..e7b56025b 100644 --- a/rust-sdk/client/package.json +++ b/rust-sdk/client/package.json @@ -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" diff --git a/rust-sdk/whirlpool/Cargo.toml b/rust-sdk/whirlpool/Cargo.toml index a624a37bb..96dfd0b7f 100644 --- a/rust-sdk/whirlpool/Cargo.toml +++ b/rust-sdk/whirlpool/Cargo.toml @@ -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" diff --git a/rust-sdk/whirlpool/src/decrease_liquidity.rs b/rust-sdk/whirlpool/src/decrease_liquidity.rs index 59b18ba4e..cceb767b2 100644 --- a/rust-sdk/whirlpool/src/decrease_liquidity.rs +++ b/rust-sdk/whirlpool/src/decrease_liquidity.rs @@ -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, @@ -668,20 +668,17 @@ mod tests { 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) + let parsed = StateWithExtensionsOwned::::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> { let account = rpc.get_account(&position_pubkey).await?; @@ -695,18 +692,22 @@ mod tests { token_b_account: Pubkey, position_mint: Pubkey, ) -> Result<(), Box> { + // 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, @@ -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!( @@ -734,115 +736,171 @@ mod tests { Ok(()) } - #[tokio::test] - #[serial] - async fn test_decrease_liquidity_multiple_combos() -> 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?; - 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, 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) + } + async fn setup_all_atas( + ctx: &RpcContext, + minted: &HashMap<&'static str, Pubkey>, + ) -> Result, Box> { 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 + ); + }); } } diff --git a/rust-sdk/whirlpool/src/harvest.rs b/rust-sdk/whirlpool/src/harvest.rs index ca23ac58d..401f42ffd 100644 --- a/rust-sdk/whirlpool/src/harvest.rs +++ b/rust-sdk/whirlpool/src/harvest.rs @@ -328,6 +328,9 @@ pub async fn harvest_position_instructions( #[cfg(test)] mod tests { + use std::collections::HashMap; + use std::error::Error; + use orca_whirlpools_client::{get_position_address, Position}; use serial_test::serial; use solana_client::nonblocking::rpc_client::RpcClient; @@ -339,31 +342,19 @@ mod tests { }; use spl_token::state::Account as TokenAccount; use spl_token_2022::{ - extension::StateWithExtensionsOwned, - state::Account as TokenAccount2022, + extension::StateWithExtensionsOwned, state::Account as TokenAccount2022, ID as TOKEN_2022_PROGRAM_ID, }; - use std::{collections::HashMap, error::Error}; + + use rstest::rstest; 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_whirlpool, RpcContext, SetupAtaConfig, }, - HarvestPositionInstruction, - IncreaseLiquidityParam, - SwapType, + HarvestPositionInstruction, IncreaseLiquidityParam, SwapType, }; async fn fetch_position( @@ -378,11 +369,11 @@ mod tests { 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) + let parsed = StateWithExtensionsOwned::::unpack(account_data.data)?; + Ok(parsed.base.amount) } else { - let token_account = TokenAccount::unpack(&account_data.data)?; - Ok(token_account.amount) + let parsed = TokenAccount::unpack(&account_data.data)?; + Ok(parsed.amount) } } @@ -426,110 +417,188 @@ mod tests { 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 + 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_te_fee = 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_te_fee); + + Ok(out) + } - 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?; + async fn setup_all_atas( + ctx: &RpcContext, + minted: &HashMap<&str, Pubkey>, + ) -> Result, Box> { + let token_balance = 1_000_000_000; + let user_ata_a = + setup_ata_with_amount(ctx, *minted.get("A").unwrap(), token_balance).await?; + let user_ata_b = + setup_ata_with_amount(ctx, *minted.get("B").unwrap(), token_balance).await?; let user_ata_te_a = setup_ata_te( - &ctx, - mint_te_a, + ctx, + *minted.get("TEA").unwrap(), + Some(SetupAtaConfig { + amount: Some(token_balance), + }), + ) + .await?; + let user_ata_te_b = setup_ata_te( + ctx, + *minted.get("TEB").unwrap(), Some(SetupAtaConfig { - amount: Some(1_000_000), + amount: Some(token_balance), }), ) .await?; - let user_ata_te_fee = setup_ata_te( - &ctx, - mint_te_fee, + let user_ata_tefee = setup_ata_te( + ctx, + *minted.get("TEFee").unwrap(), Some(SetupAtaConfig { - amount: Some(1_000_000), + amount: Some(token_balance), }), ) .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 - ); - } + let mut out = HashMap::new(); + out.insert("A", user_ata_a); + out.insert("B", user_ata_b); + out.insert("TEA", user_ata_te_a); + out.insert("TEB", user_ata_te_b); + out.insert("TEFee", user_ata_tefee); + + Ok(out) + } + + pub 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 pool name: {}", 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("TEA-TEB", "equally centered", -100, 100)] + #[case("TEA-TEB", "one sided A", -100, -1)] + #[case("A-TEFee", "equally centered", -100, 100)] + #[case("A-TEFee", "one sided A", -100, -1)] + #[serial] + fn test_harvest_position_with_swap( + #[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 (mint_a_key, mint_b_key) = parse_pool_name(pool_name); + let pubkey_a = minted.get(mint_a_key).unwrap(); + let pubkey_b = minted.get(mint_b_key).unwrap(); + let (mint_a_key, mint_b_key) = parse_pool_name(pool_name); + let pubkey_a = *minted.get(mint_a_key).unwrap(); + let pubkey_b = *minted.get(mint_b_key).unwrap(); + + 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_liq_ix = increase_liquidity_instructions( + &ctx.rpc, + position_mint, + IncreaseLiquidityParam::Liquidity(50_000), + Some(100), + Some(ctx.signer.pubkey()), + ) + .await + .unwrap(); + // send + ctx.send_transaction_with_signers(inc_liq_ix.instructions, vec![]) + .await + .unwrap(); + + let do_b_to_a = position_name.contains("one sided B"); + let swap_input_mint = if do_b_to_a { pubkey_b } else { pubkey_a }; + + let swap_ix = swap_instructions( + &ctx.rpc, + pool_pubkey, + 10, + swap_input_mint, + SwapType::ExactIn, + Some(100), // 1% slippage + Some(ctx.signer.pubkey()), + ) + .await + .unwrap(); + ctx.send_transaction_with_signers( + swap_ix.instructions, + swap_ix.additional_signers.iter().collect(), + ) + .await + .unwrap(); + + let harvest_ix = + harvest_position_instructions(&ctx.rpc, position_mint, Some(ctx.signer.pubkey())) + .await + .unwrap(); + + let ata_a = if swapped { + user_atas.get(mint_b_key).unwrap() + } else { + user_atas.get(mint_a_key).unwrap() + }; + let ata_b = if swapped { + user_atas.get(mint_a_key).unwrap() + } else { + user_atas.get(mint_b_key).unwrap() + }; + + verify_harvest_position(&ctx, &harvest_ix, *ata_a, *ata_b, position_mint) + .await + .unwrap(); + + println!( + "[harvest w/ swap] pool={}, range=({},{}), pos={} => fees & rewards harvested OK", + pool_name, lower_tick, upper_tick, position_mint + ); + }); } } diff --git a/rust-sdk/whirlpool/src/increase_liquidity.rs b/rust-sdk/whirlpool/src/increase_liquidity.rs index 4d6bfee2c..d26a0f243 100644 --- a/rust-sdk/whirlpool/src/increase_liquidity.rs +++ b/rust-sdk/whirlpool/src/increase_liquidity.rs @@ -703,6 +703,7 @@ mod tests { use std::time::Duration; use orca_whirlpools_client::{get_position_address, Position}; + use rstest::rstest; use serial_test::serial; use solana_program_test::tokio; use solana_sdk::{ @@ -727,7 +728,6 @@ mod tests { 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?; @@ -738,11 +738,9 @@ 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) } @@ -794,105 +792,246 @@ mod tests { Ok(()) } - #[tokio::test] - #[serial] - async fn test_increase_liquidity_multiple_combos() -> 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_b = setup_mint_te(&ctx, &[]).await?; // Token-2022 - let mint_te_fee = setup_mint_te_fee(&ctx).await?; // Token-2022 + transfer-fee + 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_te_fee = 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_te_fee); + + Ok(out) + } - 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?; + async fn setup_all_atas( + ctx: &RpcContext, + minted: &HashMap<&str, Pubkey>, + ) -> Result, Box> { + let token_balance = 1_000_000_000; + let user_ata_a = + setup_ata_with_amount(ctx, *minted.get("A").unwrap(), token_balance).await?; + let user_ata_b = + setup_ata_with_amount(ctx, *minted.get("B").unwrap(), token_balance).await?; let user_ata_te_a = setup_ata_te( - &ctx, - mint_te_a, + ctx, + *minted.get("TEA").unwrap(), Some(SetupAtaConfig { amount: Some(token_balance), }), ) .await?; let user_ata_te_b = setup_ata_te( - &ctx, - mint_te_b, + ctx, + *minted.get("TEB").unwrap(), Some(SetupAtaConfig { amount: Some(token_balance), }), ) .await?; let user_ata_tefee = setup_ata_te( - &ctx, - mint_te_fee, + ctx, + *minted.get("TEFee").unwrap(), 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 mut out = HashMap::new(); + out.insert("A", user_ata_a); + out.insert("B", user_ata_b); + out.insert("TEA", user_ata_te_a); + out.insert("TEB", user_ata_te_b); + out.insert("TEFee", user_ata_tefee); + + Ok(out) + } + + fn is_te_scenario(pool_name: &str, position_name: &str) -> bool { + pool_name.contains("TE") || position_name.contains("TE") + } + + pub 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 pool name: {}", pool_name), + } + } + + #[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)] + fn test_increase_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 (mint_a_key, mint_b_key) = parse_pool_name(pool_name); + let pubkey_a = minted.get(mint_a_key).unwrap(); + let pubkey_b = minted.get(mint_b_key).unwrap(); + let (mint_a_key, mint_b_key) = parse_pool_name(pool_name); 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?; + let (final_a, final_b) = if pubkey_a < pubkey_b { + (pubkey_a, pubkey_b) + } else { + (pubkey_b, pubkey_a) + }; + + // prevent flaky test by ordering the tokens correctly by lexical order + let tick_spacing = 64; + let swapped = pubkey_a > pubkey_b; + let pool_pubkey = setup_whirlpool(&ctx, final_a, final_b, tick_spacing) + .await + .unwrap(); + let user_ata_for_token_a = if swapped { + user_atas.get(mint_b_key).unwrap() + } else { + user_atas.get(mint_a_key).unwrap() + }; + let user_ata_for_token_b = if swapped { + user_atas.get(mint_a_key).unwrap() + } else { + user_atas.get(mint_b_key).unwrap() + }; + + let position_mint = + setup_position(&ctx, pool_pubkey, Some((lower_tick, upper_tick)), None) + .await + .unwrap(); + + let param = IncreaseLiquidityParam::Liquidity(10_000); + let inc_ix = increase_liquidity_instructions( + &ctx.rpc, + position_mint, + param, + Some(100), // slippage + Some(ctx.signer.pubkey()), + ) + .await + .unwrap(); + + verify_increase_liquidity( + &ctx, + &inc_ix, + *user_ata_for_token_a, + *user_ata_for_token_b, + position_mint, + ) + .await + .unwrap(); + + println!( + "[combo={}, pos_range={}] => position={} => success!", + pool_name, position_name, position_mint + ); + }); + } - for (range_name, (lower, upper)) in &position_ranges { - let position_mint = - setup_position(&ctx, pool_pubkey, Some((*lower, *upper)), None).await?; + #[tokio::test] + #[serial] + async fn test_increase_liquidity_fails_if_authority_is_default() -> Result<(), Box> { + let ctx = RpcContext::new().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?; + let minted = setup_all_mints(&ctx).await?; + let user_atas = setup_all_atas(&ctx, &minted).await?; - let ata_a = *user_atas.get(mint_a_key).unwrap(); - let ata_b = *user_atas.get(mint_b_key).unwrap(); + let mint_a_key = minted.get("A").unwrap(); + let mint_b_key = minted.get("B").unwrap(); + let pool_pubkey = setup_whirlpool(&ctx, *mint_a_key, *mint_b_key, 64).await?; - verify_increase_liquidity(&ctx, &increase_ix, ata_a, ata_b, position_mint).await?; + let position_mint = setup_position(&ctx, pool_pubkey, Some((-100, 100)), None).await?; - println!( - "[combo={}, range={}] position={} => used token A/B within quote, liquidity ok", - pool_name, range_name, position_mint - ); - } - } + use solana_sdk::pubkey::Pubkey; + let param = IncreaseLiquidityParam::Liquidity(100_000); + let res = increase_liquidity_instructions( + &ctx.rpc, + position_mint, + param, + Some(100), // slippage + Some(Pubkey::default()), + ) + .await; + + assert!(res.is_err(), "Should have failed with default authority"); + let err_str = format!("{:?}", res.err().unwrap()); + assert!( + err_str.contains("Authority must be provided") + || err_str.contains("Signer must be provided"), + "Error string was: {}", + err_str + ); + + Ok(()) + } + + #[tokio::test] + #[serial] + async fn test_increase_liquidity_fails_if_deposit_exceeds_user_balance( + ) -> Result<(), Box> { + let ctx = RpcContext::new().await; + + let minted = setup_all_mints(&ctx).await?; + let user_atas = setup_all_atas(&ctx, &minted).await?; + + let mint_a_key = minted.get("A").unwrap(); + let mint_b_key = minted.get("B").unwrap(); + let pool_pubkey = setup_whirlpool(&ctx, *mint_a_key, *mint_b_key, 64).await?; + + let position_mint = setup_position(&ctx, pool_pubkey, Some((-100, 100)), None).await?; + + // Attempt + let res = increase_liquidity_instructions( + &ctx.rpc, + position_mint, + IncreaseLiquidityParam::TokenA(2_000_000_000), + Some(100), + Some(ctx.signer.pubkey()), + ) + .await; + + assert!( + res.is_err(), + "Should fail if user tries depositing more than balance" + ); + let err_str = format!("{:?}", res.err().unwrap()); + assert!( + err_str.contains("Insufficient balance") + || err_str.contains("Error processing Instruction 0"), + "Unexpected error message: {}", + err_str + ); Ok(()) } diff --git a/ts-sdk/lint/package.json b/ts-sdk/lint/package.json index a59bc1249..0b1e030f3 100644 --- a/ts-sdk/lint/package.json +++ b/ts-sdk/lint/package.json @@ -10,6 +10,6 @@ "eslint": "^9.18.0", "prettier": "^3.4.2", "typescript": "^5.7.3", - "typescript-eslint": "^8.19.1" + "typescript-eslint": "^8.20.0" } } diff --git a/yarn.lock b/yarn.lock index 4550edffd..20335c2e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1526,9 +1526,9 @@ __metadata: languageName: node linkType: hard -"@codama/renderers-rust@npm:^1.0.5": - version: 1.0.5 - resolution: "@codama/renderers-rust@npm:1.0.5" +"@codama/renderers-rust@npm:^1.0.6": + version: 1.0.6 + resolution: "@codama/renderers-rust@npm:1.0.6" dependencies: "@codama/errors": "npm:1.2.0" "@codama/nodes": "npm:1.2.0" @@ -1536,7 +1536,7 @@ __metadata: "@codama/visitors-core": "npm:1.2.0" "@solana/codecs-strings": "npm:rc" nunjucks: "npm:^3.2.4" - checksum: 10c0/d9715eb4f34c682a6fb92e7b58ead923f3e4d3dd744bf30e53a8471c9329e07694e3180a4c24aa7cc7b639e536b83aecf747319ea390a244cbefce67aa3897da + checksum: 10c0/c62e3e5caf9568253e7f53ccd7390ec75f9cf457ac3aa8f024e138c3a8a0da0beacfcb46a9c5345fc1aae25cd5e84bfc6a7f2eda65221103330f02d3ed51d073 languageName: node linkType: hard @@ -4076,7 +4076,7 @@ __metadata: "@next/bundle-analyzer": "npm:^15.1.4" "@orca-so/whirlpools": "npm:*" "@solana/web3.js": "npm:^2.0.0" - "@types/node": "npm:^22.10.5" + "@types/node": "npm:^22.10.7" "@types/react": "npm:^18.3.13" copy-webpack-plugin: "npm:^12.0.2" next: "npm:^15.1.4" @@ -4104,7 +4104,7 @@ __metadata: eslint: "npm:^9.18.0" prettier: "npm:^3.4.2" typescript: "npm:^5.7.3" - typescript-eslint: "npm:^8.19.1" + typescript-eslint: "npm:^8.20.0" languageName: unknown linkType: soft @@ -4112,7 +4112,7 @@ __metadata: version: 0.0.0-use.local resolution: "@orca-so/whirlpools-monorepo@workspace:." dependencies: - "@types/node": "npm:^22.10.5" + "@types/node": "npm:^22.10.7" nx: "npm:^20.2.2" rimraf: "npm:^6.0.1" tsup: "npm:^8.3.5" @@ -4131,7 +4131,7 @@ __metadata: resolution: "@orca-so/whirlpools-rust-client@workspace:rust-sdk/client" dependencies: "@codama/nodes-from-anchor": "npm:^1.1.0" - "@codama/renderers-rust": "npm:^1.0.5" + "@codama/renderers-rust": "npm:^1.0.6" "@orca-so/whirlpools-program": "npm:*" "@orca-so/whirlpools-rust-core": "npm:*" codama: "npm:^1.2.0" @@ -5814,12 +5814,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^22.10.5": - version: 22.10.5 - resolution: "@types/node@npm:22.10.5" +"@types/node@npm:^22.10.7": + version: 22.10.7 + resolution: "@types/node@npm:22.10.7" dependencies: undici-types: "npm:~6.20.0" - checksum: 10c0/6a0e7d1fe6a86ef6ee19c3c6af4c15542e61aea2f4cee655b6252efb356795f1f228bc8299921e82924e80ff8eca29b74d9dd0dd5cc1a90983f892f740b480df + checksum: 10c0/c941b4689dfc4044b64a5f601306cbcb0c7210be853ba378a5dd44137898c45accedd796ee002ad9407024cac7ecaf5049304951cb1d80ce3d7cebbbae56f20e languageName: node linkType: hard @@ -6036,15 +6036,15 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/eslint-plugin@npm:8.19.1" +"@typescript-eslint/eslint-plugin@npm:8.20.0": + version: 8.20.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.20.0" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.19.1" - "@typescript-eslint/type-utils": "npm:8.19.1" - "@typescript-eslint/utils": "npm:8.19.1" - "@typescript-eslint/visitor-keys": "npm:8.19.1" + "@typescript-eslint/scope-manager": "npm:8.20.0" + "@typescript-eslint/type-utils": "npm:8.20.0" + "@typescript-eslint/utils": "npm:8.20.0" + "@typescript-eslint/visitor-keys": "npm:8.20.0" graphemer: "npm:^1.4.0" ignore: "npm:^5.3.1" natural-compare: "npm:^1.4.0" @@ -6053,64 +6053,64 @@ __metadata: "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.8.0" - checksum: 10c0/993784b04533b13c3f3919c793cfc3a369fa61692e1a2d72de6fba27df247c275d852cdcbc4e393c310b73fce8d34d210a9b632b66f4d761a1a3b4781f8fa93f + checksum: 10c0/c68d0dc5419db93c38eea8adecac19e27f8b023d015a944ffded112d584e87fa7fe512070a6a1085899cab2e12e1c8db276e10412b74bf639ca6b04052bbfedc languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/parser@npm:8.19.1" +"@typescript-eslint/parser@npm:8.20.0": + version: 8.20.0 + resolution: "@typescript-eslint/parser@npm:8.20.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.19.1" - "@typescript-eslint/types": "npm:8.19.1" - "@typescript-eslint/typescript-estree": "npm:8.19.1" - "@typescript-eslint/visitor-keys": "npm:8.19.1" + "@typescript-eslint/scope-manager": "npm:8.20.0" + "@typescript-eslint/types": "npm:8.20.0" + "@typescript-eslint/typescript-estree": "npm:8.20.0" + "@typescript-eslint/visitor-keys": "npm:8.20.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.8.0" - checksum: 10c0/1afbd2d0a25f439943bdc94637417429574eb3889a2a1ce24bd425721713aca213808a975bb518a6616171783bc04fa973167f05fc6a96cfd88c1d1666077ad4 + checksum: 10c0/fff4a86be27f603ad8d6f7dd9758c46b04a254828f0c6d8a34869c1cf30b5828b60a1dc088f72680a7b65cc5fc696848df4605de19e59a18467306d7ca56c11d languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/scope-manager@npm:8.19.1" +"@typescript-eslint/scope-manager@npm:8.20.0": + version: 8.20.0 + resolution: "@typescript-eslint/scope-manager@npm:8.20.0" dependencies: - "@typescript-eslint/types": "npm:8.19.1" - "@typescript-eslint/visitor-keys": "npm:8.19.1" - checksum: 10c0/7dca0c28ad27a0c7e26499e0f584f98efdcf34087f46aadc661b36c310484b90655e83818bafd249b5a28c7094a69c54d553f6cd403869bf134f95a9148733f5 + "@typescript-eslint/types": "npm:8.20.0" + "@typescript-eslint/visitor-keys": "npm:8.20.0" + checksum: 10c0/a8074768d06c863169294116624a45c19377ff0b8635ad5fa4ae673b43cf704d1b9b79384ceef0ff0abb78b107d345cd90fe5572354daf6ad773fe462ee71e6a languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/type-utils@npm:8.19.1" +"@typescript-eslint/type-utils@npm:8.20.0": + version: 8.20.0 + resolution: "@typescript-eslint/type-utils@npm:8.20.0" dependencies: - "@typescript-eslint/typescript-estree": "npm:8.19.1" - "@typescript-eslint/utils": "npm:8.19.1" + "@typescript-eslint/typescript-estree": "npm:8.20.0" + "@typescript-eslint/utils": "npm:8.20.0" debug: "npm:^4.3.4" ts-api-utils: "npm:^2.0.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.8.0" - checksum: 10c0/757592b515beec58c079c605aa648ba94d985ae48ba40460034e849c7bc2b603b1da6113e59688e284608c9d5ccaa27adf0a14fb032cb1782200c6acae51ddd2 + checksum: 10c0/7d46143f26ec606b71d20f0f5535b16abba2ba7a5a2daecd2584ddb61d1284dd8404f34265cc1fdfd541068b24b0211f7ad94801c94e4c60869d9f26bf3c0b9b languageName: node linkType: hard -"@typescript-eslint/types@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/types@npm:8.19.1" - checksum: 10c0/e907bf096d5ed7a812a1e537a98dd881ab5d2d47e072225bfffaa218c1433115a148b27a15744db8374b46dac721617c6d13a1da255fdeb369cf193416533f6e +"@typescript-eslint/types@npm:8.20.0": + version: 8.20.0 + resolution: "@typescript-eslint/types@npm:8.20.0" + checksum: 10c0/21292d4ca089897015d2bf5ab99909a7b362902f63f4ba10696676823b50d00c7b4cd093b4b43fba01d12bc3feca3852d2c28528c06d8e45446b7477887dbee7 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.19.1" +"@typescript-eslint/typescript-estree@npm:8.20.0": + version: 8.20.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.20.0" dependencies: - "@typescript-eslint/types": "npm:8.19.1" - "@typescript-eslint/visitor-keys": "npm:8.19.1" + "@typescript-eslint/types": "npm:8.20.0" + "@typescript-eslint/visitor-keys": "npm:8.20.0" debug: "npm:^4.3.4" fast-glob: "npm:^3.3.2" is-glob: "npm:^4.0.3" @@ -6119,32 +6119,32 @@ __metadata: ts-api-utils: "npm:^2.0.0" peerDependencies: typescript: ">=4.8.4 <5.8.0" - checksum: 10c0/549d9d565a58a25fc8397a555506f2e8d29a740f5b6ed9105479e22de5aab89d9d535959034a8e9d4115adb435de09ee6987d28e8922052eea577842ddce1a7a + checksum: 10c0/54a2c1da7d1c5f7e865b941e8a3c98eb4b5f56ed8741664a84065173bde9602cdb8866b0984b26816d6af885c1528311c11e7286e869ed424483b74366514cbd languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/utils@npm:8.19.1" +"@typescript-eslint/utils@npm:8.20.0": + version: 8.20.0 + resolution: "@typescript-eslint/utils@npm:8.20.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:8.19.1" - "@typescript-eslint/types": "npm:8.19.1" - "@typescript-eslint/typescript-estree": "npm:8.19.1" + "@typescript-eslint/scope-manager": "npm:8.20.0" + "@typescript-eslint/types": "npm:8.20.0" + "@typescript-eslint/typescript-estree": "npm:8.20.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.8.0" - checksum: 10c0/f7d2fe9a2bd8cb3ae6fafe5e465882a6784b2acf81d43d194c579381b92651c2ffc0fca69d2a35eee119f539622752a0e9ec063aaec7576d5d2bfe68b441980d + checksum: 10c0/dd36c3b22a2adde1e1462aed0c8b4720f61859b4ebb0c3ef935a786a6b1cb0ec21eb0689f5a8debe8db26d97ebb979bab68d6f8fe7b0098e6200a485cfe2991b languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.19.1": - version: 8.19.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.19.1" +"@typescript-eslint/visitor-keys@npm:8.20.0": + version: 8.20.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.20.0" dependencies: - "@typescript-eslint/types": "npm:8.19.1" + "@typescript-eslint/types": "npm:8.20.0" eslint-visitor-keys: "npm:^4.2.0" - checksum: 10c0/117537450a099f51f3f0d39186f248ae370bdc1b7f6975dbdbffcfc89e6e1aa47c1870db790d4f778a48f2c1f6cd9c269b63867c12afaa424367c63dabee8fd0 + checksum: 10c0/e95d8b2685e8beb6637bf2e9d06e4177a400d3a2b142ba749944690f969ee3186b750082fd9bf34ada82acf1c5dd5970201dfd97619029c8ecca85fb4b50dbd8 languageName: node linkType: hard @@ -17288,17 +17288,17 @@ __metadata: languageName: node linkType: hard -"typescript-eslint@npm:^8.19.1": - version: 8.19.1 - resolution: "typescript-eslint@npm:8.19.1" +"typescript-eslint@npm:^8.20.0": + version: 8.20.0 + resolution: "typescript-eslint@npm:8.20.0" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.19.1" - "@typescript-eslint/parser": "npm:8.19.1" - "@typescript-eslint/utils": "npm:8.19.1" + "@typescript-eslint/eslint-plugin": "npm:8.20.0" + "@typescript-eslint/parser": "npm:8.20.0" + "@typescript-eslint/utils": "npm:8.20.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <5.8.0" - checksum: 10c0/59cdb590a0b38bfca1634c421c1acd2d1bfc8a7325af8fb1332421103dd98d454d349d4f82175088cf06216c1540dc1a73d1dca44cff16dd1d08f969feeb0c0b + checksum: 10c0/049e0fa000657232c0fe26a062ef6a9cd16c5a58c814a74ac45971554c8b6bc67355821a66229f9537e819939a2ab065e7fcba9a70cd95c8283630dc58ac0144 languageName: node linkType: hard