use crate::{
constants::*,
dlmm::{math, state::DlmmPool},
errors::DloomError,
events::DlmmSwapResult, state::TransactionBins,
};
use anchor_lang::prelude::*;
use anchor_lang::AccountDeserialize;
use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked};
pub fn handle_dlmm_swap<'info>(
ctx: Context<'_, '_, 'info, 'info, DlmmSwap<'info>>,
amount_in: u64,
min_amount_out: u64,
) -> Result<()> {
if let Some(referrer_account_info) = &ctx.accounts.referrer_fee_account {
let data = referrer_account_info.try_borrow_data()?;
let referrer_token_account = TokenAccount::try_deserialize(&mut &data[..])?;
require!(
referrer_token_account.mint == ctx.accounts.user_source_token_account.mint,
DloomError::InvalidMint
);
}
let dlmm_pool = &ctx.accounts.dlmm_pool;
let is_a_to_b = ctx.accounts.user_source_token_account.mint == dlmm_pool.token_a_mint;
let initial_active_bin_id = dlmm_pool.active_bin_id;
let (source_token_program, destination_token_program) = if is_a_to_b {
(
ctx.accounts.token_a_program.to_account_info(),
ctx.accounts.token_b_program.to_account_info(),
)
} else {
(
ctx.accounts.token_b_program.to_account_info(),
ctx.accounts.token_a_program.to_account_info(),
)
};
let (amount_out, protocol_fee, final_active_bin_id) = if is_a_to_b {
math::swap_a_to_b(
&ctx.accounts.dlmm_pool, amount_in,
&ctx.accounts.transaction_bins,
ctx.remaining_accounts,
ctx.program_id,
&ctx.accounts.dlmm_pool.key(), )?
} else {
math::swap_b_to_a(
&ctx.accounts.dlmm_pool, amount_in,
&ctx.accounts.transaction_bins,
ctx.remaining_accounts,
ctx.program_id,
&ctx.accounts.dlmm_pool.key(), )?
};
require!(amount_out >= min_amount_out, DloomError::SlippageExceeded);
let source_mint_decimals = if is_a_to_b {
ctx.accounts.token_a_mint.decimals
} else {
ctx.accounts.token_b_mint.decimals
};
let (dest_vault, source_mint_info) = if is_a_to_b {
(
ctx.accounts.token_a_vault.to_account_info(),
ctx.accounts.token_a_mint.to_account_info(),
)
} else {
(
ctx.accounts.token_b_vault.to_account_info(),
ctx.accounts.token_b_mint.to_account_info(),
)
};
token_interface::transfer_checked(
CpiContext::new(
source_token_program.clone(),
TransferChecked {
from: ctx.accounts.user_source_token_account.to_account_info(),
to: dest_vault,
authority: ctx.accounts.owner.to_account_info(),
mint: source_mint_info,
},
),
amount_in,
source_mint_decimals,
)?;
let bin_step_bytes = &dlmm_pool.bin_step.to_le_bytes()[..];
let bump = &[dlmm_pool.bump][..];
let signer_seeds = &[
b"dlmm_pool",
dlmm_pool.token_a_mint.as_ref(),
dlmm_pool.token_b_mint.as_ref(),
bin_step_bytes,
bump,
][..];
let mut actual_protocol_fee = protocol_fee;
if protocol_fee > 0 && dlmm_pool.referrer_fee_share > 0 {
if let Some(referrer_account) = &ctx.accounts.referrer_fee_account {
let referral_fee = (protocol_fee as u128)
.checked_mul(dlmm_pool.referrer_fee_share as u128)
.ok_or(DloomError::MathOverflow)?
.checked_div(BASIS_POINT_MAX as u128)
.ok_or(DloomError::MathOverflow)? as u64;
if referral_fee > 0 {
actual_protocol_fee = protocol_fee
.checked_sub(referral_fee)
.ok_or(DloomError::MathOverflow)?;
let (fee_source_vault, fee_mint) = if is_a_to_b {
(
ctx.accounts.token_a_vault.to_account_info(),
ctx.accounts.token_a_mint.to_account_info(),
)
} else {
(
ctx.accounts.token_b_vault.to_account_info(),
ctx.accounts.token_b_mint.to_account_info(),
)
};
token_interface::transfer_checked(
CpiContext::new_with_signer(
source_token_program.clone(),
TransferChecked {
from: fee_source_vault,
to: referrer_account.to_account_info(),
authority: dlmm_pool.to_account_info(),
mint: fee_mint,
},
&[signer_seeds],
),
referral_fee,
source_mint_decimals,
)?;
}
}
}
if actual_protocol_fee > 0 {
let (source_vault, fee_vault, mint) = if is_a_to_b {
(
ctx.accounts.token_a_vault.to_account_info(),
ctx.accounts.protocol_fee_vault_a.to_account_info(),
ctx.accounts.token_a_mint.to_account_info(),
)
} else {
(
ctx.accounts.token_b_vault.to_account_info(),
ctx.accounts.protocol_fee_vault_b.to_account_info(),
ctx.accounts.token_b_mint.to_account_info(),
)
};
token_interface::transfer_checked(
CpiContext::new_with_signer(
source_token_program.clone(),
TransferChecked {
from: source_vault,
to: fee_vault,
authority: dlmm_pool.to_account_info(),
mint,
},
&[signer_seeds],
),
actual_protocol_fee,
source_mint_decimals,
)?;
}
if amount_out > 0 {
let (source_vault, mint, dest_mint_decimals) = if is_a_to_b {
(
ctx.accounts.token_b_vault.to_account_info(),
ctx.accounts.token_b_mint.to_account_info(),
ctx.accounts.token_b_mint.decimals,
)
} else {
(
ctx.accounts.token_a_vault.to_account_info(),
ctx.accounts.token_a_mint.to_account_info(),
ctx.accounts.token_a_mint.decimals,
)
};
token_interface::transfer_checked(
CpiContext::new_with_signer(
destination_token_program.clone(),
TransferChecked {
from: source_vault,
to: ctx
.accounts
.user_destination_token_account
.to_account_info(),
authority: dlmm_pool.to_account_info(),
mint,
},
&[signer_seeds],
),
amount_out,
dest_mint_decimals,
)?;
}
let dlmm_pool_mut = &mut ctx.accounts.dlmm_pool;
let bins_crossed = (final_active_bin_id - initial_active_bin_id).abs();
dlmm_pool_mut.active_bin_id = final_active_bin_id;
dlmm_pool_mut.volatility_accumulator = dlmm_pool_mut
.volatility_accumulator
.checked_add(bins_crossed as u64)
.ok_or(DloomError::MathOverflow)?;
let amount_for_lps = amount_in
.checked_sub(protocol_fee)
.ok_or(DloomError::MathOverflow)?;
if is_a_to_b {
dlmm_pool_mut.reserves_a = dlmm_pool_mut
.reserves_a
.checked_add(amount_for_lps)
.ok_or(DloomError::MathOverflow)?;
dlmm_pool_mut.reserves_b = dlmm_pool_mut
.reserves_b
.checked_sub(amount_out)
.ok_or(DloomError::MathOverflow)?;
} else {
dlmm_pool_mut.reserves_b = dlmm_pool_mut
.reserves_b
.checked_add(amount_for_lps)
.ok_or(DloomError::MathOverflow)?;
dlmm_pool_mut.reserves_a = dlmm_pool_mut
.reserves_a
.checked_sub(amount_out)
.ok_or(DloomError::MathOverflow)?;
}
emit!(DlmmSwapResult {
pool_address: ctx.accounts.dlmm_pool.key(),
trader: ctx.accounts.owner.key(),
input_mint: ctx.accounts.user_source_token_account.mint,
output_mint: ctx.accounts.user_destination_token_account.mint,
amount_in,
amount_out,
protocol_fee: actual_protocol_fee, final_active_bin_id,
referrer: ctx
.accounts
.referrer_fee_account
.as_ref()
.map(|acc| acc.key()),
});
Ok(())
}
#[derive(Accounts)]
pub struct DlmmSwap<'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,
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, has_one = owner)]
pub user_source_token_account: InterfaceAccount<'info, TokenAccount>,
#[account(mut, has_one = owner)]
pub user_destination_token_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>,
#[account(mut, address = dlmm_pool.protocol_fee_vault_a)]
pub protocol_fee_vault_a: InterfaceAccount<'info, TokenAccount>,
#[account(mut, address = dlmm_pool.protocol_fee_vault_b)]
pub protocol_fee_vault_b: InterfaceAccount<'info, TokenAccount>,
#[account(mut)]
pub referrer_fee_account: Option<AccountInfo<'info>>,
pub token_a_program: Interface<'info, TokenInterface>,
pub token_b_program: Interface<'info, TokenInterface>,
}