use crate::{
amm::{
math,
state::AmmPool,
},
constants::*,
errors::DloomError,
events::AmmSwap,
};
use anchor_lang::prelude::*;
use anchor_lang::AccountDeserialize;
use anchor_spl::token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked};
pub(crate) fn update_oracle(pool: &mut Account<AmmPool>) -> Result<()> {
let now = Clock::get()?.unix_timestamp;
if pool.last_update_timestamp == 0 {
pool.last_update_timestamp = now;
return Ok(());
}
let time_elapsed = now
.checked_sub(pool.last_update_timestamp)
.ok_or(DloomError::MathOverflow)?;
if time_elapsed > 0 && pool.reserves_a > 0 && pool.reserves_b > 0 {
let price_a = (pool.reserves_b as u128)
.checked_mul(1_000_000_000) .ok_or(DloomError::MathOverflow)?
.checked_div(pool.reserves_a as u128)
.ok_or(DloomError::MathOverflow)?;
pool.price_a_cumulative = pool
.price_a_cumulative
.checked_add(
price_a
.checked_mul(time_elapsed as u128)
.ok_or(DloomError::MathOverflow)?,
)
.ok_or(DloomError::MathOverflow)?;
let price_b = (pool.reserves_a as u128)
.checked_mul(1_000_000_000) .ok_or(DloomError::MathOverflow)?
.checked_div(pool.reserves_b as u128)
.ok_or(DloomError::MathOverflow)?;
pool.price_b_cumulative = pool
.price_b_cumulative
.checked_add(
price_b
.checked_mul(time_elapsed as u128)
.ok_or(DloomError::MathOverflow)?,
)
.ok_or(DloomError::MathOverflow)?;
}
pool.last_update_timestamp = now;
Ok(())
}
pub fn handle_swap_on_amm(
ctx: Context<SwapOnAmm>,
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
);
}
update_oracle(&mut ctx.accounts.amm_pool)?;
let amm_pool = &ctx.accounts.amm_pool;
let is_a_to_b = ctx.accounts.user_source_token_account.mint == amm_pool.token_a_mint;
let (source_reserves, destination_reserves, source_mint_decimals, destination_mint_decimals) =
if is_a_to_b {
(
amm_pool.reserves_a,
amm_pool.reserves_b,
ctx.accounts.token_a_mint.decimals,
ctx.accounts.token_b_mint.decimals,
)
} else {
require_keys_eq!(
ctx.accounts.user_source_token_account.mint,
amm_pool.token_b_mint,
DloomError::InvalidMint
);
(
amm_pool.reserves_b,
amm_pool.reserves_a,
ctx.accounts.token_b_mint.decimals,
ctx.accounts.token_a_mint.decimals,
)
};
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, lp_fee) = math::calculate_swap_out_amount(
amm_pool,
amount_in,
source_reserves,
destination_reserves,
)?;
require!(amount_out >= min_amount_out, DloomError::SlippageExceeded);
let (source_vault_info, 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: source_vault_info,
authority: ctx.accounts.trader.to_account_info(),
mint: source_mint_info,
},
),
amount_in,
source_mint_decimals,
)?;
let bump = &[amm_pool.bump][..];
let signer_seeds = &[
b"amm_pool",
amm_pool.token_a_mint.as_ref(),
amm_pool.token_b_mint.as_ref(),
bump,
][..];
let mut actual_protocol_fee = protocol_fee;
if protocol_fee > 0 && amm_pool.referrer_fee_share > 0 {
if let Some(referrer_account) = &ctx.accounts.referrer_fee_account {
let referral_fee = (protocol_fee as u128)
.checked_mul(amm_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: amm_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: amm_pool.to_account_info(),
mint,
},
&[signer_seeds],
),
actual_protocol_fee,
source_mint_decimals,
)?;
}
if amount_out > 0 {
let (dest_vault, dest_mint) = if is_a_to_b {
(
ctx.accounts.token_b_vault.to_account_info(),
ctx.accounts.token_b_mint.to_account_info(),
)
} else {
(
ctx.accounts.token_a_vault.to_account_info(),
ctx.accounts.token_a_mint.to_account_info(),
)
};
token_interface::transfer_checked(
CpiContext::new_with_signer(
destination_token_program.clone(),
TransferChecked {
from: dest_vault,
to: ctx
.accounts
.user_destination_token_account
.to_account_info(),
authority: amm_pool.to_account_info(),
mint: dest_mint,
},
&[signer_seeds],
),
amount_out,
destination_mint_decimals,
)?;
}
let amm_pool_mut = &mut ctx.accounts.amm_pool;
let amount_added_to_lp_reserves = amount_in
.checked_sub(protocol_fee)
.ok_or(DloomError::MathOverflow)?;
if is_a_to_b {
amm_pool_mut.reserves_a = source_reserves
.checked_add(amount_added_to_lp_reserves)
.ok_or(DloomError::MathOverflow)?;
amm_pool_mut.reserves_b = destination_reserves
.checked_sub(amount_out)
.ok_or(DloomError::MathOverflow)?;
} else {
amm_pool_mut.reserves_b = source_reserves
.checked_add(amount_added_to_lp_reserves)
.ok_or(DloomError::MathOverflow)?;
amm_pool_mut.reserves_a = destination_reserves
.checked_sub(amount_out)
.ok_or(DloomError::MathOverflow)?;
}
let lp_mint_supply = ctx.accounts.lp_mint.supply;
if lp_mint_supply > 0 && lp_fee > 0 {
let fee_growth_update = (lp_fee as u128)
.checked_mul(PRECISION) .ok_or(DloomError::MathOverflow)?
.checked_div(lp_mint_supply as u128)
.ok_or(DloomError::MathOverflow)?;
if is_a_to_b {
amm_pool_mut.fee_growth_per_lp_token_a = amm_pool_mut
.fee_growth_per_lp_token_a
.checked_add(fee_growth_update)
.ok_or(DloomError::MathOverflow)?;
} else {
amm_pool_mut.fee_growth_per_lp_token_b = amm_pool_mut
.fee_growth_per_lp_token_b
.checked_add(fee_growth_update)
.ok_or(DloomError::MathOverflow)?;
}
}
emit!(AmmSwap {
pool_address: ctx.accounts.amm_pool.key(),
trader: ctx.accounts.trader.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, lp_fee,
referrer: ctx
.accounts
.referrer_fee_account
.as_ref()
.map(|acc| acc.key()),
});
Ok(())
}
#[derive(Accounts)]
pub struct SwapOnAmm<'info> {
#[account(mut)]
pub trader: Signer<'info>,
#[account(
mut,
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, 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)]
pub user_source_token_account: InterfaceAccount<'info, TokenAccount>,
#[account(mut)]
pub user_destination_token_account: InterfaceAccount<'info, TokenAccount>,
#[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, address = amm_pool.protocol_fee_vault_a)]
pub protocol_fee_vault_a: InterfaceAccount<'info, TokenAccount>,
#[account(mut, address = amm_pool.protocol_fee_vault_b)]
pub protocol_fee_vault_b: InterfaceAccount<'info, TokenAccount>,
#[account(address = amm_pool.authority)]
pub authority: AccountInfo<'info>,
#[account(mut)]
pub referrer_fee_account: Option<AccountInfo<'info>>,
pub token_a_program: Interface<'info, TokenInterface>,
pub token_b_program: Interface<'info, TokenInterface>,
}