Skip to content

Commit

Permalink
Add rust-sdk position test (#601)
Browse files Browse the repository at this point in the history
* Add position test

* Address comments

* Update test to check te and bundle

* Update dependabot

* Fix dependabot space

* revert dependabot

* FMT

* Update not to create mint, instead use instructions

* Update to use actual address in positions

* Update position bundle

---------

Co-authored-by: Will <[email protected]>
  • Loading branch information
pauldragonfly and wjthieme authored Jan 14, 2025
1 parent 35e4914 commit c6c09d2
Show file tree
Hide file tree
Showing 2 changed files with 247 additions and 11 deletions.
98 changes: 98 additions & 0 deletions rust-sdk/whirlpool/src/position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -295,3 +296,100 @@ 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_position_bundle,
setup_te_position, setup_whirlpool, RpcContext,
};
use serial_test::serial;
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"]
async fn test_fetch_positions_for_owner_no_positions() -> Result<(), Box<dyn Error>> {
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<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?;
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 normal_position_pubkey = setup_position(whirlpool).await?;

// 1) Add a te_position (uses token-2022)
let te_position_pubkey = setup_te_position(whirlpool).await?;

// 2) Add a position bundle, optionally with multiple bundled positions
let position_bundle_pubkey = setup_position_bundle(whirlpool, Some(vec![(), ()])).await?;

let owner = ctx.signer.pubkey();
let positions = fetch_positions_for_owner(&ctx.rpc, owner).await?;

// Expect at least 3: normal, te_position, and a bundle
assert!(
positions.len() >= 3,
"Did not find all positions for the owner (expected normal, te_position, bundle)"
);

// Existing checks remain...
match &positions[0] {
PositionOrBundle::Position(pos) => {
assert_eq!(pos.address, normal_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<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?;
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 _normal_position_pubkey = setup_position(whirlpool).await?;

// 1) te_position
let _te_position_pubkey = setup_te_position(whirlpool).await?;

// 2) position bundle
let _position_bundle_pubkey = setup_position_bundle(whirlpool, Some(vec![(), ()])).await?;

let positions = fetch_positions_in_whirlpool(&ctx.rpc, whirlpool).await?;

// Expect at least 3: normal + te_position + bundle
assert!(
positions.len() >= 3,
"Should find multiple positions in this whirlpool, including te_position & bundle"
);

Ok(())
}
}
160 changes: 149 additions & 11 deletions rust-sdk/whirlpool/src/tests/program.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -60,14 +75,137 @@ pub async fn setup_whirlpool(
Ok(whirlpool)
}

pub async fn setup_position(_whirlpool: Pubkey) -> Result<Pubkey, Box<dyn Error>> {
todo!()
pub async fn setup_position(whirlpool: Pubkey) -> Result<Pubkey, Box<dyn Error>> {
let ctx = RpcContext::new().await;

let position_mint = ctx.get_next_keypair();

let (position_pubkey, position_bump) = get_position_address(&position_mint.pubkey())?;

let position_token_account = get_associated_token_address_with_program_id(
&ctx.signer.pubkey(),
&position_mint.pubkey(),
&TOKEN_PROGRAM_ID,
);

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_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_with_signers(vec![open_position_ix], vec![&position_mint])
.await?;

Ok(position_pubkey)
}

pub async fn setup_te_position(_whirlpool: Pubkey) -> Result<Pubkey, Box<dyn Error>> {
todo!()
pub async fn setup_te_position(whirlpool: Pubkey) -> Result<Pubkey, Box<dyn Error>> {
let ctx = RpcContext::new().await;

let te_position_mint = ctx.get_next_keypair();

let (position_pubkey, position_bump) = get_position_address(&te_position_mint.pubkey())?;

let position_token_account = get_associated_token_address_with_program_id(
&ctx.signer.pubkey(),
&te_position_mint.pubkey(),
&TOKEN_2022_PROGRAM_ID,
);

let open_position_ix = OpenPosition {
funder: ctx.signer.pubkey(),
owner: ctx.signer.pubkey(),
position: position_pubkey,
position_mint: te_position_mint.pubkey(),
position_token_account: Pubkey::default(),
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: -128,
tick_upper_index: 128,
position_bump,
});

ctx.send_transaction_with_signers(vec![open_position_ix], vec![&te_position_mint])
.await?;

Ok(position_pubkey)
}

pub async fn setup_position_bundle(_whirlpool: Pubkey) -> Result<Pubkey, Box<dyn Error>> {
todo!()
pub async fn setup_position_bundle(
whirlpool: Pubkey,
bundle_positions: Option<Vec<()>>,
) -> Result<Pubkey, Box<dyn Error>> {
let ctx = RpcContext::new().await;

let position_bundle_mint = ctx.get_next_keypair();
let (position_bundle_address, _bundle_bump) =
get_position_bundle_address(&position_bundle_mint.pubkey())?;

let position_token_account = get_associated_token_address_with_program_id(
&ctx.signer.pubkey(),
&position_bundle_mint.pubkey(),
&TOKEN_PROGRAM_ID,
);

let open_bundle_ix = InitializePositionBundle {
funder: ctx.signer.pubkey(),
position_bundle: position_bundle_address,
position_bundle_mint: position_bundle_mint.pubkey(),
position_bundle_token_account: position_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_with_signers(vec![open_bundle_ix], vec![&position_bundle_mint])
.await?;

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.pubkey(), 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: Pubkey::default(),
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)
}

0 comments on commit c6c09d2

Please sign in to comment.