use crate::{
dlmm::{
math,
state::{Bin, DlmmPool, Position},
},
errors::DloomError,
events::DlmmLiquidityUpdate,
state::TransactionBins, };
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked};
use std::collections::HashMap;
pub fn handle_dlmm_add_liquidity<'info>(
ctx: Context<'_, '_, 'info, 'info, DlmmAddLiquidity<'info>>,
start_bin_id: i32,
liquidity_per_bin: u128,
) -> Result<()> {
require!(liquidity_per_bin > 0, DloomError::ZeroLiquidity);
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 bin_step = ctx.accounts.dlmm_pool.bin_step as i32;
let mut current_bin_id = start_bin_id;
let mut total_required_a: u128 = 0;
let mut total_required_b: u128 = 0;
for _ in 0..ctx.accounts.transaction_bins.bins.len() {
require!(
current_bin_id >= ctx.accounts.position.lower_bin_id
&& current_bin_id <= ctx.accounts.position.upper_bin_id,
DloomError::InvalidBinRange
);
let (required_a, required_b) = math::calculate_required_for_bin(
ctx.accounts.dlmm_pool.active_bin_id,
current_bin_id,
ctx.accounts.dlmm_pool.bin_step,
liquidity_per_bin,
)?;
total_required_a = total_required_a
.checked_add(required_a)
.ok_or(DloomError::MathOverflow)?;
total_required_b = total_required_b
.checked_add(required_b)
.ok_or(DloomError::MathOverflow)?;
current_bin_id = current_bin_id
.checked_add(bin_step)
.ok_or(DloomError::MathOverflow)?;
}
if total_required_a > 0 {
token_interface::transfer_checked(
ctx.accounts.transfer_a_context(),
total_required_a as u64,
ctx.accounts.token_a_mint.decimals,
)?;
}
if total_required_b > 0 {
token_interface::transfer_checked(
ctx.accounts.transfer_b_context(),
total_required_b as u64,
ctx.accounts.token_b_mint.decimals,
)?;
}
let dlmm_pool = &mut ctx.accounts.dlmm_pool;
if total_required_a > 0 {
dlmm_pool.reserves_a = dlmm_pool
.reserves_a
.checked_add(total_required_a as u64)
.ok_or(DloomError::MathOverflow)?;
}
if total_required_b > 0 {
dlmm_pool.reserves_b = dlmm_pool
.reserves_b
.checked_add(total_required_b as u64)
.ok_or(DloomError::MathOverflow)?;
}
current_bin_id = start_bin_id;
for bin_pubkey in &ctx.accounts.transaction_bins.bins {
let bin_account_info = account_infos_map.get(bin_pubkey).unwrap();
let (expected_bin_pda, _) = Pubkey::find_program_address(
&[
b"bin",
dlmm_pool.key().as_ref(),
¤t_bin_id.to_le_bytes(),
],
ctx.program_id,
);
require_keys_eq!(
bin_account_info.key(),
expected_bin_pda,
DloomError::InvalidBinAccount
);
let bin_loader = AccountLoader::<'_, Bin>::try_from(bin_account_info)?;
let mut bin = bin_loader.load_mut()?;
bin.liquidity = bin
.liquidity
.checked_add(liquidity_per_bin)
.ok_or(DloomError::MathOverflow)?;
current_bin_id = current_bin_id
.checked_add(bin_step)
.ok_or(DloomError::MathOverflow)?;
}
let total_liquidity_added_in_chunk = liquidity_per_bin
.checked_mul(ctx.accounts.transaction_bins.bins.len() as u128)
.ok_or(DloomError::MathOverflow)?;
let position = &mut ctx.accounts.position;
position.liquidity = position
.liquidity
.checked_add(total_liquidity_added_in_chunk)
.ok_or(DloomError::MathOverflow)?;
emit!(DlmmLiquidityUpdate {
position_address: ctx.accounts.position.key(),
liquidity_added: total_liquidity_added_in_chunk as i128,
amount_a: total_required_a as u64,
amount_b: total_required_b as u64,
});
Ok(())
}
#[derive(Accounts)]
pub struct DlmmAddLiquidity<'info> {
#[account(mut)]
pub owner: Signer<'info>,
#[account(
mut,
seeds = [
b"dlmm_pool",
dlmm_pool.token_a_mint.as_ref(),
dlmm_pool.token_b_mint.as_ref(),
&dlmm_pool.bin_step.to_le_bytes()
],
bump = dlmm_pool.bump
)]
pub dlmm_pool: Box<Account<'info, DlmmPool>>,
#[account(
mut,
has_one = owner,
constraint = position.pool == dlmm_pool.key() @ DloomError::InvalidPool
)]
pub position: Box<Account<'info, Position>>,
#[account(
mut,
has_one = owner,
close = owner,
seeds = [b"transaction_bins", owner.key().as_ref()],
bump
)]
pub transaction_bins: Box<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 = token_a_mint, has_one = owner)]
pub user_token_a_account: InterfaceAccount<'info, TokenAccount>,
#[account(mut, token::mint = 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>,
}
impl<'info> DlmmAddLiquidity<'info> {
fn transfer_a_context(&self) -> CpiContext<'_, '_, '_, 'info, TransferChecked<'info>> {
CpiContext::new(
self.token_a_program.to_account_info(),
TransferChecked {
from: self.user_token_a_account.to_account_info(),
to: self.token_a_vault.to_account_info(),
authority: self.owner.to_account_info(),
mint: self.token_a_mint.to_account_info(),
},
)
}
fn transfer_b_context(&self) -> CpiContext<'_, '_, '_, 'info, TransferChecked<'info>> {
CpiContext::new(
self.token_b_program.to_account_info(),
TransferChecked {
from: self.user_token_b_account.to_account_info(),
to: self.token_b_vault.to_account_info(),
authority: self.owner.to_account_info(),
mint: self.token_b_mint.to_account_info(),
},
)
}
}