use crate::get_rent;
use crate::{
token::{get_current_transfer_fee, prepare_token_accounts_instructions, TokenAccountStrategy},
FUNDER, SLIPPAGE_TOLERANCE_BPS,
};
use fusionamm_client::{
get_position_address, get_tick_array_address, FusionPool, InitializeTickArray, InitializeTickArrayInstructionArgs, OpenPosition,
OpenPositionInstructionArgs, Position, TickArray, FP_NFT_UPDATE_AUTH,
};
use fusionamm_client::{IncreaseLiquidity, IncreaseLiquidityInstructionArgs};
use fusionamm_core::{
get_full_range_tick_indexes, get_initializable_tick_index, get_tick_array_start_tick_index, increase_liquidity_quote, increase_liquidity_quote_a,
increase_liquidity_quote_b, order_tick_indexes, price_to_tick_index, IncreaseLiquidityQuote, TransferFee,
};
use solana_account::Account;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_instruction::Instruction;
use solana_keypair::Keypair;
use solana_program::program_pack::Pack;
use solana_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;
pub enum PriceOrTickIndex {
Tick(i32),
Price(f64),
}
fn get_increase_liquidity_quote(
param: IncreaseLiquidityParam,
slippage_tolerance_bps: u16,
pool: &FusionPool,
tick_lower_index: i32,
tick_upper_index: i32,
transfer_fee_a: Option<TransferFee>,
transfer_fee_b: Option<TransferFee>,
) -> Result<IncreaseLiquidityQuote, Box<dyn Error>> {
let result = match param {
IncreaseLiquidityParam::TokenA(amount) => increase_liquidity_quote_a(
amount,
slippage_tolerance_bps,
pool.sqrt_price,
tick_lower_index,
tick_upper_index,
transfer_fee_a,
transfer_fee_b,
),
IncreaseLiquidityParam::TokenB(amount) => increase_liquidity_quote_b(
amount,
slippage_tolerance_bps,
pool.sqrt_price,
tick_lower_index,
tick_upper_index,
transfer_fee_a,
transfer_fee_b,
),
IncreaseLiquidityParam::Liquidity(amount) => increase_liquidity_quote(
amount,
slippage_tolerance_bps,
pool.sqrt_price,
tick_lower_index,
tick_upper_index,
transfer_fee_a,
transfer_fee_b,
),
}?;
Ok(result)
}
#[derive(Debug, Clone)]
pub enum IncreaseLiquidityParam {
TokenA(u64),
TokenB(u64),
Liquidity(u128),
}
#[derive(Debug)]
pub struct IncreaseLiquidityInstruction {
pub quote: IncreaseLiquidityQuote,
pub instructions: Vec<Instruction>,
pub additional_signers: Vec<Keypair>,
}
#[cfg(not(doctest))]
pub async fn increase_liquidity_instructions(
rpc: &RpcClient,
position_mint_address: Pubkey,
param: IncreaseLiquidityParam,
slippage_tolerance_bps: Option<u16>,
authority: Option<Pubkey>,
) -> Result<IncreaseLiquidityInstruction, Box<dyn Error>> {
let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(*SLIPPAGE_TOLERANCE_BPS.try_lock()?);
let authority = authority.unwrap_or(*FUNDER.try_lock()?);
if authority == Pubkey::default() {
return Err("Authority must be provided".into());
}
let position_address = get_position_address(&position_mint_address)?.0;
let position_info = rpc.get_account(&position_address).await?;
let position = Position::from_bytes(&position_info.data)?;
let pool_info = rpc.get_account(&position.fusion_pool).await?;
let pool = FusionPool::from_bytes(&pool_info.data)?;
let mint_infos = rpc
.get_multiple_accounts(&[pool.token_mint_a, pool.token_mint_b, position_mint_address])
.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 position_mint_info = mint_infos[2].as_ref().ok_or("Position mint info not found")?;
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 = get_increase_liquidity_quote(
param,
slippage_tolerance_bps,
&pool,
position.tick_lower_index,
position.tick_upper_index,
transfer_fee_a,
transfer_fee_b,
)?;
let mut instructions: Vec<Instruction> = Vec::new();
let lower_tick_array_start_index = get_tick_array_start_tick_index(position.tick_lower_index, pool.tick_spacing);
let upper_tick_array_start_index = get_tick_array_start_tick_index(position.tick_upper_index, pool.tick_spacing);
let position_token_account_address = get_associated_token_address_with_program_id(&authority, &position_mint_address, &position_mint_info.owner);
let lower_tick_array_address = get_tick_array_address(&position.fusion_pool, lower_tick_array_start_index)?.0;
let upper_tick_array_address = get_tick_array_address(&position.fusion_pool, upper_tick_array_start_index)?.0;
let token_accounts = prepare_token_accounts_instructions(
rpc,
authority,
vec![
TokenAccountStrategy::WithBalance(pool.token_mint_a, quote.token_max_a),
TokenAccountStrategy::WithBalance(pool.token_mint_b, quote.token_max_b),
],
)
.await?;
instructions.extend(token_accounts.create_instructions);
let token_owner_account_a = token_accounts
.token_account_addresses
.get(&pool.token_mint_a)
.ok_or("Token A owner account not found")?;
let token_owner_account_b = token_accounts
.token_account_addresses
.get(&pool.token_mint_b)
.ok_or("Token B owner account not found")?;
instructions.push(
IncreaseLiquidity {
fusion_pool: position.fusion_pool,
token_program_a: mint_a_info.owner,
token_program_b: mint_b_info.owner,
memo_program: spl_memo::ID,
position_authority: authority,
position: position_address,
position_token_account: position_token_account_address,
token_mint_a: pool.token_mint_a,
token_mint_b: pool.token_mint_b,
token_owner_account_a: *token_owner_account_a,
token_owner_account_b: *token_owner_account_b,
token_vault_a: pool.token_vault_a,
token_vault_b: pool.token_vault_b,
tick_array_lower: lower_tick_array_address,
tick_array_upper: upper_tick_array_address,
}
.instruction(IncreaseLiquidityInstructionArgs {
liquidity_amount: quote.liquidity_delta,
token_max_a: quote.token_max_a,
token_max_b: quote.token_max_b,
remaining_accounts_info: None,
}),
);
instructions.extend(token_accounts.cleanup_instructions);
Ok(IncreaseLiquidityInstruction {
quote,
instructions,
additional_signers: token_accounts.additional_signers,
})
}
#[derive(Debug)]
pub struct OpenPositionInstruction {
pub position_mint: Pubkey,
pub quote: IncreaseLiquidityQuote,
pub instructions: Vec<Instruction>,
pub additional_signers: Vec<Keypair>,
pub initialization_cost: u64,
}
#[allow(clippy::too_many_arguments)]
async fn internal_open_position(
rpc: &RpcClient,
pool_address: Pubkey,
fusion_pool: FusionPool,
param: IncreaseLiquidityParam,
lower_tick_index: i32,
upper_tick_index: i32,
mint_a_info: &Account,
mint_b_info: &Account,
slippage_tolerance_bps: Option<u16>,
funder: Option<Pubkey>,
) -> Result<OpenPositionInstruction, Box<dyn Error>> {
let funder = funder.unwrap_or(*FUNDER.try_lock()?);
let slippage_tolerance_bps = slippage_tolerance_bps.unwrap_or(*SLIPPAGE_TOLERANCE_BPS.try_lock()?);
let rent = get_rent(rpc).await?;
if funder == Pubkey::default() {
return Err("Funder must be provided".into());
}
let tick_range = order_tick_indexes(lower_tick_index, upper_tick_index);
let lower_initializable_tick_index = get_initializable_tick_index(tick_range.tick_lower_index, fusion_pool.tick_spacing, Some(false));
let upper_initializable_tick_index = get_initializable_tick_index(tick_range.tick_upper_index, fusion_pool.tick_spacing, Some(true));
let mut instructions: Vec<Instruction> = Vec::new();
let mut non_refundable_rent: u64 = 0;
let mut additional_signers: Vec<Keypair> = Vec::new();
let epoch = rpc.get_epoch_info().await?.epoch;
let transfer_fee_a = get_current_transfer_fee(Some(mint_a_info), epoch);
let transfer_fee_b = get_current_transfer_fee(Some(mint_b_info), epoch);
let quote = get_increase_liquidity_quote(
param,
slippage_tolerance_bps,
&fusion_pool,
lower_initializable_tick_index,
upper_initializable_tick_index,
transfer_fee_a,
transfer_fee_b,
)?;
additional_signers.push(Keypair::new());
let position_mint = additional_signers[0].pubkey();
let lower_tick_start_index = get_tick_array_start_tick_index(lower_initializable_tick_index, fusion_pool.tick_spacing);
let upper_tick_start_index = get_tick_array_start_tick_index(upper_initializable_tick_index, fusion_pool.tick_spacing);
let position_address = get_position_address(&position_mint)?.0;
let position_token_account_address = get_associated_token_address_with_program_id(&funder, &position_mint, &spl_token_2022::ID);
let lower_tick_array_address = get_tick_array_address(&pool_address, lower_tick_start_index)?.0;
let upper_tick_array_address = get_tick_array_address(&pool_address, upper_tick_start_index)?.0;
let token_accounts = prepare_token_accounts_instructions(
rpc,
funder,
vec![
TokenAccountStrategy::WithBalance(fusion_pool.token_mint_a, quote.token_max_a),
TokenAccountStrategy::WithBalance(fusion_pool.token_mint_b, quote.token_max_b),
],
)
.await?;
instructions.extend(token_accounts.create_instructions);
additional_signers.extend(token_accounts.additional_signers);
let tick_array_infos = rpc.get_multiple_accounts(&[lower_tick_array_address, upper_tick_array_address]).await?;
if tick_array_infos[0].is_none() {
instructions.push(
InitializeTickArray {
fusion_pool: pool_address,
funder,
tick_array: lower_tick_array_address,
system_program: solana_program::system_program::id(),
}
.instruction(InitializeTickArrayInstructionArgs {
start_tick_index: lower_tick_start_index,
}),
);
non_refundable_rent += rent.minimum_balance(TickArray::MIN_LEN);
}
if tick_array_infos[1].is_none() && lower_tick_start_index != upper_tick_start_index {
instructions.push(
InitializeTickArray {
fusion_pool: pool_address,
funder,
tick_array: upper_tick_array_address,
system_program: solana_program::system_program::id(),
}
.instruction(InitializeTickArrayInstructionArgs {
start_tick_index: upper_tick_start_index,
}),
);
non_refundable_rent += rent.minimum_balance(TickArray::MIN_LEN);
}
let token_owner_account_a = token_accounts
.token_account_addresses
.get(&fusion_pool.token_mint_a)
.ok_or("Token A owner account not found")?;
let token_owner_account_b = token_accounts
.token_account_addresses
.get(&fusion_pool.token_mint_b)
.ok_or("Token B owner account not found")?;
instructions.push(
OpenPosition {
funder,
owner: funder,
position: position_address,
position_mint,
position_token_account: position_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(OpenPositionInstructionArgs {
tick_lower_index: lower_initializable_tick_index,
tick_upper_index: upper_initializable_tick_index,
with_token_metadata_extension: true,
}),
);
instructions.push(
IncreaseLiquidity {
fusion_pool: pool_address,
token_program_a: mint_a_info.owner,
token_program_b: mint_b_info.owner,
memo_program: spl_memo::ID,
position_authority: funder,
position: position_address,
position_token_account: position_token_account_address,
token_mint_a: fusion_pool.token_mint_a,
token_mint_b: fusion_pool.token_mint_b,
token_owner_account_a: *token_owner_account_a,
token_owner_account_b: *token_owner_account_b,
token_vault_a: fusion_pool.token_vault_a,
token_vault_b: fusion_pool.token_vault_b,
tick_array_lower: lower_tick_array_address,
tick_array_upper: upper_tick_array_address,
}
.instruction(IncreaseLiquidityInstructionArgs {
liquidity_amount: quote.liquidity_delta,
token_max_a: quote.token_max_a,
token_max_b: quote.token_max_b,
remaining_accounts_info: None,
}),
);
instructions.extend(token_accounts.cleanup_instructions);
Ok(OpenPositionInstruction {
position_mint,
quote,
instructions,
additional_signers,
initialization_cost: non_refundable_rent,
})
}
#[cfg(not(doctest))]
pub async fn open_full_range_position_instructions(
rpc: &RpcClient,
pool_address: Pubkey,
param: IncreaseLiquidityParam,
slippage_tolerance_bps: Option<u16>,
funder: Option<Pubkey>,
) -> Result<OpenPositionInstruction, Box<dyn Error>> {
let fusion_pool_info = rpc.get_account(&pool_address).await?;
let fusion_pool = FusionPool::from_bytes(&fusion_pool_info.data)?;
let tick_range = get_full_range_tick_indexes(fusion_pool.tick_spacing);
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")?;
internal_open_position(
rpc,
pool_address,
fusion_pool,
param,
tick_range.tick_lower_index,
tick_range.tick_upper_index,
mint_a_info,
mint_b_info,
slippage_tolerance_bps,
funder,
)
.await
}
#[cfg(not(doctest))]
pub async fn open_position_instructions(
rpc: &RpcClient,
pool_address: Pubkey,
lower_price_or_tick_index: PriceOrTickIndex,
upper_price_or_tick_index: PriceOrTickIndex,
param: IncreaseLiquidityParam,
slippage_tolerance_bps: Option<u16>,
funder: Option<Pubkey>,
) -> Result<OpenPositionInstruction, Box<dyn Error>> {
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")?;
let mint_a = Mint::unpack(&mint_a_info.data)?;
let mint_b_info = mint_infos[1].as_ref().ok_or("Token B mint info not found")?;
let mint_b = Mint::unpack(&mint_b_info.data)?;
let decimals_a = mint_a.decimals;
let decimals_b = mint_b.decimals;
let lower_tick_index = match lower_price_or_tick_index {
PriceOrTickIndex::Tick(tick_index) => tick_index,
PriceOrTickIndex::Price(price) => price_to_tick_index(price, decimals_a, decimals_b),
};
let upper_tick_index = match upper_price_or_tick_index {
PriceOrTickIndex::Tick(tick_index) => tick_index,
PriceOrTickIndex::Price(price) => price_to_tick_index(price, decimals_a, decimals_b),
};
internal_open_position(
rpc,
pool_address,
fusion_pool,
param,
lower_tick_index,
upper_tick_index,
mint_a_info,
mint_b_info,
slippage_tolerance_bps,
funder,
)
.await
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::error::Error;
use fusionamm_client::{get_position_address, Position};
use rstest::rstest;
use serial_test::serial;
use solana_program_test::tokio;
use spl_token::state::Account as TokenAccount;
use spl_token_2022::{extension::StateWithExtensionsOwned, state::Account as TokenAccount2022, ID as TOKEN_2022_PROGRAM_ID};
use crate::{
increase_liquidity_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,
},
IncreaseLiquidityParam,
};
use crate::tests::setup_position;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_keypair::Keypair;
use solana_program::program_pack::Pack;
use solana_pubkey::Pubkey;
use solana_signer::Signer;
async fn fetch_position(rpc: &RpcClient, address: Pubkey) -> Result<Position, Box<dyn Error>> {
let account = rpc.get_account(&address).await?;
Position::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_increase_liquidity(
ctx: &RpcContext,
increase_ix: &crate::IncreaseLiquidityInstruction,
token_a_account: Pubkey,
token_b_account: Pubkey,
position_mint: 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> = increase_ix.additional_signers.iter().collect();
ctx.send_transaction_with_signers(increase_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);
let quote = &increase_ix.quote;
assert!(
used_a >= quote.token_est_a && used_a <= quote.token_max_a,
"Token A usage out of range: used={}, est={}..{}",
used_a,
quote.token_est_a,
quote.token_max_a
);
assert!(
used_b >= quote.token_est_b && used_b <= quote.token_max_b,
"Token B usage out of range: used={}, est={}..{}",
used_b,
quote.token_est_b,
quote.token_max_b
);
let position_pubkey = get_position_address(&position_mint)?.0;
let position_data = fetch_position(&ctx.rpc, position_pubkey).await?;
assert_eq!(
position_data.liquidity, quote.liquidity_delta,
"Position liquidity mismatch! expected={}, got={}",
quote.liquidity_delta, position_data.liquidity
);
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", "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_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 (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 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), 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();
});
}
#[tokio::test]
#[serial]
async fn test_increase_liquidity_fails_if_authority_is_default() -> Result<(), Box<dyn Error>> {
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_fusion_pool(&ctx, *mint_a_key, *mint_b_key, 64, 300).await?;
let position_mint = setup_position(&ctx, pool_pubkey, Some((-100, 100)), None).await?;
let param = IncreaseLiquidityParam::Liquidity(100_000);
let res = increase_liquidity_instructions(
&ctx.rpc,
position_mint,
param,
Some(100), 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<dyn Error>> {
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_fusion_pool(&ctx, *mint_a_key, *mint_b_key, 64, 300).await?;
let position_mint = setup_position(&ctx, pool_pubkey, Some((-100, 100)), None).await?;
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(())
}
}