use crate::curve::calculator::CurveCalculator;
use crate::curve::TradeDirection;
use crate::error::ErrorCode;
use crate::states::*;
use crate::utils::token::*;
use anchor_lang::prelude::*;
use anchor_lang::solana_program;
use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
#[derive(Accounts)]
pub struct Swap<'info> {
pub payer: Signer<'info>,
#[account(
seeds = [
crate::AUTH_SEED.as_bytes(),
],
bump,
)]
pub authority: UncheckedAccount<'info>,
#[account(address = pool_state.load()?.amm_config)]
pub amm_config: Box<Account<'info, AmmConfig>>,
#[account(mut)]
pub pool_state: AccountLoader<'info, PoolState>,
#[account(mut)]
pub input_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
#[account(mut)]
pub output_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
#[account(
mut,
constraint = input_vault.key() == pool_state.load()?.token_0_vault || input_vault.key() == pool_state.load()?.token_1_vault
)]
pub input_vault: Box<InterfaceAccount<'info, TokenAccount>>,
#[account(
mut,
constraint = output_vault.key() == pool_state.load()?.token_0_vault || output_vault.key() == pool_state.load()?.token_1_vault
)]
pub output_vault: Box<InterfaceAccount<'info, TokenAccount>>,
pub input_token_program: Interface<'info, TokenInterface>,
pub output_token_program: Interface<'info, TokenInterface>,
#[account(
address = input_vault.mint
)]
pub input_token_mint: Box<InterfaceAccount<'info, Mint>>,
#[account(
address = output_vault.mint
)]
pub output_token_mint: Box<InterfaceAccount<'info, Mint>>,
#[account(mut, address = pool_state.load()?.observation_key)]
pub observation_state: AccountLoader<'info, ObservationState>,
}
pub fn swap_base_input(ctx: Context<Swap>, amount_in: u64, minimum_amount_out: u64) -> Result<()> {
let block_timestamp = solana_program::clock::Clock::get()?.unix_timestamp as u64;
let pool_id = ctx.accounts.pool_state.key();
let pool_state = &mut ctx.accounts.pool_state.load_mut()?;
#[cfg(not(feature = "local-testing"))]
if !pool_state.get_status_by_bit(PoolStatusBitIndex::Swap)
|| block_timestamp < pool_state.open_time
{
return err!(ErrorCode::NotApproved);
}
let transfer_fee =
get_transfer_fee(&ctx.accounts.input_token_mint.to_account_info(), amount_in)?;
let actual_amount_in = amount_in.saturating_sub(transfer_fee);
require_gt!(actual_amount_in, 0);
let (trade_direction, total_input_token_amount, total_output_token_amount) =
if ctx.accounts.input_vault.key() == pool_state.token_0_vault
&& ctx.accounts.output_vault.key() == pool_state.token_1_vault
{
let (total_input_token_amount, total_output_token_amount) = pool_state
.vault_amount_without_fee(
ctx.accounts.input_vault.amount,
ctx.accounts.output_vault.amount,
);
(
TradeDirection::ZeroForOne,
total_input_token_amount,
total_output_token_amount,
)
} else if ctx.accounts.input_vault.key() == pool_state.token_1_vault
&& ctx.accounts.output_vault.key() == pool_state.token_0_vault
{
let (total_output_token_amount, total_input_token_amount) = pool_state
.vault_amount_without_fee(
ctx.accounts.output_vault.amount,
ctx.accounts.input_vault.amount,
);
(
TradeDirection::OneForZero,
total_input_token_amount,
total_output_token_amount,
)
} else {
return err!(ErrorCode::InvalidVault);
};
let constant_before = u128::from(total_input_token_amount)
.checked_mul(u128::from(total_output_token_amount))
.unwrap();
let (total_fee, protocol_fee, creator_fee) = match trade_direction {
TradeDirection::ZeroForOne => (
ctx.accounts.amm_config.token_0_lp_rate,
ctx.accounts.amm_config.protocol_fee,
ctx.accounts.amm_config.creator_fee,
),
TradeDirection::OneForZero => (
ctx.accounts.amm_config.token_1_lp_rate,
ctx.accounts.amm_config.protocol_fee,
ctx.accounts.amm_config.creator_fee,
),
};
let result = CurveCalculator::swap_base_input(
u128::from(actual_amount_in),
u128::from(total_input_token_amount),
u128::from(total_output_token_amount),
total_fee as u128,
protocol_fee,
creator_fee
)
.ok_or(ErrorCode::ZeroTradingTokens)?;
let constant_after = u128::from(result.new_swap_source_amount)
.checked_mul(u128::from(result.new_swap_destination_amount))
.unwrap();
#[cfg(feature = "enable-log")]
msg!(
"source_amount_swapped:{}, destination_amount_swapped:{},constant_before:{},constant_after:{}",
result.source_amount_swapped,
result.destination_amount_swapped,
constant_before,
constant_after
);
require_gte!(constant_after, constant_before);
require_eq!(
u64::try_from(result.source_amount_swapped).unwrap(),
actual_amount_in
);
let (input_transfer_amount, input_transfer_fee) = (amount_in, transfer_fee);
let (output_transfer_amount, output_transfer_fee) = {
let amount_out = u64::try_from(result.destination_amount_swapped).unwrap();
let transfer_fee = get_transfer_fee(
&ctx.accounts.output_token_mint.to_account_info(),
amount_out,
)?;
let amount_received = amount_out.checked_sub(transfer_fee).unwrap();
require_gt!(amount_received, 0);
require_gte!(
amount_received,
minimum_amount_out,
ErrorCode::ExceededSlippage
);
(amount_out, transfer_fee)
};
let protocol_fee = u64::try_from(result.protocol_fee).unwrap();
let fund_fee = u64::try_from(result.creator_fee).unwrap();
match trade_direction {
TradeDirection::ZeroForOne => {
pool_state.protocol_fees_token_0 = pool_state
.protocol_fees_token_0
.checked_add(protocol_fee)
.unwrap();
pool_state.fund_fees_token_0 =
pool_state.fund_fees_token_0.checked_add(fund_fee).unwrap();
}
TradeDirection::OneForZero => {
pool_state.protocol_fees_token_1 = pool_state
.protocol_fees_token_1
.checked_add(protocol_fee)
.unwrap();
pool_state.fund_fees_token_1 =
pool_state.fund_fees_token_1.checked_add(fund_fee).unwrap();
}
};
emit!(SwapEvent {
pool_id,
input_vault_before: total_input_token_amount,
output_vault_before: total_output_token_amount,
input_amount: u64::try_from(result.source_amount_swapped).unwrap(),
output_amount: u64::try_from(result.destination_amount_swapped).unwrap(),
input_transfer_fee,
output_transfer_fee,
base_input: true
});
transfer_from_user_to_pool_vault(
ctx.accounts.payer.to_account_info(),
ctx.accounts.input_token_account.to_account_info(),
ctx.accounts.input_vault.to_account_info(),
ctx.accounts.input_token_mint.to_account_info(),
ctx.accounts.input_token_program.to_account_info(),
input_transfer_amount,
ctx.accounts.input_token_mint.decimals,
)?;
transfer_from_pool_vault_to_user(
ctx.accounts.authority.to_account_info(),
ctx.accounts.output_vault.to_account_info(),
ctx.accounts.output_token_account.to_account_info(),
ctx.accounts.output_token_mint.to_account_info(),
ctx.accounts.output_token_program.to_account_info(),
output_transfer_amount,
ctx.accounts.output_token_mint.decimals,
&[&[crate::AUTH_SEED.as_bytes(), &[pool_state.auth_bump]]],
)?;
ctx.accounts.input_vault.reload()?;
ctx.accounts.output_vault.reload()?;
let (token_0_price_x64, token_1_price_x64) = if ctx.accounts.input_vault.key()
== pool_state.token_0_vault
&& ctx.accounts.output_vault.key() == pool_state.token_1_vault
{
pool_state.token_price_x32(
ctx.accounts.input_vault.amount,
ctx.accounts.output_vault.amount,
)
} else if ctx.accounts.input_vault.key() == pool_state.token_1_vault
&& ctx.accounts.output_vault.key() == pool_state.token_0_vault
{
pool_state.token_price_x32(
ctx.accounts.output_vault.amount,
ctx.accounts.input_vault.amount,
)
} else {
return err!(ErrorCode::InvalidVault);
};
ctx.accounts.observation_state.load_mut()?.update(
oracle::block_timestamp(),
token_0_price_x64,
token_1_price_x64,
);
pool_state.recent_epoch = Clock::get()?.epoch;
Ok(())
}