From 542ad5f30afea5160c305c7e87fdbf3b03eb0886 Mon Sep 17 00:00:00 2001 From: Paul Kwon Date: Wed, 18 Dec 2024 03:07:08 +0900 Subject: [PATCH] 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 0e4c6751..a71b65c8 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 81d735c4..145efd81 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) }