dloom_flow/dlmm/instructions/
modify_liquidity.rs1use crate::{
4 dlmm::{
5 math,
6 state::{Bin, DlmmPool, Position},
7 },
8 errors::DloomError,
9 events::DlmmLiquidityModified,
10 state::TransactionBins,
11};
12use anchor_lang::prelude::*;
13use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked};
14use std::collections::HashMap; pub fn handle_dlmm_modify_liquidity<'info>(
17 ctx: Context<'_, '_, 'info, 'info, DlmmModifyLiquidity<'info>>,
18 min_surplus_a_out: u64,
19 min_surplus_b_out: u64,
20) -> Result<()> {
21 let account_infos_map: HashMap<Pubkey, &'info AccountInfo<'info>> = ctx
24 .remaining_accounts
25 .iter()
26 .map(|acc| (acc.key(), acc)) .collect();
28
29 for bin_pubkey in &ctx.accounts.transaction_bins.bins {
31 require!(
32 account_infos_map.contains_key(bin_pubkey),
33 DloomError::BinCacheMismatch
34 );
35 }
36 let old_position_state = &*ctx.accounts.old_position;
39 let new_position_state = &*ctx.accounts.new_position;
40 let dlmm_pool_state = &*ctx.accounts.dlmm_pool;
41 let bin_step = dlmm_pool_state.bin_step as i32;
42 let liquidity_to_move = old_position_state.liquidity;
43 require!(liquidity_to_move > 0, DloomError::PositionNotEmpty);
44
45 let (principal_a, principal_b) =
48 math::calculate_claimable_amounts(dlmm_pool_state, old_position_state, liquidity_to_move)?;
49
50 let mut total_fees_a: u128 = 0;
51 let mut total_fees_b: u128 = 0;
52
53 let expected_old_bins_count =
55 ((old_position_state.upper_bin_id - old_position_state.lower_bin_id) / bin_step + 1)
56 as usize;
57 let (old_bins_pubkeys, new_bins_pubkeys) = ctx
58 .accounts
59 .transaction_bins
60 .bins
61 .split_at(expected_old_bins_count);
62
63 let liquidity_per_old_bin = liquidity_to_move
65 .checked_div(expected_old_bins_count as u128)
66 .ok_or(DloomError::MathOverflow)?;
67
68 for bin_pubkey in old_bins_pubkeys.iter() {
69 let bin_info = account_infos_map.get(bin_pubkey).unwrap(); let bin_loader = AccountLoader::<'_, Bin>::try_from(bin_info)?;
71 let mut bin = bin_loader.load_mut()?;
72 let (fees_a, fees_b) = math::calculate_accrued_fees(old_position_state, &bin);
73 total_fees_a = total_fees_a
74 .checked_add(fees_a as u128)
75 .ok_or(DloomError::MathOverflow)?;
76 total_fees_b = total_fees_b
77 .checked_add(fees_b as u128)
78 .ok_or(DloomError::MathOverflow)?;
79 bin.liquidity = bin
80 .liquidity
81 .checked_sub(liquidity_per_old_bin)
82 .ok_or(DloomError::MathOverflow)?;
83 }
84
85 let total_claimable_a = principal_a
86 .checked_add(total_fees_a)
87 .ok_or(DloomError::MathOverflow)?;
88 let total_claimable_b = principal_b
89 .checked_add(total_fees_b)
90 .ok_or(DloomError::MathOverflow)?;
91
92 let (required_a, required_b) = math::calculate_required_token_amounts(
94 dlmm_pool_state,
95 new_position_state.lower_bin_id,
96 new_position_state.upper_bin_id,
97 liquidity_to_move,
98 )?;
99
100 let surplus_a = total_claimable_a
102 .checked_sub(required_a)
103 .ok_or(DloomError::MathOverflow)?;
104 let surplus_b = total_claimable_b
105 .checked_sub(required_b)
106 .ok_or(DloomError::MathOverflow)?;
107 require!(
108 surplus_a >= min_surplus_a_out as u128 && surplus_b >= min_surplus_b_out as u128,
109 DloomError::SlippageExceeded
110 );
111
112 let bin_step_bytes = &dlmm_pool_state.bin_step.to_le_bytes()[..];
115 let bump = &[dlmm_pool_state.bump][..];
116 let signer_seeds = &[
117 b"dlmm_pool",
118 dlmm_pool_state.token_a_mint.as_ref(),
119 dlmm_pool_state.token_b_mint.as_ref(),
120 bin_step_bytes,
121 bump,
122 ][..];
123
124 if surplus_a > 0 {
126 token_interface::transfer_checked(
127 CpiContext::new_with_signer(
128 ctx.accounts.token_a_program.to_account_info(),
129 TransferChecked {
130 from: ctx.accounts.token_a_vault.to_account_info(),
131 to: ctx.accounts.user_token_a_account.to_account_info(),
132 authority: ctx.accounts.dlmm_pool.to_account_info(),
133 mint: ctx.accounts.token_a_mint.to_account_info(),
134 },
135 &[signer_seeds],
136 ),
137 surplus_a as u64,
138 ctx.accounts.token_a_mint.decimals,
139 )?;
140 }
141 if surplus_b > 0 {
142 token_interface::transfer_checked(
143 CpiContext::new_with_signer(
144 ctx.accounts.token_b_program.to_account_info(),
145 TransferChecked {
146 from: ctx.accounts.token_b_vault.to_account_info(),
147 to: ctx.accounts.user_token_b_account.to_account_info(),
148 authority: ctx.accounts.dlmm_pool.to_account_info(),
149 mint: ctx.accounts.token_b_mint.to_account_info(),
150 },
151 &[signer_seeds],
152 ),
153 surplus_b as u64,
154 ctx.accounts.token_b_mint.decimals,
155 )?;
156 }
157
158 let dlmm_pool = &mut ctx.accounts.dlmm_pool;
160 dlmm_pool.reserves_a = dlmm_pool
161 .reserves_a
162 .checked_sub(surplus_a as u64)
163 .ok_or(DloomError::MathOverflow)?;
164 dlmm_pool.reserves_b = dlmm_pool
165 .reserves_b
166 .checked_sub(surplus_b as u64)
167 .ok_or(DloomError::MathOverflow)?;
168
169 ctx.accounts.old_position.liquidity = 0;
170
171 let new_position = &mut ctx.accounts.new_position;
172 new_position.liquidity = new_position
173 .liquidity
174 .checked_add(liquidity_to_move)
175 .ok_or(DloomError::MathOverflow)?;
176
177 let liquidity_per_new_bin = liquidity_to_move
178 .checked_div(new_bins_pubkeys.len() as u128)
179 .ok_or(DloomError::MathOverflow)?;
180 let mut snapshot_a: u128 = 0;
181 let mut snapshot_b: u128 = 0;
182
183 for bin_pubkey in new_bins_pubkeys.iter() {
184 let bin_info = account_infos_map.get(bin_pubkey).unwrap(); let bin_loader = AccountLoader::<'_, Bin>::try_from(bin_info)?;
186 let mut bin = bin_loader.load_mut()?;
187 bin.liquidity = bin
188 .liquidity
189 .checked_add(liquidity_per_new_bin)
190 .ok_or(DloomError::MathOverflow)?;
191 snapshot_a = snapshot_a.max(bin.fee_growth_per_unit_a);
192 snapshot_b = snapshot_b.max(bin.fee_growth_per_unit_b);
193 }
194 new_position.fee_growth_snapshot_a = snapshot_a;
195 new_position.fee_growth_snapshot_b = snapshot_b;
196
197 emit!(DlmmLiquidityModified {
198 owner: ctx.accounts.owner.key(),
199 pool_address: ctx.accounts.dlmm_pool.key(),
200 old_position_address: ctx.accounts.old_position.key(),
201 new_position_address: ctx.accounts.new_position.key(),
202 liquidity_to_move,
203 surplus_a_out: surplus_a as u64,
204 surplus_b_out: surplus_b as u64,
205 });
206
207 Ok(())
208}
209
210#[derive(Accounts)]
211pub struct DlmmModifyLiquidity<'info> {
212 #[account(mut)]
213 pub owner: Signer<'info>,
214
215 #[account(mut)]
216 pub dlmm_pool: Box<Account<'info, DlmmPool>>,
217
218 #[account(
219 mut,
220 has_one = owner @ DloomError::Unauthorized,
221 constraint = old_position.pool == dlmm_pool.key() @ DloomError::InvalidPool
222 )]
223 pub old_position: Box<Account<'info, Position>>,
224
225 #[account(
226 mut,
227 has_one = owner @ DloomError::Unauthorized,
228 constraint = new_position.pool == dlmm_pool.key() @ DloomError::InvalidPool
229 )]
230 pub new_position: Box<Account<'info, Position>>,
231
232 #[account(
234 mut,
235 has_one = owner,
236 close = owner,
237 seeds = [b"transaction_bins", owner.key().as_ref()],
238 bump
239 )]
240 pub transaction_bins: Account<'info, TransactionBins>,
241
242 #[account(address = dlmm_pool.token_a_mint)]
243 pub token_a_mint: InterfaceAccount<'info, Mint>,
244 #[account(address = dlmm_pool.token_b_mint)]
245 pub token_b_mint: InterfaceAccount<'info, Mint>,
246
247 #[account(mut, token::mint = dlmm_pool.token_a_mint, has_one = owner)]
248 pub user_token_a_account: InterfaceAccount<'info, TokenAccount>,
249 #[account(mut, token::mint = dlmm_pool.token_b_mint, has_one = owner)]
250 pub user_token_b_account: InterfaceAccount<'info, TokenAccount>,
251
252 #[account(mut, address = dlmm_pool.token_a_vault)]
253 pub token_a_vault: InterfaceAccount<'info, TokenAccount>,
254 #[account(mut, address = dlmm_pool.token_b_vault)]
255 pub token_b_vault: InterfaceAccount<'info, TokenAccount>,
256
257 pub token_a_program: Interface<'info, TokenInterface>,
258 pub token_b_program: Interface<'info, TokenInterface>,
259}