From 197f717d6e939da13ffcc10485a3cfac2b0e43e1 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Wed, 20 Aug 2025 16:43:57 +0300 Subject: [PATCH 1/4] Parametrize currencies in swap interface --- common/src/currency.rs | 4 +- common/src/lib.rs | 12 +- pallets/swap-interface/src/lib.rs | 52 ++++++--- pallets/swap-interface/src/order.rs | 15 +++ pallets/swap/src/pallet/impls.rs | 168 ++++++++++++++++------------ pallets/swap/src/pallet/mod.rs | 16 ++- 6 files changed, 168 insertions(+), 99 deletions(-) create mode 100644 pallets/swap-interface/src/order.rs diff --git a/common/src/currency.rs b/common/src/currency.rs index 8233383e95..48826b1933 100644 --- a/common/src/currency.rs +++ b/common/src/currency.rs @@ -227,7 +227,9 @@ macro_rules! impl_approx { }; } -pub trait Currency: ToFixed + Into + From + Clone + Copy { +pub trait Currency: + ToFixed + Into + From + Clone + Copy + Eq + Ord + PartialEq + PartialOrd + Display +{ const MAX: Self; const ZERO: Self; diff --git a/common/src/lib.rs b/common/src/lib.rs index a3882a88fc..dab0aeb40e 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -169,13 +169,17 @@ impl Default for ProxyType { } pub trait SubnetInfo { - fn tao_reserve(netuid: NetUid) -> TaoCurrency; - fn alpha_reserve(netuid: NetUid) -> AlphaCurrency; fn exists(netuid: NetUid) -> bool; fn mechanism(netuid: NetUid) -> u16; fn is_owner(account_id: &AccountId, netuid: NetUid) -> bool; } +pub trait CurrencyReserve { + fn reserve(netuid: NetUid) -> C; + fn increase_provided(netuid: NetUid, amount: C); + fn decrease_provided(netuid: NetUid, amount: C); +} + pub trait BalanceOps { fn tao_balance(account_id: &AccountId) -> TaoCurrency; fn alpha_balance(netuid: NetUid, coldkey: &AccountId, hotkey: &AccountId) -> AlphaCurrency; @@ -196,10 +200,6 @@ pub trait BalanceOps { netuid: NetUid, alpha: AlphaCurrency, ) -> Result; - fn increase_provided_tao_reserve(netuid: NetUid, tao: TaoCurrency); - fn decrease_provided_tao_reserve(netuid: NetUid, tao: TaoCurrency); - fn increase_provided_alpha_reserve(netuid: NetUid, alpha: AlphaCurrency); - fn decrease_provided_alpha_reserve(netuid: NetUid, alpha: AlphaCurrency); } pub mod time { diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index a0b39e151f..00540ca378 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -2,7 +2,9 @@ use frame_support::pallet_prelude::*; use substrate_fixed::types::U96F32; -use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; +use subtensor_runtime_common::{AlphaCurrency, Currency, CurrencyReserve, NetUid, TaoCurrency}; + +pub mod order; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OrderType { @@ -11,23 +13,33 @@ pub enum OrderType { } pub trait SwapHandler { - fn swap( + fn swap( netuid: NetUid, order_t: OrderType, - amount: u64, - price_limit: u64, + amount: PaidIn, + price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result; - fn sim_swap( + ) -> Result, DispatchError> + where + PaidIn: Currency, + PaidOut: Currency, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve; + fn sim_swap( netuid: NetUid, order_t: OrderType, - amount: u64, - ) -> Result; - fn approx_fee_amount(netuid: NetUid, amount: u64) -> u64; + amount: PaidIn, + ) -> Result, DispatchError> + where + PaidIn: Currency, + PaidOut: Currency, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve; + fn approx_fee_amount(netuid: NetUid, amount: T) -> T; fn current_alpha_price(netuid: NetUid) -> U96F32; - fn max_price() -> u64; - fn min_price() -> u64; + fn max_price() -> C; + fn min_price() -> C; fn adjust_protocol_liquidity( netuid: NetUid, tao_delta: TaoCurrency, @@ -36,12 +48,16 @@ pub trait SwapHandler { fn is_user_liquidity_enabled(netuid: NetUid) -> bool; } -#[derive(Debug, PartialEq)] -pub struct SwapResult { - pub amount_paid_in: u64, - pub amount_paid_out: u64, - pub fee_paid: u64, +#[derive(Decode, Encode, PartialEq, Eq, Clone, Debug, TypeInfo)] +pub struct SwapResult +where + PaidIn: Currency, + PaidOut: Currency, +{ + pub amount_paid_in: PaidIn, + pub amount_paid_out: PaidOut, + pub fee_paid: PaidIn, // For calculation of new tao/alpha reserves - pub tao_reserve_delta: i64, - pub alpha_reserve_delta: i64, + pub tao_reserve_delta: i128, + pub alpha_reserve_delta: i128, } diff --git a/pallets/swap-interface/src/order.rs b/pallets/swap-interface/src/order.rs new file mode 100644 index 0000000000..e96fcf45bd --- /dev/null +++ b/pallets/swap-interface/src/order.rs @@ -0,0 +1,15 @@ +use core::marker::PhantomData; + +use subtensor_runtime_common::Currency; + +pub struct OrderType +where + PaidIn: Currency, + PaidOut: Currency, +{ + amount: PaidIn, + _paid_in: PhantomData, + _paid_out: PhantomData, +} + +pub trait Order {} diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index deebabd673..99fa7bf577 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -8,7 +8,7 @@ use sp_arithmetic::helpers_128bit; use sp_runtime::traits::AccountIdConversion; use substrate_fixed::types::{I64F64, U64F64, U96F32}; use subtensor_runtime_common::{ - AlphaCurrency, BalanceOps, Currency, NetUid, SubnetInfo, TaoCurrency, + AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, }; use subtensor_swap_interface::{SwapHandler, SwapResult}; @@ -292,8 +292,8 @@ impl Pallet { let sqrt_price = AlphaSqrtPrice::::get(netuid); U96F32::saturating_from_num(sqrt_price.saturating_mul(sqrt_price)) } else { - let tao_reserve = T::SubnetInfo::tao_reserve(netuid.into()); - let alpha_reserve = T::SubnetInfo::alpha_reserve(netuid.into()); + let tao_reserve = T::TaoReserve::reserve(netuid.into()); + let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); if !alpha_reserve.is_zero() { U96F32::saturating_from_num(tao_reserve) .saturating_div(U96F32::saturating_from_num(alpha_reserve)) @@ -318,8 +318,8 @@ impl Pallet { // Initialize the v3: // Reserves are re-purposed, nothing to set, just query values for liquidity and price calculation - let tao_reserve = ::SubnetInfo::tao_reserve(netuid.into()); - let alpha_reserve = ::SubnetInfo::alpha_reserve(netuid.into()); + let tao_reserve = T::TaoReserve::reserve(netuid.into()); + let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); // Set price let price = U64F64::saturating_from_num(tao_reserve) @@ -428,22 +428,31 @@ impl Pallet { /// 2. Skips reserve checks — it may return an `amount_paid_out` greater than the available reserve. /// /// Use simulation mode to preview the outcome of a swap without modifying the blockchain state. - pub fn do_swap( + pub fn do_swap( netuid: NetUid, order_type: OrderType, - amount: u64, + amount: PaidIn, limit_sqrt_price: SqrtPrice, drop_fees: bool, simulate: bool, - ) -> Result { + ) -> Result, DispatchError> + where + PaidIn: Currency, + PaidOut: Currency, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, + { transactional::with_transaction(|| { - // Read alpha and tao reserves before transaction - let tao_reserve = T::SubnetInfo::tao_reserve(netuid.into()); - let alpha_reserve = T::SubnetInfo::alpha_reserve(netuid.into()); + let reserve = ReserveOut::reserve(netuid.into()); - let mut result = - Self::swap_inner(netuid, order_type, amount, limit_sqrt_price, drop_fees) - .map_err(Into::into); + let result = Self::swap_inner::( + netuid, + order_type, + amount, + limit_sqrt_price, + drop_fees, + ) + .map_err(Into::into); if simulate || result.is_err() { // Simulation only @@ -453,13 +462,10 @@ impl Pallet { // Check if reserves are overused if let Ok(ref swap_result) = result { - let checked_reserve = match order_type { - OrderType::Buy => alpha_reserve.to_u64(), - OrderType::Sell => tao_reserve.to_u64(), - }; - - if checked_reserve < swap_result.amount_paid_out { - result = Err(Error::::InsufficientLiquidity.into()); + if reserve < swap_result.amount_paid_out { + return TransactionOutcome::Commit(Err( + Error::::InsufficientLiquidity.into() + )); } } @@ -468,25 +474,23 @@ impl Pallet { }) } - fn swap_inner( + fn swap_inner( netuid: NetUid, order_type: OrderType, - amount: u64, + amount: PaidIn, limit_sqrt_price: SqrtPrice, drop_fees: bool, - ) -> Result> { - match order_type { - OrderType::Buy => ensure!( - T::SubnetInfo::alpha_reserve(netuid.into()).to_u64() - >= T::MinimumReserve::get().get(), - Error::::ReservesTooLow - ), - OrderType::Sell => ensure!( - T::SubnetInfo::tao_reserve(netuid.into()).to_u64() - >= T::MinimumReserve::get().get(), - Error::::ReservesTooLow - ), - } + ) -> Result, Error> + where + PaidIn: Currency, + PaidOut: Currency, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, + { + ensure!( + ReserveOut::reserve(netuid).to_u64() >= T::MinimumReserve::get().get(), + Error::::ReservesTooLow + ); Self::maybe_initialize_v3(netuid)?; @@ -512,7 +516,7 @@ impl Pallet { log::trace!("Amount Remaining: {amount_remaining}"); // Swap one tick at a time until we reach one of the stop conditions - while amount_remaining > 0 { + while !amount_remaining.is_zero() { log::trace!("\nIteration: {iteration_counter}"); log::trace!( "\tCurrent Liquidity: {}", @@ -523,7 +527,7 @@ impl Pallet { let mut swap_step = SwapStep::::new( netuid, order_type, - amount_remaining, + amount_remaining.into(), limit_sqrt_price, drop_fees, ); @@ -532,16 +536,16 @@ impl Pallet { in_acc = in_acc.saturating_add(swap_result.delta_in); fee_acc = fee_acc.saturating_add(swap_result.fee_paid); - amount_remaining = amount_remaining.saturating_sub(swap_result.amount_to_take); + amount_remaining = amount_remaining.saturating_sub(swap_result.amount_to_take.into()); amount_paid_out = amount_paid_out.saturating_add(swap_result.delta_out); if swap_step.action == SwapStepAction::Stop { - amount_remaining = 0; + amount_remaining = PaidIn::ZERO; } // The swap step didn't exchange anything if swap_result.amount_to_take == 0 { - amount_remaining = 0; + amount_remaining = PaidIn::ZERO; } iteration_counter = iteration_counter.saturating_add(1); @@ -556,14 +560,14 @@ impl Pallet { log::trace!("======== End Swap ========"); let (tao_reserve_delta, alpha_reserve_delta) = match order_type { - OrderType::Buy => (in_acc as i64, (amount_paid_out as i64).neg()), - OrderType::Sell => ((amount_paid_out as i64).neg(), in_acc as i64), + OrderType::Buy => (in_acc as i128, (amount_paid_out as i128).neg()), + OrderType::Sell => ((amount_paid_out as i128).neg(), in_acc as i128), }; Ok(SwapResult { - amount_paid_in: in_acc, - amount_paid_out, - fee_paid: fee_acc, + amount_paid_in: in_acc.into(), + amount_paid_out: amount_paid_out.into(), + fee_paid: fee_acc.into(), tao_reserve_delta, alpha_reserve_delta, }) @@ -712,8 +716,12 @@ impl Pallet { // No liquidity means that price should go to the limit if liquidity_curr == 0 { return match order_type { - OrderType::Sell => SqrtPrice::saturating_from_num(Self::min_price()), - OrderType::Buy => SqrtPrice::saturating_from_num(Self::max_price()), + OrderType::Sell => { + SqrtPrice::saturating_from_num(Self::min_price::().to_u64()) + } + OrderType::Buy => { + SqrtPrice::saturating_from_num(Self::max_price::().to_u64()) + } }; } @@ -1215,20 +1223,26 @@ impl Pallet { } impl SwapHandler for Pallet { - fn swap( + fn swap( netuid: NetUid, order_t: OrderType, - amount: u64, - price_limit: u64, + amount: PaidIn, + price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result { - let limit_sqrt_price = SqrtPrice::saturating_from_num(price_limit) + ) -> Result, DispatchError> + where + PaidIn: Currency, + PaidOut: Currency, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, + { + let limit_sqrt_price = SqrtPrice::saturating_from_num(price_limit.to_u64()) .safe_div(SqrtPrice::saturating_from_num(1_000_000_000)) .checked_sqrt(SqrtPrice::saturating_from_num(0.0000000001)) .ok_or(Error::::PriceLimitExceeded)?; - Self::do_swap( + Self::do_swap::( NetUid::from(netuid), order_t, amount, @@ -1239,51 +1253,67 @@ impl SwapHandler for Pallet { .map_err(Into::into) } - fn sim_swap( + fn sim_swap( netuid: NetUid, order_t: OrderType, - amount: u64, - ) -> Result { + amount: PaidIn, + ) -> Result, DispatchError> + where + PaidIn: Currency, + PaidOut: Currency, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, + { match T::SubnetInfo::mechanism(netuid) { 1 => { let price_limit = match order_t { - OrderType::Buy => Self::max_price(), - OrderType::Sell => Self::min_price(), - }; + OrderType::Buy => Self::max_price::(), + OrderType::Sell => Self::min_price::(), + } + .to_u64(); - Self::swap(netuid, order_t, amount, price_limit, false, true) + Self::swap::( + netuid, + order_t, + amount, + price_limit.into(), + false, + true, + ) } _ => Ok(SwapResult { amount_paid_in: amount, - amount_paid_out: amount, - fee_paid: 0, + amount_paid_out: amount.to_u64().into(), + fee_paid: 0.into(), tao_reserve_delta: 0, alpha_reserve_delta: 0, }), } } - fn approx_fee_amount(netuid: NetUid, amount: u64) -> u64 { - Self::calculate_fee_amount(netuid.into(), amount, false) + fn approx_fee_amount(netuid: NetUid, amount: C) -> C { + Self::calculate_fee_amount(netuid, amount.to_u64(), false).into() } fn current_alpha_price(netuid: NetUid) -> U96F32 { Self::current_price(netuid.into()) } - fn min_price() -> u64 { + fn min_price() -> C { TickIndex::min_sqrt_price() .saturating_mul(TickIndex::min_sqrt_price()) .saturating_mul(SqrtPrice::saturating_from_num(1_000_000_000)) - .saturating_to_num() + .saturating_to_num::() + .into() } - fn max_price() -> u64 { + fn max_price() -> C { TickIndex::max_sqrt_price() .saturating_mul(TickIndex::max_sqrt_price()) .saturating_mul(SqrtPrice::saturating_from_num(1_000_000_000)) .saturating_round() - .saturating_to_num() + .saturating_to_num::() + .into() } fn adjust_protocol_liquidity( diff --git a/pallets/swap/src/pallet/mod.rs b/pallets/swap/src/pallet/mod.rs index 894099de7f..a7f3520541 100644 --- a/pallets/swap/src/pallet/mod.rs +++ b/pallets/swap/src/pallet/mod.rs @@ -5,7 +5,7 @@ use frame_support::{PalletId, pallet_prelude::*, traits::Get}; use frame_system::pallet_prelude::*; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{ - AlphaCurrency, BalanceOps, Currency, NetUid, SubnetInfo, TaoCurrency, + AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, }; use crate::{ @@ -39,6 +39,12 @@ mod pallet { /// [`SubnetInfo`](subtensor_swap_interface::SubnetInfo). type SubnetInfo: SubnetInfo; + /// Tao reserves info. + type TaoReserve: CurrencyReserve; + + /// Alpha reserves info. + type AlphaReserve: CurrencyReserve; + /// Implementor of /// [`BalanceOps`](subtensor_swap_interface::BalanceOps). type BalanceOps: BalanceOps; @@ -386,8 +392,8 @@ mod pallet { ensure!(alpha_provided == alpha, Error::::InsufficientBalance); // Add provided liquidity to user-provided reserves - T::BalanceOps::increase_provided_tao_reserve(netuid.into(), tao_provided); - T::BalanceOps::increase_provided_alpha_reserve(netuid.into(), alpha_provided); + T::TaoReserve::increase_provided(netuid.into(), tao_provided); + T::AlphaReserve::increase_provided(netuid.into(), alpha_provided); // Emit an event Self::deposit_event(Event::LiquidityAdded { @@ -442,8 +448,8 @@ mod pallet { )?; // Remove withdrawn liquidity from user-provided reserves - T::BalanceOps::decrease_provided_tao_reserve(netuid.into(), result.tao); - T::BalanceOps::decrease_provided_alpha_reserve(netuid.into(), result.alpha); + T::TaoReserve::decrease_provided(netuid.into(), result.tao); + T::AlphaReserve::decrease_provided(netuid.into(), result.alpha); // Emit an event Self::deposit_event(Event::LiquidityRemoved { From f7726fc8885dcbfb13be0040c98f3a07a3c29b74 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Wed, 27 Aug 2025 14:31:42 +0300 Subject: [PATCH 2/4] Abstract SwapStep --- pallets/swap-interface/src/lib.rs | 4 +- pallets/swap-interface/src/order.rs | 31 +- pallets/swap/src/pallet/impls.rs | 569 +++----------------------- pallets/swap/src/pallet/mod.rs | 1 + pallets/swap/src/pallet/swap_step.rs | 576 +++++++++++++++++++++++++++ 5 files changed, 664 insertions(+), 517 deletions(-) create mode 100644 pallets/swap/src/pallet/swap_step.rs diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index 00540ca378..5bb92c948d 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -4,7 +4,9 @@ use frame_support::pallet_prelude::*; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, CurrencyReserve, NetUid, TaoCurrency}; -pub mod order; +pub use order::*; + +mod order; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OrderType { diff --git a/pallets/swap-interface/src/order.rs b/pallets/swap-interface/src/order.rs index e96fcf45bd..44ec9513d1 100644 --- a/pallets/swap-interface/src/order.rs +++ b/pallets/swap-interface/src/order.rs @@ -1,8 +1,15 @@ use core::marker::PhantomData; -use subtensor_runtime_common::Currency; +use substrate_fixed::types::U64F64; +use subtensor_runtime_common::{AlphaCurrency, Currency, TaoCurrency}; -pub struct OrderType +pub trait Order: Clone { + fn amount(&self) -> PaidIn; + fn is_beyond_price_limit(&self, alpha_sqrt_price: U64F64, limit_sqrt_price: U64F64) -> bool; +} + +#[derive(Clone)] +pub struct BasicOrder where PaidIn: Currency, PaidOut: Currency, @@ -12,4 +19,22 @@ where _paid_out: PhantomData, } -pub trait Order {} +impl Order for BasicOrder { + fn amount(&self) -> TaoCurrency { + self.amount + } + + fn is_beyond_price_limit(&self, alpha_sqrt_price: U64F64, limit_sqrt_price: U64F64) -> bool { + alpha_sqrt_price < limit_sqrt_price + } +} + +impl Order for BasicOrder { + fn amount(&self) -> AlphaCurrency { + self.amount + } + + fn is_beyond_price_limit(&self, alpha_sqrt_price: U64F64, limit_sqrt_price: U64F64) -> bool { + alpha_sqrt_price > limit_sqrt_price + } +} diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 99fa7bf577..fe93c53ba2 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -1,4 +1,3 @@ -use core::marker::PhantomData; use core::ops::Neg; use frame_support::storage::{TransactionOutcome, transactional}; @@ -10,9 +9,10 @@ use substrate_fixed::types::{I64F64, U64F64, U96F32}; use subtensor_runtime_common::{ AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, }; -use subtensor_swap_interface::{SwapHandler, SwapResult}; +use subtensor_swap_interface::{Order as OrderT, SwapHandler, SwapResult}; use super::pallet::*; +use super::swap_step::{SwapStep, SwapStepAction}; use crate::{ OrderType, SqrtPrice, position::{Position, PositionId}, @@ -42,247 +42,6 @@ pub struct RemoveLiquidityResult { pub tick_high: TickIndex, pub liquidity: u64, } -/// A struct representing a single swap step with all its parameters and state -struct SwapStep { - // Input parameters - netuid: NetUid, - order_type: OrderType, - drop_fees: bool, - - // Computed values - current_liquidity: U64F64, - possible_delta_in: u64, - - // Ticks and prices (current, limit, edge, target) - target_sqrt_price: SqrtPrice, - limit_sqrt_price: SqrtPrice, - current_sqrt_price: SqrtPrice, - edge_sqrt_price: SqrtPrice, - edge_tick: TickIndex, - - // Result values - action: SwapStepAction, - delta_in: u64, - final_price: SqrtPrice, - fee: u64, - - // Phantom data to use T - _phantom: PhantomData, -} - -impl SwapStep { - /// Creates and initializes a new swap step - fn new( - netuid: NetUid, - order_type: OrderType, - amount_remaining: u64, - limit_sqrt_price: SqrtPrice, - drop_fees: bool, - ) -> Self { - // Calculate prices and ticks - let current_tick = CurrentTick::::get(netuid); - let current_sqrt_price = Pallet::::current_price_sqrt(netuid); - let edge_tick = Pallet::::tick_edge(netuid, current_tick, order_type); - let edge_sqrt_price = edge_tick.as_sqrt_price_bounded(); - - let fee = Pallet::::calculate_fee_amount(netuid, amount_remaining, drop_fees); - let possible_delta_in = amount_remaining.saturating_sub(fee); - - // Target price and quantities - let current_liquidity = U64F64::saturating_from_num(CurrentLiquidity::::get(netuid)); - let target_sqrt_price = Pallet::::sqrt_price_target( - order_type, - current_liquidity, - current_sqrt_price, - possible_delta_in, - ); - - Self { - netuid, - order_type, - drop_fees, - target_sqrt_price, - limit_sqrt_price, - current_sqrt_price, - edge_sqrt_price, - edge_tick, - possible_delta_in, - current_liquidity, - action: SwapStepAction::Stop, - delta_in: 0, - final_price: target_sqrt_price, - fee, - _phantom: PhantomData, - } - } - - /// Execute the swap step and return the result - fn execute(&mut self) -> Result> { - self.determine_action(); - self.process_swap() - } - - /// Returns True if sq_price1 is closer to the current price than sq_price2 - /// in terms of order direction. - /// For buying: sq_price1 <= sq_price2 - /// For selling: sq_price1 >= sq_price2 - /// - fn price_is_closer(&self, sq_price1: &SqrtPrice, sq_price2: &SqrtPrice) -> bool { - match self.order_type { - OrderType::Buy => sq_price1 <= sq_price2, - OrderType::Sell => sq_price1 >= sq_price2, - } - } - - /// 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, - // exchange the full amount, or reach the edge price. - if self.price_is_closer(&self.target_sqrt_price, &self.limit_sqrt_price) - && self.price_is_closer(&self.target_sqrt_price, &self.edge_sqrt_price) - { - // Case 1. target_quantity is the lowest - // The trade completely happens within one tick, no tick crossing happens. - self.action = SwapStepAction::Stop; - self.final_price = self.target_sqrt_price; - self.delta_in = self.possible_delta_in; - } else if self.price_is_closer(&self.limit_sqrt_price, &self.target_sqrt_price) - && self.price_is_closer(&self.limit_sqrt_price, &self.edge_sqrt_price) - { - // Case 2. lim_quantity is the lowest - // The trade also completely happens within one tick, no tick crossing happens. - self.action = SwapStepAction::Stop; - self.final_price = self.limit_sqrt_price; - self.delta_in = Self::delta_in( - self.order_type, - self.current_liquidity, - self.current_sqrt_price, - self.limit_sqrt_price, - ); - recalculate_fee = true; - } else { - // Case 3. edge_quantity is the lowest - // Tick crossing is likely - self.action = SwapStepAction::Crossing; - self.delta_in = Self::delta_in( - self.order_type, - self.current_liquidity, - self.current_sqrt_price, - self.edge_sqrt_price, - ); - self.final_price = self.edge_sqrt_price; - recalculate_fee = true; - } - - log::trace!("\tAction : {:?}", self.action); - log::trace!( - "\tCurrent Price : {}", - self.current_sqrt_price - .saturating_mul(self.current_sqrt_price) - ); - log::trace!( - "\tTarget Price : {}", - self.target_sqrt_price - .saturating_mul(self.target_sqrt_price) - ); - log::trace!( - "\tLimit Price : {}", - self.limit_sqrt_price.saturating_mul(self.limit_sqrt_price) - ); - log::trace!( - "\tEdge Price : {}", - self.edge_sqrt_price.saturating_mul(self.edge_sqrt_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 or the edge 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::(); - } - - // Now correct the action if we stopped exactly at the edge no matter what was the case above - // Because order type buy moves the price up and tick semi-open interval doesn't include its right - // point, we cross on buys and stop on sells. - let natural_reason_stop_price = - if self.price_is_closer(&self.limit_sqrt_price, &self.target_sqrt_price) { - self.limit_sqrt_price - } else { - self.target_sqrt_price - }; - if natural_reason_stop_price == self.edge_sqrt_price { - self.action = match self.order_type { - OrderType::Buy => SwapStepAction::Crossing, - OrderType::Sell => SwapStepAction::Stop, - }; - } - } - - /// Process a single step of a swap - fn process_swap(&self) -> Result> { - // Hold the fees - Pallet::::add_fees(self.netuid, self.order_type, self.fee); - let delta_out = Pallet::::convert_deltas(self.netuid, self.order_type, self.delta_in); - log::trace!("\tDelta Out : {delta_out:?}"); - - if self.action == SwapStepAction::Crossing { - let mut tick = Ticks::::get(self.netuid, self.edge_tick).unwrap_or_default(); - tick.fees_out_tao = I64F64::saturating_from_num(FeeGlobalTao::::get(self.netuid)) - .saturating_sub(tick.fees_out_tao); - tick.fees_out_alpha = - I64F64::saturating_from_num(FeeGlobalAlpha::::get(self.netuid)) - .saturating_sub(tick.fees_out_alpha); - Pallet::::update_liquidity_at_crossing(self.netuid, self.order_type)?; - Ticks::::insert(self.netuid, self.edge_tick, tick); - } - - // Update current price - AlphaSqrtPrice::::set(self.netuid, self.final_price); - - // Update current tick - let new_current_tick = TickIndex::from_sqrt_price_bounded(self.final_price); - CurrentTick::::set(self.netuid, new_current_tick); - - Ok(SwapStepResult { - amount_to_take: self.delta_in.saturating_add(self.fee), - fee_paid: self.fee, - delta_in: self.delta_in, - delta_out, - }) - } - - /// Get the input amount needed to reach the target price - fn delta_in( - order_type: OrderType, - liquidity_curr: U64F64, - sqrt_price_curr: SqrtPrice, - sqrt_price_target: SqrtPrice, - ) -> u64 { - let one = U64F64::saturating_from_num(1); - - (match order_type { - OrderType::Sell => liquidity_curr.saturating_mul( - one.safe_div(sqrt_price_target.into()) - .saturating_sub(one.safe_div(sqrt_price_curr)), - ), - OrderType::Buy => { - liquidity_curr.saturating_mul(sqrt_price_target.saturating_sub(sqrt_price_curr)) - } - }) - .saturating_to_num::() - } -} impl Pallet { pub fn current_price(netuid: NetUid) -> U96F32 { @@ -306,10 +65,6 @@ impl Pallet { } } - pub fn current_price_sqrt(netuid: NetUid) -> SqrtPrice { - AlphaSqrtPrice::::get(netuid) - } - // initializes V3 swap for a subnet if needed pub(super) fn maybe_initialize_v3(netuid: NetUid) -> Result<(), Error> { if SwapV3Initialized::::get(netuid) { @@ -372,7 +127,7 @@ impl Pallet { let (tao_fees, alpha_fees) = position.collect_fees(); // Adjust liquidity - let current_sqrt_price = Pallet::::current_price_sqrt(netuid); + let current_sqrt_price = AlphaSqrtPrice::::get(netuid); let maybe_token_amounts = position.to_token_amounts(current_sqrt_price); if let Ok((tao, alpha)) = maybe_token_amounts { // Get updated reserves, calculate liquidity @@ -428,10 +183,9 @@ impl Pallet { /// 2. Skips reserve checks — it may return an `amount_paid_out` greater than the available reserve. /// /// Use simulation mode to preview the outcome of a swap without modifying the blockchain state. - pub fn do_swap( + pub fn do_swap( netuid: NetUid, - order_type: OrderType, - amount: PaidIn, + order: Order, limit_sqrt_price: SqrtPrice, drop_fees: bool, simulate: bool, @@ -441,14 +195,14 @@ impl Pallet { PaidOut: Currency, ReserveIn: CurrencyReserve, ReserveOut: CurrencyReserve, + Order: OrderT, { transactional::with_transaction(|| { let reserve = ReserveOut::reserve(netuid.into()); - let result = Self::swap_inner::( + let result = Self::swap_inner::( netuid, - order_type, - amount, + order, limit_sqrt_price, drop_fees, ) @@ -474,10 +228,9 @@ impl Pallet { }) } - fn swap_inner( + fn swap_inner( netuid: NetUid, - order_type: OrderType, - amount: PaidIn, + order: Order, limit_sqrt_price: SqrtPrice, drop_fees: bool, ) -> Result, Error> @@ -486,6 +239,7 @@ impl Pallet { PaidOut: Currency, ReserveIn: CurrencyReserve, ReserveOut: CurrencyReserve, + Order: OrderT, { ensure!( ReserveOut::reserve(netuid).to_u64() >= T::MinimumReserve::get().get(), @@ -495,18 +249,12 @@ impl Pallet { Self::maybe_initialize_v3(netuid)?; // Because user specifies the limit price, check that it is in fact beoynd the current one - match order_type { - OrderType::Buy => ensure!( - AlphaSqrtPrice::::get(netuid) < limit_sqrt_price, - Error::::PriceLimitExceeded - ), - OrderType::Sell => ensure!( - AlphaSqrtPrice::::get(netuid) > limit_sqrt_price, - Error::::PriceLimitExceeded - ), - }; + ensure!( + order.is_beyond_price_limit(AlphaSqrtPrice::::get(netuid), limit_sqrt_price), + Error::::PriceLimitExceeded + ); - let mut amount_remaining = amount; + let mut amount_remaining = order.amount(); let mut amount_paid_out: u64 = 0; let mut iteration_counter: u16 = 0; let mut in_acc: u64 = 0; @@ -524,13 +272,8 @@ impl Pallet { ); // Create and execute a swap step - let mut swap_step = SwapStep::::new( - netuid, - order_type, - amount_remaining.into(), - limit_sqrt_price, - drop_fees, - ); + let mut swap_step = + SwapStep::::new(netuid, order, amount_remaining, limit_sqrt_price, drop_fees); let swap_result = swap_step.execute()?; @@ -573,223 +316,29 @@ impl Pallet { }) } - /// Get the tick at the current tick edge for the given direction (order type) If - /// order type is Buy, then edge tick is the high tick, otherwise it is the low - /// tick. - /// - /// If anything is wrong with tick math and it returns Err, we just abort the deal, i.e. return - /// the edge that is impossible to execute - fn tick_edge(netuid: NetUid, current_tick: TickIndex, order_type: OrderType) -> TickIndex { - match order_type { - OrderType::Buy => ActiveTickIndexManager::::find_closest_higher( - netuid, - current_tick.next().unwrap_or(TickIndex::MAX), - ) - .unwrap_or(TickIndex::MAX), - OrderType::Sell => { - let current_price = Pallet::::current_price_sqrt(netuid); - let current_tick_price = current_tick.as_sqrt_price_bounded(); - let is_active = ActiveTickIndexManager::::tick_is_active(netuid, current_tick); - - if is_active && current_price > current_tick_price { - ActiveTickIndexManager::::find_closest_lower(netuid, current_tick) - .unwrap_or(TickIndex::MIN) - } else { - ActiveTickIndexManager::::find_closest_lower( - netuid, - current_tick.prev().unwrap_or(TickIndex::MIN), - ) - .unwrap_or(TickIndex::MIN) - } - } - } - } - /// Calculate fee amount /// /// Fee is provided by state ops as u16-normalized value. - fn calculate_fee_amount(netuid: NetUid, amount: u64, drop_fees: bool) -> u64 { + pub(crate) fn calculate_fee_amount( + netuid: NetUid, + amount: C, + drop_fees: bool, + ) -> C { if drop_fees { - 0 - } else { - match T::SubnetInfo::mechanism(netuid) { - 1 => { - let fee_rate = U64F64::saturating_from_num(FeeRate::::get(netuid)) - .safe_div(U64F64::saturating_from_num(u16::MAX)); - U64F64::saturating_from_num(amount) - .saturating_mul(fee_rate) - .saturating_to_num::() - } - _ => 0, - } - } - } - - /// Add fees to the global fee counters - fn add_fees(netuid: NetUid, order_type: OrderType, fee: u64) { - let liquidity_curr = Self::current_liquidity_safe(netuid); - - if liquidity_curr == 0 { - return; - } - - let fee_global_tao = FeeGlobalTao::::get(netuid); - let fee_global_alpha = FeeGlobalAlpha::::get(netuid); - let fee_fixed = U64F64::saturating_from_num(fee); - - match order_type { - OrderType::Sell => { - FeeGlobalAlpha::::set( - netuid, - fee_global_alpha.saturating_add(fee_fixed.safe_div(liquidity_curr)), - ); - } - OrderType::Buy => { - FeeGlobalTao::::set( - netuid, - fee_global_tao.saturating_add(fee_fixed.safe_div(liquidity_curr)), - ); - } - } - } - - /// Convert input amount (delta_in) to output amount (delta_out) - /// - /// This is the core method of uniswap V3 that tells how much output token is given for an - /// amount of input token within one price tick. - pub(super) fn convert_deltas(netuid: NetUid, order_type: OrderType, delta_in: u64) -> u64 { - // Skip conversion if delta_in is zero - if delta_in == 0 { - return 0; - } - - let liquidity_curr = SqrtPrice::saturating_from_num(CurrentLiquidity::::get(netuid)); - let sqrt_price_curr = Pallet::::current_price_sqrt(netuid); - let delta_fixed = SqrtPrice::saturating_from_num(delta_in); - - // Calculate result based on order type with proper fixed-point math - // Using safe math operations throughout to prevent overflows - let result = match order_type { - OrderType::Sell => { - // liquidity_curr / (liquidity_curr / sqrt_price_curr + delta_fixed); - let denom = liquidity_curr - .safe_div(sqrt_price_curr) - .saturating_add(delta_fixed); - let a = liquidity_curr.safe_div(denom); - // a * sqrt_price_curr; - let b = a.saturating_mul(sqrt_price_curr); - - // delta_fixed * b; - delta_fixed.saturating_mul(b) - } - OrderType::Buy => { - // (liquidity_curr * sqrt_price_curr + delta_fixed) * sqrt_price_curr; - let a = liquidity_curr - .saturating_mul(sqrt_price_curr) - .saturating_add(delta_fixed) - .saturating_mul(sqrt_price_curr); - // liquidity_curr / a; - let b = liquidity_curr.safe_div(a); - // b * delta_fixed; - b.saturating_mul(delta_fixed) - } - }; - - result.saturating_to_num::() - } - - /// Get the target square root price based on the input amount - /// - /// This is the price that would be reached if - /// - There are no liquidity positions other than protocol liquidity - /// - Full delta_in amount is executed - /// - fn sqrt_price_target( - order_type: OrderType, - liquidity_curr: U64F64, - sqrt_price_curr: SqrtPrice, - delta_in: u64, - ) -> SqrtPrice { - let delta_fixed = U64F64::saturating_from_num(delta_in); - let one = U64F64::saturating_from_num(1); - - // No liquidity means that price should go to the limit - if liquidity_curr == 0 { - return match order_type { - OrderType::Sell => { - SqrtPrice::saturating_from_num(Self::min_price::().to_u64()) - } - OrderType::Buy => { - SqrtPrice::saturating_from_num(Self::max_price::().to_u64()) - } - }; - } - - match order_type { - OrderType::Sell => one.safe_div( - delta_fixed - .safe_div(liquidity_curr) - .saturating_add(one.safe_div(sqrt_price_curr)), - ), - OrderType::Buy => delta_fixed - .safe_div(liquidity_curr) - .saturating_add(sqrt_price_curr), + return C::ZERO; } - } - - /// Update liquidity when crossing a tick - fn update_liquidity_at_crossing(netuid: NetUid, order_type: OrderType) -> Result<(), Error> { - let mut liquidity_curr = CurrentLiquidity::::get(netuid); - let current_tick_index = TickIndex::current_bounded::(netuid); - // Find the appropriate tick based on order type - let tick = match order_type { - OrderType::Sell => { - // Self::find_closest_lower_active_tick(netuid, current_tick_index) - let current_price = Pallet::::current_price_sqrt(netuid); - let current_tick_price = current_tick_index.as_sqrt_price_bounded(); - let is_active = - ActiveTickIndexManager::::tick_is_active(netuid, current_tick_index); - - let lower_tick = if is_active && current_price > current_tick_price { - ActiveTickIndexManager::::find_closest_lower(netuid, current_tick_index) - .unwrap_or(TickIndex::MIN) - } else { - ActiveTickIndexManager::::find_closest_lower( - netuid, - current_tick_index.prev().unwrap_or(TickIndex::MIN), - ) - .unwrap_or(TickIndex::MIN) - }; - Ticks::::get(netuid, lower_tick) - } - OrderType::Buy => { - // Self::find_closest_higher_active_tick(netuid, current_tick_index), - let upper_tick = ActiveTickIndexManager::::find_closest_higher( - netuid, - current_tick_index.next().unwrap_or(TickIndex::MAX), - ) - .unwrap_or(TickIndex::MAX); - Ticks::::get(netuid, upper_tick) + match T::SubnetInfo::mechanism(netuid) { + 1 => { + let fee_rate = U64F64::saturating_from_num(FeeRate::::get(netuid)) + .safe_div(U64F64::saturating_from_num(u16::MAX)); + U64F64::saturating_from_num(amount) + .saturating_mul(fee_rate) + .saturating_to_num::() + .into() } + _ => C::ZERO, } - .ok_or(Error::::InsufficientLiquidity)?; - - let liquidity_update_abs_u64 = tick.liquidity_net_as_u64(); - - // Update liquidity based on the sign of liquidity_net and the order type - liquidity_curr = match (order_type, tick.liquidity_net >= 0) { - (OrderType::Sell, true) | (OrderType::Buy, false) => { - liquidity_curr.saturating_sub(liquidity_update_abs_u64) - } - (OrderType::Sell, false) | (OrderType::Buy, true) => { - liquidity_curr.saturating_add(liquidity_update_abs_u64) - } - }; - - CurrentLiquidity::::set(netuid, liquidity_curr); - - Ok(()) } pub fn find_closest_lower_active_tick(netuid: NetUid, index: TickIndex) -> Option { @@ -803,7 +352,7 @@ impl Pallet { } /// Here we subtract minimum safe liquidity from current liquidity to stay in the safe range - fn current_liquidity_safe(netuid: NetUid) -> U64F64 { + pub(crate) fn current_liquidity_safe(netuid: NetUid) -> U64F64 { U64F64::saturating_from_num( CurrentLiquidity::::get(netuid).saturating_sub(T::MinimumLiquidity::get()), ) @@ -914,7 +463,7 @@ impl Pallet { let position_id = PositionId::new::(); let position = Position::new(position_id, netuid, tick_low, tick_high, liquidity); - let current_price_sqrt = Pallet::::current_price_sqrt(netuid); + let current_price_sqrt = AlphaSqrtPrice::::get(netuid); let (tao, alpha) = position.to_token_amounts(current_price_sqrt)?; SwapV3Initialized::::set(netuid, true); @@ -993,7 +542,7 @@ impl Pallet { let mut delta_liquidity_abs = liquidity_delta.unsigned_abs(); // Determine the effective price for token calculations - let current_price_sqrt = Pallet::::current_price_sqrt(netuid); + let current_price_sqrt = AlphaSqrtPrice::::get(netuid); let sqrt_pa: SqrtPrice = position .tick_low .try_to_sqrt_price() @@ -1220,6 +769,23 @@ impl Pallet { pub fn protocol_account_id() -> T::AccountId { T::ProtocolId::get().into_account_truncating() } + + pub(crate) fn min_price_inner() -> C { + TickIndex::min_sqrt_price() + .saturating_mul(TickIndex::min_sqrt_price()) + .saturating_mul(SqrtPrice::saturating_from_num(1_000_000_000)) + .saturating_to_num::() + .into() + } + + pub(crate) fn max_price_inner() -> C { + TickIndex::max_sqrt_price() + .saturating_mul(TickIndex::max_sqrt_price()) + .saturating_mul(SqrtPrice::saturating_from_num(1_000_000_000)) + .saturating_round() + .saturating_to_num::() + .into() + } } impl SwapHandler for Pallet { @@ -1292,7 +858,7 @@ impl SwapHandler for Pallet { } fn approx_fee_amount(netuid: NetUid, amount: C) -> C { - Self::calculate_fee_amount(netuid, amount.to_u64(), false).into() + Self::calculate_fee_amount(netuid, amount, false) } fn current_alpha_price(netuid: NetUid) -> U96F32 { @@ -1300,20 +866,11 @@ impl SwapHandler for Pallet { } fn min_price() -> C { - TickIndex::min_sqrt_price() - .saturating_mul(TickIndex::min_sqrt_price()) - .saturating_mul(SqrtPrice::saturating_from_num(1_000_000_000)) - .saturating_to_num::() - .into() + Self::min_price() } fn max_price() -> C { - TickIndex::max_sqrt_price() - .saturating_mul(TickIndex::max_sqrt_price()) - .saturating_mul(SqrtPrice::saturating_from_num(1_000_000_000)) - .saturating_round() - .saturating_to_num::() - .into() + Self::max_price() } fn adjust_protocol_liquidity( @@ -1328,17 +885,3 @@ impl SwapHandler for Pallet { EnabledUserLiquidity::::get(netuid) } } - -#[derive(Debug, PartialEq)] -struct SwapStepResult { - amount_to_take: u64, - fee_paid: u64, - delta_in: u64, - delta_out: u64, -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum SwapStepAction { - Crossing, - Stop, -} diff --git a/pallets/swap/src/pallet/mod.rs b/pallets/swap/src/pallet/mod.rs index a7f3520541..c43aad4a8b 100644 --- a/pallets/swap/src/pallet/mod.rs +++ b/pallets/swap/src/pallet/mod.rs @@ -17,6 +17,7 @@ use crate::{ pub use pallet::*; mod impls; +mod swap_step; #[cfg(test)] mod tests; diff --git a/pallets/swap/src/pallet/swap_step.rs b/pallets/swap/src/pallet/swap_step.rs new file mode 100644 index 0000000000..2cffc5aa60 --- /dev/null +++ b/pallets/swap/src/pallet/swap_step.rs @@ -0,0 +1,576 @@ +use core::marker::PhantomData; + +use safe_math::*; +use substrate_fixed::types::{I64F64, U64F64}; +use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; +use subtensor_swap_interface::Order as OrderT; + +use super::pallet::*; +use crate::{ + SqrtPrice, + tick::{ActiveTickIndexManager, TickIndex}, +}; + +/// A struct representing a single swap step with all its parameters and state +pub(crate) struct BasicSwapStep +where + T: Config, + PaidIn: Currency, + PaidOut: Currency, + Order: OrderT, +{ + // Input parameters + netuid: NetUid, + order: Order, + drop_fees: bool, + + // Computed values + current_liquidity: U64F64, + possible_delta_in: PaidIn, + + // Ticks and prices (current, limit, edge, target) + target_sqrt_price: SqrtPrice, + limit_sqrt_price: SqrtPrice, + current_sqrt_price: SqrtPrice, + edge_sqrt_price: SqrtPrice, + edge_tick: TickIndex, + + // Result values + action: SwapStepAction, + delta_in: PaidIn, + final_price: SqrtPrice, + fee: PaidIn, + + // Phantom data to use T + _phantom: PhantomData, + _paid_in: PhantomData, + _paid_out: PhantomData, +} + +impl BasicSwapStep +where + T: Config, + PaidIn: Currency, + PaidOut: Currency, + Order: OrderT, + Self: SwapStep, +{ + /// Creates and initializes a new swap step + fn new( + netuid: NetUid, + order: Order, + amount_remaining: PaidIn, + limit_sqrt_price: SqrtPrice, + drop_fees: bool, + ) -> Self { + // Calculate prices and ticks + let current_tick = CurrentTick::::get(netuid); + let current_sqrt_price = AlphaSqrtPrice::::get(netuid); + let edge_tick = Self::tick_edge(netuid, current_tick); + let edge_sqrt_price = edge_tick.as_sqrt_price_bounded(); + + let fee = Pallet::::calculate_fee_amount(netuid, amount_remaining, drop_fees); + let possible_delta_in = amount_remaining.saturating_sub(fee); + + // Target price and quantities + let current_liquidity = U64F64::saturating_from_num(CurrentLiquidity::::get(netuid)); + let target_sqrt_price = + Self::sqrt_price_target(current_liquidity, current_sqrt_price, possible_delta_in); + + Self { + netuid, + order, + drop_fees, + target_sqrt_price, + limit_sqrt_price, + current_sqrt_price, + edge_sqrt_price, + edge_tick, + possible_delta_in, + current_liquidity, + action: SwapStepAction::Stop, + delta_in: PaidIn::ZERO, + final_price: target_sqrt_price, + fee, + _phantom: PhantomData, + _paid_in: PhantomData, + _paid_out: 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, + // exchange the full amount, or reach the edge price. + if Self::price_is_closer(&self.target_sqrt_price, &self.limit_sqrt_price) + && Self::price_is_closer(&self.target_sqrt_price, &self.edge_sqrt_price) + { + // Case 1. target_quantity is the lowest + // The trade completely happens within one tick, no tick crossing happens. + self.action = SwapStepAction::Stop; + self.final_price = self.target_sqrt_price; + self.delta_in = self.possible_delta_in; + } else if Self::price_is_closer(&self.limit_sqrt_price, &self.target_sqrt_price) + && Self::price_is_closer(&self.limit_sqrt_price, &self.edge_sqrt_price) + { + // Case 2. lim_quantity is the lowest + // The trade also completely happens within one tick, no tick crossing happens. + self.action = SwapStepAction::Stop; + self.final_price = self.limit_sqrt_price; + self.delta_in = Self::delta_in( + self.current_liquidity, + self.current_sqrt_price, + self.limit_sqrt_price, + ); + recalculate_fee = true; + } else { + // Case 3. edge_quantity is the lowest + // Tick crossing is likely + self.action = SwapStepAction::Crossing; + self.delta_in = Self::delta_in( + self.current_liquidity, + self.current_sqrt_price, + self.edge_sqrt_price, + ); + self.final_price = self.edge_sqrt_price; + recalculate_fee = true; + } + + log::trace!("\tAction : {:?}", self.action); + log::trace!( + "\tCurrent Price : {}", + self.current_sqrt_price + .saturating_mul(self.current_sqrt_price) + ); + log::trace!( + "\tTarget Price : {}", + self.target_sqrt_price + .saturating_mul(self.target_sqrt_price) + ); + log::trace!( + "\tLimit Price : {}", + self.limit_sqrt_price.saturating_mul(self.limit_sqrt_price) + ); + log::trace!( + "\tEdge Price : {}", + self.edge_sqrt_price.saturating_mul(self.edge_sqrt_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 or the edge 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(); + } + + // Now correct the action if we stopped exactly at the edge no matter what was the case + // above. Because order type buy moves the price up and tick semi-open interval doesn't + // include its right point, we cross on buys and stop on sells. + let natural_reason_stop_price = + if Self::price_is_closer(&self.limit_sqrt_price, &self.target_sqrt_price) { + self.limit_sqrt_price + } else { + self.target_sqrt_price + }; + if natural_reason_stop_price == self.edge_sqrt_price { + self.action = Self::action_on_edge_sqrt_price(); + } + } + + /// Process a single step of a swap + fn process_swap(&self) -> Result, Error> { + // Hold the fees + Self::add_fees( + self.netuid, + Pallet::::current_liquidity_safe(self.netuid), + self.fee, + ); + let delta_out = Self::convert_deltas(self.netuid, self.delta_in); + // log::trace!("\tDelta Out : {delta_out:?}"); + + if self.action == SwapStepAction::Crossing { + let mut tick = Ticks::::get(self.netuid, self.edge_tick).unwrap_or_default(); + tick.fees_out_tao = I64F64::saturating_from_num(FeeGlobalTao::::get(self.netuid)) + .saturating_sub(tick.fees_out_tao); + tick.fees_out_alpha = + I64F64::saturating_from_num(FeeGlobalAlpha::::get(self.netuid)) + .saturating_sub(tick.fees_out_alpha); + Self::update_liquidity_at_crossing(self.netuid)?; + Ticks::::insert(self.netuid, self.edge_tick, tick); + } + + // Update current price + AlphaSqrtPrice::::set(self.netuid, self.final_price); + + // Update current tick + let new_current_tick = TickIndex::from_sqrt_price_bounded(self.final_price); + CurrentTick::::set(self.netuid, new_current_tick); + + Ok(SwapStepResult { + amount_to_take: self.delta_in.saturating_add(self.fee), + fee_paid: self.fee, + delta_in: self.delta_in, + delta_out, + }) + } +} + +impl SwapStep + for BasicSwapStep +where + T: Config, + Order: OrderT, +{ + fn delta_in( + liquidity_curr: U64F64, + sqrt_price_curr: SqrtPrice, + sqrt_price_target: SqrtPrice, + ) -> TaoCurrency { + liquidity_curr + .saturating_mul(sqrt_price_target.saturating_sub(sqrt_price_curr)) + .saturating_to_num::() + .into() + } + + fn tick_edge(netuid: NetUid, current_tick: TickIndex) -> TickIndex { + ActiveTickIndexManager::::find_closest_higher( + netuid, + current_tick.next().unwrap_or(TickIndex::MAX), + ) + .unwrap_or(TickIndex::MAX) + } + + fn sqrt_price_target( + liquidity_curr: U64F64, + sqrt_price_curr: SqrtPrice, + delta_in: TaoCurrency, + ) -> SqrtPrice { + let delta_fixed = U64F64::saturating_from_num(delta_in); + + // No liquidity means that price should go to the limit + if liquidity_curr == 0 { + return SqrtPrice::saturating_from_num( + Pallet::::max_price_inner::().to_u64(), + ); + } + + delta_fixed + .safe_div(liquidity_curr) + .saturating_add(sqrt_price_curr) + } + + fn price_is_closer(sq_price1: &SqrtPrice, sq_price2: &SqrtPrice) -> bool { + sq_price1 <= sq_price2 + } + + fn action_on_edge_sqrt_price() -> SwapStepAction { + SwapStepAction::Crossing + } + + fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: TaoCurrency) { + if current_liquidity == 0 { + return; + } + + let fee_fixed = U64F64::saturating_from_num(fee.to_u64()); + + FeeGlobalTao::::mutate(netuid, |value| { + *value = value.saturating_add(fee_fixed.safe_div(current_liquidity)) + }); + } + + fn convert_deltas(netuid: NetUid, delta_in: TaoCurrency) -> AlphaCurrency { + // Skip conversion if delta_in is zero + if delta_in.is_zero() { + return AlphaCurrency::ZERO; + } + + let liquidity_curr = SqrtPrice::saturating_from_num(CurrentLiquidity::::get(netuid)); + let sqrt_price_curr = AlphaSqrtPrice::::get(netuid); + let delta_fixed = SqrtPrice::saturating_from_num(delta_in.to_u64()); + + // Calculate result based on order type with proper fixed-point math + // Using safe math operations throughout to prevent overflows + let result = { + // (liquidity_curr * sqrt_price_curr + delta_fixed) * sqrt_price_curr; + let a = liquidity_curr + .saturating_mul(sqrt_price_curr) + .saturating_add(delta_fixed) + .saturating_mul(sqrt_price_curr); + // liquidity_curr / a; + let b = liquidity_curr.safe_div(a); + // b * delta_fixed; + b.saturating_mul(delta_fixed) + }; + + result.saturating_to_num::().into() + } + + fn update_liquidity_at_crossing(netuid: NetUid) -> Result<(), Error> { + let mut liquidity_curr = CurrentLiquidity::::get(netuid); + let current_tick_index = TickIndex::current_bounded::(netuid); + + // Find the appropriate tick based on order type + let tick = { + // Self::find_closest_higher_active_tick(netuid, current_tick_index), + let upper_tick = ActiveTickIndexManager::::find_closest_higher( + netuid, + current_tick_index.next().unwrap_or(TickIndex::MAX), + ) + .unwrap_or(TickIndex::MAX); + Ticks::::get(netuid, upper_tick) + } + .ok_or(Error::::InsufficientLiquidity)?; + + let liquidity_update_abs_u64 = tick.liquidity_net_as_u64(); + + // Update liquidity based on the sign of liquidity_net and the order type + liquidity_curr = if tick.liquidity_net >= 0 { + liquidity_curr.saturating_add(liquidity_update_abs_u64) + } else { + liquidity_curr.saturating_sub(liquidity_update_abs_u64) + }; + + CurrentLiquidity::::set(netuid, liquidity_curr); + + Ok(()) + } +} + +impl SwapStep + for BasicSwapStep +where + T: Config, + Order: OrderT, +{ + fn delta_in( + liquidity_curr: U64F64, + sqrt_price_curr: SqrtPrice, + sqrt_price_target: SqrtPrice, + ) -> AlphaCurrency { + let one = U64F64::saturating_from_num(1); + + liquidity_curr + .saturating_mul( + one.safe_div(sqrt_price_target.into()) + .saturating_sub(one.safe_div(sqrt_price_curr)), + ) + .saturating_to_num::() + .into() + } + + fn tick_edge(netuid: NetUid, current_tick: TickIndex) -> TickIndex { + let current_price: SqrtPrice = AlphaSqrtPrice::::get(netuid); + let current_tick_price = current_tick.as_sqrt_price_bounded(); + let is_active = ActiveTickIndexManager::::tick_is_active(netuid, current_tick); + + if is_active && current_price > current_tick_price { + return ActiveTickIndexManager::::find_closest_lower(netuid, current_tick) + .unwrap_or(TickIndex::MIN); + } + + ActiveTickIndexManager::::find_closest_lower( + netuid, + current_tick.prev().unwrap_or(TickIndex::MIN), + ) + .unwrap_or(TickIndex::MIN) + } + + fn sqrt_price_target( + liquidity_curr: U64F64, + sqrt_price_curr: SqrtPrice, + delta_in: AlphaCurrency, + ) -> SqrtPrice { + let delta_fixed = U64F64::saturating_from_num(delta_in); + let one = U64F64::saturating_from_num(1); + + // No liquidity means that price should go to the limit + if liquidity_curr == 0 { + return SqrtPrice::saturating_from_num( + Pallet::::min_price_inner::().to_u64(), + ); + } + + one.safe_div( + delta_fixed + .safe_div(liquidity_curr) + .saturating_add(one.safe_div(sqrt_price_curr)), + ) + } + + fn price_is_closer(sq_price1: &SqrtPrice, sq_price2: &SqrtPrice) -> bool { + sq_price1 >= sq_price2 + } + + fn action_on_edge_sqrt_price() -> SwapStepAction { + SwapStepAction::Stop + } + + fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: AlphaCurrency) { + if current_liquidity == 0 { + return; + } + + let fee_fixed = U64F64::saturating_from_num(fee.to_u64()); + + FeeGlobalAlpha::::mutate(netuid, |value| { + *value = value.saturating_add(fee_fixed.safe_div(current_liquidity)) + }); + } + + fn convert_deltas(netuid: NetUid, delta_in: AlphaCurrency) -> TaoCurrency { + // Skip conversion if delta_in is zero + if delta_in.is_zero() { + return TaoCurrency::ZERO; + } + + let liquidity_curr = SqrtPrice::saturating_from_num(CurrentLiquidity::::get(netuid)); + let sqrt_price_curr = AlphaSqrtPrice::::get(netuid); + let delta_fixed = SqrtPrice::saturating_from_num(delta_in.to_u64()); + + // Calculate result based on order type with proper fixed-point math + // Using safe math operations throughout to prevent overflows + let result = { + // liquidity_curr / (liquidity_curr / sqrt_price_curr + delta_fixed); + let denom = liquidity_curr + .safe_div(sqrt_price_curr) + .saturating_add(delta_fixed); + let a = liquidity_curr.safe_div(denom); + // a * sqrt_price_curr; + let b = a.saturating_mul(sqrt_price_curr); + + // delta_fixed * b; + delta_fixed.saturating_mul(b) + }; + + result.saturating_to_num::().into() + } + + fn update_liquidity_at_crossing(netuid: NetUid) -> Result<(), Error> { + let mut liquidity_curr = CurrentLiquidity::::get(netuid); + let current_tick_index = TickIndex::current_bounded::(netuid); + + // Find the appropriate tick based on order type + let tick = { + // Self::find_closest_lower_active_tick(netuid, current_tick_index) + let current_price = AlphaSqrtPrice::::get(netuid); + let current_tick_price = current_tick_index.as_sqrt_price_bounded(); + let is_active = ActiveTickIndexManager::::tick_is_active(netuid, current_tick_index); + + let lower_tick = if is_active && current_price > current_tick_price { + ActiveTickIndexManager::::find_closest_lower(netuid, current_tick_index) + .unwrap_or(TickIndex::MIN) + } else { + ActiveTickIndexManager::::find_closest_lower( + netuid, + current_tick_index.prev().unwrap_or(TickIndex::MIN), + ) + .unwrap_or(TickIndex::MIN) + }; + Ticks::::get(netuid, lower_tick) + } + .ok_or(Error::::InsufficientLiquidity)?; + + let liquidity_update_abs_u64 = tick.liquidity_net_as_u64(); + + // Update liquidity based on the sign of liquidity_net and the order type + liquidity_curr = if tick.liquidity_net >= 0 { + liquidity_curr.saturating_sub(liquidity_update_abs_u64) + } else { + liquidity_curr.saturating_add(liquidity_update_abs_u64) + }; + + CurrentLiquidity::::set(netuid, liquidity_curr); + + Ok(()) + } +} + +pub(crate) trait SwapStep +where + T: Config, + PaidIn: Currency, + PaidOut: Currency, + Order: OrderT, +{ + /// Get the input amount needed to reach the target price + fn delta_in( + liquidity_curr: U64F64, + sqrt_price_curr: SqrtPrice, + sqrt_price_target: SqrtPrice, + ) -> PaidIn; + + /// Get the tick at the current tick edge. + /// + /// If anything is wrong with tick math and it returns Err, we just abort the deal, i.e. return + /// the edge that is impossible to execute + fn tick_edge(netuid: NetUid, current_tick: TickIndex) -> TickIndex; + + /// Get the target square root price based on the input amount + /// + /// This is the price that would be reached if + /// - There are no liquidity positions other than protocol liquidity + /// - Full delta_in amount is executed + fn sqrt_price_target( + liquidity_curr: U64F64, + sqrt_price_curr: SqrtPrice, + delta_in: PaidIn, + ) -> SqrtPrice; + + /// Returns True if sq_price1 is closer to the current price than sq_price2 + /// in terms of order direction. + /// For buying: sq_price1 <= sq_price2 + /// For selling: sq_price1 >= sq_price2 + fn price_is_closer(sq_price1: &SqrtPrice, sq_price2: &SqrtPrice) -> bool; + + /// Get swap step action on the edge sqrt price. + fn action_on_edge_sqrt_price() -> SwapStepAction; + + /// Add fees to the global fee counters + fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: PaidIn); + + /// Convert input amount (delta_in) to output amount (delta_out) + /// + /// This is the core method of uniswap V3 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; + + /// Update liquidity when crossing a tick + fn update_liquidity_at_crossing(netuid: NetUid) -> Result<(), Error>; +} + +#[derive(Debug, PartialEq)] +pub(crate) struct SwapStepResult +where + PaidIn: Currency, + PaidOut: Currency, +{ + amount_to_take: PaidIn, + fee_paid: PaidIn, + delta_in: PaidIn, + delta_out: PaidOut, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum SwapStepAction { + Crossing, + Stop, +} From b3be47d09c0dd6ace27669f7e47af72940282d71 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Tue, 2 Sep 2025 21:33:50 +0300 Subject: [PATCH 3/4] Integrate SwapStep into Swap implementation --- pallets/swap-interface/src/lib.rs | 48 ++++++++----- pallets/swap-interface/src/order.rs | 3 +- pallets/swap/src/lib.rs | 1 - pallets/swap/src/pallet/impls.rs | 100 +++++++++++++++------------ pallets/swap/src/pallet/swap_step.rs | 21 +++--- 5 files changed, 98 insertions(+), 75 deletions(-) diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index 5bb92c948d..4456c1a9f3 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(feature = "std"), no_std)] +use core::ops::Neg; use frame_support::pallet_prelude::*; use substrate_fixed::types::U96F32; @@ -8,17 +9,10 @@ pub use order::*; mod order; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum OrderType { - Sell, - Buy, -} - pub trait SwapHandler { - fn swap( + fn swap( netuid: NetUid, - order_t: OrderType, - amount: PaidIn, + order: OrderT, price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, @@ -27,17 +21,20 @@ pub trait SwapHandler { PaidIn: Currency, PaidOut: Currency, ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve; - fn sim_swap( + ReserveOut: CurrencyReserve, + OrderT: Order; + fn sim_swap( netuid: NetUid, - order_t: OrderType, + order: OrderT, amount: PaidIn, ) -> Result, DispatchError> where PaidIn: Currency, PaidOut: Currency, ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve; + ReserveOut: CurrencyReserve, + OrderT: Order, + Self: DefaultPriceLimit; fn approx_fee_amount(netuid: NetUid, amount: T) -> T; fn current_alpha_price(netuid: NetUid) -> U96F32; fn max_price() -> C; @@ -50,6 +47,14 @@ pub trait SwapHandler { fn is_user_liquidity_enabled(netuid: NetUid) -> bool; } +pub trait DefaultPriceLimit +where + PaidIn: Currency, + PaidOut: Currency, +{ + fn default_price_limit() -> C; +} + #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug, TypeInfo)] pub struct SwapResult where @@ -59,7 +64,18 @@ where pub amount_paid_in: PaidIn, pub amount_paid_out: PaidOut, pub fee_paid: PaidIn, - // For calculation of new tao/alpha reserves - pub tao_reserve_delta: i128, - pub alpha_reserve_delta: i128, +} + +impl SwapResult +where + PaidIn: Currency, + PaidOut: Currency, +{ + pub fn paid_in_reserve_delta(&self) -> i128 { + self.amount_paid_in.to_u64() as i128 + } + + pub fn paid_out_reserve_delta(&self) -> i128 { + (self.amount_paid_out.to_u64() as i128).neg() + } } diff --git a/pallets/swap-interface/src/order.rs b/pallets/swap-interface/src/order.rs index 44ec9513d1..537e107de1 100644 --- a/pallets/swap-interface/src/order.rs +++ b/pallets/swap-interface/src/order.rs @@ -15,8 +15,7 @@ where PaidOut: Currency, { amount: PaidIn, - _paid_in: PhantomData, - _paid_out: PhantomData, + _phantom: PhantomData<(PaidIn, PaidOut)>, } impl Order for BasicOrder { diff --git a/pallets/swap/src/lib.rs b/pallets/swap/src/lib.rs index b9d05bd435..6257df852b 100644 --- a/pallets/swap/src/lib.rs +++ b/pallets/swap/src/lib.rs @@ -1,7 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] use substrate_fixed::types::U64F64; -use subtensor_swap_interface::OrderType; pub mod pallet; pub mod position; diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index fe93c53ba2..74f534d4e7 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -9,12 +9,12 @@ use substrate_fixed::types::{I64F64, U64F64, U96F32}; use subtensor_runtime_common::{ AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, }; -use subtensor_swap_interface::{Order as OrderT, SwapHandler, SwapResult}; +use subtensor_swap_interface::{DefaultPriceLimit, Order as OrderT, SwapHandler, SwapResult}; use super::pallet::*; -use super::swap_step::{SwapStep, SwapStepAction}; +use super::swap_step::{BasicSwapStep, SwapStep, SwapStepAction}; use crate::{ - OrderType, SqrtPrice, + SqrtPrice, position::{Position, PositionId}, tick::{ActiveTickIndexManager, Tick, TickIndex}, }; @@ -72,7 +72,8 @@ impl Pallet { } // Initialize the v3: - // Reserves are re-purposed, nothing to set, just query values for liquidity and price calculation + // Reserves are re-purposed, nothing to set, just query values for liquidity and price + // calculation let tao_reserve = T::TaoReserve::reserve(netuid.into()); let alpha_reserve = T::AlphaReserve::reserve(netuid.into()); @@ -168,7 +169,8 @@ impl Pallet { /// - `order_type`: The type of the swap (e.g., Buy or Sell). /// - `amount`: The amount of tokens to swap. /// - `limit_sqrt_price`: A price limit (expressed as a square root) to bound the swap. - /// - `simulate`: If `true`, the function runs in simulation mode and does not persist any changes. + /// - `simulate`: If `true`, the function runs in simulation mode and does not persist any + /// changes. /// /// # Returns /// Returns a [`Result`] with a [`SwapResult`] on success, or a [`DispatchError`] on failure. @@ -180,7 +182,8 @@ impl Pallet { /// # Simulation Mode /// When `simulate` is set to `true`, the function: /// 1. Executes all logic without persisting any state changes (i.e., performs a dry run). - /// 2. Skips reserve checks — it may return an `amount_paid_out` greater than the available reserve. + /// 2. Skips reserve checks — it may return an `amount_paid_out` greater than the available + /// reserve. /// /// Use simulation mode to preview the outcome of a swap without modifying the blockchain state. pub fn do_swap( @@ -196,6 +199,7 @@ impl Pallet { ReserveIn: CurrencyReserve, ReserveOut: CurrencyReserve, Order: OrderT, + BasicSwapStep: SwapStep, { transactional::with_transaction(|| { let reserve = ReserveOut::reserve(netuid.into()); @@ -240,6 +244,7 @@ impl Pallet { ReserveIn: CurrencyReserve, ReserveOut: CurrencyReserve, Order: OrderT, + BasicSwapStep: SwapStep, { ensure!( ReserveOut::reserve(netuid).to_u64() >= T::MinimumReserve::get().get(), @@ -255,10 +260,10 @@ impl Pallet { ); let mut amount_remaining = order.amount(); - let mut amount_paid_out: u64 = 0; + let mut amount_paid_out = PaidOut::ZERO; let mut iteration_counter: u16 = 0; - let mut in_acc: u64 = 0; - let mut fee_acc: u64 = 0; + let mut in_acc = PaidIn::ZERO; + let mut fee_acc = PaidIn::ZERO; log::trace!("======== Start Swap ========"); log::trace!("Amount Remaining: {amount_remaining}"); @@ -272,22 +277,27 @@ impl Pallet { ); // Create and execute a swap step - let mut swap_step = - SwapStep::::new(netuid, order, amount_remaining, limit_sqrt_price, drop_fees); + let mut swap_step = BasicSwapStep::::new( + netuid, + order.clone(), + amount_remaining, + limit_sqrt_price, + drop_fees, + ); let swap_result = swap_step.execute()?; in_acc = in_acc.saturating_add(swap_result.delta_in); fee_acc = fee_acc.saturating_add(swap_result.fee_paid); - amount_remaining = amount_remaining.saturating_sub(swap_result.amount_to_take.into()); + amount_remaining = amount_remaining.saturating_sub(swap_result.amount_to_take); amount_paid_out = amount_paid_out.saturating_add(swap_result.delta_out); - if swap_step.action == SwapStepAction::Stop { + if swap_step.action() == SwapStepAction::Stop { amount_remaining = PaidIn::ZERO; } // The swap step didn't exchange anything - if swap_result.amount_to_take == 0 { + if swap_result.amount_to_take.is_zero() { amount_remaining = PaidIn::ZERO; } @@ -302,17 +312,10 @@ impl Pallet { log::trace!("\nAmount Paid Out: {amount_paid_out}"); log::trace!("======== End Swap ========"); - let (tao_reserve_delta, alpha_reserve_delta) = match order_type { - OrderType::Buy => (in_acc as i128, (amount_paid_out as i128).neg()), - OrderType::Sell => ((amount_paid_out as i128).neg(), in_acc as i128), - }; - Ok(SwapResult { - amount_paid_in: in_acc.into(), - amount_paid_out: amount_paid_out.into(), - fee_paid: fee_acc.into(), - tao_reserve_delta, - alpha_reserve_delta, + amount_paid_in: in_acc, + amount_paid_out, + fee_paid: fee_acc, }) } @@ -788,11 +791,22 @@ impl Pallet { } } +impl DefaultPriceLimit for Pallet { + fn default_price_limit() -> C { + Self::max_price_inner::() + } +} + +impl DefaultPriceLimit for Pallet { + fn default_price_limit() -> C { + Self::min_price_inner::() + } +} + impl SwapHandler for Pallet { - fn swap( + fn swap( netuid: NetUid, - order_t: OrderType, - amount: PaidIn, + order: Order, price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, @@ -802,16 +816,17 @@ impl SwapHandler for Pallet { PaidOut: Currency, ReserveIn: CurrencyReserve, ReserveOut: CurrencyReserve, + Order: OrderT, + BasicSwapStep: SwapStep, { let limit_sqrt_price = SqrtPrice::saturating_from_num(price_limit.to_u64()) .safe_div(SqrtPrice::saturating_from_num(1_000_000_000)) .checked_sqrt(SqrtPrice::saturating_from_num(0.0000000001)) .ok_or(Error::::PriceLimitExceeded)?; - Self::do_swap::( + Self::do_swap::( NetUid::from(netuid), - order_t, - amount, + order, limit_sqrt_price, drop_fees, should_rollback, @@ -819,9 +834,9 @@ impl SwapHandler for Pallet { .map_err(Into::into) } - fn sim_swap( + fn sim_swap( netuid: NetUid, - order_t: OrderType, + order: Order, amount: PaidIn, ) -> Result, DispatchError> where @@ -829,20 +844,17 @@ impl SwapHandler for Pallet { PaidOut: Currency, ReserveIn: CurrencyReserve, ReserveOut: CurrencyReserve, + Order: OrderT, + Self: DefaultPriceLimit, { match T::SubnetInfo::mechanism(netuid) { 1 => { - let price_limit = match order_t { - OrderType::Buy => Self::max_price::(), - OrderType::Sell => Self::min_price::(), - } - .to_u64(); + let price_limit = Self::default_price_limit::(); - Self::swap::( + Self::swap::( netuid, - order_t, - amount, - price_limit.into(), + order, + price_limit, false, true, ) @@ -851,8 +863,6 @@ impl SwapHandler for Pallet { amount_paid_in: amount, amount_paid_out: amount.to_u64().into(), fee_paid: 0.into(), - tao_reserve_delta: 0, - alpha_reserve_delta: 0, }), } } @@ -866,11 +876,11 @@ impl SwapHandler for Pallet { } fn min_price() -> C { - Self::min_price() + Self::min_price_inner() } fn max_price() -> C { - Self::max_price() + Self::max_price_inner() } fn adjust_protocol_liquidity( diff --git a/pallets/swap/src/pallet/swap_step.rs b/pallets/swap/src/pallet/swap_step.rs index 2cffc5aa60..6a7e0c9bc0 100644 --- a/pallets/swap/src/pallet/swap_step.rs +++ b/pallets/swap/src/pallet/swap_step.rs @@ -41,10 +41,7 @@ where final_price: SqrtPrice, fee: PaidIn, - // Phantom data to use T - _phantom: PhantomData, - _paid_in: PhantomData, - _paid_out: PhantomData, + _phantom: PhantomData<(T, PaidIn, PaidOut)>, } impl BasicSwapStep @@ -56,7 +53,7 @@ where Self: SwapStep, { /// Creates and initializes a new swap step - fn new( + pub(crate) fn new( netuid: NetUid, order: Order, amount_remaining: PaidIn, @@ -93,8 +90,6 @@ where final_price: target_sqrt_price, fee, _phantom: PhantomData, - _paid_in: PhantomData, - _paid_out: PhantomData, } } @@ -231,6 +226,10 @@ where delta_out, }) } + + pub(crate) fn action(&self) -> SwapStepAction { + self.action + } } impl SwapStep @@ -563,10 +562,10 @@ where PaidIn: Currency, PaidOut: Currency, { - amount_to_take: PaidIn, - fee_paid: PaidIn, - delta_in: PaidIn, - delta_out: PaidOut, + pub(crate) amount_to_take: PaidIn, + pub(crate) fee_paid: PaidIn, + pub(crate) delta_in: PaidIn, + pub(crate) delta_out: PaidOut, } #[derive(Clone, Copy, Debug, PartialEq)] From 48cac2fc0175ac4a83f89d44745922d8bc365e4f Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Thu, 11 Sep 2025 17:30:58 +0300 Subject: [PATCH 4/4] Resolve trait requirements --- pallets/swap-interface/src/lib.rs | 23 +++++++++- pallets/swap/src/pallet/impls.rs | 68 ++++++++++++++++++++-------- pallets/swap/src/pallet/swap_step.rs | 5 +- 3 files changed, 71 insertions(+), 25 deletions(-) diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index 4456c1a9f3..e54c5752f0 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -22,7 +22,8 @@ pub trait SwapHandler { PaidOut: Currency, ReserveIn: CurrencyReserve, ReserveOut: CurrencyReserve, - OrderT: Order; + OrderT: Order, + Self: SwapEngine; fn sim_swap( netuid: NetUid, order: OrderT, @@ -34,7 +35,8 @@ pub trait SwapHandler { ReserveIn: CurrencyReserve, ReserveOut: CurrencyReserve, OrderT: Order, - Self: DefaultPriceLimit; + Self: DefaultPriceLimit + + SwapEngine; fn approx_fee_amount(netuid: NetUid, amount: T) -> T; fn current_alpha_price(netuid: NetUid) -> U96F32; fn max_price() -> C; @@ -55,6 +57,23 @@ where fn default_price_limit() -> C; } +pub trait SwapEngine +where + PaidIn: Currency, + PaidOut: Currency, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, + OrderT: Order, +{ + fn swap( + netuid: NetUid, + order: OrderT, + price_limit: TaoCurrency, + drop_fees: bool, + should_rollback: bool, + ) -> Result, DispatchError>; +} + #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug, TypeInfo)] pub struct SwapResult where diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 74f534d4e7..c27ea9213b 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -9,7 +9,9 @@ use substrate_fixed::types::{I64F64, U64F64, U96F32}; use subtensor_runtime_common::{ AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, }; -use subtensor_swap_interface::{DefaultPriceLimit, Order as OrderT, SwapHandler, SwapResult}; +use subtensor_swap_interface::{ + DefaultPriceLimit, Order as OrderT, SwapEngine, SwapHandler, SwapResult, +}; use super::pallet::*; use super::swap_step::{BasicSwapStep, SwapStep, SwapStepAction}; @@ -186,7 +188,7 @@ impl Pallet { /// reserve. /// /// Use simulation mode to preview the outcome of a swap without modifying the blockchain state. - pub fn do_swap( + pub(crate) fn do_swap( netuid: NetUid, order: Order, limit_sqrt_price: SqrtPrice, @@ -279,7 +281,6 @@ impl Pallet { // Create and execute a swap step let mut swap_step = BasicSwapStep::::new( netuid, - order.clone(), amount_remaining, limit_sqrt_price, drop_fees, @@ -803,6 +804,39 @@ impl DefaultPriceLimit for Pallet { } } +impl + SwapEngine for Pallet +where + PaidIn: Currency, + PaidOut: Currency, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, + Order: OrderT, + BasicSwapStep: SwapStep, +{ + fn swap( + netuid: NetUid, + order: Order, + price_limit: TaoCurrency, + drop_fees: bool, + should_rollback: bool, + ) -> Result, DispatchError> { + let limit_sqrt_price = SqrtPrice::saturating_from_num(price_limit.to_u64()) + .safe_div(SqrtPrice::saturating_from_num(1_000_000_000)) + .checked_sqrt(SqrtPrice::saturating_from_num(0.0000000001)) + .ok_or(Error::::PriceLimitExceeded)?; + + Self::do_swap::( + NetUid::from(netuid), + order, + limit_sqrt_price, + drop_fees, + should_rollback, + ) + .map_err(Into::into) + } +} + impl SwapHandler for Pallet { fn swap( netuid: NetUid, @@ -817,17 +851,12 @@ impl SwapHandler for Pallet { ReserveIn: CurrencyReserve, ReserveOut: CurrencyReserve, Order: OrderT, - BasicSwapStep: SwapStep, + Self: SwapEngine, { - let limit_sqrt_price = SqrtPrice::saturating_from_num(price_limit.to_u64()) - .safe_div(SqrtPrice::saturating_from_num(1_000_000_000)) - .checked_sqrt(SqrtPrice::saturating_from_num(0.0000000001)) - .ok_or(Error::::PriceLimitExceeded)?; - - Self::do_swap::( + >::swap( NetUid::from(netuid), order, - limit_sqrt_price, + price_limit, drop_fees, should_rollback, ) @@ -845,19 +874,20 @@ impl SwapHandler for Pallet { ReserveIn: CurrencyReserve, ReserveOut: CurrencyReserve, Order: OrderT, - Self: DefaultPriceLimit, + Self: DefaultPriceLimit + + SwapEngine, { match T::SubnetInfo::mechanism(netuid) { 1 => { let price_limit = Self::default_price_limit::(); - Self::swap::( - netuid, - order, - price_limit, - false, - true, - ) + >::swap::< + PaidIn, + PaidOut, + ReserveIn, + ReserveOut, + Order, + >(netuid, order, price_limit, false, true) } _ => Ok(SwapResult { amount_paid_in: amount, diff --git a/pallets/swap/src/pallet/swap_step.rs b/pallets/swap/src/pallet/swap_step.rs index 6a7e0c9bc0..4a83c0dbeb 100644 --- a/pallets/swap/src/pallet/swap_step.rs +++ b/pallets/swap/src/pallet/swap_step.rs @@ -21,7 +21,6 @@ where { // Input parameters netuid: NetUid, - order: Order, drop_fees: bool, // Computed values @@ -41,7 +40,7 @@ where final_price: SqrtPrice, fee: PaidIn, - _phantom: PhantomData<(T, PaidIn, PaidOut)>, + _phantom: PhantomData<(T, PaidIn, PaidOut, Order)>, } impl BasicSwapStep @@ -55,7 +54,6 @@ where /// Creates and initializes a new swap step pub(crate) fn new( netuid: NetUid, - order: Order, amount_remaining: PaidIn, limit_sqrt_price: SqrtPrice, drop_fees: bool, @@ -76,7 +74,6 @@ where Self { netuid, - order, drop_fees, target_sqrt_price, limit_sqrt_price,