use crate::{
dlmm::{
math,
state::{Bin, DlmmPool, Position},
},
errors::DloomError,
events::DlmmLiquidityModified,
state::TransactionBins,
};
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked};
use std::collections::HashMap;
pub fn handle_dlmm_modify_liquidity<'info>(
ctx: Context<'_, '_, 'info, 'info, DlmmModifyLiquidity<'info>>,
min_surplus_a_out: u64,
min_surplus_b_out: u64,
) -> Result<()> {
let account_infos_map: HashMap<Pubkey, &'info AccountInfo<'info>> = ctx
.remaining_accounts
.iter()
.map(|acc| (acc.key(), acc)) .collect();
for bin_pubkey in &ctx.accounts.transaction_bins.bins {
require!(
account_infos_map.contains_key(bin_pubkey),
DloomError::BinCacheMismatch
);
}
let old_position_state = &*ctx.accounts.old_position;
let new_position_state = &*ctx.accounts.new_position;
let dlmm_pool_state = &*ctx.accounts.dlmm_pool;
let bin_step = dlmm_pool_state.bin_step as i32;
let liquidity_to_move = old_position_state.liquidity;
require!(liquidity_to_move > 0, DloomError::PositionNotEmpty);
let (principal_a, principal_b) =
math::calculate_claimable_amounts(dlmm_pool_state, old_position_state, liquidity_to_move)?;
let mut total_fees_a: u128 = 0;
let mut total_fees_b: u128 = 0;
let expected_old_bins_count =
((old_position_state.upper_bin_id - old_position_state.lower_bin_id) / bin_step + 1)
as usize;
let (old_bins_pubkeys, new_bins_pubkeys) = ctx
.accounts
.transaction_bins
.bins
.split_at(expected_old_bins_count);
let liquidity_per_old_bin = liquidity_to_move
.checked_div(expected_old_bins_count as u128)
.ok_or(DloomError::MathOverflow)?;
for bin_pubkey in old_bins_pubkeys.iter() {
let bin_info = account_infos_map.get(bin_pubkey).unwrap(); let bin_loader = AccountLoader::<'_, Bin>::try_from(bin_info)?;
let mut bin = bin_loader.load_mut()?;
let (fees_a, fees_b) = math::calculate_accrued_fees(old_position_state, &bin);
total_fees_a = total_fees_a
.checked_add(fees_a as u128)
.ok_or(DloomError::MathOverflow)?;
total_fees_b = total_fees_b
.checked_add(fees_b as u128)
.ok_or(DloomError::MathOverflow)?;
bin.liquidity = bin
.liquidity
.checked_sub(liquidity_per_old_bin)
.ok_or(DloomError::MathOverflow)?;
}
let total_claimable_a = principal_a
.checked_add(total_fees_a)
.ok_or(DloomError::MathOverflow)?;
let total_claimable_b = principal_b
.checked_add(total_fees_b)
.ok_or(DloomError::MathOverflow)?;
let (required_a, required_b) = math::calculate_required_token_amounts(
dlmm_pool_state,
new_position_state.lower_bin_id,
new_position_state.upper_bin_id,
liquidity_to_move,
)?;
let surplus_a = total_claimable_a
.checked_sub(required_a)
.ok_or(DloomError::MathOverflow)?;
let surplus_b = total_claimable_b
.checked_sub(required_b)
.ok_or(DloomError::MathOverflow)?;
require!(
surplus_a >= min_surplus_a_out as u128 && surplus_b >= min_surplus_b_out as u128,
DloomError::SlippageExceeded
);
let bin_step_bytes = &dlmm_pool_state.bin_step.to_le_bytes()[..];
let bump = &[dlmm_pool_state.bump][..];
let signer_seeds = &[
b"dlmm_pool",
dlmm_pool_state.token_a_mint.as_ref(),
dlmm_pool_state.token_b_mint.as_ref(),
bin_step_bytes,
bump,
][..];
if surplus_a > 0 {
token_interface::transfer_checked(
CpiContext::new_with_signer(
ctx.accounts.token_a_program.to_account_info(),
TransferChecked {
from: ctx.accounts.token_a_vault.to_account_info(),
to: ctx.accounts.user_token_a_account.to_account_info(),
authority: ctx.accounts.dlmm_pool.to_account_info(),
mint: ctx.accounts.token_a_mint.to_account_info(),
},
&[signer_seeds],
),
surplus_a as u64,
ctx.accounts.token_a_mint.decimals,
)?;
}
if surplus_b > 0 {
token_interface::transfer_checked(
CpiContext::new_with_signer(
ctx.accounts.token_b_program.to_account_info(),
TransferChecked {
from: ctx.accounts.token_b_vault.to_account_info(),
to: ctx.accounts.user_token_b_account.to_account_info(),
authority: ctx.accounts.dlmm_pool.to_account_info(),
mint: ctx.accounts.token_b_mint.to_account_info(),
},
&[signer_seeds],
),
surplus_b as u64,
ctx.accounts.token_b_mint.decimals,
)?;
}
let dlmm_pool = &mut ctx.accounts.dlmm_pool;
dlmm_pool.reserves_a = dlmm_pool
.reserves_a
.checked_sub(surplus_a as u64)
.ok_or(DloomError::MathOverflow)?;
dlmm_pool.reserves_b = dlmm_pool
.reserves_b
.checked_sub(surplus_b as u64)
.ok_or(DloomError::MathOverflow)?;
ctx.accounts.old_position.liquidity = 0;
let new_position = &mut ctx.accounts.new_position;
new_position.liquidity = new_position
.liquidity
.checked_add(liquidity_to_move)
.ok_or(DloomError::MathOverflow)?;
let liquidity_per_new_bin = liquidity_to_move
.checked_div(new_bins_pubkeys.len() as u128)
.ok_or(DloomError::MathOverflow)?;
let mut snapshot_a: u128 = 0;
let mut snapshot_b: u128 = 0;
for bin_pubkey in new_bins_pubkeys.iter() {
let bin_info = account_infos_map.get(bin_pubkey).unwrap(); let bin_loader = AccountLoader::<'_, Bin>::try_from(bin_info)?;
let mut bin = bin_loader.load_mut()?;
bin.liquidity = bin
.liquidity
.checked_add(liquidity_per_new_bin)
.ok_or(DloomError::MathOverflow)?;
snapshot_a = snapshot_a.max(bin.fee_growth_per_unit_a);
snapshot_b = snapshot_b.max(bin.fee_growth_per_unit_b);
}
new_position.fee_growth_snapshot_a = snapshot_a;
new_position.fee_growth_snapshot_b = snapshot_b;
emit!(DlmmLiquidityModified {
owner: ctx.accounts.owner.key(),
pool_address: ctx.accounts.dlmm_pool.key(),
old_position_address: ctx.accounts.old_position.key(),
new_position_address: ctx.accounts.new_position.key(),
liquidity_to_move,
surplus_a_out: surplus_a as u64,
surplus_b_out: surplus_b as u64,
});
Ok(())
}
#[derive(Accounts)]
pub struct DlmmModifyLiquidity<'info> {
#[account(mut)]
pub owner: Signer<'info>,
#[account(mut)]
pub dlmm_pool: Box<Account<'info, DlmmPool>>,
#[account(
mut,
has_one = owner @ DloomError::Unauthorized,
constraint = old_position.pool == dlmm_pool.key() @ DloomError::InvalidPool
)]
pub old_position: Box<Account<'info, Position>>,
#[account(
mut,
has_one = owner @ DloomError::Unauthorized,
constraint = new_position.pool == dlmm_pool.key() @ DloomError::InvalidPool
)]
pub new_position: Box<Account<'info, Position>>,
#[account(
mut,
has_one = owner,
close = owner,
seeds = [b"transaction_bins", owner.key().as_ref()],
bump
)]
pub transaction_bins: Account<'info, TransactionBins>,
#[account(address = dlmm_pool.token_a_mint)]
pub token_a_mint: InterfaceAccount<'info, Mint>,
#[account(address = dlmm_pool.token_b_mint)]
pub token_b_mint: InterfaceAccount<'info, Mint>,
#[account(mut, token::mint = dlmm_pool.token_a_mint, has_one = owner)]
pub user_token_a_account: InterfaceAccount<'info, TokenAccount>,
#[account(mut, token::mint = dlmm_pool.token_b_mint, has_one = owner)]
pub user_token_b_account: InterfaceAccount<'info, TokenAccount>,
#[account(mut, address = dlmm_pool.token_a_vault)]
pub token_a_vault: InterfaceAccount<'info, TokenAccount>,
#[account(mut, address = dlmm_pool.token_b_vault)]
pub token_b_vault: InterfaceAccount<'info, TokenAccount>,
pub token_a_program: Interface<'info, TokenInterface>,
pub token_b_program: Interface<'info, TokenInterface>,
}