use core::marker::PhantomData; use frame_support::ensure; use safe_math::*; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaBalance, NetUid, TaoBalance, Token, TokenReserve}; use super::pallet::*; pub(crate) const MAX_SWAP_INPUT_RESERVE_MULTIPLIER: u64 = 1_000; /// A struct representing a single swap step with all its parameters and state pub(crate) struct BasicSwapStep where T: Config, PaidIn: Token, PaidOut: Token, { // Input parameters netuid: NetUid, drop_fees: bool, requested_delta_in: PaidIn, limit_price: U64F64, // Intermediate calculations target_price: U64F64, current_price: U64F64, // Result values delta_in: PaidIn, final_price: U64F64, fee: PaidIn, _phantom: PhantomData<(T, PaidIn, PaidOut)>, } impl BasicSwapStep where T: Config, PaidIn: Token, PaidOut: Token, Self: SwapStep, { /// Creates and initializes a new swap step pub(crate) fn new( netuid: NetUid, amount_remaining: PaidIn, limit_price: U64F64, drop_fees: bool, ) -> Self { let fee = Pallet::::calculate_fee_amount(netuid, amount_remaining, drop_fees); let requested_delta_in = amount_remaining.saturating_sub(fee); // Target and current prices let target_price = Self::price_target(netuid, requested_delta_in); let current_price = Pallet::::current_price(netuid); Self { netuid, drop_fees, requested_delta_in, limit_price, target_price, current_price, delta_in: PaidIn::ZERO, final_price: target_price, fee, _phantom: PhantomData, } } /// Execute the swap step and return the result pub(crate) fn execute(&mut self) -> Result, Error> { self.determine_action(); self.process_swap() } /// Determine the appropriate action for this swap step fn determine_action(&mut self) { let mut recalculate_fee = false; // Calculate the stopping price: The price at which we either reach the limit price, // or exchange the full amount. if Self::price_is_closer(&self.target_price, &self.limit_price) { // Case 1. target_quantity is the lowest, execute in full self.final_price = self.target_price; self.delta_in = self.requested_delta_in; } else { // Case 2. lim_quantity is the lowest self.final_price = self.limit_price; self.delta_in = Self::delta_in(self.netuid, self.current_price, self.limit_price); recalculate_fee = true; } log::trace!("\tCurrent Price : {}", self.current_price); log::trace!("\tTarget Price : {}", self.target_price); log::trace!("\tLimit Price : {}", self.limit_price); log::trace!("\tDelta In : {}", self.delta_in); // Because on step creation we calculate fee off the total amount, we might need to // recalculate it in case if we hit the limit price. if recalculate_fee { let u16_max = U64F64::saturating_from_num(u16::MAX); let fee_rate = if self.drop_fees { U64F64::saturating_from_num(0) } else { U64F64::saturating_from_num(FeeRate::::get(self.netuid)) }; let delta_fixed = U64F64::saturating_from_num(self.delta_in); self.fee = delta_fixed .saturating_mul(fee_rate.safe_div(u16_max.saturating_sub(fee_rate))) .saturating_to_num::() .into(); } } /// Process a single step of a swap fn process_swap(&self) -> Result, Error> { // Convert amounts, actual swap happens here let delta_out = Self::convert_deltas(self.netuid, self.delta_in); log::trace!("\tDelta Out : {delta_out}"); let mut fee_to_block_author = 0.into(); if !self.delta_in.is_zero() { ensure!(!delta_out.is_zero(), Error::::ReservesTooLow); // 100% of swap fees to to block builder fee_to_block_author = self.fee; } Ok(SwapStepResult { fee_paid: self.fee, delta_in: self.delta_in, delta_out, fee_to_block_author, }) } } impl SwapStep for BasicSwapStep { fn delta_in(netuid: NetUid, price_curr: U64F64, price_target: U64F64) -> TaoBalance { let tao_reserve = T::TaoReserve::reserve(netuid.into()); let balancer = SwapBalancer::::get(netuid); TaoBalance::from(balancer.calculate_quote_delta_in( price_curr, price_target, tao_reserve.into(), )) } fn price_target(netuid: NetUid, delta_in: TaoBalance) -> U64F64 { let tao_reserve = T::TaoReserve::reserve(netuid.into()); let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); let balancer = SwapBalancer::::get(netuid); let dy = delta_in; let dx = Self::convert_deltas(netuid, dy); balancer.calculate_price( u64::from(alpha_reserve.saturating_sub(dx)), u64::from(tao_reserve.saturating_add(dy)), ) } fn price_is_closer(price1: &U64F64, price2: &U64F64) -> bool { price1 <= price2 } fn convert_deltas(netuid: NetUid, delta_in: TaoBalance) -> AlphaBalance { let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); let tao_reserve = T::TaoReserve::reserve(netuid.into()); let balancer = SwapBalancer::::get(netuid); let e = balancer.exp_quote_base(tao_reserve.into(), delta_in.into()); let one = U64F64::from_num(1); let alpha_reserve_fixed = U64F64::from_num(alpha_reserve); AlphaBalance::from( alpha_reserve_fixed .saturating_mul(one.saturating_sub(e)) .saturating_to_num::(), ) } } impl SwapStep for BasicSwapStep { fn delta_in(netuid: NetUid, price_curr: U64F64, price_target: U64F64) -> AlphaBalance { let alpha_reserve = T::AlphaReserve::reserve(netuid); let balancer = SwapBalancer::::get(netuid); AlphaBalance::from(balancer.calculate_base_delta_in( price_curr, price_target, alpha_reserve.into(), )) } fn price_target(netuid: NetUid, delta_in: AlphaBalance) -> U64F64 { let tao_reserve = T::TaoReserve::reserve(netuid.into()); let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); let balancer = SwapBalancer::::get(netuid); let dx = delta_in; let dy = Self::convert_deltas(netuid, dx); balancer.calculate_price( u64::from(alpha_reserve.saturating_add(dx)), u64::from(tao_reserve.saturating_sub(dy)), ) } fn price_is_closer(price1: &U64F64, price2: &U64F64) -> bool { price1 >= price2 } fn convert_deltas(netuid: NetUid, delta_in: AlphaBalance) -> TaoBalance { let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); let tao_reserve = T::TaoReserve::reserve(netuid.into()); let balancer = SwapBalancer::::get(netuid); let e = balancer.exp_base_quote(alpha_reserve.into(), delta_in.into()); let one = U64F64::from_num(1); let tao_reserve_fixed = U64F64::from_num(u64::from(tao_reserve)); TaoBalance::from( tao_reserve_fixed .saturating_mul(one.saturating_sub(e)) .saturating_to_num::(), ) } } pub(crate) trait SwapStep where T: Config, PaidIn: Token, PaidOut: Token, { /// Get the input amount needed to reach the target price fn delta_in(netuid: NetUid, price_curr: U64F64, price_target: U64F64) -> PaidIn; /// Get the target price based on the input amount fn price_target(netuid: NetUid, delta_in: PaidIn) -> U64F64; /// Returns True if price1 is closer to the current price than price2 /// For buying: price1 <= price2 /// For selling: price1 >= price2 fn price_is_closer(price1: &U64F64, price2: &U64F64) -> bool; /// Convert input amount (delta_in) to output amount (delta_out) /// /// This is the core method of the swap that tells how much output token is given for an /// amount of input token within one price tick. fn convert_deltas(netuid: NetUid, delta_in: PaidIn) -> PaidOut; } #[derive(Debug, PartialEq)] pub(crate) struct SwapStepResult where PaidIn: Token, PaidOut: Token, { pub(crate) fee_paid: PaidIn, pub(crate) delta_in: PaidIn, pub(crate) delta_out: PaidOut, pub(crate) fee_to_block_author: PaidIn, }