-
-
Save nazariyv/08b6351c97f8e74789a66d0d1b3ed152 to your computer and use it in GitHub Desktop.
Meteora Dlmm Quote Math
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use super::bin_array::{Bin, BinArray}; | |
use super::lb_pair::{LbPair, LbPairExtension}; | |
use crate::commons::WSOL_MINT; | |
use arrayvec::ArrayVec; | |
use pinocchio::{ | |
account_info::AccountInfo, msg, program_error::ProgramError, pubkey::Pubkey, sysvars::Sysvar, | |
sysvars::clock::Clock, | |
}; | |
use spl_token_2022::{ | |
self, | |
extension::{ | |
BaseStateWithExtensions, StateWithExtensions, | |
transfer_fee::{MAX_FEE_BASIS_POINTS, TransferFee, TransferFeeConfig}, | |
}, | |
}; | |
pub const MAX_EXPONENTIAL: u32 = 0x80000; // 1048576 | |
pub const MAX_FEE_RATE: u64 = 100_000_000; | |
pub const ONE: u128 = 1u128 << SCALE_OFFSET; | |
pub const BASIS_POINT_MAX: i32 = 10000; | |
pub const SCALE_OFFSET: u8 = 64; | |
pub const FEE_PRECISION: u64 = 1_000_000_000; | |
pub const MAX_BIN_PER_ARRAY: usize = 70; | |
pub const NUM_REWARDS: usize = 2; | |
pub fn get_epoch_transfer_fee( | |
mint_info: &AccountInfo, | |
epoch: u64, | |
) -> Result<Option<TransferFee>, ProgramError> { | |
let token_mint_data = mint_info.try_borrow_data()?; | |
let token_mint_unpacked = StateWithExtensions::<spl_token_2022::state::Mint>::unpack( | |
&token_mint_data, | |
) | |
.map_err(|err| { | |
msg!("failed to unpack tocken mint"); | |
ProgramError::InvalidAccountData | |
})?; | |
if let Ok(transfer_fee_config) = token_mint_unpacked.get_extension::<TransferFeeConfig>() { | |
return Ok(Some(*transfer_fee_config.get_epoch_fee(epoch))); | |
} | |
Ok(None) | |
} | |
pub fn calculate_transfer_fee_excluded_amount( | |
mint_account: &AccountInfo, | |
transfer_fee_included_amount: u64, | |
epoch: u64, | |
) -> Result<u64, ProgramError> { | |
if let Some(epoch_transfer_fee) = get_epoch_transfer_fee(mint_account, epoch)? { | |
let transfer_fee = epoch_transfer_fee | |
.calculate_fee(transfer_fee_included_amount) | |
.ok_or(ProgramError::ArithmeticOverflow)?; | |
let transfer_fee_excluded_amount = transfer_fee_included_amount | |
.checked_sub(transfer_fee) | |
.ok_or(ProgramError::ArithmeticOverflow)?; | |
return Ok(transfer_fee_excluded_amount); | |
} | |
Ok(transfer_fee_included_amount) | |
} | |
const ONE_IN_BASIS_POINTS: u128 = MAX_FEE_BASIS_POINTS as u128; | |
pub fn calculate_pre_fee_amount(transfer_fee: &TransferFee, post_fee_amount: u64) -> Option<u64> { | |
if post_fee_amount == 0 { | |
return Some(0); | |
} | |
let maximum_fee = u64::from(transfer_fee.maximum_fee); | |
let transfer_fee_basis_points = u16::from(transfer_fee.transfer_fee_basis_points) as u128; | |
if transfer_fee_basis_points == 0 { | |
Some(post_fee_amount) | |
} else if transfer_fee_basis_points == ONE_IN_BASIS_POINTS { | |
Some(maximum_fee.checked_add(post_fee_amount)?) | |
} else { | |
let numerator = (post_fee_amount as u128).checked_mul(ONE_IN_BASIS_POINTS)?; | |
let denominator = ONE_IN_BASIS_POINTS.checked_sub(transfer_fee_basis_points)?; | |
let raw_pre_fee_amount = numerator | |
.checked_add(denominator)? | |
.checked_sub(1)? | |
.checked_div(denominator)?; | |
if raw_pre_fee_amount.checked_sub(post_fee_amount as u128)? >= maximum_fee as u128 { | |
post_fee_amount.checked_add(maximum_fee) | |
} else { | |
u64::try_from(raw_pre_fee_amount).ok() | |
} | |
} | |
} | |
pub fn calculate_inverse_fee(transfer_fee: &TransferFee, post_fee_amount: u64) -> Option<u64> { | |
let pre_fee_amount = calculate_pre_fee_amount(transfer_fee, post_fee_amount)?; | |
transfer_fee.calculate_fee(pre_fee_amount) | |
} | |
pub fn calculate_transfer_fee_included_amount( | |
mint_account: &AccountInfo, | |
transfer_fee_excluded_amount: u64, | |
epoch: u64, | |
) -> Result<u64, ProgramError> { | |
if transfer_fee_excluded_amount == 0 { | |
return Ok(0); | |
} | |
if let Some(epoch_transfer_fee) = get_epoch_transfer_fee(mint_account, epoch)? { | |
let transfer_fee: u64 = | |
if u16::from(epoch_transfer_fee.transfer_fee_basis_points) == MAX_FEE_BASIS_POINTS { | |
u64::from(epoch_transfer_fee.maximum_fee) | |
} else { | |
calculate_inverse_fee(&epoch_transfer_fee, transfer_fee_excluded_amount) | |
.ok_or(ProgramError::ArithmeticOverflow)? | |
}; | |
let transfer_fee_included_amount = transfer_fee_excluded_amount | |
.checked_add(transfer_fee) | |
.ok_or(ProgramError::ArithmeticOverflow)?; | |
return Ok(transfer_fee_included_amount); | |
} | |
Ok(transfer_fee_excluded_amount) | |
}pub fn swap_quote( | |
accounts: &[AccountInfo], | |
offset: usize, | |
sol_amount: u64, | |
) -> Result<(u64, u64), ProgramError> { | |
let lb_pair_data = accounts[offset + 3].try_borrow_data()?; | |
let mut lb_pair: LbPair = *bytemuck::from_bytes(&lb_pair_data[8..]); | |
let mut clock = Clock::get()?; | |
let mut epoch = clock.epoch; | |
let swap_for_y = *accounts[offset + 1].key() == WSOL_MINT; | |
let (in_idx, out_idx) = if swap_for_y { (1, 6) } else { (6, 1) }; | |
lb_pair.update_references(clock.unix_timestamp)?; | |
let (in_mint_account, out_mint_account) = (&accounts[in_idx], &accounts[out_idx]); | |
// Collect bin arrays | |
let mut bin_arrays: ArrayVec<&AccountInfo, 3> = ArrayVec::new(); | |
let bin_range = if swap_for_y { | |
[offset + 7, offset + 8, offset + 9] | |
} else { | |
[offset + 9, offset + 8, offset + 7] | |
}; | |
for i in bin_range { | |
if i < accounts.len() && accounts[i].lamports() > 0 { | |
bin_arrays.push(&accounts[i]); | |
} | |
} | |
// === Forward: SOL → Token (Exact In) === | |
let amount_in = calculate_transfer_fee_excluded_amount(in_mint_account, sol_amount, epoch)?; | |
let mut amount_left = amount_in; | |
let mut total_amount_out = 0u64; | |
for acc in &bin_arrays { | |
let data = acc.try_borrow_data()?; | |
let active_bin_array: &BinArray = bytemuck::from_bytes(&data[8..]); | |
while amount_left > 0 && active_bin_array.is_bin_id_within_range(lb_pair.active_id)? { | |
lb_pair.update_volatility_accumulator()?; | |
let mut active_bin = active_bin_array.get_bin_by_id(lb_pair.active_id)?; | |
let price = active_bin.get_or_store_bin_price(lb_pair.active_id, lb_pair.bin_step)?; | |
if !active_bin.is_empty(!swap_for_y) { | |
let (amount_in_with_fees, amount_out) = | |
active_bin.swap(amount_left, price, swap_for_y, &lb_pair)?; | |
amount_left = amount_left | |
.checked_sub(amount_in_with_fees) | |
.ok_or(ProgramError::ArithmeticOverflow)?; | |
total_amount_out = total_amount_out | |
.checked_add(amount_out) | |
.ok_or(ProgramError::ArithmeticOverflow)?; | |
} | |
if amount_left > 0 { | |
lb_pair.advance_active_bin(swap_for_y)?; | |
} else { | |
break; | |
} | |
} | |
if amount_left == 0 { | |
break; | |
} | |
} | |
let amount_out = | |
calculate_transfer_fee_excluded_amount(out_mint_account, total_amount_out, epoch)?; | |
// === Reverse: Token → SOL (Exact Out) === | |
let mut lb_pair: LbPair = *bytemuck::from_bytes(&lb_pair_data[8..]); | |
lb_pair.update_references(clock.unix_timestamp)?; | |
let swap_for_y: bool = !swap_for_y; | |
let (in_mint_account, out_mint_account) = (&accounts[out_idx], &accounts[in_idx]); //revert accounts | |
let mut total_amount_in = 0u64; | |
let mut total_fee = 0u64; | |
let mut sol_amount_remaining = sol_amount; // SOL has no transfer fee | |
bin_arrays.reverse(); | |
sol_amount_remaining = | |
calculate_transfer_fee_included_amount(out_mint_account, sol_amount_remaining, epoch)?; | |
for acc in &bin_arrays { | |
let data = acc.try_borrow_data()?; | |
let active_bin_array: &BinArray = bytemuck::from_bytes(&data[8..]); | |
while sol_amount_remaining > 0 | |
&& active_bin_array.is_bin_id_within_range(lb_pair.active_id)? | |
{ | |
lb_pair.update_volatility_accumulator()?; | |
let mut active_bin = active_bin_array.get_bin_by_id(lb_pair.active_id)?; | |
let price = active_bin.get_or_store_bin_price(lb_pair.active_id, lb_pair.bin_step)?; | |
if !active_bin.is_empty(!swap_for_y) { | |
let max_out = active_bin.get_max_amount_out(swap_for_y); | |
if sol_amount_remaining >= max_out { | |
let max_in = active_bin.get_max_amount_in(price, swap_for_y)?; | |
let fee = lb_pair.compute_fee(max_in)?; | |
total_amount_in = total_amount_in | |
.checked_add(max_in) | |
.ok_or(ProgramError::ArithmeticOverflow)?; | |
total_fee = total_fee | |
.checked_add(fee) | |
.ok_or(ProgramError::ArithmeticOverflow)?; | |
sol_amount_remaining = sol_amount_remaining | |
.checked_sub(max_out) | |
.ok_or(ProgramError::ArithmeticOverflow)?; | |
} else { | |
let amount_in = Bin::get_amount_in(sol_amount_remaining, price, swap_for_y)?; | |
let fee = lb_pair.compute_fee(amount_in)?; | |
total_amount_in = total_amount_in | |
.checked_add(amount_in) | |
.ok_or(ProgramError::ArithmeticOverflow)?; | |
total_fee = total_fee | |
.checked_add(fee) | |
.ok_or(ProgramError::ArithmeticOverflow)?; | |
sol_amount_remaining = 0; | |
} | |
} | |
if sol_amount_remaining > 0 { | |
lb_pair.advance_active_bin(swap_for_y)?; | |
} | |
} | |
if sol_amount_remaining == 0 { | |
break; | |
} | |
} | |
total_amount_in = total_amount_in | |
.checked_add(total_fee) | |
.ok_or(ProgramError::ArithmeticOverflow)?; | |
let total_amount_in = | |
calculate_transfer_fee_included_amount(in_mint_account, total_amount_in, epoch)?; | |
Ok((amount_out, total_amount_in)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment