use crate::{
constants::PRECISION,
errors::DloomError,
amm::{state::{AmmPool, AmmPosition}},
events::AmmFeesClaimed,
};
use anchor_lang::prelude::*;
use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked};
pub fn handle_claim_lp_fees(ctx: Context<ClaimLpFees>) -> Result<()> {
let amm_pool = &ctx.accounts.amm_pool;
let position = &mut ctx.accounts.amm_position;
let fee_growth_a = amm_pool.fee_growth_per_lp_token_a
.checked_sub(position.fee_growth_snapshot_a)
.unwrap_or(0);
let fee_growth_b = amm_pool.fee_growth_per_lp_token_b
.checked_sub(position.fee_growth_snapshot_b)
.unwrap_or(0);
let fees_to_claim_a = fee_growth_a
.checked_mul(position.lp_token_amount as u128)
.unwrap_or(0)
.checked_div(PRECISION)
.unwrap_or(0) as u64;
let fees_to_claim_b = fee_growth_b
.checked_mul(position.lp_token_amount as u128)
.unwrap_or(0)
.checked_div(PRECISION)
.unwrap_or(0) as u64;
position.fee_growth_snapshot_a = amm_pool.fee_growth_per_lp_token_a;
position.fee_growth_snapshot_b = amm_pool.fee_growth_per_lp_token_b;
let bump = &[ctx.accounts.amm_pool.bump][..];
let signer_seeds = &[
b"amm_pool",
ctx.accounts.amm_pool.token_a_mint.as_ref(),
ctx.accounts.amm_pool.token_b_mint.as_ref(),
bump,
][..];
if fees_to_claim_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.amm_pool.to_account_info(),
mint: ctx.accounts.token_a_mint.to_account_info(),
},
&[signer_seeds],
),
fees_to_claim_a,
ctx.accounts.token_a_mint.decimals,
)?;
}
if fees_to_claim_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.amm_pool.to_account_info(),
mint: ctx.accounts.token_b_mint.to_account_info(),
},
&[signer_seeds],
),
fees_to_claim_b,
ctx.accounts.token_b_mint.decimals,
)?;
}
let amm_pool_mut = &mut ctx.accounts.amm_pool;
if fees_to_claim_a > 0 {
amm_pool_mut.reserves_a = amm_pool_mut.reserves_a.checked_sub(fees_to_claim_a).ok_or(DloomError::MathOverflow)?;
}
if fees_to_claim_b > 0 {
amm_pool_mut.reserves_b = amm_pool_mut.reserves_b.checked_sub(fees_to_claim_b).ok_or(DloomError::MathOverflow)?;
}
emit!(AmmFeesClaimed {
pool_address: ctx.accounts.amm_pool.key(),
user: ctx.accounts.owner.key(),
fees_claimed_a: fees_to_claim_a,
fees_claimed_b: fees_to_claim_b,
});
Ok(())
}
#[derive(Accounts)]
pub struct ClaimLpFees<'info> {
#[account(mut)]
pub owner: Signer<'info>,
#[account(
mut, // The pool needs to be mutable to update reserves when fees are taken out
seeds = [b"amm_pool", amm_pool.token_a_mint.as_ref(), amm_pool.token_b_mint.as_ref()],
bump = amm_pool.bump,
)]
pub amm_pool: Box<Account<'info, AmmPool>>,
#[account(
mut,
has_one = owner,
constraint = amm_position.pool == amm_pool.key() @ DloomError::InvalidPool,
)]
pub amm_position: Box<Account<'info, AmmPosition>>,
#[account(address = amm_pool.lp_mint)]
pub lp_mint: InterfaceAccount<'info, Mint>,
#[account(address = amm_pool.token_a_mint)]
pub token_a_mint: InterfaceAccount<'info, Mint>,
#[account(address = amm_pool.token_b_mint)]
pub token_b_mint: InterfaceAccount<'info, Mint>,
#[account(mut, address = amm_pool.token_a_vault)]
pub token_a_vault: InterfaceAccount<'info, TokenAccount>,
#[account(mut, address = amm_pool.token_b_vault)]
pub token_b_vault: InterfaceAccount<'info, TokenAccount>,
#[account(mut, has_one = owner)]
pub user_token_a_account: InterfaceAccount<'info, TokenAccount>,
#[account(mut, has_one = owner)]
pub user_token_b_account: InterfaceAccount<'info, TokenAccount>,
pub token_a_program: Interface<'info, TokenInterface>,
pub token_b_program: Interface<'info, TokenInterface>,
}