use {
crate::{
constraints::{SwapConstraints, SWAP_CONSTRAINTS},
curve::{
base::SwapCurve,
calculator::{RoundDirection, TradeDirection},
fees::Fees,
},
error::SwapError,
instruction::{
DepositAllTokenTypes, DepositSingleTokenTypeExactAmountIn, Initialize, Swap,
SwapInstruction, WithdrawAllTokenTypes, WithdrawSingleTokenTypeExactAmountOut,
},
state::{SwapState, SwapV1, SwapVersion},
},
num_traits::FromPrimitive,
solana_program::{
account_info::{next_account_info, AccountInfo},
clock::Clock,
decode_error::DecodeError,
entrypoint::ProgramResult,
instruction::{AccountMeta, Instruction},
msg,
program::invoke_signed,
program_error::{PrintProgramError, ProgramError},
program_option::COption,
pubkey::Pubkey,
sysvar::Sysvar,
},
spl_token_2022::{
check_spl_token_program_account,
error::TokenError,
extension::{
mint_close_authority::MintCloseAuthority, transfer_fee::TransferFeeConfig,
BaseStateWithExtensions, StateWithExtensions,
},
state::{Account, Mint},
},
std::{convert::TryInto, error::Error, ops::Deref},
};
pub struct Processor {}
impl Processor {
pub fn unpack_token_account(
account_info: &AccountInfo,
token_program_id: &Pubkey,
) -> Result<Account, SwapError> {
if account_info.owner != token_program_id
&& check_spl_token_program_account(account_info.owner).is_err()
{
Err(SwapError::IncorrectTokenProgramId)
} else {
StateWithExtensions::<Account>::unpack(&account_info.data.borrow())
.map(|a| a.base)
.map_err(|_| SwapError::ExpectedAccount)
}
}
pub fn unpack_mint(
account_info: &AccountInfo,
token_program_id: &Pubkey,
) -> Result<Mint, SwapError> {
if account_info.owner != token_program_id
&& check_spl_token_program_account(account_info.owner).is_err()
{
Err(SwapError::IncorrectTokenProgramId)
} else {
StateWithExtensions::<Mint>::unpack(&account_info.data.borrow())
.map(|m| m.base)
.map_err(|_| SwapError::ExpectedMint)
}
}
pub fn unpack_mint_with_extensions<'a>(
account_data: &'a [u8],
owner: &Pubkey,
token_program_id: &Pubkey,
) -> Result<StateWithExtensions<'a, Mint>, SwapError> {
if owner != token_program_id && check_spl_token_program_account(owner).is_err() {
Err(SwapError::IncorrectTokenProgramId)
} else {
StateWithExtensions::<Mint>::unpack(account_data).map_err(|_| SwapError::ExpectedMint)
}
}
pub fn authority_id(
program_id: &Pubkey,
my_info: &Pubkey,
bump_seed: u8,
) -> Result<Pubkey, SwapError> {
Pubkey::create_program_address(&[&my_info.to_bytes()[..32], &[bump_seed]], program_id)
.or(Err(SwapError::InvalidProgramAddress))
}
pub fn token_burn<'a>(
swap: &Pubkey,
token_program: AccountInfo<'a>,
burn_account: AccountInfo<'a>,
mint: AccountInfo<'a>,
authority: AccountInfo<'a>,
bump_seed: u8,
amount: u64,
) -> Result<(), ProgramError> {
let swap_bytes = swap.to_bytes();
let authority_signature_seeds = [&swap_bytes[..32], &[bump_seed]];
let signers = &[&authority_signature_seeds[..]];
let ix = spl_token_2022::instruction::burn(
token_program.key,
burn_account.key,
mint.key,
authority.key,
&[],
amount,
)?;
invoke_signed_wrapper::<TokenError>(
&ix,
&[burn_account, mint, authority, token_program],
signers,
)
}
pub fn token_mint_to<'a>(
swap: &Pubkey,
token_program: AccountInfo<'a>,
mint: AccountInfo<'a>,
destination: AccountInfo<'a>,
authority: AccountInfo<'a>,
bump_seed: u8,
amount: u64,
) -> Result<(), ProgramError> {
let swap_bytes = swap.to_bytes();
let authority_signature_seeds = [&swap_bytes[..32], &[bump_seed]];
let signers = &[&authority_signature_seeds[..]];
let ix = spl_token_2022::instruction::mint_to(
token_program.key,
mint.key,
destination.key,
authority.key,
&[],
amount,
)?;
invoke_signed_wrapper::<TokenError>(
&ix,
&[mint, destination, authority, token_program],
signers,
)
}
#[allow(clippy::too_many_arguments)]
pub fn token_transfer<'a>(
swap: &Pubkey,
token_program: AccountInfo<'a>,
source: AccountInfo<'a>,
mint: AccountInfo<'a>,
destination: AccountInfo<'a>,
authority: AccountInfo<'a>,
bump_seed: u8,
amount: u64,
decimals: u8,
extra_accounts: Option<&[AccountInfo<'a>]>,
) -> Result<(), ProgramError> {
let swap_bytes = swap.to_bytes();
let authority_signature_seeds = [&swap_bytes[..32], &[bump_seed]];
let signers = &[&authority_signature_seeds[..]];
let mut ix: Instruction = spl_token_2022::instruction::transfer_checked(
token_program.key,
source.key,
mint.key,
destination.key,
authority.key,
&[],
amount,
decimals,
)?;
let mut account_infos: Vec<AccountInfo<'_>> = Vec::new();
account_infos.extend([source, mint, destination, authority, token_program]);
if let Some(extra_accounts) = extra_accounts {
let mut i = 0;
extra_accounts.iter().for_each(|v| {
account_infos.push(v.clone());
if i == 0 {
ix.accounts.push(AccountMeta::new(*v.key, false));
} else {
ix.accounts.push(AccountMeta::new_readonly(*v.key, false));
}
i += 1;
});
}
invoke_signed_wrapper::<TokenError>(&ix, &account_infos[..], signers)
}
#[allow(clippy::too_many_arguments)]
fn check_accounts(
token_swap: &dyn SwapState,
program_id: &Pubkey,
swap_account_info: &AccountInfo,
authority_info: &AccountInfo,
token_a_info: &AccountInfo,
token_b_info: &AccountInfo,
pool_mint_info: &AccountInfo,
pool_token_program_info: &AccountInfo,
user_token_a_info: Option<&AccountInfo>,
user_token_b_info: Option<&AccountInfo>,
pool_fee_account_info: Option<&AccountInfo>,
) -> ProgramResult {
if swap_account_info.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}
if *authority_info.key
!= Self::authority_id(program_id, swap_account_info.key, token_swap.bump_seed())?
{
return Err(SwapError::InvalidProgramAddress.into());
}
if *token_a_info.key != *token_swap.token_a_account() {
return Err(SwapError::IncorrectSwapAccount.into());
}
if *token_b_info.key != *token_swap.token_b_account() {
return Err(SwapError::IncorrectSwapAccount.into());
}
if *pool_mint_info.key != *token_swap.pool_mint() {
return Err(SwapError::IncorrectPoolMint.into());
}
if *pool_token_program_info.key != *token_swap.token_program_id() {
return Err(SwapError::IncorrectTokenProgramId.into());
}
if let Some(user_token_a_info) = user_token_a_info {
if token_a_info.key == user_token_a_info.key {
return Err(SwapError::InvalidInput.into());
}
}
if let Some(user_token_b_info) = user_token_b_info {
if token_b_info.key == user_token_b_info.key {
return Err(SwapError::InvalidInput.into());
}
}
if let Some(pool_fee_account_info) = pool_fee_account_info {
if *pool_fee_account_info.key != *token_swap.pool_fee_account() {
return Err(SwapError::IncorrectFeeAccount.into());
}
}
Ok(())
}
pub fn process_initialize(
program_id: &Pubkey,
fees: Fees,
swap_curve: SwapCurve,
accounts: &[AccountInfo],
swap_constraints: &Option<SwapConstraints>,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let swap_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let token_a_info = next_account_info(account_info_iter)?;
let token_b_info = next_account_info(account_info_iter)?;
let pool_mint_info = next_account_info(account_info_iter)?;
let fee_account_info = next_account_info(account_info_iter)?;
let destination_info = next_account_info(account_info_iter)?;
let pool_token_program_info = next_account_info(account_info_iter)?;
let token_program_id = *pool_token_program_info.key;
if SwapVersion::is_initialized(&swap_info.data.borrow()) {
return Err(SwapError::AlreadyInUse.into());
}
let (swap_authority, bump_seed) =
Pubkey::find_program_address(&[&swap_info.key.to_bytes()], program_id);
if *authority_info.key != swap_authority {
return Err(SwapError::InvalidProgramAddress.into());
}
let token_a = Self::unpack_token_account(token_a_info, &token_program_id)?;
let token_b = Self::unpack_token_account(token_b_info, &token_program_id)?;
let fee_account = Self::unpack_token_account(fee_account_info, &token_program_id)?;
let destination = Self::unpack_token_account(destination_info, &token_program_id)?;
let pool_mint = {
let pool_mint_data = pool_mint_info.data.borrow();
let pool_mint = Self::unpack_mint_with_extensions(
&pool_mint_data,
pool_mint_info.owner,
&token_program_id,
)?;
if let Ok(extension) = pool_mint.get_extension::<MintCloseAuthority>() {
let close_authority: Option<Pubkey> = extension.close_authority.into();
if close_authority.is_some() {
return Err(SwapError::InvalidCloseAuthority.into());
}
}
pool_mint.base
};
if *authority_info.key != token_a.owner {
return Err(SwapError::InvalidOwner.into());
}
if *authority_info.key != token_b.owner {
return Err(SwapError::InvalidOwner.into());
}
if *authority_info.key == destination.owner {
return Err(SwapError::InvalidOutputOwner.into());
}
if *authority_info.key == fee_account.owner {
return Err(SwapError::InvalidOutputOwner.into());
}
if COption::Some(*authority_info.key) != pool_mint.mint_authority {
return Err(SwapError::InvalidOwner.into());
}
if token_a.mint == token_b.mint {
return Err(SwapError::RepeatedMint.into());
}
swap_curve
.calculator
.validate_supply(token_a.amount, token_b.amount)?;
if token_a.delegate.is_some() {
return Err(SwapError::InvalidDelegate.into());
}
if token_b.delegate.is_some() {
return Err(SwapError::InvalidDelegate.into());
}
if token_a.close_authority.is_some() {
return Err(SwapError::InvalidCloseAuthority.into());
}
if token_b.close_authority.is_some() {
return Err(SwapError::InvalidCloseAuthority.into());
}
if pool_mint.supply != 0 {
return Err(SwapError::InvalidSupply.into());
}
if pool_mint.freeze_authority.is_some() {
return Err(SwapError::InvalidFreezeAuthority.into());
}
if *pool_mint_info.key != fee_account.mint {
return Err(SwapError::IncorrectPoolMint.into());
}
if let Some(swap_constraints) = swap_constraints {
let owner_key = swap_constraints
.owner_key
.parse::<Pubkey>()
.map_err(|_| SwapError::InvalidOwner)?;
if fee_account.owner != owner_key {
return Err(SwapError::InvalidOwner.into());
}
swap_constraints.validate_curve(&swap_curve)?;
swap_constraints.validate_fees(&fees)?;
}
fees.validate()?;
swap_curve.calculator.validate()?;
let initial_amount = swap_curve.calculator.new_pool_supply();
Self::token_mint_to(
swap_info.key,
pool_token_program_info.clone(),
pool_mint_info.clone(),
destination_info.clone(),
authority_info.clone(),
bump_seed,
to_u64(initial_amount)?,
)?;
let obj = SwapVersion::SwapV1(SwapV1 {
is_initialized: true,
bump_seed,
token_program_id,
token_a: *token_a_info.key,
token_b: *token_b_info.key,
pool_mint: *pool_mint_info.key,
token_a_mint: token_a.mint,
token_b_mint: token_b.mint,
pool_fee_account: *fee_account_info.key,
fees,
swap_curve,
});
SwapVersion::pack(obj, &mut swap_info.data.borrow_mut())?;
Ok(())
}
pub fn process_swap(
program_id: &Pubkey,
amount_in: u64,
minimum_amount_out: u64,
accounts: &[AccountInfo],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let swap_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let source_info = next_account_info(account_info_iter)?;
let swap_source_info = next_account_info(account_info_iter)?;
let swap_destination_info = next_account_info(account_info_iter)?;
let destination_info = next_account_info(account_info_iter)?;
let pool_mint_info = next_account_info(account_info_iter)?;
let pool_fee_account_info = next_account_info(account_info_iter)?;
let source_token_mint_info = next_account_info(account_info_iter)?;
let destination_token_mint_info = next_account_info(account_info_iter)?;
let source_token_program_info = next_account_info(account_info_iter)?;
let destination_token_program_info = next_account_info(account_info_iter)?;
let pool_token_program_info = next_account_info(account_info_iter)?;
let mut fee_account: Option<&AccountInfo<'_>> = None;
let mut extra_accounts: Vec<AccountInfo<'_>> = vec![];
if account_info_iter.size_hint().0 == 4 {
fee_account = Some(next_account_info(account_info_iter)?);
}
if account_info_iter.size_hint().0 == 3 {
loop {
if let Ok(next_account) = next_account_info(account_info_iter) {
extra_accounts.push(next_account.clone());
} else {
break;
}
}
}
if swap_info.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}
let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?;
if *authority_info.key
!= Self::authority_id(program_id, swap_info.key, token_swap.bump_seed())?
{
return Err(SwapError::InvalidProgramAddress.into());
}
if !(*swap_source_info.key == *token_swap.token_a_account()
|| *swap_source_info.key == *token_swap.token_b_account())
{
return Err(SwapError::IncorrectSwapAccount.into());
}
if !(*swap_destination_info.key == *token_swap.token_a_account()
|| *swap_destination_info.key == *token_swap.token_b_account())
{
return Err(SwapError::IncorrectSwapAccount.into());
}
if *swap_source_info.key == *swap_destination_info.key {
return Err(SwapError::InvalidInput.into());
}
if swap_source_info.key == source_info.key {
return Err(SwapError::InvalidInput.into());
}
if swap_destination_info.key == destination_info.key {
return Err(SwapError::InvalidInput.into());
}
if *pool_mint_info.key != *token_swap.pool_mint() {
return Err(SwapError::IncorrectPoolMint.into());
}
if *pool_fee_account_info.key != *token_swap.pool_fee_account() {
return Err(SwapError::IncorrectFeeAccount.into());
}
if *pool_token_program_info.key != *token_swap.token_program_id() {
return Err(SwapError::IncorrectTokenProgramId.into());
}
let source_account =
Self::unpack_token_account(swap_source_info, token_swap.token_program_id())?;
let dest_account =
Self::unpack_token_account(swap_destination_info, token_swap.token_program_id())?;
let pool_mint = Self::unpack_mint(pool_mint_info, token_swap.token_program_id())?;
let actual_amount_in = {
let source_mint_data = source_token_mint_info.data.borrow();
let source_mint = Self::unpack_mint_with_extensions(
&source_mint_data,
source_token_mint_info.owner,
token_swap.token_program_id(),
)?;
if let Ok(transfer_fee_config) = source_mint.get_extension::<TransferFeeConfig>() {
amount_in.saturating_sub(
transfer_fee_config
.calculate_epoch_fee(Clock::get()?.epoch, amount_in)
.ok_or(SwapError::FeeCalculationFailure)?,
)
} else {
amount_in
}
};
let trade_direction = if *swap_source_info.key == *token_swap.token_a_account() {
TradeDirection::AtoB
} else {
TradeDirection::BtoA
};
let result = token_swap
.swap_curve()
.swap(
to_u128(actual_amount_in)?,
to_u128(source_account.amount)?,
to_u128(dest_account.amount)?,
trade_direction,
token_swap.fees(),
)
.ok_or(SwapError::ZeroTradingTokens)?;
let (source_transfer_amount, source_mint_decimals) = {
let source_amount_swapped = to_u64(result.source_amount_swapped)?;
let source_mint_data = source_token_mint_info.data.borrow();
let source_mint = Self::unpack_mint_with_extensions(
&source_mint_data,
source_token_mint_info.owner,
token_swap.token_program_id(),
)?;
let amount =
if let Ok(transfer_fee_config) = source_mint.get_extension::<TransferFeeConfig>() {
source_amount_swapped.saturating_add(
transfer_fee_config
.calculate_inverse_epoch_fee(Clock::get()?.epoch, source_amount_swapped)
.ok_or(SwapError::FeeCalculationFailure)?,
)
} else {
source_amount_swapped
};
(amount, source_mint.base.decimals)
};
let (destination_transfer_amount, destination_mint_decimals) = {
let destination_mint_data = destination_token_mint_info.data.borrow();
let destination_mint = Self::unpack_mint_with_extensions(
&destination_mint_data,
source_token_mint_info.owner,
token_swap.token_program_id(),
)?;
let amount_out = to_u64(result.destination_amount_swapped)?;
let amount_received = if let Ok(transfer_fee_config) =
destination_mint.get_extension::<TransferFeeConfig>()
{
amount_out.saturating_sub(
transfer_fee_config
.calculate_epoch_fee(Clock::get()?.epoch, amount_out)
.ok_or(SwapError::FeeCalculationFailure)?,
)
} else {
amount_out
};
if amount_received < minimum_amount_out {
return Err(SwapError::ExceededSlippage.into());
}
(amount_out, destination_mint.base.decimals)
};
let (swap_token_a_amount, swap_token_b_amount) = match trade_direction {
TradeDirection::AtoB => (
result.new_swap_source_amount,
result.new_swap_destination_amount,
),
TradeDirection::BtoA => (
result.new_swap_destination_amount,
result.new_swap_source_amount,
),
};
let (source_extra, dest_extra) = if !extra_accounts.len() > 0 {
if trade_direction == TradeDirection::AtoB {
(Some(extra_accounts.as_slice()), None)
} else {
(None, Some(extra_accounts.as_slice()))
}
} else {
(None, None)
};
Self::token_transfer(
swap_info.key,
source_token_program_info.clone(),
source_info.clone(),
source_token_mint_info.clone(),
swap_source_info.clone(),
user_transfer_authority_info.clone(),
token_swap.bump_seed(),
source_transfer_amount,
source_mint_decimals,
source_extra, )?;
if result.owner_fee > 0 {
let mut pool_token_amount = token_swap
.swap_curve()
.calculator
.withdraw_single_token_type_exact_out(
result.owner_fee,
swap_token_a_amount,
swap_token_b_amount,
to_u128(pool_mint.supply)?,
trade_direction,
RoundDirection::Floor,
)
.ok_or(SwapError::FeeCalculationFailure)?;
if let Some(host_fee_account_info) = fee_account {
let host_fee_account = Self::unpack_token_account(
host_fee_account_info,
token_swap.token_program_id(),
)?;
if *pool_mint_info.key != host_fee_account.mint {
return Err(SwapError::IncorrectPoolMint.into());
}
let host_fee = token_swap
.fees()
.host_fee(pool_token_amount)
.ok_or(SwapError::FeeCalculationFailure)?;
if host_fee > 0 {
pool_token_amount = pool_token_amount
.checked_sub(host_fee)
.ok_or(SwapError::FeeCalculationFailure)?;
Self::token_mint_to(
swap_info.key,
pool_token_program_info.clone(),
pool_mint_info.clone(),
host_fee_account_info.clone(),
authority_info.clone(),
token_swap.bump_seed(),
to_u64(host_fee)?,
)?;
}
}
if token_swap
.check_pool_fee_info(pool_fee_account_info)
.is_ok()
{
Self::token_mint_to(
swap_info.key,
pool_token_program_info.clone(),
pool_mint_info.clone(),
pool_fee_account_info.clone(),
authority_info.clone(),
token_swap.bump_seed(),
to_u64(pool_token_amount)?,
)?;
};
}
Self::token_transfer(
swap_info.key,
destination_token_program_info.clone(),
swap_destination_info.clone(),
destination_token_mint_info.clone(),
destination_info.clone(),
authority_info.clone(),
token_swap.bump_seed(),
destination_transfer_amount,
destination_mint_decimals,
dest_extra, )?;
Ok(())
}
pub fn process_deposit_all_token_types(
program_id: &Pubkey,
pool_token_amount: u64,
maximum_token_a_amount: u64,
maximum_token_b_amount: u64,
accounts: &[AccountInfo],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let swap_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let source_a_info = next_account_info(account_info_iter)?;
let source_b_info = next_account_info(account_info_iter)?;
let token_a_info = next_account_info(account_info_iter)?;
let token_b_info = next_account_info(account_info_iter)?;
let pool_mint_info = next_account_info(account_info_iter)?;
let dest_info = next_account_info(account_info_iter)?;
let token_a_mint_info = next_account_info(account_info_iter)?;
let token_b_mint_info = next_account_info(account_info_iter)?;
let token_a_program_info = next_account_info(account_info_iter)?;
let token_b_program_info = next_account_info(account_info_iter)?;
let pool_token_program_info = next_account_info(account_info_iter)?;
let mut extra_accounts: Vec<AccountInfo<'_>> = vec![];
if account_info_iter.size_hint().0 == 3 {
loop {
if let Ok(next_account) = next_account_info(account_info_iter) {
extra_accounts.push(next_account.clone());
} else {
break;
}
}
}
let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?;
let calculator = &token_swap.swap_curve().calculator;
if !calculator.allows_deposits() {
return Err(SwapError::UnsupportedCurveOperation.into());
}
Self::check_accounts(
token_swap.as_ref(),
program_id,
swap_info,
authority_info,
token_a_info,
token_b_info,
pool_mint_info,
pool_token_program_info,
Some(source_a_info),
Some(source_b_info),
None,
)?;
let token_a = Self::unpack_token_account(token_a_info, token_swap.token_program_id())?;
let token_b = Self::unpack_token_account(token_b_info, token_swap.token_program_id())?;
let pool_mint = Self::unpack_mint(pool_mint_info, token_swap.token_program_id())?;
let current_pool_mint_supply = to_u128(pool_mint.supply)?;
let (pool_token_amount, pool_mint_supply) = if current_pool_mint_supply > 0 {
(to_u128(pool_token_amount)?, current_pool_mint_supply)
} else {
(calculator.new_pool_supply(), calculator.new_pool_supply())
};
let results = calculator
.pool_tokens_to_trading_tokens(
pool_token_amount,
pool_mint_supply,
to_u128(token_a.amount)?,
to_u128(token_b.amount)?,
RoundDirection::Ceiling,
)
.ok_or(SwapError::ZeroTradingTokens)?;
let token_a_amount = to_u64(results.token_a_amount)?;
if token_a_amount > maximum_token_a_amount {
return Err(SwapError::ExceededSlippage.into());
}
if token_a_amount == 0 {
return Err(SwapError::ZeroTradingTokens.into());
}
let token_b_amount = to_u64(results.token_b_amount)?;
if token_b_amount > maximum_token_b_amount {
return Err(SwapError::ExceededSlippage.into());
}
if token_b_amount == 0 {
return Err(SwapError::ZeroTradingTokens.into());
}
let pool_token_amount = to_u64(pool_token_amount)?;
Self::token_transfer(
swap_info.key,
token_a_program_info.clone(),
source_a_info.clone(),
token_a_mint_info.clone(),
token_a_info.clone(),
user_transfer_authority_info.clone(),
token_swap.bump_seed(),
token_a_amount,
Self::unpack_mint(token_a_mint_info, token_swap.token_program_id())?.decimals,
Some(extra_accounts.as_slice()),
)?;
Self::token_transfer(
swap_info.key,
token_b_program_info.clone(),
source_b_info.clone(),
token_b_mint_info.clone(),
token_b_info.clone(),
user_transfer_authority_info.clone(),
token_swap.bump_seed(),
token_b_amount,
Self::unpack_mint(token_b_mint_info, token_swap.token_program_id())?.decimals,
None,
)?;
Self::token_mint_to(
swap_info.key,
pool_token_program_info.clone(),
pool_mint_info.clone(),
dest_info.clone(),
authority_info.clone(),
token_swap.bump_seed(),
pool_token_amount,
)?;
Ok(())
}
pub fn process_withdraw_all_token_types(
program_id: &Pubkey,
pool_token_amount: u64,
minimum_token_a_amount: u64,
minimum_token_b_amount: u64,
accounts: &[AccountInfo],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let swap_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let pool_mint_info = next_account_info(account_info_iter)?;
let source_info = next_account_info(account_info_iter)?;
let token_a_info = next_account_info(account_info_iter)?;
let token_b_info = next_account_info(account_info_iter)?;
let dest_token_a_info = next_account_info(account_info_iter)?;
let dest_token_b_info = next_account_info(account_info_iter)?;
let pool_fee_account_info = next_account_info(account_info_iter)?;
let token_a_mint_info = next_account_info(account_info_iter)?;
let token_b_mint_info = next_account_info(account_info_iter)?;
let pool_token_program_info = next_account_info(account_info_iter)?;
let token_a_program_info = next_account_info(account_info_iter)?;
let token_b_program_info = next_account_info(account_info_iter)?;
let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?;
Self::check_accounts(
token_swap.as_ref(),
program_id,
swap_info,
authority_info,
token_a_info,
token_b_info,
pool_mint_info,
pool_token_program_info,
Some(dest_token_a_info),
Some(dest_token_b_info),
Some(pool_fee_account_info),
)?;
let token_a = Self::unpack_token_account(token_a_info, token_swap.token_program_id())?;
let token_b = Self::unpack_token_account(token_b_info, token_swap.token_program_id())?;
let pool_mint = Self::unpack_mint(pool_mint_info, token_swap.token_program_id())?;
let calculator = &token_swap.swap_curve().calculator;
let withdraw_fee = match token_swap.check_pool_fee_info(pool_fee_account_info) {
Ok(_) => {
if *pool_fee_account_info.key == *source_info.key {
0
} else {
token_swap
.fees()
.owner_withdraw_fee(to_u128(pool_token_amount)?)
.ok_or(SwapError::FeeCalculationFailure)?
}
}
Err(_) => 0,
};
let pool_token_amount = to_u128(pool_token_amount)?
.checked_sub(withdraw_fee)
.ok_or(SwapError::CalculationFailure)?;
let results = calculator
.pool_tokens_to_trading_tokens(
pool_token_amount,
to_u128(pool_mint.supply)?,
to_u128(token_a.amount)?,
to_u128(token_b.amount)?,
RoundDirection::Floor,
)
.ok_or(SwapError::ZeroTradingTokens)?;
let token_a_amount = to_u64(results.token_a_amount)?;
let token_a_amount = std::cmp::min(token_a.amount, token_a_amount);
if token_a_amount < minimum_token_a_amount {
return Err(SwapError::ExceededSlippage.into());
}
if token_a_amount == 0 && token_a.amount != 0 {
return Err(SwapError::ZeroTradingTokens.into());
}
let token_b_amount = to_u64(results.token_b_amount)?;
let token_b_amount = std::cmp::min(token_b.amount, token_b_amount);
if token_b_amount < minimum_token_b_amount {
return Err(SwapError::ExceededSlippage.into());
}
if token_b_amount == 0 && token_b.amount != 0 {
return Err(SwapError::ZeroTradingTokens.into());
}
if withdraw_fee > 0 {
Self::token_transfer(
swap_info.key,
pool_token_program_info.clone(),
source_info.clone(),
pool_mint_info.clone(),
pool_fee_account_info.clone(),
user_transfer_authority_info.clone(),
token_swap.bump_seed(),
to_u64(withdraw_fee)?,
pool_mint.decimals,
None,
)?;
}
Self::token_burn(
swap_info.key,
pool_token_program_info.clone(),
source_info.clone(),
pool_mint_info.clone(),
user_transfer_authority_info.clone(),
token_swap.bump_seed(),
to_u64(pool_token_amount)?,
)?;
if token_a_amount > 0 {
Self::token_transfer(
swap_info.key,
token_a_program_info.clone(),
token_a_info.clone(),
token_a_mint_info.clone(),
dest_token_a_info.clone(),
authority_info.clone(),
token_swap.bump_seed(),
token_a_amount,
Self::unpack_mint(token_a_mint_info, token_swap.token_program_id())?.decimals,
None,
)?;
}
if token_b_amount > 0 {
Self::token_transfer(
swap_info.key,
token_b_program_info.clone(),
token_b_info.clone(),
token_b_mint_info.clone(),
dest_token_b_info.clone(),
authority_info.clone(),
token_swap.bump_seed(),
token_b_amount,
Self::unpack_mint(token_b_mint_info, token_swap.token_program_id())?.decimals,
None,
)?;
}
Ok(())
}
pub fn process_deposit_single_token_type_exact_amount_in(
program_id: &Pubkey,
source_token_amount: u64,
minimum_pool_token_amount: u64,
accounts: &[AccountInfo],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let swap_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let source_info = next_account_info(account_info_iter)?;
let swap_token_a_info = next_account_info(account_info_iter)?;
let swap_token_b_info = next_account_info(account_info_iter)?;
let pool_mint_info = next_account_info(account_info_iter)?;
let destination_info = next_account_info(account_info_iter)?;
let source_token_mint_info = next_account_info(account_info_iter)?;
let source_token_program_info = next_account_info(account_info_iter)?;
let pool_token_program_info = next_account_info(account_info_iter)?;
let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?;
let calculator = &token_swap.swap_curve().calculator;
if !calculator.allows_deposits() {
return Err(SwapError::UnsupportedCurveOperation.into());
}
let source_account =
Self::unpack_token_account(source_info, token_swap.token_program_id())?;
let swap_token_a =
Self::unpack_token_account(swap_token_a_info, token_swap.token_program_id())?;
let swap_token_b =
Self::unpack_token_account(swap_token_b_info, token_swap.token_program_id())?;
let trade_direction = if source_account.mint == swap_token_a.mint {
TradeDirection::AtoB
} else if source_account.mint == swap_token_b.mint {
TradeDirection::BtoA
} else {
return Err(SwapError::IncorrectSwapAccount.into());
};
let (source_a_info, source_b_info) = match trade_direction {
TradeDirection::AtoB => (Some(source_info), None),
TradeDirection::BtoA => (None, Some(source_info)),
};
Self::check_accounts(
token_swap.as_ref(),
program_id,
swap_info,
authority_info,
swap_token_a_info,
swap_token_b_info,
pool_mint_info,
pool_token_program_info,
source_a_info,
source_b_info,
None,
)?;
let pool_mint = Self::unpack_mint(pool_mint_info, token_swap.token_program_id())?;
let pool_mint_supply = to_u128(pool_mint.supply)?;
let pool_token_amount = if pool_mint_supply > 0 {
token_swap
.swap_curve()
.deposit_single_token_type(
to_u128(source_token_amount)?,
to_u128(swap_token_a.amount)?,
to_u128(swap_token_b.amount)?,
pool_mint_supply,
trade_direction,
token_swap.fees(),
)
.ok_or(SwapError::ZeroTradingTokens)?
} else {
calculator.new_pool_supply()
};
let pool_token_amount = to_u64(pool_token_amount)?;
if pool_token_amount < minimum_pool_token_amount {
return Err(SwapError::ExceededSlippage.into());
}
if pool_token_amount == 0 {
return Err(SwapError::ZeroTradingTokens.into());
}
match trade_direction {
TradeDirection::AtoB => {
Self::token_transfer(
swap_info.key,
source_token_program_info.clone(),
source_info.clone(),
source_token_mint_info.clone(),
swap_token_a_info.clone(),
user_transfer_authority_info.clone(),
token_swap.bump_seed(),
source_token_amount,
Self::unpack_mint(source_token_mint_info, token_swap.token_program_id())?
.decimals,
None,
)?;
}
TradeDirection::BtoA => {
Self::token_transfer(
swap_info.key,
source_token_program_info.clone(),
source_info.clone(),
source_token_mint_info.clone(),
swap_token_b_info.clone(),
user_transfer_authority_info.clone(),
token_swap.bump_seed(),
source_token_amount,
Self::unpack_mint(source_token_mint_info, token_swap.token_program_id())?
.decimals,
None,
)?;
}
}
Self::token_mint_to(
swap_info.key,
pool_token_program_info.clone(),
pool_mint_info.clone(),
destination_info.clone(),
authority_info.clone(),
token_swap.bump_seed(),
pool_token_amount,
)?;
Ok(())
}
pub fn process_withdraw_single_token_type_exact_amount_out(
program_id: &Pubkey,
destination_token_amount: u64,
maximum_pool_token_amount: u64,
accounts: &[AccountInfo],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let swap_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let user_transfer_authority_info = next_account_info(account_info_iter)?;
let pool_mint_info = next_account_info(account_info_iter)?;
let source_info = next_account_info(account_info_iter)?;
let swap_token_a_info = next_account_info(account_info_iter)?;
let swap_token_b_info = next_account_info(account_info_iter)?;
let destination_info = next_account_info(account_info_iter)?;
let pool_fee_account_info = next_account_info(account_info_iter)?;
let destination_token_mint_info = next_account_info(account_info_iter)?;
let pool_token_program_info = next_account_info(account_info_iter)?;
let destination_token_program_info = next_account_info(account_info_iter)?;
let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?;
let destination_account =
Self::unpack_token_account(destination_info, token_swap.token_program_id())?;
let swap_token_a =
Self::unpack_token_account(swap_token_a_info, token_swap.token_program_id())?;
let swap_token_b =
Self::unpack_token_account(swap_token_b_info, token_swap.token_program_id())?;
let trade_direction = if destination_account.mint == swap_token_a.mint {
TradeDirection::AtoB
} else if destination_account.mint == swap_token_b.mint {
TradeDirection::BtoA
} else {
return Err(SwapError::IncorrectSwapAccount.into());
};
let (destination_a_info, destination_b_info) = match trade_direction {
TradeDirection::AtoB => (Some(destination_info), None),
TradeDirection::BtoA => (None, Some(destination_info)),
};
Self::check_accounts(
token_swap.as_ref(),
program_id,
swap_info,
authority_info,
swap_token_a_info,
swap_token_b_info,
pool_mint_info,
pool_token_program_info,
destination_a_info,
destination_b_info,
Some(pool_fee_account_info),
)?;
let pool_mint = Self::unpack_mint(pool_mint_info, token_swap.token_program_id())?;
let pool_mint_supply = to_u128(pool_mint.supply)?;
let swap_token_a_amount = to_u128(swap_token_a.amount)?;
let swap_token_b_amount = to_u128(swap_token_b.amount)?;
let burn_pool_token_amount = token_swap
.swap_curve()
.withdraw_single_token_type_exact_out(
to_u128(destination_token_amount)?,
swap_token_a_amount,
swap_token_b_amount,
pool_mint_supply,
trade_direction,
token_swap.fees(),
)
.ok_or(SwapError::ZeroTradingTokens)?;
let withdraw_fee = match token_swap.check_pool_fee_info(pool_fee_account_info) {
Ok(_) => {
if *pool_fee_account_info.key == *source_info.key {
0
} else {
token_swap
.fees()
.owner_withdraw_fee(burn_pool_token_amount)
.ok_or(SwapError::FeeCalculationFailure)?
}
}
Err(_) => 0,
};
let pool_token_amount = burn_pool_token_amount
.checked_add(withdraw_fee)
.ok_or(SwapError::CalculationFailure)?;
if to_u64(pool_token_amount)? > maximum_pool_token_amount {
return Err(SwapError::ExceededSlippage.into());
}
if pool_token_amount == 0 {
return Err(SwapError::ZeroTradingTokens.into());
}
if withdraw_fee > 0 {
Self::token_transfer(
swap_info.key,
pool_token_program_info.clone(),
source_info.clone(),
pool_mint_info.clone(),
pool_fee_account_info.clone(),
user_transfer_authority_info.clone(),
token_swap.bump_seed(),
to_u64(withdraw_fee)?,
pool_mint.decimals,
None,
)?;
}
Self::token_burn(
swap_info.key,
pool_token_program_info.clone(),
source_info.clone(),
pool_mint_info.clone(),
user_transfer_authority_info.clone(),
token_swap.bump_seed(),
to_u64(burn_pool_token_amount)?,
)?;
match trade_direction {
TradeDirection::AtoB => {
Self::token_transfer(
swap_info.key,
destination_token_program_info.clone(),
swap_token_a_info.clone(),
destination_token_mint_info.clone(),
destination_info.clone(),
authority_info.clone(),
token_swap.bump_seed(),
destination_token_amount,
Self::unpack_mint(destination_token_mint_info, token_swap.token_program_id())?
.decimals,
None,
)?;
}
TradeDirection::BtoA => {
Self::token_transfer(
swap_info.key,
destination_token_program_info.clone(),
swap_token_b_info.clone(),
destination_token_mint_info.clone(),
destination_info.clone(),
authority_info.clone(),
token_swap.bump_seed(),
destination_token_amount,
Self::unpack_mint(destination_token_mint_info, token_swap.token_program_id())?
.decimals,
None,
)?;
}
}
Ok(())
}
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
Self::process_with_constraints(program_id, accounts, input, &SWAP_CONSTRAINTS)
}
pub fn process_with_constraints(
program_id: &Pubkey,
accounts: &[AccountInfo],
input: &[u8],
swap_constraints: &Option<SwapConstraints>,
) -> ProgramResult {
let instruction = SwapInstruction::unpack(input)?;
match instruction {
SwapInstruction::Initialize(Initialize { fees, swap_curve }) => {
msg!("Instruction: Init");
Self::process_initialize(program_id, fees, swap_curve, accounts, swap_constraints)
}
SwapInstruction::Swap(Swap {
amount_in,
minimum_amount_out,
}) => {
msg!("Instruction: Swap");
Self::process_swap(program_id, amount_in, minimum_amount_out, accounts)
}
SwapInstruction::DepositAllTokenTypes(DepositAllTokenTypes {
pool_token_amount,
maximum_token_a_amount,
maximum_token_b_amount,
}) => {
msg!("Instruction: DepositAllTokenTypes");
Self::process_deposit_all_token_types(
program_id,
pool_token_amount,
maximum_token_a_amount,
maximum_token_b_amount,
accounts,
)
}
SwapInstruction::WithdrawAllTokenTypes(WithdrawAllTokenTypes {
pool_token_amount,
minimum_token_a_amount,
minimum_token_b_amount,
}) => {
msg!("Instruction: WithdrawAllTokenTypes");
Self::process_withdraw_all_token_types(
program_id,
pool_token_amount,
minimum_token_a_amount,
minimum_token_b_amount,
accounts,
)
}
SwapInstruction::DepositSingleTokenTypeExactAmountIn(
DepositSingleTokenTypeExactAmountIn {
source_token_amount,
minimum_pool_token_amount,
},
) => {
msg!("Instruction: DepositSingleTokenTypeExactAmountIn");
Self::process_deposit_single_token_type_exact_amount_in(
program_id,
source_token_amount,
minimum_pool_token_amount,
accounts,
)
}
SwapInstruction::WithdrawSingleTokenTypeExactAmountOut(
WithdrawSingleTokenTypeExactAmountOut {
destination_token_amount,
maximum_pool_token_amount,
},
) => {
msg!("Instruction: WithdrawSingleTokenTypeExactAmountOut");
Self::process_withdraw_single_token_type_exact_amount_out(
program_id,
destination_token_amount,
maximum_pool_token_amount,
accounts,
)
}
}
}
}
fn to_u128(val: u64) -> Result<u128, SwapError> {
val.try_into().map_err(|_| SwapError::ConversionFailure)
}
fn to_u64(val: u128) -> Result<u64, SwapError> {
val.try_into().map_err(|_| SwapError::ConversionFailure)
}
fn invoke_signed_wrapper<T>(
instruction: &Instruction,
account_infos: &[AccountInfo],
signers_seeds: &[&[&[u8]]],
) -> Result<(), ProgramError>
where
T: 'static + PrintProgramError + DecodeError<T> + FromPrimitive + Error,
{
invoke_signed(instruction, account_infos, signers_seeds).map_err(|err| {
err.print::<T>();
err
})
}