use crate::account::get_rent;
use crate::token::{get_current_transfer_fee, prepare_token_accounts_instructions, TokenAccountStrategy};
use crate::{PriceOrTickIndex, FUNDER};
use fusionamm_client::{
get_limit_order_address, get_tick_array_address, CloseLimitOrder, DecreaseLimitOrder, DecreaseLimitOrderInstructionArgs, FusionPool,
IncreaseLimitOrder, IncreaseLimitOrderInstructionArgs, InitializeTickArray, InitializeTickArrayInstructionArgs, LimitOrder, OpenLimitOrder,
OpenLimitOrderInstructionArgs, TickArray, FP_NFT_UPDATE_AUTH,
};
use fusionamm_core::{
decrease_limit_order_quote, get_initializable_tick_index, get_tick_array_start_tick_index, price_to_tick_index, try_reverse_apply_transfer_fee,
LimitOrderDecreaseQuote,
};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_keypair::Keypair;
use solana_program::instruction::Instruction;
use solana_program::program_pack::Pack;
use solana_program::pubkey::Pubkey;
use solana_signer::Signer;
use spl_associated_token_account::get_associated_token_address_with_program_id;
use spl_token_2022::state::Mint;
use std::error::Error;
#[derive(Debug)]
pub struct OpenLimitOrderInstruction {
pub limit_order_mint: Pubkey,
pub instructions: Vec<Instruction>,
pub additional_signers: Vec<Keypair>,
pub quote_a: u64,
pub quote_b: u64,
pub initialization_cost: u64,
}
#[derive(Debug)]
pub struct IncreaseLimitOrderInstruction {
pub instructions: Vec<Instruction>,
pub quote_a: u64,
pub quote_b: u64,
pub additional_signers: Vec<Keypair>,
}
#[derive(Debug)]
pub struct DecreaseLimitOrderInstruction {
pub instructions: Vec<Instruction>,
pub quote: LimitOrderDecreaseQuote,
pub additional_signers: Vec<Keypair>,
}
#[cfg(not(doctest))]
pub async fn open_limit_order_instructions(
rpc: &RpcClient,
pool_address: Pubkey,
amount: u64,
price_or_tick_index: PriceOrTickIndex,
a_to_b: bool,
funder: Option<Pubkey>,
) -> Result<OpenLimitOrderInstruction, Box<dyn Error>> {
let funder = funder.unwrap_or(*FUNDER.try_lock()?);
let rent = get_rent(rpc).await?;
if funder == Pubkey::default() {
return Err("Funder must be provided".into());
}
let fusion_pool_info = rpc.get_account(&pool_address).await?;
let fusion_pool = FusionPool::from_bytes(&fusion_pool_info.data)?;
let mint_infos = rpc.get_multiple_accounts(&[fusion_pool.token_mint_a, fusion_pool.token_mint_b]).await?;
let mint_a_info = mint_infos[0].as_ref().ok_or("Token A mint info not found")?;
if mint_a_info.data.len() < Mint::LEN {
return Err("Wrong token A mint account length".into());
}
let mint_a = Mint::unpack_from_slice(&mint_a_info.data).expect("Failed to unpack token A mint");
let mint_b_info = mint_infos[1].as_ref().ok_or("Token B mint info not found")?;
if mint_b_info.data.len() < Mint::LEN {
return Err("Wrong token B mint account length".into());
}
let mint_b = Mint::unpack_from_slice(&mint_b_info.data).expect("Failed to unpack token B mint");
let tick_index = match price_or_tick_index {
PriceOrTickIndex::Tick(tick_index) => tick_index,
PriceOrTickIndex::Price(price) => price_to_tick_index(price, mint_a.decimals, mint_b.decimals),
};
let (mint_address, mint_info) = if a_to_b {
(fusion_pool.token_mint_a, mint_a_info)
} else {
(fusion_pool.token_mint_b, mint_b_info)
};
let mut instructions: Vec<Instruction> = Vec::new();
let mut non_refundable_rent: u64 = 0;
let mut additional_signers: Vec<Keypair> = Vec::new();
let initializable_tick_index = get_initializable_tick_index(tick_index, fusion_pool.tick_spacing, Some(false));
let epoch = rpc.get_epoch_info().await?.epoch;
let transfer_fee = get_current_transfer_fee(Some(mint_info), epoch);
let amount_with_fee = if transfer_fee.is_some() {
try_reverse_apply_transfer_fee(amount, transfer_fee.unwrap_or_default())?
} else {
amount
};
additional_signers.push(Keypair::new());
let limit_order_mint = additional_signers[0].pubkey();
let tick_array_start_index = get_tick_array_start_tick_index(initializable_tick_index, fusion_pool.tick_spacing);
let limit_order_address = get_limit_order_address(&limit_order_mint)?.0;
let limit_order_token_account_address = get_associated_token_address_with_program_id(&funder, &limit_order_mint, &spl_token_2022::ID);
let tick_array_address = get_tick_array_address(&pool_address, tick_array_start_index)?.0;
let token_accounts =
prepare_token_accounts_instructions(rpc, funder, vec![TokenAccountStrategy::WithBalance(mint_address, amount_with_fee)]).await?;
instructions.extend(token_accounts.create_instructions);
additional_signers.extend(token_accounts.additional_signers);
let tick_array_info = rpc.get_account(&tick_array_address).await;
if tick_array_info.is_err() {
instructions.push(
InitializeTickArray {
fusion_pool: pool_address,
funder,
tick_array: tick_array_address,
system_program: solana_program::system_program::id(),
}
.instruction(InitializeTickArrayInstructionArgs {
start_tick_index: tick_array_start_index,
}),
);
non_refundable_rent += rent.minimum_balance(TickArray::MIN_LEN);
}
let token_owner_account = token_accounts
.token_account_addresses
.get(&mint_address)
.ok_or("Token owner account not found")?;
instructions.push(
OpenLimitOrder {
funder,
owner: funder,
limit_order: limit_order_address,
limit_order_mint,
limit_order_token_account: limit_order_token_account_address,
fusion_pool: pool_address,
token2022_program: spl_token_2022::ID,
system_program: solana_program::system_program::id(),
associated_token_program: spl_associated_token_account::ID,
metadata_update_auth: FP_NFT_UPDATE_AUTH,
}
.instruction(OpenLimitOrderInstructionArgs {
tick_index,
a_to_b,
with_token_metadata_extension: true,
}),
);
instructions.push(
IncreaseLimitOrder {
limit_order_authority: funder,
fusion_pool: pool_address,
limit_order: limit_order_address,
limit_order_token_account: limit_order_token_account_address,
token_mint: mint_address,
token_owner_account: *token_owner_account,
token_vault: if a_to_b { fusion_pool.token_vault_a } else { fusion_pool.token_vault_b },
tick_array: tick_array_address,
token_program: mint_info.owner,
memo_program: spl_memo::ID,
}
.instruction(IncreaseLimitOrderInstructionArgs {
amount,
remaining_accounts_info: None,
}),
);
instructions.extend(token_accounts.cleanup_instructions);
Ok(OpenLimitOrderInstruction {
limit_order_mint,
instructions,
additional_signers,
quote_a: if a_to_b { amount_with_fee } else { 0 },
quote_b: if a_to_b { 0 } else { amount_with_fee },
initialization_cost: non_refundable_rent,
})
}
pub async fn increase_limit_order_instructions(
rpc: &RpcClient,
limit_order_mint: Pubkey,
amount: u64,
authority: Option<Pubkey>,
) -> Result<IncreaseLimitOrderInstruction, Box<dyn Error>> {
let funder = authority.unwrap_or(*FUNDER.try_lock()?);
if funder == Pubkey::default() {
return Err("Funder must be provided".into());
}
let mut instructions: Vec<Instruction> = Vec::new();
let limit_order_address = get_limit_order_address(&limit_order_mint)?.0;
let limit_order_info = rpc.get_account(&limit_order_address).await?;
let limit_order = LimitOrder::from_bytes(&limit_order_info.data)?;
let fusion_pool_info = rpc.get_account(&limit_order.fusion_pool).await?;
let fusion_pool = FusionPool::from_bytes(&fusion_pool_info.data)?;
let mint_infos = rpc.get_multiple_accounts(&[fusion_pool.token_mint_a, fusion_pool.token_mint_b]).await?;
let mint_a_info = mint_infos[0].as_ref().ok_or("Token A mint info not found")?;
let mint_b_info = mint_infos[1].as_ref().ok_or("Token B mint info not found")?;
let (mint_address, mint_info) = if limit_order.a_to_b {
(fusion_pool.token_mint_a, mint_a_info)
} else {
(fusion_pool.token_mint_b, mint_b_info)
};
let tick_array_start_index = get_tick_array_start_tick_index(limit_order.tick_index, fusion_pool.tick_spacing);
let limit_order_token_account_address = get_associated_token_address_with_program_id(&funder, &limit_order_mint, &spl_token_2022::ID);
let tick_array_address = get_tick_array_address(&limit_order.fusion_pool, tick_array_start_index)?.0;
let epoch = rpc.get_epoch_info().await?.epoch;
let transfer_fee = get_current_transfer_fee(Some(mint_info), epoch);
let amount_with_fee = if transfer_fee.is_some() {
try_reverse_apply_transfer_fee(amount, transfer_fee.unwrap_or_default())?
} else {
amount
};
let token_accounts =
prepare_token_accounts_instructions(rpc, funder, vec![TokenAccountStrategy::WithBalance(mint_address, amount_with_fee)]).await?;
instructions.extend(token_accounts.create_instructions);
let token_owner_account = token_accounts
.token_account_addresses
.get(&mint_address)
.ok_or("Token owner account not found")?;
instructions.push(
IncreaseLimitOrder {
limit_order_authority: funder,
fusion_pool: limit_order.fusion_pool,
limit_order: limit_order_address,
limit_order_token_account: limit_order_token_account_address,
token_mint: mint_address,
token_owner_account: *token_owner_account,
token_vault: if limit_order.a_to_b {
fusion_pool.token_vault_a
} else {
fusion_pool.token_vault_b
},
tick_array: tick_array_address,
token_program: mint_info.owner,
memo_program: spl_memo::ID,
}
.instruction(IncreaseLimitOrderInstructionArgs {
amount,
remaining_accounts_info: None,
}),
);
instructions.extend(token_accounts.cleanup_instructions);
Ok(IncreaseLimitOrderInstruction {
instructions,
additional_signers: token_accounts.additional_signers,
quote_a: if limit_order.a_to_b { amount_with_fee } else { 0 },
quote_b: if limit_order.a_to_b { 0 } else { amount_with_fee },
})
}
#[cfg(not(doctest))]
pub async fn close_limit_order_instructions(
rpc: &RpcClient,
limit_order_mint: Pubkey,
authority: Option<Pubkey>,
) -> Result<DecreaseLimitOrderInstruction, Box<dyn Error>> {
internal_decrease_and_close_limit_order_instructions(rpc, limit_order_mint, None, authority).await
}
#[cfg(not(doctest))]
pub async fn decrease_limit_order_instructions(
rpc: &RpcClient,
limit_order_mint: Pubkey,
amount: u64,
authority: Option<Pubkey>,
) -> Result<DecreaseLimitOrderInstruction, Box<dyn Error>> {
internal_decrease_and_close_limit_order_instructions(rpc, limit_order_mint, Some(amount), authority).await
}
async fn internal_decrease_and_close_limit_order_instructions(
rpc: &RpcClient,
limit_order_mint: Pubkey,
amount: Option<u64>,
authority: Option<Pubkey>,
) -> Result<DecreaseLimitOrderInstruction, Box<dyn Error>> {
let funder = authority.unwrap_or(*FUNDER.try_lock()?);
if funder == Pubkey::default() {
return Err("Funder must be provided".into());
}
let mut instructions: Vec<Instruction> = Vec::new();
let limit_order_address = get_limit_order_address(&limit_order_mint)?.0;
let limit_order_info = rpc.get_account(&limit_order_address).await?;
let limit_order = LimitOrder::from_bytes(&limit_order_info.data)?;
let fusion_pool_info = rpc.get_account(&limit_order.fusion_pool).await?;
let fusion_pool = FusionPool::from_bytes(&fusion_pool_info.data)?;
let mint_infos = rpc.get_multiple_accounts(&[fusion_pool.token_mint_a, fusion_pool.token_mint_b]).await?;
let mint_a_info = mint_infos[0].as_ref().ok_or("Token A mint info not found")?;
let mint_b_info = mint_infos[1].as_ref().ok_or("Token B mint info not found")?;
let tick_array_start_index = get_tick_array_start_tick_index(limit_order.tick_index, fusion_pool.tick_spacing);
let limit_order_token_account_address = get_associated_token_address_with_program_id(&funder, &limit_order_mint, &spl_token_2022::ID);
let tick_array_address = get_tick_array_address(&limit_order.fusion_pool, tick_array_start_index)?.0;
let tick_array_info = rpc.get_account(&tick_array_address).await?;
let tick_array = TickArray::from_bytes(&tick_array_info.data)?;
let tick = &tick_array.ticks[((limit_order.tick_index - tick_array_start_index) / fusion_pool.tick_spacing as i32) as usize];
let decrease_amount = match amount {
None => limit_order.amount,
Some(amount) => amount,
};
let current_epoch = rpc.get_epoch_info().await?.epoch;
let transfer_fee_a = get_current_transfer_fee(Some(mint_a_info), current_epoch);
let transfer_fee_b = get_current_transfer_fee(Some(mint_b_info), current_epoch);
let quote = decrease_limit_order_quote(
fusion_pool.clone().into(),
limit_order.clone().into(),
tick.clone().into(),
decrease_amount,
transfer_fee_a,
transfer_fee_b,
)?;
let token_accounts = prepare_token_accounts_instructions(
rpc,
funder,
vec![
TokenAccountStrategy::WithoutBalance(fusion_pool.token_mint_a),
TokenAccountStrategy::WithoutBalance(fusion_pool.token_mint_b),
],
)
.await?;
instructions.extend(token_accounts.create_instructions);
instructions.push(
DecreaseLimitOrder {
limit_order_authority: funder,
fusion_pool: limit_order.fusion_pool,
limit_order: limit_order_address,
limit_order_token_account: limit_order_token_account_address,
token_mint_a: fusion_pool.token_mint_a,
token_mint_b: fusion_pool.token_mint_b,
token_owner_account_a: *token_accounts.token_account_addresses.get(&fusion_pool.token_mint_a).unwrap(),
token_owner_account_b: *token_accounts.token_account_addresses.get(&fusion_pool.token_mint_b).unwrap(),
token_vault_a: fusion_pool.token_vault_a,
token_vault_b: fusion_pool.token_vault_b,
tick_array: tick_array_address,
token_program_a: mint_a_info.owner,
token_program_b: mint_b_info.owner,
memo_program: spl_memo::ID,
}
.instruction(DecreaseLimitOrderInstructionArgs {
amount: decrease_amount,
remaining_accounts_info: None,
}),
);
if amount.is_none() {
instructions.push(
CloseLimitOrder {
limit_order_authority: funder,
receiver: funder,
limit_order: limit_order_address,
limit_order_mint,
limit_order_token_account: limit_order_token_account_address,
token2022_program: spl_token_2022::ID,
}
.instruction(),
);
}
instructions.extend(token_accounts.cleanup_instructions);
Ok(DecreaseLimitOrderInstruction {
instructions,
quote,
additional_signers: token_accounts.additional_signers,
})
}
#[cfg(test)]
mod tests {
use crate::{
close_limit_order_instructions, decrease_limit_order_instructions, increase_limit_order_instructions, open_limit_order_instructions,
tests::{
setup_ata_te, setup_ata_with_amount, setup_fusion_pool, setup_mint_te, setup_mint_te_fee, setup_mint_with_decimals, RpcContext,
SetupAtaConfig,
},
DecreaseLimitOrderInstruction, IncreaseLimitOrderInstruction, OpenLimitOrderInstruction, PriceOrTickIndex,
};
use fusionamm_client::{get_limit_order_address, LimitOrder};
use rstest::rstest;
use serial_test::serial;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_keypair::Keypair;
use solana_program::program_pack::Pack;
use solana_program_test::tokio;
use solana_pubkey::Pubkey;
use solana_signer::Signer;
use spl_token::state::Account as TokenAccount;
use spl_token_2022::{extension::StateWithExtensionsOwned, state::Account as TokenAccount2022, ID as TOKEN_2022_PROGRAM_ID};
use std::collections::HashMap;
use std::error::Error;
async fn fetch_limit_order(rpc: &RpcClient, address: Pubkey) -> Result<LimitOrder, Box<dyn Error>> {
let account = rpc.get_account(&address).await?;
LimitOrder::from_bytes(&account.data).map_err(|e| e.into())
}
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 {
let state = StateWithExtensionsOwned::<TokenAccount2022>::unpack(account_data.data)?;
Ok(state.base.amount)
} else {
let token_account = TokenAccount::unpack(&account_data.data)?;
Ok(token_account.amount)
}
}
async fn verify_open_limit_order(
ctx: &RpcContext,
open_ix: &OpenLimitOrderInstruction,
amount: u64,
token_a_account: Pubkey,
token_b_account: Pubkey,
) -> Result<(), Box<dyn Error>> {
let before_a = get_token_balance(&ctx.rpc, token_a_account).await?;
let before_b = get_token_balance(&ctx.rpc, token_b_account).await?;
let signers: Vec<&Keypair> = open_ix.additional_signers.iter().collect();
ctx.send_transaction_with_signers(open_ix.instructions.clone(), signers).await?;
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 used_a = before_a.saturating_sub(after_a);
let used_b = before_b.saturating_sub(after_b);
assert!(used_a == open_ix.quote_a, "Token A usage mismatch! expected={}, got={}", open_ix.quote_a, used_a);
assert!(used_b == open_ix.quote_b, "Token B usage mismatch! expected={}, got={}", open_ix.quote_b, used_b);
let limit_order_address = get_limit_order_address(&open_ix.limit_order_mint)?.0;
let limit_order = fetch_limit_order(&ctx.rpc, limit_order_address).await?;
assert_eq!(limit_order.amount, amount, "Limit order amount mismatch! expected={}, got={}", amount, limit_order.amount);
Ok(())
}
async fn verify_increase_limit_order(
ctx: &RpcContext,
open_ix: &IncreaseLimitOrderInstruction,
limit_order_mint: Pubkey,
amount: u64,
token_a_account: Pubkey,
token_b_account: Pubkey,
) -> Result<(), Box<dyn Error>> {
let limit_order_address = get_limit_order_address(&limit_order_mint)?.0;
let limit_order_before = fetch_limit_order(&ctx.rpc, limit_order_address).await?;
let before_a = get_token_balance(&ctx.rpc, token_a_account).await?;
let before_b = get_token_balance(&ctx.rpc, token_b_account).await?;
let signers: Vec<&Keypair> = open_ix.additional_signers.iter().collect();
ctx.send_transaction_with_signers(open_ix.instructions.clone(), signers).await?;
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 used_a = before_a.saturating_sub(after_a);
let used_b = before_b.saturating_sub(after_b);
assert!(used_a == open_ix.quote_a, "Token A usage mismatch! expected={}, got={}", open_ix.quote_a, used_a);
assert!(used_b == open_ix.quote_b, "Token B usage mismatch! expected={}, got={}", open_ix.quote_b, used_b);
let limit_order_after = fetch_limit_order(&ctx.rpc, limit_order_address).await?;
assert_eq!(
limit_order_after.amount - limit_order_before.amount,
amount,
"Limit order amount increase mismatch! expected={}, got={}",
amount,
limit_order_after.amount - limit_order_before.amount
);
Ok(())
}
async fn verify_decrease_limit_order(
ctx: &RpcContext,
decrese_ix: &DecreaseLimitOrderInstruction,
limit_order_mint: Pubkey,
amount: u64,
token_a_account: Pubkey,
token_b_account: Pubkey,
) -> Result<(), Box<dyn Error>> {
let limit_order_address = get_limit_order_address(&limit_order_mint)?.0;
let limit_order_before = fetch_limit_order(&ctx.rpc, limit_order_address).await?;
let before_a = get_token_balance(&ctx.rpc, token_a_account).await?;
let before_b = get_token_balance(&ctx.rpc, token_b_account).await?;
let signers: Vec<&Keypair> = decrese_ix.additional_signers.iter().collect();
ctx.send_transaction_with_signers(decrese_ix.instructions.clone(), signers).await?;
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 used_a = after_a - before_a;
let used_b = after_b - before_b;
assert_eq!(
used_a, decrese_ix.quote.amount_out_a,
"Token A withdraw mismatch! expected={}, got={}",
decrese_ix.quote.amount_out_a, used_a
);
assert_eq!(
used_b, decrese_ix.quote.amount_out_b,
"Token B withdraw mismatch! expected={}, got={}",
decrese_ix.quote.amount_out_b, used_b
);
let limit_order_after = fetch_limit_order(&ctx.rpc, limit_order_address).await?;
assert_eq!(
limit_order_before.amount - limit_order_after.amount,
amount,
"Limit order amount decrease mismatch! expected={}, got={}",
amount,
limit_order_before.amount - limit_order_after.amount
);
Ok(())
}
async fn verify_close_limit_order(
ctx: &RpcContext,
close_ix: &DecreaseLimitOrderInstruction,
token_a_account: Pubkey,
token_b_account: Pubkey,
) -> Result<(), Box<dyn Error>> {
let before_a = get_token_balance(&ctx.rpc, token_a_account).await?;
let before_b = get_token_balance(&ctx.rpc, token_b_account).await?;
let signers: Vec<&Keypair> = close_ix.additional_signers.iter().collect();
ctx.send_transaction_with_signers(close_ix.instructions.clone(), signers).await?;
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 used_a = after_a - before_a;
let used_b = after_b - before_b;
assert_eq!(
used_a, close_ix.quote.amount_out_a,
"Token A withdraw mismatch! expected={}, got={}",
close_ix.quote.amount_out_a, used_a
);
assert_eq!(
used_b, close_ix.quote.amount_out_b,
"Token B withdraw mismatch! expected={}, got={}",
close_ix.quote.amount_out_b, used_b
);
Ok(())
}
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_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)
}
async fn setup_all_atas(ctx: &RpcContext, minted: &HashMap<&str, Pubkey>) -> Result<HashMap<&'static str, Pubkey>, Box<dyn Error>> {
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, *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(token_balance) })).await?;
let user_ata_tefee = setup_ata_te(ctx, *minted.get("TEFee").unwrap(), Some(SetupAtaConfig { amount: Some(token_balance) })).await?;
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),
}
}
#[rstest]
#[case("A-B", "input amount in A", 128, true)]
#[case("A-B", "input amount in B", -128, false)]
#[case("A-TEA", "input amount in A", 128, true)]
#[case("A-TEA", "input amount in B", -128, false)]
#[case("TEA-TEB", "input amount in A", 128, true)]
#[case("TEA-TEB", "input amount in B", -128, false)]
#[case("A-TEFee", "input amount in A", 128, true)]
#[case("A-TEFee", "input amount in B", -128, false)]
#[serial]
fn test_open_increase_decrease_and_close_limit_order_cases(
#[case] pool_name: &str,
#[case] _limit_order_name: &str,
#[case] tick_index: i32,
#[case] a_to_b: bool,
) {
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 (final_a, final_b) = if pubkey_a < pubkey_b {
(pubkey_a, pubkey_b)
} else {
(pubkey_b, pubkey_a)
};
let tick_spacing = 64;
let fee_rate = 300;
let swapped = pubkey_a > pubkey_b;
let pool_pubkey = setup_fusion_pool(&ctx, final_a, final_b, tick_spacing, fee_rate).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 initial_amount = 2_000_000;
let modify_amount = 1_000_000;
let open_ix = open_limit_order_instructions(
&ctx.rpc,
pool_pubkey,
initial_amount,
PriceOrTickIndex::Tick(tick_index),
a_to_b,
Some(ctx.signer.pubkey()),
)
.await
.unwrap();
verify_open_limit_order(&ctx, &open_ix, initial_amount, *user_ata_for_token_a, *user_ata_for_token_b)
.await
.unwrap();
let increase_ix = increase_limit_order_instructions(&ctx.rpc, open_ix.limit_order_mint, modify_amount, Some(ctx.signer.pubkey()))
.await
.unwrap();
verify_increase_limit_order(&ctx, &increase_ix, open_ix.limit_order_mint, modify_amount, *user_ata_for_token_a, *user_ata_for_token_b)
.await
.unwrap();
let decrease_ix = decrease_limit_order_instructions(&ctx.rpc, open_ix.limit_order_mint, modify_amount, Some(ctx.signer.pubkey()))
.await
.unwrap();
verify_decrease_limit_order(&ctx, &decrease_ix, open_ix.limit_order_mint, modify_amount, *user_ata_for_token_a, *user_ata_for_token_b)
.await
.unwrap();
let close_ix = close_limit_order_instructions(&ctx.rpc, open_ix.limit_order_mint, Some(ctx.signer.pubkey()))
.await
.unwrap();
verify_close_limit_order(&ctx, &close_ix, *user_ata_for_token_a, *user_ata_for_token_b)
.await
.unwrap();
});
}
}