From 197f717d6e939da13ffcc10485a3cfac2b0e43e1 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Wed, 20 Aug 2025 16:43:57 +0300 Subject: [PATCH 01/14] 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 02/14] 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 03/14] 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 04/14] 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, From f8b18245b1610c65f5370abf75144f85e5ca4a02 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Mon, 15 Sep 2025 16:59:35 +0300 Subject: [PATCH 05/14] Simplify swap interface type parameters --- pallets/swap-interface/src/lib.rs | 66 +++----- pallets/swap-interface/src/order.rs | 42 +++-- pallets/swap/src/mock.rs | 35 ++-- pallets/swap/src/pallet/impls.rs | 116 ++++++------- pallets/swap/src/pallet/swap_step.rs | 106 ++++++------ pallets/swap/src/pallet/tests.rs | 234 ++++++++++++++------------- 6 files changed, 293 insertions(+), 306 deletions(-) diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index e54c5752f0..6ae257820c 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -10,33 +10,27 @@ pub use order::*; mod order; pub trait SwapHandler { - fn swap( + fn swap( netuid: NetUid, order: OrderT, price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result, DispatchError> + ) -> Result, DispatchError> where - PaidIn: Currency, - PaidOut: Currency, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - OrderT: Order, - Self: SwapEngine; - fn sim_swap( + OrderT: Order, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, + Self: SwapEngine; + fn sim_swap( netuid: NetUid, order: OrderT, - amount: PaidIn, - ) -> Result, DispatchError> + ) -> Result, DispatchError> where - PaidIn: Currency, - PaidOut: Currency, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - OrderT: Order, - Self: DefaultPriceLimit - + SwapEngine; + OrderT: Order, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, + Self: DefaultPriceLimit + SwapEngine; fn approx_fee_amount(netuid: NetUid, amount: T) -> T; fn current_alpha_price(netuid: NetUid) -> U96F32; fn max_price() -> C; @@ -49,21 +43,15 @@ pub trait SwapHandler { fn is_user_liquidity_enabled(netuid: NetUid) -> bool; } -pub trait DefaultPriceLimit -where - PaidIn: Currency, - PaidOut: Currency, -{ +pub trait DefaultPriceLimit { fn default_price_limit() -> C; } -pub trait SwapEngine +pub trait SwapEngine where - PaidIn: Currency, - PaidOut: Currency, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - OrderT: Order, + OrderT: Order, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, { fn swap( netuid: NetUid, @@ -71,25 +59,17 @@ where price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result, DispatchError>; + ) -> Result, DispatchError>; } #[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, +pub struct SwapResult { + pub amount_paid_in: OrderT::PaidIn, + pub amount_paid_out: OrderT::PaidOut, + pub fee_paid: OrderT::PaidIn, } -impl SwapResult -where - PaidIn: Currency, - PaidOut: Currency, -{ +impl SwapResult { pub fn paid_in_reserve_delta(&self) -> i128 { self.amount_paid_in.to_u64() as i128 } diff --git a/pallets/swap-interface/src/order.rs b/pallets/swap-interface/src/order.rs index 537e107de1..a1bbb0dcb1 100644 --- a/pallets/swap-interface/src/order.rs +++ b/pallets/swap-interface/src/order.rs @@ -1,24 +1,28 @@ -use core::marker::PhantomData; - use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaCurrency, Currency, TaoCurrency}; -pub trait Order: Clone { - fn amount(&self) -> PaidIn; +pub trait Order: Clone { + type PaidIn: Currency; + type PaidOut: Currency; + + fn with_amount(amount: Self::PaidIn) -> Self; + fn amount(&self) -> 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, -{ - amount: PaidIn, - _phantom: PhantomData<(PaidIn, PaidOut)>, +pub struct AlphaForTao { + amount: TaoCurrency, } -impl Order for BasicOrder { +impl Order for AlphaForTao { + type PaidIn = TaoCurrency; + type PaidOut = AlphaCurrency; + + fn with_amount(amount: TaoCurrency) -> Self { + Self { amount } + } + fn amount(&self) -> TaoCurrency { self.amount } @@ -28,7 +32,19 @@ impl Order for BasicOrder for BasicOrder { +#[derive(Clone)] +pub struct TaoForAlpha { + amount: AlphaCurrency, +} + +impl Order for TaoForAlpha { + type PaidIn = AlphaCurrency; + type PaidOut = TaoCurrency; + + fn with_amount(amount: AlphaCurrency) -> Self { + Self { amount } + } + fn amount(&self) -> AlphaCurrency { self.amount } diff --git a/pallets/swap/src/mock.rs b/pallets/swap/src/mock.rs index 78a8f925c8..d92fb837b5 100644 --- a/pallets/swap/src/mock.rs +++ b/pallets/swap/src/mock.rs @@ -14,7 +14,9 @@ use sp_runtime::{ BuildStorage, traits::{BlakeTwo256, IdentityLookup}, }; -use subtensor_runtime_common::{AlphaCurrency, BalanceOps, NetUid, SubnetInfo, TaoCurrency}; +use subtensor_runtime_common::{ + AlphaCurrency, BalanceOps, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, +}; use crate::pallet::EnabledUserLiquidity; @@ -83,11 +85,10 @@ parameter_types! { pub const MinimumReserves: NonZeroU64 = NonZeroU64::new(1).unwrap(); } -// Mock implementor of SubnetInfo trait -pub struct MockLiquidityProvider; +pub(crate) struct TaoReserve; -impl SubnetInfo for MockLiquidityProvider { - fn tao_reserve(netuid: NetUid) -> TaoCurrency { +impl CurrencyReserve for TaoReserve { + fn reserve(netuid: NetUid) -> TaoCurrency { match netuid.into() { 123u16 => 10_000, WRAPPING_FEES_NETUID => 100_000_000_000, @@ -96,7 +97,14 @@ impl SubnetInfo for MockLiquidityProvider { .into() } - fn alpha_reserve(netuid: NetUid) -> AlphaCurrency { + fn increase_provided(netuid: NetUid, amount: TaoCurrency) {} + fn decrease_provided(netuid: NetUid, amount: TaoCurrency) {} +} + +pub(crate) struct AlphaReserve; + +impl CurrencyReserve for AlphaReserve { + fn reserve(netuid: NetUid) -> AlphaCurrency { match netuid.into() { 123u16 => 10_000.into(), WRAPPING_FEES_NETUID => 400_000_000_000.into(), @@ -104,6 +112,14 @@ impl SubnetInfo for MockLiquidityProvider { } } + fn increase_provided(netuid: NetUid, amount: AlphaCurrency) {} + fn decrease_provided(netuid: NetUid, amount: AlphaCurrency) {} +} + +// Mock implementor of SubnetInfo trait +pub struct MockLiquidityProvider; + +impl SubnetInfo for MockLiquidityProvider { fn exists(netuid: NetUid) -> bool { netuid != NON_EXISTENT_NETUID.into() } @@ -172,16 +188,13 @@ impl BalanceOps for MockBalanceOps { ) -> Result { Ok(alpha) } - - 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) {} } impl crate::pallet::Config for Test { type RuntimeEvent = RuntimeEvent; type SubnetInfo = MockLiquidityProvider; + type TaoReserve = TaoReserve; + type AlphaReserve = AlphaReserve; type BalanceOps = MockBalanceOps; type ProtocolId = SwapProtocolId; type MaxFeeRate = MaxFeeRate; diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index c27ea9213b..c1ec795c1b 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -10,7 +10,8 @@ use subtensor_runtime_common::{ AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, }; use subtensor_swap_interface::{ - DefaultPriceLimit, Order as OrderT, SwapEngine, SwapHandler, SwapResult, + AlphaForTao, DefaultPriceLimit, Order as OrderT, SwapEngine, SwapHandler, SwapResult, + TaoForAlpha, }; use super::pallet::*; @@ -188,25 +189,23 @@ impl Pallet { /// reserve. /// /// Use simulation mode to preview the outcome of a swap without modifying the blockchain state. - pub(crate) fn do_swap( + pub(crate) fn do_swap( netuid: NetUid, order: Order, limit_sqrt_price: SqrtPrice, drop_fees: bool, simulate: bool, - ) -> Result, DispatchError> + ) -> Result, DispatchError> where - PaidIn: Currency, - PaidOut: Currency, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - Order: OrderT, - BasicSwapStep: SwapStep, + Order: OrderT, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, + BasicSwapStep: SwapStep, { transactional::with_transaction(|| { let reserve = ReserveOut::reserve(netuid.into()); - let result = Self::swap_inner::( + let result = Self::swap_inner::( netuid, order, limit_sqrt_price, @@ -234,19 +233,17 @@ impl Pallet { }) } - fn swap_inner( + fn swap_inner( netuid: NetUid, order: Order, limit_sqrt_price: SqrtPrice, drop_fees: bool, - ) -> Result, Error> + ) -> Result, Error> where - PaidIn: Currency, - PaidOut: Currency, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - Order: OrderT, - BasicSwapStep: SwapStep, + Order: OrderT, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, + BasicSwapStep: SwapStep, { ensure!( ReserveOut::reserve(netuid).to_u64() >= T::MinimumReserve::get().get(), @@ -262,10 +259,10 @@ impl Pallet { ); let mut amount_remaining = order.amount(); - let mut amount_paid_out = PaidOut::ZERO; + let mut amount_paid_out = Order::PaidOut::ZERO; let mut iteration_counter: u16 = 0; - let mut in_acc = PaidIn::ZERO; - let mut fee_acc = PaidIn::ZERO; + let mut in_acc = Order::PaidIn::ZERO; + let mut fee_acc = Order::PaidIn::ZERO; log::trace!("======== Start Swap ========"); log::trace!("Amount Remaining: {amount_remaining}"); @@ -279,7 +276,7 @@ impl Pallet { ); // Create and execute a swap step - let mut swap_step = BasicSwapStep::::new( + let mut swap_step = BasicSwapStep::::new( netuid, amount_remaining, limit_sqrt_price, @@ -294,12 +291,12 @@ impl Pallet { amount_paid_out = amount_paid_out.saturating_add(swap_result.delta_out); if swap_step.action() == SwapStepAction::Stop { - amount_remaining = PaidIn::ZERO; + amount_remaining = Order::PaidIn::ZERO; } // The swap step didn't exchange anything if swap_result.amount_to_take.is_zero() { - amount_remaining = PaidIn::ZERO; + amount_remaining = Order::PaidIn::ZERO; } iteration_counter = iteration_counter.saturating_add(1); @@ -792,27 +789,24 @@ impl Pallet { } } -impl DefaultPriceLimit for Pallet { +impl DefaultPriceLimit for Pallet { fn default_price_limit() -> C { Self::max_price_inner::() } } -impl DefaultPriceLimit for Pallet { +impl DefaultPriceLimit for Pallet { fn default_price_limit() -> C { Self::min_price_inner::() } } -impl - SwapEngine for Pallet +impl SwapEngine for Pallet where - PaidIn: Currency, - PaidOut: Currency, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - Order: OrderT, - BasicSwapStep: SwapStep, + Order: OrderT, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, + BasicSwapStep: SwapStep, { fn swap( netuid: NetUid, @@ -820,13 +814,13 @@ where price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result, DispatchError> { + ) -> 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::( + Self::do_swap::( NetUid::from(netuid), order, limit_sqrt_price, @@ -838,22 +832,20 @@ where } impl SwapHandler for Pallet { - fn swap( + fn swap( netuid: NetUid, order: Order, price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result, DispatchError> + ) -> Result, DispatchError> where - PaidIn: Currency, - PaidOut: Currency, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - Order: OrderT, - Self: SwapEngine, + Order: OrderT, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, + Self: SwapEngine, { - >::swap( + >::swap( NetUid::from(netuid), order, price_limit, @@ -863,35 +855,31 @@ impl SwapHandler for Pallet { .map_err(Into::into) } - fn sim_swap( + fn sim_swap( netuid: NetUid, order: Order, - amount: PaidIn, - ) -> Result, DispatchError> + ) -> Result, DispatchError> where - PaidIn: Currency, - PaidOut: Currency, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - Order: OrderT, - Self: DefaultPriceLimit - + SwapEngine, + Order: OrderT, + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, + Self: DefaultPriceLimit + SwapEngine, { match T::SubnetInfo::mechanism(netuid) { 1 => { let price_limit = Self::default_price_limit::(); - >::swap::< - PaidIn, - PaidOut, - ReserveIn, - ReserveOut, - Order, - >(netuid, order, price_limit, false, true) + >::swap::( + netuid, + order, + price_limit, + false, + true, + ) } _ => Ok(SwapResult { - amount_paid_in: amount, - amount_paid_out: amount.to_u64().into(), + amount_paid_in: order.amount(), + amount_paid_out: order.amount().to_u64().into(), fee_paid: 0.into(), }), } diff --git a/pallets/swap/src/pallet/swap_step.rs b/pallets/swap/src/pallet/swap_step.rs index 4a83c0dbeb..deffb0d59b 100644 --- a/pallets/swap/src/pallet/swap_step.rs +++ b/pallets/swap/src/pallet/swap_step.rs @@ -2,8 +2,8 @@ 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 subtensor_runtime_common::{Currency, NetUid}; +use subtensor_swap_interface::{AlphaForTao, Order as OrderT, TaoForAlpha}; use super::pallet::*; use crate::{ @@ -12,12 +12,10 @@ use crate::{ }; /// A struct representing a single swap step with all its parameters and state -pub(crate) struct BasicSwapStep +pub(crate) struct BasicSwapStep where T: Config, - PaidIn: Currency, - PaidOut: Currency, - Order: OrderT, + Order: OrderT, { // Input parameters netuid: NetUid, @@ -25,7 +23,7 @@ where // Computed values current_liquidity: U64F64, - possible_delta_in: PaidIn, + possible_delta_in: Order::PaidIn, // Ticks and prices (current, limit, edge, target) target_sqrt_price: SqrtPrice, @@ -36,25 +34,23 @@ where // Result values action: SwapStepAction, - delta_in: PaidIn, + delta_in: Order::PaidIn, final_price: SqrtPrice, - fee: PaidIn, + fee: Order::PaidIn, - _phantom: PhantomData<(T, PaidIn, PaidOut, Order)>, + _phantom: PhantomData<(T, Order)>, } -impl BasicSwapStep +impl BasicSwapStep where T: Config, - PaidIn: Currency, - PaidOut: Currency, - Order: OrderT, - Self: SwapStep, + Order: OrderT, + Self: SwapStep, { /// Creates and initializes a new swap step pub(crate) fn new( netuid: NetUid, - amount_remaining: PaidIn, + amount_remaining: Order::PaidIn, limit_sqrt_price: SqrtPrice, drop_fees: bool, ) -> Self { @@ -83,7 +79,7 @@ where possible_delta_in, current_liquidity, action: SwapStepAction::Stop, - delta_in: PaidIn::ZERO, + delta_in: Order::PaidIn::ZERO, final_price: target_sqrt_price, fee, _phantom: PhantomData, @@ -91,7 +87,7 @@ where } /// Execute the swap step and return the result - pub(crate) fn execute(&mut self) -> Result, Error> { + pub(crate) fn execute(&mut self) -> Result, Error> { self.determine_action(); self.process_swap() } @@ -188,7 +184,7 @@ where } /// Process a single step of a swap - fn process_swap(&self) -> Result, Error> { + fn process_swap(&self) -> Result, Error> { // Hold the fees Self::add_fees( self.netuid, @@ -229,17 +225,12 @@ where } } -impl SwapStep - for BasicSwapStep -where - T: Config, - Order: OrderT, -{ +impl SwapStep for BasicSwapStep { fn delta_in( liquidity_curr: U64F64, sqrt_price_curr: SqrtPrice, sqrt_price_target: SqrtPrice, - ) -> TaoCurrency { + ) -> ::PaidIn { liquidity_curr .saturating_mul(sqrt_price_target.saturating_sub(sqrt_price_curr)) .saturating_to_num::() @@ -257,14 +248,14 @@ where fn sqrt_price_target( liquidity_curr: U64F64, sqrt_price_curr: SqrtPrice, - delta_in: TaoCurrency, + delta_in: ::PaidIn, ) -> 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(), + Pallet::::max_price_inner::<::PaidIn>().to_u64(), ); } @@ -281,7 +272,7 @@ where SwapStepAction::Crossing } - fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: TaoCurrency) { + fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: ::PaidIn) { if current_liquidity == 0 { return; } @@ -293,10 +284,13 @@ where }); } - fn convert_deltas(netuid: NetUid, delta_in: TaoCurrency) -> AlphaCurrency { + fn convert_deltas( + netuid: NetUid, + delta_in: ::PaidIn, + ) -> ::PaidOut { // Skip conversion if delta_in is zero if delta_in.is_zero() { - return AlphaCurrency::ZERO; + return ::PaidOut::ZERO; } let liquidity_curr = SqrtPrice::saturating_from_num(CurrentLiquidity::::get(netuid)); @@ -351,17 +345,12 @@ where } } -impl SwapStep - for BasicSwapStep -where - T: Config, - Order: OrderT, -{ +impl SwapStep for BasicSwapStep { fn delta_in( liquidity_curr: U64F64, sqrt_price_curr: SqrtPrice, sqrt_price_target: SqrtPrice, - ) -> AlphaCurrency { + ) -> ::PaidIn { let one = U64F64::saturating_from_num(1); liquidity_curr @@ -393,7 +382,7 @@ where fn sqrt_price_target( liquidity_curr: U64F64, sqrt_price_curr: SqrtPrice, - delta_in: AlphaCurrency, + delta_in: ::PaidIn, ) -> SqrtPrice { let delta_fixed = U64F64::saturating_from_num(delta_in); let one = U64F64::saturating_from_num(1); @@ -401,7 +390,7 @@ where // 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(), + Pallet::::min_price_inner::<::PaidIn>().to_u64(), ); } @@ -420,7 +409,7 @@ where SwapStepAction::Stop } - fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: AlphaCurrency) { + fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: ::PaidIn) { if current_liquidity == 0 { return; } @@ -432,10 +421,13 @@ where }); } - fn convert_deltas(netuid: NetUid, delta_in: AlphaCurrency) -> TaoCurrency { + fn convert_deltas( + netuid: NetUid, + delta_in: ::PaidIn, + ) -> ::PaidOut { // Skip conversion if delta_in is zero if delta_in.is_zero() { - return TaoCurrency::ZERO; + return ::PaidOut::ZERO; } let liquidity_curr = SqrtPrice::saturating_from_num(CurrentLiquidity::::get(netuid)); @@ -500,19 +492,17 @@ where } } -pub(crate) trait SwapStep +pub(crate) trait SwapStep where T: Config, - PaidIn: Currency, - PaidOut: Currency, - Order: OrderT, + 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; + ) -> Order::PaidIn; /// Get the tick at the current tick edge. /// @@ -528,7 +518,7 @@ where fn sqrt_price_target( liquidity_curr: U64F64, sqrt_price_curr: SqrtPrice, - delta_in: PaidIn, + delta_in: Order::PaidIn, ) -> SqrtPrice; /// Returns True if sq_price1 is closer to the current price than sq_price2 @@ -541,28 +531,24 @@ where fn action_on_edge_sqrt_price() -> SwapStepAction; /// Add fees to the global fee counters - fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: PaidIn); + fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: Order::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; + fn convert_deltas(netuid: NetUid, delta_in: Order::PaidIn) -> Order::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, -{ - pub(crate) amount_to_take: PaidIn, - pub(crate) fee_paid: PaidIn, - pub(crate) delta_in: PaidIn, - pub(crate) delta_out: PaidOut, +pub(crate) struct SwapStepResult { + pub(crate) amount_to_take: Order::PaidIn, + pub(crate) fee_paid: Order::PaidIn, + pub(crate) delta_in: Order::PaidIn, + pub(crate) delta_out: Order::PaidOut, } #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/pallets/swap/src/pallet/tests.rs b/pallets/swap/src/pallet/tests.rs index 4c3a890c9b..848ecc5f21 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -7,10 +7,11 @@ use frame_support::{assert_err, assert_noop, assert_ok}; use sp_arithmetic::helpers_128bit; use sp_runtime::DispatchError; use substrate_fixed::types::U96F32; -use subtensor_runtime_common::NetUid; +use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; +use subtensor_swap_interface::BasicOrder; use super::*; -use crate::{OrderType, SqrtPrice, mock::*}; +use crate::{SqrtPrice, mock::*}; // this function is used to convert price (NON-SQRT price!) to TickIndex. it's only utility for // testing, all the implementation logic is based on sqrt prices @@ -153,8 +154,8 @@ fn test_swap_initialization() { let netuid = NetUid::from(1); // Get reserves from the mock provider - let tao = MockLiquidityProvider::tao_reserve(netuid.into()); - let alpha = MockLiquidityProvider::alpha_reserve(netuid.into()); + let tao = TaoReserve::reserve(netuid.into()); + let alpha = AlphaReserve::reserve(netuid.into()); assert_ok!(Pallet::::maybe_initialize_v3(netuid)); @@ -630,10 +631,11 @@ fn test_modify_position_basic() { // Swap to create fees on the position let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); - Pallet::::do_swap( + let order = + BasicOrder::::with_amount((liquidity / 10).into()); + Pallet::::do_swap::<_, _, TaoReserve, AlphaReserve, _>( netuid, - OrderType::Buy, - liquidity / 10, + order, sqrt_limit_price, false, false, @@ -721,134 +723,136 @@ fn test_modify_position_basic() { fn test_swap_basic() { new_test_ext().execute_with(|| { // Current price is 0.25 - // Test case is (order_type, liquidity, limit_price, output_amount) + // Test case is (order, limit_price, output_amount) [ - (OrderType::Buy, 1_000u64, 1000.0_f64, 3990_u64), - (OrderType::Sell, 1_000u64, 0.0001_f64, 250_u64), - (OrderType::Buy, 500_000_000, 1000.0, 2_000_000_000), + ( + BasicOrder::::with_amount(1000), + 1000.0_f64, + 3990_u64, + ), + ( + BasicOrder::::with_amount(1000), + 0.0001_f64, + 250_u64, + ), + ( + BasicOrder::::with_amount(500_000_000), + 1000.0, + 2_000_000_000, + ), ] .into_iter() .enumerate() - .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2, v.3)) - .for_each( - |(netuid, order_type, liquidity, limit_price, output_amount)| { - // Consumed liquidity ticks - let tick_low = TickIndex::MIN; - let tick_high = TickIndex::MAX; + .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2)) + .for_each(|(netuid, order, limit_price, output_amount)| { + // Consumed liquidity ticks + let tick_low = TickIndex::MIN; + let tick_high = TickIndex::MAX; - // Setup swap - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + // Setup swap + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - // Get tick infos before the swap - let tick_low_info_before = Ticks::::get(netuid, tick_low).unwrap_or_default(); - let tick_high_info_before = - Ticks::::get(netuid, tick_high).unwrap_or_default(); - let liquidity_before = CurrentLiquidity::::get(netuid); + // Get tick infos before the swap + let tick_low_info_before = Ticks::::get(netuid, tick_low).unwrap_or_default(); + let tick_high_info_before = Ticks::::get(netuid, tick_high).unwrap_or_default(); + let liquidity_before = CurrentLiquidity::::get(netuid); - // Get current price - let current_price = Pallet::::current_price(netuid); + // Get current price + let current_price = Pallet::::current_price(netuid); - // Swap - let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); - let swap_result = Pallet::::do_swap( - netuid, - order_type, - liquidity, - sqrt_limit_price, - false, - false, - ) - .unwrap(); - assert_abs_diff_eq!( - swap_result.amount_paid_out, - output_amount, - epsilon = output_amount / 100 - ); + // Swap + let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); + let swap_result = + Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); + assert_abs_diff_eq!( + swap_result.amount_paid_out, + output_amount, + epsilon = output_amount / 100 + ); - let (tao_delta_expected, alpha_delta_expected) = match order_type { - OrderType::Buy => (liquidity as i64, -(output_amount as i64)), - OrderType::Sell => (-(output_amount as i64), liquidity as i64), - }; + let (tao_delta_expected, alpha_delta_expected) = match order_type { + OrderType::Buy => (liquidity as i64, -(output_amount as i64)), + OrderType::Sell => (-(output_amount as i64), liquidity as i64), + }; - assert_abs_diff_eq!( - swap_result.alpha_reserve_delta, - alpha_delta_expected, - epsilon = alpha_delta_expected.abs() / 10 - ); - assert_abs_diff_eq!( - swap_result.tao_reserve_delta, - tao_delta_expected, - epsilon = tao_delta_expected.abs() / 10 - ); + assert_abs_diff_eq!( + swap_result.alpha_reserve_delta, + alpha_delta_expected, + epsilon = alpha_delta_expected.abs() / 10 + ); + assert_abs_diff_eq!( + swap_result.tao_reserve_delta, + tao_delta_expected, + epsilon = tao_delta_expected.abs() / 10 + ); - // Check that low and high ticks' fees were updated properly, and liquidity values were not updated - let tick_low_info = Ticks::::get(netuid, tick_low).unwrap(); - let tick_high_info = Ticks::::get(netuid, tick_high).unwrap(); - let expected_liquidity_net_low = tick_low_info_before.liquidity_net; - let expected_liquidity_gross_low = tick_low_info_before.liquidity_gross; - let expected_liquidity_net_high = tick_high_info_before.liquidity_net; - let expected_liquidity_gross_high = tick_high_info_before.liquidity_gross; - assert_eq!(tick_low_info.liquidity_net, expected_liquidity_net_low,); - assert_eq!(tick_low_info.liquidity_gross, expected_liquidity_gross_low,); - assert_eq!(tick_high_info.liquidity_net, expected_liquidity_net_high,); - assert_eq!( - tick_high_info.liquidity_gross, - expected_liquidity_gross_high, - ); + // Check that low and high ticks' fees were updated properly, and liquidity values were not updated + let tick_low_info = Ticks::::get(netuid, tick_low).unwrap(); + let tick_high_info = Ticks::::get(netuid, tick_high).unwrap(); + let expected_liquidity_net_low = tick_low_info_before.liquidity_net; + let expected_liquidity_gross_low = tick_low_info_before.liquidity_gross; + let expected_liquidity_net_high = tick_high_info_before.liquidity_net; + let expected_liquidity_gross_high = tick_high_info_before.liquidity_gross; + assert_eq!(tick_low_info.liquidity_net, expected_liquidity_net_low,); + assert_eq!(tick_low_info.liquidity_gross, expected_liquidity_gross_low,); + assert_eq!(tick_high_info.liquidity_net, expected_liquidity_net_high,); + assert_eq!( + tick_high_info.liquidity_gross, + expected_liquidity_gross_high, + ); - // Expected fee amount - let fee_rate = FeeRate::::get(netuid) as f64 / u16::MAX as f64; - let expected_fee = (liquidity as f64 * fee_rate) as u64; + // Expected fee amount + let fee_rate = FeeRate::::get(netuid) as f64 / u16::MAX as f64; + let expected_fee = (liquidity as f64 * fee_rate) as u64; - // Global fees should be updated - let actual_global_fee = ((match order_type { - OrderType::Buy => FeeGlobalTao::::get(netuid), - OrderType::Sell => FeeGlobalAlpha::::get(netuid), - }) - .to_num::() - * (liquidity_before as f64)) as u64; + // Global fees should be updated + let actual_global_fee = ((match order_type { + OrderType::Buy => FeeGlobalTao::::get(netuid), + OrderType::Sell => FeeGlobalAlpha::::get(netuid), + }) + .to_num::() + * (liquidity_before as f64)) as u64; - assert!((swap_result.fee_paid as i64 - expected_fee as i64).abs() <= 1); - assert!((actual_global_fee as i64 - expected_fee as i64).abs() <= 1); + assert!((swap_result.fee_paid as i64 - expected_fee as i64).abs() <= 1); + assert!((actual_global_fee as i64 - expected_fee as i64).abs() <= 1); - // Tick fees should be updated + // Tick fees should be updated - // Liquidity position should not be updated - let protocol_id = Pallet::::protocol_account_id(); - let positions = Positions::::iter_prefix_values((netuid, protocol_id)) - .collect::>(); - let position = positions.first().unwrap(); + // Liquidity position should not be updated + let protocol_id = Pallet::::protocol_account_id(); + let positions = + Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); + let position = positions.first().unwrap(); - assert_eq!( - position.liquidity, - helpers_128bit::sqrt( - MockLiquidityProvider::tao_reserve(netuid.into()).to_u64() as u128 - * MockLiquidityProvider::alpha_reserve(netuid.into()).to_u64() as u128 - ) as u64 - ); - assert_eq!(position.tick_low, tick_low); - assert_eq!(position.tick_high, tick_high); - assert_eq!(position.fees_alpha, 0); - assert_eq!(position.fees_tao, 0); + assert_eq!( + position.liquidity, + helpers_128bit::sqrt( + MockLiquidityProvider::tao_reserve(netuid.into()).to_u64() as u128 + * MockLiquidityProvider::alpha_reserve(netuid.into()).to_u64() as u128 + ) as u64 + ); + assert_eq!(position.tick_low, tick_low); + assert_eq!(position.tick_high, tick_high); + assert_eq!(position.fees_alpha, 0); + assert_eq!(position.fees_tao, 0); - // Current liquidity is not updated - assert_eq!(CurrentLiquidity::::get(netuid), liquidity_before); + // Current liquidity is not updated + assert_eq!(CurrentLiquidity::::get(netuid), liquidity_before); - // Assert that price movement is in correct direction - let sqrt_current_price_after = Pallet::::current_price_sqrt(netuid); - let current_price_after = Pallet::::current_price(netuid); - match order_type { - OrderType::Buy => assert!(current_price_after >= current_price), - OrderType::Sell => assert!(current_price_after <= current_price), - } + // Assert that price movement is in correct direction + let sqrt_current_price_after = Pallet::::current_price_sqrt(netuid); + let current_price_after = Pallet::::current_price(netuid); + match order_type { + OrderType::Buy => assert!(current_price_after >= current_price), + OrderType::Sell => assert!(current_price_after <= current_price), + } - // Assert that current tick is updated - let current_tick = CurrentTick::::get(netuid); - let expected_current_tick = - TickIndex::from_sqrt_price_bounded(sqrt_current_price_after); - assert_eq!(current_tick, expected_current_tick); - }, - ); + // Assert that current tick is updated + let current_tick = CurrentTick::::get(netuid); + let expected_current_tick = + TickIndex::from_sqrt_price_bounded(sqrt_current_price_after); + assert_eq!(current_tick, expected_current_tick); + }); }); } From f34603bdb88501f997b5e209b38fe389bcf484cc Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Thu, 18 Sep 2025 17:57:37 +0300 Subject: [PATCH 06/14] Restore swap tests --- pallets/swap/src/pallet/tests.rs | 862 ++++++++++++++++++++++++++----- 1 file changed, 742 insertions(+), 120 deletions(-) diff --git a/pallets/swap/src/pallet/tests.rs b/pallets/swap/src/pallet/tests.rs index 848ecc5f21..dc7f08baa8 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -7,11 +7,10 @@ use frame_support::{assert_err, assert_noop, assert_ok}; use sp_arithmetic::helpers_128bit; use sp_runtime::DispatchError; use substrate_fixed::types::U96F32; -use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; -use subtensor_swap_interface::BasicOrder; +use subtensor_runtime_common::NetUid; use super::*; -use crate::{SqrtPrice, mock::*}; +use crate::{OrderType, SqrtPrice, mock::*}; // this function is used to convert price (NON-SQRT price!) to TickIndex. it's only utility for // testing, all the implementation logic is based on sqrt prices @@ -142,7 +141,7 @@ mod dispatchables { NON_EXISTENT_NETUID.into(), true ), - Error::::SubNetworkDoesNotExist + Error::::MechanismDoesNotExist ); }); } @@ -154,8 +153,8 @@ fn test_swap_initialization() { let netuid = NetUid::from(1); // Get reserves from the mock provider - let tao = TaoReserve::reserve(netuid.into()); - let alpha = AlphaReserve::reserve(netuid.into()); + let tao = MockLiquidityProvider::tao_reserve(netuid.into()); + let alpha = MockLiquidityProvider::alpha_reserve(netuid.into()); assert_ok!(Pallet::::maybe_initialize_v3(netuid)); @@ -343,6 +342,40 @@ fn test_add_liquidity_basic() { }); } +#[test] +fn test_add_liquidity_max_limit_enforced() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + let liquidity = 2_000_000_000_u64; + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + let limit = MaxPositions::get() as usize; + + for _ in 0..limit { + Pallet::::do_add_liquidity( + netuid, + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + TickIndex::MIN, + TickIndex::MAX, + liquidity, + ) + .unwrap(); + } + + let test_result = Pallet::::do_add_liquidity( + netuid, + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + TickIndex::MIN, + TickIndex::MAX, + liquidity, + ); + + assert_err!(test_result, Error::::MaxPositionsExceeded); + }); +} + #[test] fn test_add_liquidity_out_of_bounds() { new_test_ext().execute_with(|| { @@ -631,11 +664,10 @@ fn test_modify_position_basic() { // Swap to create fees on the position let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); - let order = - BasicOrder::::with_amount((liquidity / 10).into()); - Pallet::::do_swap::<_, _, TaoReserve, AlphaReserve, _>( + Pallet::::do_swap( netuid, - order, + OrderType::Buy, + liquidity / 10, sqrt_limit_price, false, false, @@ -723,136 +755,134 @@ fn test_modify_position_basic() { fn test_swap_basic() { new_test_ext().execute_with(|| { // Current price is 0.25 - // Test case is (order, limit_price, output_amount) + // Test case is (order_type, liquidity, limit_price, output_amount) [ - ( - BasicOrder::::with_amount(1000), - 1000.0_f64, - 3990_u64, - ), - ( - BasicOrder::::with_amount(1000), - 0.0001_f64, - 250_u64, - ), - ( - BasicOrder::::with_amount(500_000_000), - 1000.0, - 2_000_000_000, - ), + (OrderType::Buy, 1_000u64, 1000.0_f64, 3990_u64), + (OrderType::Sell, 1_000u64, 0.0001_f64, 250_u64), + (OrderType::Buy, 500_000_000, 1000.0, 2_000_000_000), ] .into_iter() .enumerate() - .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2)) - .for_each(|(netuid, order, limit_price, output_amount)| { - // Consumed liquidity ticks - let tick_low = TickIndex::MIN; - let tick_high = TickIndex::MAX; + .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2, v.3)) + .for_each( + |(netuid, order_type, liquidity, limit_price, output_amount)| { + // Consumed liquidity ticks + let tick_low = TickIndex::MIN; + let tick_high = TickIndex::MAX; - // Setup swap - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + // Setup swap + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - // Get tick infos before the swap - let tick_low_info_before = Ticks::::get(netuid, tick_low).unwrap_or_default(); - let tick_high_info_before = Ticks::::get(netuid, tick_high).unwrap_or_default(); - let liquidity_before = CurrentLiquidity::::get(netuid); + // Get tick infos before the swap + let tick_low_info_before = Ticks::::get(netuid, tick_low).unwrap_or_default(); + let tick_high_info_before = + Ticks::::get(netuid, tick_high).unwrap_or_default(); + let liquidity_before = CurrentLiquidity::::get(netuid); - // Get current price - let current_price = Pallet::::current_price(netuid); + // Get current price + let current_price = Pallet::::current_price(netuid); - // Swap - let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); - let swap_result = - Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); - assert_abs_diff_eq!( - swap_result.amount_paid_out, - output_amount, - epsilon = output_amount / 100 - ); + // Swap + let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); + let swap_result = Pallet::::do_swap( + netuid, + order_type, + liquidity, + sqrt_limit_price, + false, + false, + ) + .unwrap(); + assert_abs_diff_eq!( + swap_result.amount_paid_out, + output_amount, + epsilon = output_amount / 100 + ); - let (tao_delta_expected, alpha_delta_expected) = match order_type { - OrderType::Buy => (liquidity as i64, -(output_amount as i64)), - OrderType::Sell => (-(output_amount as i64), liquidity as i64), - }; + let (tao_delta_expected, alpha_delta_expected) = match order_type { + OrderType::Buy => (liquidity as i64, -(output_amount as i64)), + OrderType::Sell => (-(output_amount as i64), liquidity as i64), + }; - assert_abs_diff_eq!( - swap_result.alpha_reserve_delta, - alpha_delta_expected, - epsilon = alpha_delta_expected.abs() / 10 - ); - assert_abs_diff_eq!( - swap_result.tao_reserve_delta, - tao_delta_expected, - epsilon = tao_delta_expected.abs() / 10 - ); + assert_abs_diff_eq!( + swap_result.alpha_reserve_delta, + alpha_delta_expected, + epsilon = alpha_delta_expected.abs() / 10 + ); + assert_abs_diff_eq!( + swap_result.tao_reserve_delta, + tao_delta_expected, + epsilon = tao_delta_expected.abs() / 10 + ); - // Check that low and high ticks' fees were updated properly, and liquidity values were not updated - let tick_low_info = Ticks::::get(netuid, tick_low).unwrap(); - let tick_high_info = Ticks::::get(netuid, tick_high).unwrap(); - let expected_liquidity_net_low = tick_low_info_before.liquidity_net; - let expected_liquidity_gross_low = tick_low_info_before.liquidity_gross; - let expected_liquidity_net_high = tick_high_info_before.liquidity_net; - let expected_liquidity_gross_high = tick_high_info_before.liquidity_gross; - assert_eq!(tick_low_info.liquidity_net, expected_liquidity_net_low,); - assert_eq!(tick_low_info.liquidity_gross, expected_liquidity_gross_low,); - assert_eq!(tick_high_info.liquidity_net, expected_liquidity_net_high,); - assert_eq!( - tick_high_info.liquidity_gross, - expected_liquidity_gross_high, - ); + // Check that low and high ticks' fees were updated properly, and liquidity values were not updated + let tick_low_info = Ticks::::get(netuid, tick_low).unwrap(); + let tick_high_info = Ticks::::get(netuid, tick_high).unwrap(); + let expected_liquidity_net_low = tick_low_info_before.liquidity_net; + let expected_liquidity_gross_low = tick_low_info_before.liquidity_gross; + let expected_liquidity_net_high = tick_high_info_before.liquidity_net; + let expected_liquidity_gross_high = tick_high_info_before.liquidity_gross; + assert_eq!(tick_low_info.liquidity_net, expected_liquidity_net_low,); + assert_eq!(tick_low_info.liquidity_gross, expected_liquidity_gross_low,); + assert_eq!(tick_high_info.liquidity_net, expected_liquidity_net_high,); + assert_eq!( + tick_high_info.liquidity_gross, + expected_liquidity_gross_high, + ); - // Expected fee amount - let fee_rate = FeeRate::::get(netuid) as f64 / u16::MAX as f64; - let expected_fee = (liquidity as f64 * fee_rate) as u64; + // Expected fee amount + let fee_rate = FeeRate::::get(netuid) as f64 / u16::MAX as f64; + let expected_fee = (liquidity as f64 * fee_rate) as u64; - // Global fees should be updated - let actual_global_fee = ((match order_type { - OrderType::Buy => FeeGlobalTao::::get(netuid), - OrderType::Sell => FeeGlobalAlpha::::get(netuid), - }) - .to_num::() - * (liquidity_before as f64)) as u64; + // Global fees should be updated + let actual_global_fee = ((match order_type { + OrderType::Buy => FeeGlobalTao::::get(netuid), + OrderType::Sell => FeeGlobalAlpha::::get(netuid), + }) + .to_num::() + * (liquidity_before as f64)) as u64; - assert!((swap_result.fee_paid as i64 - expected_fee as i64).abs() <= 1); - assert!((actual_global_fee as i64 - expected_fee as i64).abs() <= 1); + assert!((swap_result.fee_paid as i64 - expected_fee as i64).abs() <= 1); + assert!((actual_global_fee as i64 - expected_fee as i64).abs() <= 1); - // Tick fees should be updated + // Tick fees should be updated - // Liquidity position should not be updated - let protocol_id = Pallet::::protocol_account_id(); - let positions = - Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); - let position = positions.first().unwrap(); + // Liquidity position should not be updated + let protocol_id = Pallet::::protocol_account_id(); + let positions = Positions::::iter_prefix_values((netuid, protocol_id)) + .collect::>(); + let position = positions.first().unwrap(); - assert_eq!( - position.liquidity, - helpers_128bit::sqrt( - MockLiquidityProvider::tao_reserve(netuid.into()).to_u64() as u128 - * MockLiquidityProvider::alpha_reserve(netuid.into()).to_u64() as u128 - ) as u64 - ); - assert_eq!(position.tick_low, tick_low); - assert_eq!(position.tick_high, tick_high); - assert_eq!(position.fees_alpha, 0); - assert_eq!(position.fees_tao, 0); + assert_eq!( + position.liquidity, + helpers_128bit::sqrt( + MockLiquidityProvider::tao_reserve(netuid.into()).to_u64() as u128 + * MockLiquidityProvider::alpha_reserve(netuid.into()).to_u64() as u128 + ) as u64 + ); + assert_eq!(position.tick_low, tick_low); + assert_eq!(position.tick_high, tick_high); + assert_eq!(position.fees_alpha, 0); + assert_eq!(position.fees_tao, 0); - // Current liquidity is not updated - assert_eq!(CurrentLiquidity::::get(netuid), liquidity_before); + // Current liquidity is not updated + assert_eq!(CurrentLiquidity::::get(netuid), liquidity_before); - // Assert that price movement is in correct direction - let sqrt_current_price_after = Pallet::::current_price_sqrt(netuid); - let current_price_after = Pallet::::current_price(netuid); - match order_type { - OrderType::Buy => assert!(current_price_after >= current_price), - OrderType::Sell => assert!(current_price_after <= current_price), - } + // Assert that price movement is in correct direction + let sqrt_current_price_after = Pallet::::current_price_sqrt(netuid); + let current_price_after = Pallet::::current_price(netuid); + match order_type { + OrderType::Buy => assert!(current_price_after >= current_price), + OrderType::Sell => assert!(current_price_after <= current_price), + } - // Assert that current tick is updated - let current_tick = CurrentTick::::get(netuid); - let expected_current_tick = - TickIndex::from_sqrt_price_bounded(sqrt_current_price_after); - assert_eq!(current_tick, expected_current_tick); - }); + // Assert that current tick is updated + let current_tick = CurrentTick::::get(netuid); + let expected_current_tick = + TickIndex::from_sqrt_price_bounded(sqrt_current_price_after); + assert_eq!(current_tick, expected_current_tick); + }, + ); }); } @@ -1914,3 +1944,595 @@ fn test_less_price_movement() { }); }); } + +#[test] +fn test_swap_subtoken_disabled() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(SUBTOKEN_DISABLED_NETUID); // Use a netuid not used elsewhere + let price_low = 0.1; + let price_high = 0.2; + let tick_low = price_to_tick(price_low); + let tick_high = price_to_tick(price_high); + let liquidity = 1_000_000_u64; + + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + assert_noop!( + Pallet::::add_liquidity( + RuntimeOrigin::signed(OK_COLDKEY_ACCOUNT_ID), + OK_HOTKEY_ACCOUNT_ID, + netuid, + tick_low, + tick_high, + liquidity, + ), + Error::::SubtokenDisabled + ); + + assert_noop!( + Pallet::::modify_position( + RuntimeOrigin::signed(OK_COLDKEY_ACCOUNT_ID), + OK_HOTKEY_ACCOUNT_ID, + netuid, + PositionId::from(0), + liquidity as i64, + ), + Error::::SubtokenDisabled + ); + }); +} + +/// V3 path: protocol + user positions exist, fees accrued, everything must be removed. +#[test] +fn test_liquidate_v3_removes_positions_ticks_and_state() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + + // Initialize V3 (creates protocol position, ticks, price, liquidity) + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + assert!(SwapV3Initialized::::get(netuid)); + + // Enable user LP (mock usually enables for 0..=100, but be explicit and consistent) + assert_ok!(Swap::toggle_user_liquidity( + RuntimeOrigin::root(), + netuid.into(), + true + )); + + // Add a user position across the full range to ensure ticks/bitmap are populated. + let min_price = tick_to_price(TickIndex::MIN); + let max_price = tick_to_price(TickIndex::MAX); + let tick_low = price_to_tick(min_price); + let tick_high = price_to_tick(max_price); + let liquidity = 2_000_000_000_u64; + + let (_pos_id, _tao, _alpha) = Pallet::::do_add_liquidity( + netuid, + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + tick_low, + tick_high, + liquidity, + ) + .expect("add liquidity"); + + // Accrue some global fees so we can verify fee storage is cleared later. + let sqrt_limit_price = SqrtPrice::from_num(1_000_000.0); + assert_ok!(Pallet::::do_swap( + netuid, + OrderType::Buy, + 1_000_000, + sqrt_limit_price, + false, + false + )); + + // Sanity: protocol & user positions exist, ticks exist, liquidity > 0 + let protocol_id = Pallet::::protocol_account_id(); + let prot_positions = + Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); + assert!(!prot_positions.is_empty()); + + let user_positions = Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .collect::>(); + assert_eq!(user_positions.len(), 1); + + assert!(Ticks::::get(netuid, TickIndex::MIN).is_some()); + assert!(Ticks::::get(netuid, TickIndex::MAX).is_some()); + assert!(CurrentLiquidity::::get(netuid) > 0); + + // There should be some bitmap words (active ticks) after adding a position. + let had_bitmap_words = TickIndexBitmapWords::::iter_prefix((netuid,)) + .next() + .is_some(); + assert!(had_bitmap_words); + + // ACT: Liquidate & reset swap state + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + + // ASSERT: positions cleared (both user and protocol) + assert_eq!( + Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), + 0 + ); + let prot_positions_after = + Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); + assert!(prot_positions_after.is_empty()); + let user_positions_after = + Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .collect::>(); + assert!(user_positions_after.is_empty()); + + // ASSERT: ticks cleared + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert!(Ticks::::get(netuid, TickIndex::MIN).is_none()); + assert!(Ticks::::get(netuid, TickIndex::MAX).is_none()); + + // ASSERT: fee globals cleared + assert!(!FeeGlobalTao::::contains_key(netuid)); + assert!(!FeeGlobalAlpha::::contains_key(netuid)); + + // ASSERT: price/tick/liquidity flags cleared + assert!(!AlphaSqrtPrice::::contains_key(netuid)); + assert!(!CurrentTick::::contains_key(netuid)); + assert!(!CurrentLiquidity::::contains_key(netuid)); + assert!(!SwapV3Initialized::::contains_key(netuid)); + + // ASSERT: active tick bitmap cleared + assert!( + TickIndexBitmapWords::::iter_prefix((netuid,)) + .next() + .is_none() + ); + + // ASSERT: knobs removed on dereg + assert!(!FeeRate::::contains_key(netuid)); + assert!(!EnabledUserLiquidity::::contains_key(netuid)); + }); +} + +/// V3 path with user liquidity disabled at teardown: must still remove all positions and clear state. +#[test] +fn test_liquidate_v3_with_user_liquidity_disabled() { + new_test_ext().execute_with(|| { + // Pick a netuid the mock treats as "disabled" by default (per your comment >100), + // then explicitly walk through enable -> add -> disable -> liquidate. + let netuid = NetUid::from(101); + + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + assert!(SwapV3Initialized::::get(netuid)); + + // Enable temporarily to add a user position + assert_ok!(Swap::toggle_user_liquidity( + RuntimeOrigin::root(), + netuid.into(), + true + )); + + let min_price = tick_to_price(TickIndex::MIN); + let max_price = tick_to_price(TickIndex::MAX); + let tick_low = price_to_tick(min_price); + let tick_high = price_to_tick(max_price); + let liquidity = 1_000_000_000_u64; + + let (_pos_id, _tao, _alpha) = Pallet::::do_add_liquidity( + netuid, + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + tick_low, + tick_high, + liquidity, + ) + .expect("add liquidity"); + + // Disable user LP *before* liquidation to validate that removal ignores this flag. + assert_ok!(Swap::toggle_user_liquidity( + RuntimeOrigin::root(), + netuid.into(), + false + )); + + // ACT + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + + // ASSERT: positions & ticks gone, state reset + assert_eq!( + Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), + 0 + ); + assert!( + Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .next() + .is_none() + ); + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert!( + TickIndexBitmapWords::::iter_prefix((netuid,)) + .next() + .is_none() + ); + assert!(!SwapV3Initialized::::contains_key(netuid)); + assert!(!AlphaSqrtPrice::::contains_key(netuid)); + assert!(!CurrentTick::::contains_key(netuid)); + assert!(!CurrentLiquidity::::contains_key(netuid)); + assert!(!FeeGlobalTao::::contains_key(netuid)); + assert!(!FeeGlobalAlpha::::contains_key(netuid)); + + // `EnabledUserLiquidity` is removed by liquidation. + assert!(!EnabledUserLiquidity::::contains_key(netuid)); + }); +} + +/// Non‑V3 path: V3 not initialized (no positions); function must still clear any residual storages and succeed. +#[test] +fn test_liquidate_non_v3_uninitialized_ok_and_clears() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(202); + + // Sanity: V3 is not initialized + assert!(!SwapV3Initialized::::get(netuid)); + assert!( + Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .next() + .is_none() + ); + + // ACT + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + + // ASSERT: Defensive clears leave no residues and do not panic + assert!( + Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .next() + .is_none() + ); + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert!( + TickIndexBitmapWords::::iter_prefix((netuid,)) + .next() + .is_none() + ); + + // All single-key maps should not have the key after liquidation + assert!(!FeeGlobalTao::::contains_key(netuid)); + assert!(!FeeGlobalAlpha::::contains_key(netuid)); + assert!(!CurrentLiquidity::::contains_key(netuid)); + assert!(!CurrentTick::::contains_key(netuid)); + assert!(!AlphaSqrtPrice::::contains_key(netuid)); + assert!(!SwapV3Initialized::::contains_key(netuid)); + assert!(!FeeRate::::contains_key(netuid)); + assert!(!EnabledUserLiquidity::::contains_key(netuid)); + }); +} + +/// Idempotency: calling liquidation twice is safe (both V3 and non‑V3 flavors). +#[test] +fn test_liquidate_idempotent() { + // V3 flavor + new_test_ext().execute_with(|| { + let netuid = NetUid::from(7); + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + // Add a small user position + assert_ok!(Swap::toggle_user_liquidity( + RuntimeOrigin::root(), + netuid.into(), + true + )); + let tick_low = price_to_tick(0.2); + let tick_high = price_to_tick(0.3); + assert_ok!(Pallet::::do_add_liquidity( + netuid, + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + tick_low, + tick_high, + 123_456_789 + )); + + // 1st liquidation + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + // 2nd liquidation (no state left) — must still succeed + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + + // State remains empty + assert!( + Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .next() + .is_none() + ); + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert!( + TickIndexBitmapWords::::iter_prefix((netuid,)) + .next() + .is_none() + ); + assert!(!SwapV3Initialized::::contains_key(netuid)); + }); + + // Non‑V3 flavor + new_test_ext().execute_with(|| { + let netuid = NetUid::from(8); + + // Never initialize V3 + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + + assert!( + Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .next() + .is_none() + ); + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert!( + TickIndexBitmapWords::::iter_prefix((netuid,)) + .next() + .is_none() + ); + assert!(!SwapV3Initialized::::contains_key(netuid)); + }); +} + +#[test] +fn liquidate_v3_refunds_user_funds_and_clears_state() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + + // Enable V3 path & initialize price/ticks (also creates a protocol position). + assert_ok!(Pallet::::toggle_user_liquidity( + RuntimeOrigin::root(), + netuid, + true + )); + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + // Use distinct cold/hot to demonstrate alpha refund goes to (owner, owner). + let cold = OK_COLDKEY_ACCOUNT_ID; + let hot = OK_HOTKEY_ACCOUNT_ID; + + // Tight in‑range band around current tick. + let ct = CurrentTick::::get(netuid); + let tick_low = ct.saturating_sub(10); + let tick_high = ct.saturating_add(10); + let liquidity: u64 = 1_000_000; + + // Snapshot balances BEFORE. + let tao_before = ::BalanceOps::tao_balance(&cold); + let alpha_before_hot = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); + let alpha_before_owner = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); + let alpha_before_total = alpha_before_hot + alpha_before_owner; + + // Create the user position (storage & v3 state only; no balances moved yet). + let (_pos_id, need_tao, need_alpha) = + Pallet::::do_add_liquidity(netuid, &cold, &hot, tick_low, tick_high, liquidity) + .expect("add liquidity"); + + // Mirror extrinsic bookkeeping: withdraw funds & bump provided‑reserve counters. + let tao_taken = ::BalanceOps::decrease_balance(&cold, need_tao.into()) + .expect("decrease TAO"); + let alpha_taken = ::BalanceOps::decrease_stake( + &cold, + &hot, + netuid.into(), + need_alpha.into(), + ) + .expect("decrease ALPHA"); + ::BalanceOps::increase_provided_tao_reserve(netuid.into(), tao_taken); + ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), alpha_taken); + + // Liquidate everything on the subnet. + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + + // Expect balances restored to BEFORE snapshots (no swaps ran -> zero fees). + // TAO: we withdrew 'need_tao' above and liquidation refunded it, so we should be back to 'tao_before'. + let tao_after = ::BalanceOps::tao_balance(&cold); + assert_eq!(tao_after, tao_before, "TAO principal must be refunded"); + + // ALPHA: refund is credited to (coldkey=cold, hotkey=cold). Compare totals across both ledgers. + let alpha_after_hot = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); + let alpha_after_owner = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); + let alpha_after_total = alpha_after_hot + alpha_after_owner; + assert_eq!( + alpha_after_total, alpha_before_total, + "ALPHA principal must be refunded to the account (may be credited to (owner, owner))" + ); + + // User position(s) are gone and all V3 state cleared. + assert_eq!(Pallet::::count_positions(netuid, &cold), 0); + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert!(!SwapV3Initialized::::contains_key(netuid)); + }); +} + +#[test] +fn refund_alpha_single_provider_exact() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(11); + let cold = OK_COLDKEY_ACCOUNT_ID; + let hot = OK_HOTKEY_ACCOUNT_ID; + + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + // --- Create an alpha‑only position (range entirely above current tick → TAO = 0, ALPHA > 0). + let ct = CurrentTick::::get(netuid); + let tick_low = ct.next().expect("current tick should not be MAX in tests"); + let tick_high = TickIndex::MAX; + + let liquidity = 1_000_000_u64; + let (_pos_id, tao_needed, alpha_needed) = + Pallet::::do_add_liquidity(netuid, &cold, &hot, tick_low, tick_high, liquidity) + .expect("add alpha-only liquidity"); + assert_eq!(tao_needed, 0, "alpha-only position must not require TAO"); + assert!(alpha_needed > 0, "alpha-only position must require ALPHA"); + + // --- Snapshot BEFORE we withdraw funds (baseline for conservation). + let alpha_before_hot = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); + let alpha_before_owner = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); + let alpha_before_total = alpha_before_hot + alpha_before_owner; + + // --- Mimic extrinsic bookkeeping: withdraw α and record provided reserve. + let alpha_taken = ::BalanceOps::decrease_stake( + &cold, + &hot, + netuid.into(), + alpha_needed.into(), + ) + .expect("decrease ALPHA"); + ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), alpha_taken); + + // --- Act: dissolve (calls refund_alpha inside). + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + + // --- Assert: refunded back to the owner (may credit to (cold,cold)). + let alpha_after_hot = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot); + let alpha_after_owner = + ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); + let alpha_after_total = alpha_after_hot + alpha_after_owner; + assert_eq!( + alpha_after_total, alpha_before_total, + "ALPHA principal must be conserved to the owner" + ); + + // --- State is cleared. + assert!(Ticks::::iter_prefix(netuid).next().is_none()); + assert_eq!(Pallet::::count_positions(netuid, &cold), 0); + assert!(!SwapV3Initialized::::contains_key(netuid)); + }); +} + +#[test] +fn refund_alpha_multiple_providers_proportional_to_principal() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(12); + let c1 = OK_COLDKEY_ACCOUNT_ID; + let h1 = OK_HOTKEY_ACCOUNT_ID; + let c2 = OK_COLDKEY_ACCOUNT_ID_2; + let h2 = OK_HOTKEY_ACCOUNT_ID_2; + + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + // Use the same "above current tick" trick for alpha‑only positions. + let ct = CurrentTick::::get(netuid); + let tick_low = ct.next().expect("current tick should not be MAX in tests"); + let tick_high = TickIndex::MAX; + + // Provider #1 (smaller α) + let liq1 = 700_000_u64; + let (_p1, t1, a1) = + Pallet::::do_add_liquidity(netuid, &c1, &h1, tick_low, tick_high, liq1) + .expect("add alpha-only liquidity #1"); + assert_eq!(t1, 0); + assert!(a1 > 0); + + // Provider #2 (larger α) + let liq2 = 2_100_000_u64; + let (_p2, t2, a2) = + Pallet::::do_add_liquidity(netuid, &c2, &h2, tick_low, tick_high, liq2) + .expect("add alpha-only liquidity #2"); + assert_eq!(t2, 0); + assert!(a2 > 0); + + // Baselines BEFORE withdrawing + let a1_before_hot = ::BalanceOps::alpha_balance(netuid.into(), &c1, &h1); + let a1_before_owner = ::BalanceOps::alpha_balance(netuid.into(), &c1, &c1); + let a1_before = a1_before_hot + a1_before_owner; + + let a2_before_hot = ::BalanceOps::alpha_balance(netuid.into(), &c2, &h2); + let a2_before_owner = ::BalanceOps::alpha_balance(netuid.into(), &c2, &c2); + let a2_before = a2_before_hot + a2_before_owner; + + // Withdraw α and account reserves for each provider. + let a1_taken = + ::BalanceOps::decrease_stake(&c1, &h1, netuid.into(), a1.into()) + .expect("decrease α #1"); + ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), a1_taken); + + let a2_taken = + ::BalanceOps::decrease_stake(&c2, &h2, netuid.into(), a2.into()) + .expect("decrease α #2"); + ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), a2_taken); + + // Act + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + + // Each owner is restored to their exact baseline. + let a1_after_hot = ::BalanceOps::alpha_balance(netuid.into(), &c1, &h1); + let a1_after_owner = ::BalanceOps::alpha_balance(netuid.into(), &c1, &c1); + let a1_after = a1_after_hot + a1_after_owner; + assert_eq!( + a1_after, a1_before, + "owner #1 must receive their α principal back" + ); + + let a2_after_hot = ::BalanceOps::alpha_balance(netuid.into(), &c2, &h2); + let a2_after_owner = ::BalanceOps::alpha_balance(netuid.into(), &c2, &c2); + let a2_after = a2_after_hot + a2_after_owner; + assert_eq!( + a2_after, a2_before, + "owner #2 must receive their α principal back" + ); + }); +} + +#[test] +fn refund_alpha_same_cold_multiple_hotkeys_conserved_to_owner() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(13); + let cold = OK_COLDKEY_ACCOUNT_ID; + let hot1 = OK_HOTKEY_ACCOUNT_ID; + let hot2 = OK_HOTKEY_ACCOUNT_ID_2; + + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + // Two alpha‑only positions on different hotkeys of the same owner. + let ct = CurrentTick::::get(netuid); + let tick_low = ct.next().expect("current tick should not be MAX in tests"); + let tick_high = TickIndex::MAX; + + let (_p1, _t1, a1) = + Pallet::::do_add_liquidity(netuid, &cold, &hot1, tick_low, tick_high, 900_000) + .expect("add alpha-only pos (hot1)"); + let (_p2, _t2, a2) = + Pallet::::do_add_liquidity(netuid, &cold, &hot2, tick_low, tick_high, 1_500_000) + .expect("add alpha-only pos (hot2)"); + assert!(a1 > 0 && a2 > 0); + + // Baseline BEFORE: sum over (cold,hot1) + (cold,hot2) + (cold,cold). + let before_hot1 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot1); + let before_hot2 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot2); + let before_owner = ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); + let before_total = before_hot1 + before_hot2 + before_owner; + + // Withdraw α from both hotkeys; track provided‑reserve. + let t1 = + ::BalanceOps::decrease_stake(&cold, &hot1, netuid.into(), a1.into()) + .expect("decr α #hot1"); + ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), t1); + + let t2 = + ::BalanceOps::decrease_stake(&cold, &hot2, netuid.into(), a2.into()) + .expect("decr α #hot2"); + ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), t2); + + // Act + assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); + + // The total α "owned" by the coldkey is conserved (credit may land on (cold,cold)). + let after_hot1 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot1); + let after_hot2 = ::BalanceOps::alpha_balance(netuid.into(), &cold, &hot2); + let after_owner = ::BalanceOps::alpha_balance(netuid.into(), &cold, &cold); + let after_total = after_hot1 + after_hot2 + after_owner; + + assert_eq!( + after_total, before_total, + "owner’s α must be conserved across hot ledgers + (owner,owner)" + ); + }); +} From 7fae3a07722d14074e8c011f6c0e6c01abae3813 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Thu, 18 Sep 2025 19:49:43 +0300 Subject: [PATCH 07/14] Simplify swap interface generic requirements --- pallets/swap-interface/src/lib.rs | 51 ++++++++------- pallets/swap-interface/src/order.rs | 52 ++++++++++++--- pallets/swap/src/pallet/impls.rs | 68 ++++++++------------ pallets/swap/src/pallet/swap_step.rs | 96 +++++++++++++++------------- pallets/swap/src/pallet/tests.rs | 13 ++-- 5 files changed, 152 insertions(+), 128 deletions(-) diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index 9f00c4f578..eac342ce6c 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -3,34 +3,30 @@ use core::ops::Neg; use frame_support::pallet_prelude::*; use substrate_fixed::types::U96F32; -use subtensor_runtime_common::{AlphaCurrency, Currency, CurrencyReserve, NetUid, TaoCurrency}; +use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; pub use order::*; mod order; pub trait SwapHandler { - fn swap( + fn swap( netuid: NetUid, order: OrderT, price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result, DispatchError> + ) -> Result, DispatchError> where OrderT: Order, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - Self: SwapEngine; - fn sim_swap( + Self: SwapEngine; + fn sim_swap( netuid: NetUid, order: OrderT, - ) -> Result, DispatchError> + ) -> Result, DispatchError> where OrderT: Order, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - Self: DefaultPriceLimit + SwapEngine; + Self: DefaultPriceLimit + SwapEngine; fn approx_fee_amount(netuid: NetUid, amount: T) -> T; fn current_alpha_price(netuid: NetUid) -> U96F32; fn max_price() -> C; @@ -45,33 +41,40 @@ pub trait SwapHandler { fn toggle_user_liquidity(netuid: NetUid, enabled: bool); } -pub trait DefaultPriceLimit { +pub trait DefaultPriceLimit +where + PaidIn: Currency, + PaidOut: Currency, +{ fn default_price_limit() -> C; } -pub trait SwapEngine -where - OrderT: Order, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, -{ +pub trait SwapEngine { fn swap( netuid: NetUid, order: OrderT, price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result, DispatchError>; + ) -> Result, DispatchError>; } #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug, TypeInfo)] -pub struct SwapResult { - pub amount_paid_in: OrderT::PaidIn, - pub amount_paid_out: OrderT::PaidOut, - pub fee_paid: OrderT::PaidIn, +pub struct SwapResult +where + PaidIn: Currency, + PaidOut: Currency, +{ + pub amount_paid_in: PaidIn, + pub amount_paid_out: PaidOut, + pub fee_paid: PaidIn, } -impl SwapResult { +impl SwapResult +where + PaidIn: Currency, + PaidOut: Currency, +{ pub fn paid_in_reserve_delta(&self) -> i128 { self.amount_paid_in.to_u64() as i128 } diff --git a/pallets/swap-interface/src/order.rs b/pallets/swap-interface/src/order.rs index a1bbb0dcb1..72a1dafbae 100644 --- a/pallets/swap-interface/src/order.rs +++ b/pallets/swap-interface/src/order.rs @@ -1,26 +1,44 @@ +use core::marker::PhantomData; + use substrate_fixed::types::U64F64; -use subtensor_runtime_common::{AlphaCurrency, Currency, TaoCurrency}; +use subtensor_runtime_common::{AlphaCurrency, Currency, CurrencyReserve, TaoCurrency}; pub trait Order: Clone { type PaidIn: Currency; type PaidOut: Currency; + type ReserveIn: CurrencyReserve; + type ReserveOut: CurrencyReserve; - fn with_amount(amount: Self::PaidIn) -> Self; + fn with_amount(amount: impl Into) -> Self; fn amount(&self) -> Self::PaidIn; fn is_beyond_price_limit(&self, alpha_sqrt_price: U64F64, limit_sqrt_price: U64F64) -> bool; } #[derive(Clone)] -pub struct AlphaForTao { +pub struct AlphaForTao +where + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, +{ amount: TaoCurrency, + _phantom: PhantomData<(ReserveIn, ReserveOut)>, } -impl Order for AlphaForTao { +impl Order for AlphaForTao +where + ReserveIn: CurrencyReserve + Clone, + ReserveOut: CurrencyReserve + Clone, +{ type PaidIn = TaoCurrency; type PaidOut = AlphaCurrency; + type ReserveIn = ReserveIn; + type ReserveOut = ReserveOut; - fn with_amount(amount: TaoCurrency) -> Self { - Self { amount } + fn with_amount(amount: impl Into) -> Self { + Self { + amount: amount.into(), + _phantom: PhantomData, + } } fn amount(&self) -> TaoCurrency { @@ -33,16 +51,30 @@ impl Order for AlphaForTao { } #[derive(Clone)] -pub struct TaoForAlpha { +pub struct TaoForAlpha +where + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, +{ amount: AlphaCurrency, + _phantom: PhantomData<(ReserveIn, ReserveOut)>, } -impl Order for TaoForAlpha { +impl Order for TaoForAlpha +where + ReserveIn: CurrencyReserve + Clone, + ReserveOut: CurrencyReserve + Clone, +{ type PaidIn = AlphaCurrency; type PaidOut = TaoCurrency; + type ReserveIn = ReserveIn; + type ReserveOut = ReserveOut; - fn with_amount(amount: AlphaCurrency) -> Self { - Self { amount } + fn with_amount(amount: impl Into) -> Self { + Self { + amount: amount.into(), + _phantom: PhantomData, + } } fn amount(&self) -> AlphaCurrency { diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 10738ceb7f..88b2d53c14 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -10,8 +10,7 @@ use subtensor_runtime_common::{ AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, }; use subtensor_swap_interface::{ - AlphaForTao, DefaultPriceLimit, Order as OrderT, SwapEngine, SwapHandler, SwapResult, - TaoForAlpha, + DefaultPriceLimit, Order as OrderT, SwapEngine, SwapHandler, SwapResult, }; use super::pallet::*; @@ -189,29 +188,22 @@ impl Pallet { /// reserve. /// /// Use simulation mode to preview the outcome of a swap without modifying the blockchain state. - pub(crate) fn do_swap( + pub(crate) fn do_swap( netuid: NetUid, order: Order, limit_sqrt_price: SqrtPrice, drop_fees: bool, simulate: bool, - ) -> Result, DispatchError> + ) -> Result, DispatchError> where Order: OrderT, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - BasicSwapStep: SwapStep, + BasicSwapStep: SwapStep, { transactional::with_transaction(|| { - let reserve = ReserveOut::reserve(netuid.into()); + let reserve = Order::ReserveOut::reserve(netuid.into()); - let result = Self::swap_inner::( - netuid, - order, - limit_sqrt_price, - drop_fees, - ) - .map_err(Into::into); + let result = Self::swap_inner::(netuid, order, limit_sqrt_price, drop_fees) + .map_err(Into::into); if simulate || result.is_err() { // Simulation only @@ -233,20 +225,18 @@ impl Pallet { }) } - fn swap_inner( + fn swap_inner( netuid: NetUid, order: Order, limit_sqrt_price: SqrtPrice, drop_fees: bool, - ) -> Result, Error> + ) -> Result, Error> where Order: OrderT, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - BasicSwapStep: SwapStep, + BasicSwapStep: SwapStep, { ensure!( - ReserveOut::reserve(netuid).to_u64() >= T::MinimumReserve::get().get(), + Order::ReserveOut::reserve(netuid).to_u64() >= T::MinimumReserve::get().get(), Error::::ReservesTooLow ); @@ -276,7 +266,7 @@ impl Pallet { ); // Create and execute a swap step - let mut swap_step = BasicSwapStep::::new( + let mut swap_step = BasicSwapStep::::new( netuid, amount_remaining, limit_sqrt_price, @@ -887,24 +877,22 @@ impl Pallet { } } -impl DefaultPriceLimit for Pallet { +impl DefaultPriceLimit for Pallet { fn default_price_limit() -> C { Self::max_price_inner::() } } -impl DefaultPriceLimit for Pallet { +impl DefaultPriceLimit for Pallet { fn default_price_limit() -> C { Self::min_price_inner::() } } -impl SwapEngine for Pallet +impl SwapEngine for Pallet where Order: OrderT, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - BasicSwapStep: SwapStep, + BasicSwapStep: SwapStep, { fn swap( netuid: NetUid, @@ -912,13 +900,13 @@ where price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result, DispatchError> { + ) -> 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::( + Self::do_swap::( NetUid::from(netuid), order, limit_sqrt_price, @@ -930,20 +918,18 @@ where } impl SwapHandler for Pallet { - fn swap( + fn swap( netuid: NetUid, order: Order, price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result, DispatchError> + ) -> Result, DispatchError> where Order: OrderT, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - Self: SwapEngine, + Self: SwapEngine, { - >::swap( + >::swap( NetUid::from(netuid), order, price_limit, @@ -953,21 +939,19 @@ impl SwapHandler for Pallet { .map_err(Into::into) } - fn sim_swap( + fn sim_swap( netuid: NetUid, order: Order, - ) -> Result, DispatchError> + ) -> Result, DispatchError> where Order: OrderT, - ReserveIn: CurrencyReserve, - ReserveOut: CurrencyReserve, - Self: DefaultPriceLimit + SwapEngine, + Self: DefaultPriceLimit + SwapEngine, { match T::SubnetInfo::mechanism(netuid) { 1 => { let price_limit = Self::default_price_limit::(); - >::swap::( + >::swap::( netuid, order, price_limit, diff --git a/pallets/swap/src/pallet/swap_step.rs b/pallets/swap/src/pallet/swap_step.rs index deffb0d59b..6791835b1a 100644 --- a/pallets/swap/src/pallet/swap_step.rs +++ b/pallets/swap/src/pallet/swap_step.rs @@ -2,8 +2,7 @@ use core::marker::PhantomData; use safe_math::*; use substrate_fixed::types::{I64F64, U64F64}; -use subtensor_runtime_common::{Currency, NetUid}; -use subtensor_swap_interface::{AlphaForTao, Order as OrderT, TaoForAlpha}; +use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; use super::pallet::*; use crate::{ @@ -12,10 +11,11 @@ use crate::{ }; /// A struct representing a single swap step with all its parameters and state -pub(crate) struct BasicSwapStep +pub(crate) struct BasicSwapStep where T: Config, - Order: OrderT, + PaidIn: Currency, + PaidOut: Currency, { // Input parameters netuid: NetUid, @@ -23,7 +23,7 @@ where // Computed values current_liquidity: U64F64, - possible_delta_in: Order::PaidIn, + possible_delta_in: PaidIn, // Ticks and prices (current, limit, edge, target) target_sqrt_price: SqrtPrice, @@ -34,23 +34,24 @@ where // Result values action: SwapStepAction, - delta_in: Order::PaidIn, + delta_in: PaidIn, final_price: SqrtPrice, - fee: Order::PaidIn, + fee: PaidIn, - _phantom: PhantomData<(T, Order)>, + _phantom: PhantomData<(T, PaidIn, PaidOut)>, } -impl BasicSwapStep +impl BasicSwapStep where T: Config, - Order: OrderT, - Self: SwapStep, + PaidIn: Currency, + PaidOut: Currency, + Self: SwapStep, { /// Creates and initializes a new swap step pub(crate) fn new( netuid: NetUid, - amount_remaining: Order::PaidIn, + amount_remaining: PaidIn, limit_sqrt_price: SqrtPrice, drop_fees: bool, ) -> Self { @@ -79,7 +80,7 @@ where possible_delta_in, current_liquidity, action: SwapStepAction::Stop, - delta_in: Order::PaidIn::ZERO, + delta_in: PaidIn::ZERO, final_price: target_sqrt_price, fee, _phantom: PhantomData, @@ -87,7 +88,7 @@ where } /// Execute the swap step and return the result - pub(crate) fn execute(&mut self) -> Result, Error> { + pub(crate) fn execute(&mut self) -> Result, Error> { self.determine_action(); self.process_swap() } @@ -184,7 +185,7 @@ where } /// Process a single step of a swap - fn process_swap(&self) -> Result, Error> { + fn process_swap(&self) -> Result, Error> { // Hold the fees Self::add_fees( self.netuid, @@ -225,12 +226,14 @@ where } } -impl SwapStep for BasicSwapStep { +impl SwapStep + for BasicSwapStep +{ fn delta_in( liquidity_curr: U64F64, sqrt_price_curr: SqrtPrice, sqrt_price_target: SqrtPrice, - ) -> ::PaidIn { + ) -> TaoCurrency { liquidity_curr .saturating_mul(sqrt_price_target.saturating_sub(sqrt_price_curr)) .saturating_to_num::() @@ -248,14 +251,14 @@ impl SwapStep for BasicSwapStep { fn sqrt_price_target( liquidity_curr: U64F64, sqrt_price_curr: SqrtPrice, - delta_in: ::PaidIn, + 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::<::PaidIn>().to_u64(), + Pallet::::max_price_inner::().to_u64(), ); } @@ -272,7 +275,7 @@ impl SwapStep for BasicSwapStep { SwapStepAction::Crossing } - fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: ::PaidIn) { + fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: TaoCurrency) { if current_liquidity == 0 { return; } @@ -284,13 +287,10 @@ impl SwapStep for BasicSwapStep { }); } - fn convert_deltas( - netuid: NetUid, - delta_in: ::PaidIn, - ) -> ::PaidOut { + fn convert_deltas(netuid: NetUid, delta_in: TaoCurrency) -> AlphaCurrency { // Skip conversion if delta_in is zero if delta_in.is_zero() { - return ::PaidOut::ZERO; + return AlphaCurrency::ZERO; } let liquidity_curr = SqrtPrice::saturating_from_num(CurrentLiquidity::::get(netuid)); @@ -345,12 +345,14 @@ impl SwapStep for BasicSwapStep { } } -impl SwapStep for BasicSwapStep { +impl SwapStep + for BasicSwapStep +{ fn delta_in( liquidity_curr: U64F64, sqrt_price_curr: SqrtPrice, sqrt_price_target: SqrtPrice, - ) -> ::PaidIn { + ) -> AlphaCurrency { let one = U64F64::saturating_from_num(1); liquidity_curr @@ -382,7 +384,7 @@ impl SwapStep for BasicSwapStep { fn sqrt_price_target( liquidity_curr: U64F64, sqrt_price_curr: SqrtPrice, - delta_in: ::PaidIn, + delta_in: AlphaCurrency, ) -> SqrtPrice { let delta_fixed = U64F64::saturating_from_num(delta_in); let one = U64F64::saturating_from_num(1); @@ -390,7 +392,7 @@ impl SwapStep for BasicSwapStep { // No liquidity means that price should go to the limit if liquidity_curr == 0 { return SqrtPrice::saturating_from_num( - Pallet::::min_price_inner::<::PaidIn>().to_u64(), + Pallet::::min_price_inner::().to_u64(), ); } @@ -409,7 +411,7 @@ impl SwapStep for BasicSwapStep { SwapStepAction::Stop } - fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: ::PaidIn) { + fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: AlphaCurrency) { if current_liquidity == 0 { return; } @@ -421,13 +423,10 @@ impl SwapStep for BasicSwapStep { }); } - fn convert_deltas( - netuid: NetUid, - delta_in: ::PaidIn, - ) -> ::PaidOut { + fn convert_deltas(netuid: NetUid, delta_in: AlphaCurrency) -> TaoCurrency { // Skip conversion if delta_in is zero if delta_in.is_zero() { - return ::PaidOut::ZERO; + return TaoCurrency::ZERO; } let liquidity_curr = SqrtPrice::saturating_from_num(CurrentLiquidity::::get(netuid)); @@ -492,17 +491,18 @@ impl SwapStep for BasicSwapStep { } } -pub(crate) trait SwapStep +pub(crate) trait SwapStep where T: Config, - Order: OrderT, + PaidIn: Currency, + PaidOut: Currency, { /// Get the input amount needed to reach the target price fn delta_in( liquidity_curr: U64F64, sqrt_price_curr: SqrtPrice, sqrt_price_target: SqrtPrice, - ) -> Order::PaidIn; + ) -> PaidIn; /// Get the tick at the current tick edge. /// @@ -518,7 +518,7 @@ where fn sqrt_price_target( liquidity_curr: U64F64, sqrt_price_curr: SqrtPrice, - delta_in: Order::PaidIn, + delta_in: PaidIn, ) -> SqrtPrice; /// Returns True if sq_price1 is closer to the current price than sq_price2 @@ -531,24 +531,28 @@ where fn action_on_edge_sqrt_price() -> SwapStepAction; /// Add fees to the global fee counters - fn add_fees(netuid: NetUid, current_liquidity: U64F64, fee: Order::PaidIn); + 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: Order::PaidIn) -> Order::PaidOut; + 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 { - pub(crate) amount_to_take: Order::PaidIn, - pub(crate) fee_paid: Order::PaidIn, - pub(crate) delta_in: Order::PaidIn, - pub(crate) delta_out: Order::PaidOut, +pub(crate) struct SwapStepResult +where + PaidIn: Currency, + PaidOut: Currency, +{ + 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)] diff --git a/pallets/swap/src/pallet/tests.rs b/pallets/swap/src/pallet/tests.rs index dc7f08baa8..011a74936a 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -8,9 +8,10 @@ use sp_arithmetic::helpers_128bit; use sp_runtime::DispatchError; use substrate_fixed::types::U96F32; use subtensor_runtime_common::NetUid; +use subtensor_swap_interface::{AlphaForTao, Order, TaoForAlpha}; use super::*; -use crate::{OrderType, SqrtPrice, mock::*}; +use crate::{SqrtPrice, mock::*}; // this function is used to convert price (NON-SQRT price!) to TickIndex. it's only utility for // testing, all the implementation logic is based on sqrt prices @@ -153,8 +154,8 @@ fn test_swap_initialization() { let netuid = NetUid::from(1); // Get reserves from the mock provider - let tao = MockLiquidityProvider::tao_reserve(netuid.into()); - let alpha = MockLiquidityProvider::alpha_reserve(netuid.into()); + let tao = TaoReserve::reserve(netuid.into()); + let alpha = AlphaReserve::reserve(netuid.into()); assert_ok!(Pallet::::maybe_initialize_v3(netuid)); @@ -664,10 +665,10 @@ fn test_modify_position_basic() { // Swap to create fees on the position let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); - Pallet::::do_swap( + let order = AlphaForTao::with_amount(liquidity / 10); + Pallet::::do_swap::<_, TaoReserve, AlphaReserve>( netuid, - OrderType::Buy, - liquidity / 10, + order, sqrt_limit_price, false, false, From dae1677d33e80afc4b3668d02d1e35153cb611c4 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Tue, 23 Sep 2025 19:29:04 +0300 Subject: [PATCH 08/14] Update tests in swap pallet --- pallets/swap-interface/src/order.rs | 4 +- pallets/swap/Cargo.toml | 2 +- pallets/swap/src/mock.rs | 71 ++- pallets/swap/src/pallet/tests.rs | 943 +++++++++++++--------------- 4 files changed, 513 insertions(+), 507 deletions(-) diff --git a/pallets/swap-interface/src/order.rs b/pallets/swap-interface/src/order.rs index 72a1dafbae..5fbfad9923 100644 --- a/pallets/swap-interface/src/order.rs +++ b/pallets/swap-interface/src/order.rs @@ -14,7 +14,7 @@ pub trait Order: Clone { fn is_beyond_price_limit(&self, alpha_sqrt_price: U64F64, limit_sqrt_price: U64F64) -> bool; } -#[derive(Clone)] +#[derive(Clone, Default)] pub struct AlphaForTao where ReserveIn: CurrencyReserve, @@ -50,7 +50,7 @@ where } } -#[derive(Clone)] +#[derive(Clone, Default)] pub struct TaoForAlpha where ReserveIn: CurrencyReserve, diff --git a/pallets/swap/Cargo.toml b/pallets/swap/Cargo.toml index a013b8468e..7de8e49c1d 100644 --- a/pallets/swap/Cargo.toml +++ b/pallets/swap/Cargo.toml @@ -23,7 +23,7 @@ substrate-fixed.workspace = true pallet-subtensor-swap-runtime-api.workspace = true subtensor-macros.workspace = true -subtensor-runtime-common.workspace = true +subtensor-runtime-common = { workspace = true, features = ["approx"] } subtensor-swap-interface.workspace = true [dev-dependencies] diff --git a/pallets/swap/src/mock.rs b/pallets/swap/src/mock.rs index cb1dfec260..9feee8a72c 100644 --- a/pallets/swap/src/mock.rs +++ b/pallets/swap/src/mock.rs @@ -14,11 +14,13 @@ use sp_runtime::{ BuildStorage, Vec, traits::{BlakeTwo256, IdentityLookup}, }; +use substrate_fixed::types::U64F64; use subtensor_runtime_common::{ - AlphaCurrency, BalanceOps, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, + AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, }; +use subtensor_swap_interface::Order; -use crate::pallet::EnabledUserLiquidity; +use crate::pallet::{EnabledUserLiquidity, FeeGlobalAlpha, FeeGlobalTao}; construct_runtime!( pub enum Test { @@ -85,7 +87,8 @@ parameter_types! { pub const MinimumReserves: NonZeroU64 = NonZeroU64::new(1).unwrap(); } -pub(crate) struct TaoReserve; +#[derive(Clone)] +pub struct TaoReserve; impl CurrencyReserve for TaoReserve { fn reserve(netuid: NetUid) -> TaoCurrency { @@ -97,11 +100,12 @@ impl CurrencyReserve for TaoReserve { .into() } - fn increase_provided(netuid: NetUid, amount: TaoCurrency) {} - fn decrease_provided(netuid: NetUid, amount: TaoCurrency) {} + fn increase_provided(_: NetUid, _: TaoCurrency) {} + fn decrease_provided(_: NetUid, _: TaoCurrency) {} } -pub(crate) struct AlphaReserve; +#[derive(Clone)] +pub struct AlphaReserve; impl CurrencyReserve for AlphaReserve { fn reserve(netuid: NetUid) -> AlphaCurrency { @@ -112,8 +116,59 @@ impl CurrencyReserve for AlphaReserve { } } - fn increase_provided(netuid: NetUid, amount: AlphaCurrency) {} - fn decrease_provided(netuid: NetUid, amount: AlphaCurrency) {} + fn increase_provided(_: NetUid, _: AlphaCurrency) {} + fn decrease_provided(_: NetUid, _: AlphaCurrency) {} +} + +pub type AlphaForTao = subtensor_swap_interface::AlphaForTao; +pub type TaoForAlpha = subtensor_swap_interface::TaoForAlpha; + +pub(crate) trait GlobalFeeInfo: Currency { + fn global_fee(&self, netuid: NetUid) -> U64F64; +} + +impl GlobalFeeInfo for TaoCurrency { + fn global_fee(&self, netuid: NetUid) -> U64F64 { + FeeGlobalTao::::get(netuid) + } +} + +impl GlobalFeeInfo for AlphaCurrency { + fn global_fee(&self, netuid: NetUid) -> U64F64 { + FeeGlobalAlpha::::get(netuid) + } +} + +pub(crate) trait TestExt { + fn approx_expected_swap_output( + sqrt_current_price: f64, + liquidity_before: f64, + order_liquidity: f64, + ) -> f64; +} + +impl TestExt for Test { + fn approx_expected_swap_output( + sqrt_current_price: f64, + liquidity_before: f64, + order_liquidity: f64, + ) -> f64 { + let denom = sqrt_current_price * (sqrt_current_price * liquidity_before + order_liquidity); + let per_order_liq = liquidity_before / denom; + per_order_liq * order_liquidity + } +} + +impl TestExt for Test { + fn approx_expected_swap_output( + sqrt_current_price: f64, + liquidity_before: f64, + order_liquidity: f64, + ) -> f64 { + let denom = liquidity_before / sqrt_current_price + order_liquidity; + let per_order_liq = sqrt_current_price * liquidity_before / denom; + per_order_liq * order_liquidity + } } // Mock implementor of SubnetInfo trait diff --git a/pallets/swap/src/pallet/tests.rs b/pallets/swap/src/pallet/tests.rs index 011a74936a..3d73c24d43 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -8,9 +8,10 @@ use sp_arithmetic::helpers_128bit; use sp_runtime::DispatchError; use substrate_fixed::types::U96F32; use subtensor_runtime_common::NetUid; -use subtensor_swap_interface::{AlphaForTao, Order, TaoForAlpha}; +use subtensor_swap_interface::Order as OrderT; use super::*; +use crate::pallet::swap_step::*; use crate::{SqrtPrice, mock::*}; // this function is used to convert price (NON-SQRT price!) to TickIndex. it's only utility for @@ -666,14 +667,7 @@ fn test_modify_position_basic() { // Swap to create fees on the position let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); let order = AlphaForTao::with_amount(liquidity / 10); - Pallet::::do_swap::<_, TaoReserve, AlphaReserve>( - netuid, - order, - sqrt_limit_price, - false, - false, - ) - .unwrap(); + Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); // Modify liquidity (also causes claiming of fees) let liquidity_before = CurrentLiquidity::::get(netuid); @@ -755,68 +749,272 @@ fn test_modify_position_basic() { #[test] fn test_swap_basic() { new_test_ext().execute_with(|| { + fn perform_test( + netuid: NetUid, + order: Order, + limit_price: f64, + output_amount: u64, + price_should_grow: bool, + ) where + Order: OrderT, + Order::PaidIn: GlobalFeeInfo, + BasicSwapStep: + SwapStep, + { + // Consumed liquidity ticks + let tick_low = TickIndex::MIN; + let tick_high = TickIndex::MAX; + let liquidity = order.amount().to_u64(); + + // Setup swap + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + + // Get tick infos before the swap + let tick_low_info_before = Ticks::::get(netuid, tick_low).unwrap_or_default(); + let tick_high_info_before = Ticks::::get(netuid, tick_high).unwrap_or_default(); + let liquidity_before = CurrentLiquidity::::get(netuid); + + // Get current price + let current_price = Pallet::::current_price(netuid); + + // Swap + let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); + let swap_result = + Pallet::::do_swap(netuid, order.clone(), sqrt_limit_price, false, false) + .unwrap(); + assert_abs_diff_eq!( + swap_result.amount_paid_out.to_u64(), + output_amount, + epsilon = output_amount / 100 + ); + + assert_abs_diff_eq!( + swap_result.paid_in_reserve_delta() as u64, + liquidity, + epsilon = liquidity / 10 + ); + assert_abs_diff_eq!( + swap_result.paid_out_reserve_delta() as i64, + -(output_amount as i64), + epsilon = output_amount as i64 / 10 + ); + + // Check that low and high ticks' fees were updated properly, and liquidity values were not updated + let tick_low_info = Ticks::::get(netuid, tick_low).unwrap(); + let tick_high_info = Ticks::::get(netuid, tick_high).unwrap(); + let expected_liquidity_net_low = tick_low_info_before.liquidity_net; + let expected_liquidity_gross_low = tick_low_info_before.liquidity_gross; + let expected_liquidity_net_high = tick_high_info_before.liquidity_net; + let expected_liquidity_gross_high = tick_high_info_before.liquidity_gross; + assert_eq!(tick_low_info.liquidity_net, expected_liquidity_net_low,); + assert_eq!(tick_low_info.liquidity_gross, expected_liquidity_gross_low,); + assert_eq!(tick_high_info.liquidity_net, expected_liquidity_net_high,); + assert_eq!( + tick_high_info.liquidity_gross, + expected_liquidity_gross_high, + ); + + // Expected fee amount + let fee_rate = FeeRate::::get(netuid) as f64 / u16::MAX as f64; + let expected_fee = (liquidity as f64 * fee_rate) as u64; + + // Global fees should be updated + let actual_global_fee = (order.amount().global_fee(netuid).to_num::() + * (liquidity_before as f64)) as u64; + + assert!((swap_result.fee_paid.to_u64() as i64 - expected_fee as i64).abs() <= 1); + assert!((actual_global_fee as i64 - expected_fee as i64).abs() <= 1); + + // Tick fees should be updated + + // Liquidity position should not be updated + let protocol_id = Pallet::::protocol_account_id(); + let positions = + Positions::::iter_prefix_values((netuid, protocol_id)).collect::>(); + let position = positions.first().unwrap(); + + assert_eq!( + position.liquidity, + helpers_128bit::sqrt( + TaoReserve::reserve(netuid.into()).to_u64() as u128 + * AlphaReserve::reserve(netuid.into()).to_u64() as u128 + ) as u64 + ); + assert_eq!(position.tick_low, tick_low); + assert_eq!(position.tick_high, tick_high); + assert_eq!(position.fees_alpha, 0); + assert_eq!(position.fees_tao, 0); + + // Current liquidity is not updated + assert_eq!(CurrentLiquidity::::get(netuid), liquidity_before); + + // Assert that price movement is in correct direction + let sqrt_current_price_after = AlphaSqrtPrice::::get(netuid); + let current_price_after = Pallet::::current_price(netuid); + assert_eq!(current_price_after >= current_price, price_should_grow); + + // Assert that current tick is updated + let current_tick = CurrentTick::::get(netuid); + let expected_current_tick = + TickIndex::from_sqrt_price_bounded(sqrt_current_price_after); + assert_eq!(current_tick, expected_current_tick); + } + // Current price is 0.25 // Test case is (order_type, liquidity, limit_price, output_amount) - [ - (OrderType::Buy, 1_000u64, 1000.0_f64, 3990_u64), - (OrderType::Sell, 1_000u64, 0.0001_f64, 250_u64), - (OrderType::Buy, 500_000_000, 1000.0, 2_000_000_000), - ] - .into_iter() - .enumerate() - .map(|(n, v)| (NetUid::from(n as u16 + 1), v.0, v.1, v.2, v.3)) - .for_each( - |(netuid, order_type, liquidity, limit_price, output_amount)| { - // Consumed liquidity ticks - let tick_low = TickIndex::MIN; - let tick_high = TickIndex::MAX; + perform_test( + 1.into(), + AlphaForTao::with_amount(1_000), + 1000.0, + 3990, + true, + ); + perform_test( + 2.into(), + TaoForAlpha::with_amount(1_000), + 0.0001, + 250, + false, + ); + perform_test( + 3.into(), + AlphaForTao::with_amount(500_000_000), + 1000.0, + 2_000_000_000, + true, + ); + }); +} - // Setup swap +// In this test the swap starts and ends within one (large liquidity) position +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor-swap --lib -- pallet::tests::test_swap_single_position --exact --show-output +#[test] +fn test_swap_single_position() { + let min_price = tick_to_price(TickIndex::MIN); + let max_price = tick_to_price(TickIndex::MAX); + let max_tick = price_to_tick(max_price); + let netuid = NetUid::from(1); + assert_eq!(max_tick, TickIndex::MAX); + + let mut current_price_low = 0_f64; + let mut current_price_high = 0_f64; + let mut current_price = 0_f64; + new_test_ext().execute_with(|| { + let (low, high) = get_ticked_prices_around_current_price(); + current_price_low = low; + current_price_high = high; + current_price = Pallet::::current_price(netuid).to_num::(); + }); + + macro_rules! perform_test { + ($order_t:ident, + $price_low_offset:expr, + $price_high_offset:expr, + $position_liquidity:expr, + $liquidity_fraction:expr, + $limit_price:expr, + $price_should_grow:expr + ) => { + new_test_ext().execute_with(|| { + let price_low_offset = $price_low_offset; + let price_high_offset = $price_high_offset; + let position_liquidity = $position_liquidity; + let order_liquidity_fraction = $liquidity_fraction; + let limit_price = $limit_price; + let price_should_grow = $price_should_grow; + + ////////////////////////////////////////////// + // Initialize pool and add the user position assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + let tao_reserve = TaoReserve::reserve(netuid.into()).to_u64(); + let alpha_reserve = AlphaReserve::reserve(netuid.into()).to_u64(); + let protocol_liquidity = (tao_reserve as f64 * alpha_reserve as f64).sqrt(); + + // Add liquidity + let current_price = Pallet::::current_price(netuid).to_num::(); + let sqrt_current_price = AlphaSqrtPrice::::get(netuid).to_num::(); + + let price_low = price_low_offset + current_price; + let price_high = price_high_offset + current_price; + let tick_low = price_to_tick(price_low); + let tick_high = price_to_tick(price_high); + let (_position_id, _tao, _alpha) = Pallet::::do_add_liquidity( + netuid, + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + tick_low, + tick_high, + position_liquidity, + ) + .unwrap(); + + // Liquidity position at correct ticks + assert_eq!( + Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), + 1 + ); // Get tick infos before the swap let tick_low_info_before = Ticks::::get(netuid, tick_low).unwrap_or_default(); let tick_high_info_before = Ticks::::get(netuid, tick_high).unwrap_or_default(); let liquidity_before = CurrentLiquidity::::get(netuid); + assert_abs_diff_eq!( + liquidity_before as f64, + protocol_liquidity + position_liquidity as f64, + epsilon = liquidity_before as f64 / 1000. + ); - // Get current price - let current_price = Pallet::::current_price(netuid); - + ////////////////////////////////////////////// // Swap + + // Calculate the expected output amount for the cornercase of one step + let order_liquidity = order_liquidity_fraction * position_liquidity as f64; + + let output_amount = >::approx_expected_swap_output( + sqrt_current_price, + liquidity_before as f64, + order_liquidity, + ); + + // Do the swap let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); - let swap_result = Pallet::::do_swap( - netuid, - order_type, - liquidity, - sqrt_limit_price, - false, - false, - ) - .unwrap(); + let order = $order_t::with_amount(order_liquidity as u64); + let swap_result = + Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); assert_abs_diff_eq!( - swap_result.amount_paid_out, + swap_result.amount_paid_out.to_u64() as f64, output_amount, - epsilon = output_amount / 100 + epsilon = output_amount / 10. ); - let (tao_delta_expected, alpha_delta_expected) = match order_type { - OrderType::Buy => (liquidity as i64, -(output_amount as i64)), - OrderType::Sell => (-(output_amount as i64), liquidity as i64), - }; + if order_liquidity_fraction <= 0.001 { + assert_abs_diff_eq!( + swap_result.paid_in_reserve_delta() as i64, + order_liquidity as i64, + epsilon = order_liquidity as i64 / 10 + ); + assert_abs_diff_eq!( + swap_result.paid_out_reserve_delta() as i64, + -(output_amount as i64), + epsilon = output_amount as i64 / 10 + ); + } - assert_abs_diff_eq!( - swap_result.alpha_reserve_delta, - alpha_delta_expected, - epsilon = alpha_delta_expected.abs() / 10 - ); - assert_abs_diff_eq!( - swap_result.tao_reserve_delta, - tao_delta_expected, - epsilon = tao_delta_expected.abs() / 10 - ); + // Assert that price movement is in correct direction + let current_price_after = Pallet::::current_price(netuid); + assert_eq!(price_should_grow, current_price_after > current_price); + + // Assert that for small amounts price stays within the user position + if (order_liquidity_fraction <= 0.001) + && (price_low_offset > 0.0001) + && (price_high_offset > 0.0001) + { + assert!(current_price_after <= price_high); + assert!(current_price_after >= price_low); + } - // Check that low and high ticks' fees were updated properly, and liquidity values were not updated + // Check that low and high ticks' fees were updated properly let tick_low_info = Ticks::::get(netuid, tick_low).unwrap(); let tick_high_info = Ticks::::get(netuid, tick_high).unwrap(); let expected_liquidity_net_low = tick_low_info_before.liquidity_net; @@ -833,79 +1031,38 @@ fn test_swap_basic() { // Expected fee amount let fee_rate = FeeRate::::get(netuid) as f64 / u16::MAX as f64; - let expected_fee = (liquidity as f64 * fee_rate) as u64; - - // Global fees should be updated - let actual_global_fee = ((match order_type { - OrderType::Buy => FeeGlobalTao::::get(netuid), - OrderType::Sell => FeeGlobalAlpha::::get(netuid), - }) - .to_num::() + let expected_fee = (order_liquidity - order_liquidity / (1.0 + fee_rate)) as u64; + + // // Global fees should be updated + let actual_global_fee = ($order_t::with_amount(0) + .amount() + .global_fee(netuid) + .to_num::() * (liquidity_before as f64)) as u64; - assert!((swap_result.fee_paid as i64 - expected_fee as i64).abs() <= 1); - assert!((actual_global_fee as i64 - expected_fee as i64).abs() <= 1); + assert_abs_diff_eq!( + swap_result.fee_paid.to_u64(), + expected_fee, + epsilon = expected_fee / 10 + ); + assert_abs_diff_eq!(actual_global_fee, expected_fee, epsilon = expected_fee / 10); // Tick fees should be updated // Liquidity position should not be updated - let protocol_id = Pallet::::protocol_account_id(); - let positions = Positions::::iter_prefix_values((netuid, protocol_id)) - .collect::>(); + let positions = + Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) + .collect::>(); let position = positions.first().unwrap(); - assert_eq!( - position.liquidity, - helpers_128bit::sqrt( - MockLiquidityProvider::tao_reserve(netuid.into()).to_u64() as u128 - * MockLiquidityProvider::alpha_reserve(netuid.into()).to_u64() as u128 - ) as u64 - ); + assert_eq!(position.liquidity, position_liquidity,); assert_eq!(position.tick_low, tick_low); assert_eq!(position.tick_high, tick_high); assert_eq!(position.fees_alpha, 0); assert_eq!(position.fees_tao, 0); - - // Current liquidity is not updated - assert_eq!(CurrentLiquidity::::get(netuid), liquidity_before); - - // Assert that price movement is in correct direction - let sqrt_current_price_after = Pallet::::current_price_sqrt(netuid); - let current_price_after = Pallet::::current_price(netuid); - match order_type { - OrderType::Buy => assert!(current_price_after >= current_price), - OrderType::Sell => assert!(current_price_after <= current_price), - } - - // Assert that current tick is updated - let current_tick = CurrentTick::::get(netuid); - let expected_current_tick = - TickIndex::from_sqrt_price_bounded(sqrt_current_price_after); - assert_eq!(current_tick, expected_current_tick); - }, - ); - }); -} - -// In this test the swap starts and ends within one (large liquidity) position -// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor-swap --lib -- pallet::tests::test_swap_single_position --exact --show-output -#[test] -fn test_swap_single_position() { - let min_price = tick_to_price(TickIndex::MIN); - let max_price = tick_to_price(TickIndex::MAX); - let max_tick = price_to_tick(max_price); - let netuid = NetUid::from(1); - assert_eq!(max_tick, TickIndex::MAX); - - let mut current_price_low = 0_f64; - let mut current_price_high = 0_f64; - let mut current_price = 0_f64; - new_test_ext().execute_with(|| { - let (low, high) = get_ticked_prices_around_current_price(); - current_price_low = low; - current_price_high = high; - current_price = Pallet::::current_price(netuid).to_num::(); - }); + }); + }; + } // Current price is 0.25 // The test case is based on the current price and position prices are defined as a price @@ -946,195 +1103,26 @@ fn test_swap_single_position() { |(price_low_offset, price_high_offset, position_liquidity)| { // Inner part of test case is Order: (order_type, order_liquidity, limit_price) // order_liquidity is represented as a fraction of position_liquidity - [ - (OrderType::Buy, 0.0001, 1000.0_f64), - (OrderType::Sell, 0.0001, 0.0001_f64), - (OrderType::Buy, 0.001, 1000.0_f64), - (OrderType::Sell, 0.001, 0.0001_f64), - (OrderType::Buy, 0.01, 1000.0_f64), - (OrderType::Sell, 0.01, 0.0001_f64), - (OrderType::Buy, 0.1, 1000.0_f64), - (OrderType::Sell, 0.1, 0.0001), - (OrderType::Buy, 0.2, 1000.0_f64), - (OrderType::Sell, 0.2, 0.0001), - (OrderType::Buy, 0.5, 1000.0), - (OrderType::Sell, 0.5, 0.0001), - ] - .into_iter() - .for_each(|(order_type, order_liquidity_fraction, limit_price)| { - new_test_ext().execute_with(|| { - ////////////////////////////////////////////// - // Initialize pool and add the user position - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - let tao_reserve = MockLiquidityProvider::tao_reserve(netuid.into()).to_u64(); - let alpha_reserve = - MockLiquidityProvider::alpha_reserve(netuid.into()).to_u64(); - let protocol_liquidity = (tao_reserve as f64 * alpha_reserve as f64).sqrt(); - - // Add liquidity - let current_price = Pallet::::current_price(netuid).to_num::(); - let sqrt_current_price = - Pallet::::current_price_sqrt(netuid).to_num::(); - - let price_low = price_low_offset + current_price; - let price_high = price_high_offset + current_price; - let tick_low = price_to_tick(price_low); - let tick_high = price_to_tick(price_high); - let (_position_id, _tao, _alpha) = Pallet::::do_add_liquidity( - netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - position_liquidity, - ) - .unwrap(); - - // Liquidity position at correct ticks - assert_eq!( - Pallet::::count_positions(netuid, &OK_COLDKEY_ACCOUNT_ID), - 1 - ); - - // Get tick infos before the swap - let tick_low_info_before = - Ticks::::get(netuid, tick_low).unwrap_or_default(); - let tick_high_info_before = - Ticks::::get(netuid, tick_high).unwrap_or_default(); - let liquidity_before = CurrentLiquidity::::get(netuid); - assert_abs_diff_eq!( - liquidity_before as f64, - protocol_liquidity + position_liquidity as f64, - epsilon = liquidity_before as f64 / 1000. - ); - - ////////////////////////////////////////////// - // Swap - - // Calculate the expected output amount for the cornercase of one step - let order_liquidity = order_liquidity_fraction * position_liquidity as f64; - - let output_amount = match order_type { - OrderType::Buy => { - let denom = sqrt_current_price - * (sqrt_current_price * liquidity_before as f64 + order_liquidity); - let per_order_liq = liquidity_before as f64 / denom; - per_order_liq * order_liquidity - } - OrderType::Sell => { - let denom = - liquidity_before as f64 / sqrt_current_price + order_liquidity; - let per_order_liq = - sqrt_current_price * liquidity_before as f64 / denom; - per_order_liq * order_liquidity - } - }; - - // Do the swap - let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); - let swap_result = Pallet::::do_swap( - netuid, - order_type, - order_liquidity as u64, - sqrt_limit_price, - false, - false, - ) - .unwrap(); - assert_abs_diff_eq!( - swap_result.amount_paid_out as f64, - output_amount, - epsilon = output_amount / 10. - ); - - if order_liquidity_fraction <= 0.001 { - let (tao_delta_expected, alpha_delta_expected) = match order_type { - OrderType::Buy => (order_liquidity as i64, -(output_amount as i64)), - OrderType::Sell => (-(output_amount as i64), order_liquidity as i64), - }; - assert_abs_diff_eq!( - swap_result.alpha_reserve_delta, - alpha_delta_expected, - epsilon = alpha_delta_expected.abs() / 10 - ); - assert_abs_diff_eq!( - swap_result.tao_reserve_delta, - tao_delta_expected, - epsilon = tao_delta_expected.abs() / 10 - ); - } - - // Assert that price movement is in correct direction - let current_price_after = Pallet::::current_price(netuid); - match order_type { - OrderType::Buy => assert!(current_price_after > current_price), - OrderType::Sell => assert!(current_price_after < current_price), - } - - // Assert that for small amounts price stays within the user position - if (order_liquidity_fraction <= 0.001) - && (price_low_offset > 0.0001) - && (price_high_offset > 0.0001) - { - assert!(current_price_after <= price_high); - assert!(current_price_after >= price_low); - } - - // Check that low and high ticks' fees were updated properly - let tick_low_info = Ticks::::get(netuid, tick_low).unwrap(); - let tick_high_info = Ticks::::get(netuid, tick_high).unwrap(); - let expected_liquidity_net_low = tick_low_info_before.liquidity_net; - let expected_liquidity_gross_low = tick_low_info_before.liquidity_gross; - let expected_liquidity_net_high = tick_high_info_before.liquidity_net; - let expected_liquidity_gross_high = tick_high_info_before.liquidity_gross; - assert_eq!(tick_low_info.liquidity_net, expected_liquidity_net_low,); - assert_eq!(tick_low_info.liquidity_gross, expected_liquidity_gross_low,); - assert_eq!(tick_high_info.liquidity_net, expected_liquidity_net_high,); - assert_eq!( - tick_high_info.liquidity_gross, - expected_liquidity_gross_high, - ); - - // Expected fee amount - let fee_rate = FeeRate::::get(netuid) as f64 / u16::MAX as f64; - let expected_fee = - (order_liquidity - order_liquidity / (1.0 + fee_rate)) as u64; - - // Global fees should be updated - let actual_global_fee = ((match order_type { - OrderType::Buy => FeeGlobalTao::::get(netuid), - OrderType::Sell => FeeGlobalAlpha::::get(netuid), - }) - .to_num::() - * (liquidity_before as f64)) - as u64; - - assert_abs_diff_eq!( - swap_result.fee_paid, - expected_fee, - epsilon = expected_fee / 10 - ); - assert_abs_diff_eq!( - actual_global_fee, - expected_fee, - epsilon = expected_fee / 10 - ); - - // Tick fees should be updated - - // Liquidity position should not be updated - let positions = - Positions::::iter_prefix_values((netuid, OK_COLDKEY_ACCOUNT_ID)) - .collect::>(); - let position = positions.first().unwrap(); - - assert_eq!(position.liquidity, position_liquidity,); - assert_eq!(position.tick_low, tick_low); - assert_eq!(position.tick_high, tick_high); - assert_eq!(position.fees_alpha, 0); - assert_eq!(position.fees_tao, 0); - }); - }); + for liquidity_fraction in [0.0001, 0.001, 0.01, 0.1, 0.2, 0.5] { + perform_test!( + AlphaForTao, + price_low_offset, + price_high_offset, + position_liquidity, + liquidity_fraction, + 1000.0_f64, + true + ); + perform_test!( + TaoForAlpha, + price_low_offset, + price_high_offset, + position_liquidity, + liquidity_fraction, + 0.0001_f64, + false + ); + } }, ); } @@ -1210,102 +1198,78 @@ fn test_swap_multiple_positions() { }, ); - // All these orders are executed without swap reset - [ - (OrderType::Buy, 100_000_u64, 1000.0_f64), - (OrderType::Sell, 100_000, 0.0001_f64), - (OrderType::Buy, 1_000_000, 1000.0_f64), - (OrderType::Sell, 1_000_000, 0.0001_f64), - (OrderType::Buy, 10_000_000, 1000.0_f64), - (OrderType::Sell, 10_000_000, 0.0001_f64), - (OrderType::Buy, 100_000_000, 1000.0), - (OrderType::Sell, 100_000_000, 0.0001), - (OrderType::Buy, 200_000_000, 1000.0_f64), - (OrderType::Sell, 200_000_000, 0.0001), - (OrderType::Buy, 500_000_000, 1000.0), - (OrderType::Sell, 500_000_000, 0.0001), - (OrderType::Buy, 1_000_000_000, 1000.0), - (OrderType::Sell, 1_000_000_000, 0.0001), - (OrderType::Buy, 10_000_000_000, 1000.0), - (OrderType::Sell, 10_000_000_000, 0.0001), - ] - .into_iter() - .for_each(|(order_type, order_liquidity, limit_price)| { - ////////////////////////////////////////////// - // Swap - let sqrt_current_price = Pallet::::current_price_sqrt(netuid); - let current_price = (sqrt_current_price * sqrt_current_price).to_num::(); - let liquidity_before = CurrentLiquidity::::get(netuid); + macro_rules! perform_test { + ($order_t:ident, $order_liquidity:expr, $limit_price:expr, $should_price_grow:expr) => { + ////////////////////////////////////////////// + // Swap + let order_liquidity = $order_liquidity; + let limit_price = $limit_price; + let should_price_grow = $should_price_grow; - let output_amount = match order_type { - OrderType::Buy => { - let denom = sqrt_current_price.to_num::() - * (sqrt_current_price.to_num::() * liquidity_before as f64 - + order_liquidity as f64); - let per_order_liq = liquidity_before as f64 / denom; - per_order_liq * order_liquidity as f64 - } - OrderType::Sell => { - let denom = liquidity_before as f64 / sqrt_current_price.to_num::() - + order_liquidity as f64; - let per_order_liq = - sqrt_current_price.to_num::() * liquidity_before as f64 / denom; - per_order_liq * order_liquidity as f64 - } - }; + let sqrt_current_price = AlphaSqrtPrice::::get(netuid); + let current_price = (sqrt_current_price * sqrt_current_price).to_num::(); + let liquidity_before = CurrentLiquidity::::get(netuid); + let output_amount = >::approx_expected_swap_output( + sqrt_current_price.to_num(), + liquidity_before as f64, + order_liquidity as f64, + ); - // Do the swap - let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); - let swap_result = Pallet::::do_swap( - netuid, - order_type, - order_liquidity, - sqrt_limit_price, - false, - false, - ) - .unwrap(); - assert_abs_diff_eq!( - swap_result.amount_paid_out as f64, - output_amount, - epsilon = output_amount / 10. - ); + // Do the swap + let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); + let order = $order_t::with_amount(order_liquidity); + let swap_result = + Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); + assert_abs_diff_eq!( + swap_result.amount_paid_out.to_u64() as f64, + output_amount, + epsilon = output_amount / 10. + ); - let tao_reserve = MockLiquidityProvider::tao_reserve(netuid.into()).to_u64(); - let alpha_reserve = MockLiquidityProvider::alpha_reserve(netuid.into()).to_u64(); - let output_amount = output_amount as u64; + let tao_reserve = TaoReserve::reserve(netuid.into()).to_u64(); + let alpha_reserve = AlphaReserve::reserve(netuid.into()).to_u64(); + let output_amount = output_amount as u64; - assert!(output_amount > 0); + assert!(output_amount > 0); - if alpha_reserve > order_liquidity && tao_reserve > order_liquidity { - let (tao_delta_expected, alpha_delta_expected) = match order_type { - OrderType::Buy => (order_liquidity as i64, -(output_amount as i64)), - OrderType::Sell => (-(output_amount as i64), order_liquidity as i64), - }; - assert_abs_diff_eq!( - swap_result.alpha_reserve_delta, - alpha_delta_expected, - epsilon = alpha_delta_expected.abs() / 100 - ); - assert_abs_diff_eq!( - swap_result.tao_reserve_delta, - tao_delta_expected, - epsilon = tao_delta_expected.abs() / 100 - ); - } + if alpha_reserve > order_liquidity && tao_reserve > order_liquidity { + assert_abs_diff_eq!( + swap_result.paid_in_reserve_delta() as i64, + order_liquidity as i64, + epsilon = order_liquidity as i64 / 100 + ); + assert_abs_diff_eq!( + swap_result.paid_out_reserve_delta() as i64, + -(output_amount as i64), + epsilon = output_amount as i64 / 100 + ); + } - // Assert that price movement is in correct direction - let sqrt_current_price_after = Pallet::::current_price_sqrt(netuid); - let current_price_after = - (sqrt_current_price_after * sqrt_current_price_after).to_num::(); - match order_type { - OrderType::Buy => assert!(current_price_after > current_price), - OrderType::Sell => assert!(current_price_after < current_price), - } - }); + // Assert that price movement is in correct direction + let sqrt_current_price_after = AlphaSqrtPrice::::get(netuid); + let current_price_after = + (sqrt_current_price_after * sqrt_current_price_after).to_num::(); + assert_eq!(should_price_grow, current_price_after > current_price); + }; + } + + // All these orders are executed without swap reset + for order_liquidity in [ + (100_000_u64), + (1_000_000), + (10_000_000), + (100_000_000), + (200_000_000), + (500_000_000), + (1_000_000_000), + (10_000_000_000), + ] { + perform_test!(AlphaForTao, order_liquidity, 1000.0_f64, true); + perform_test!(TaoForAlpha, order_liquidity, 0.0001_f64, false); + } // Current price shouldn't be much different from the original - let sqrt_current_price_after = Pallet::::current_price_sqrt(netuid); + let sqrt_current_price_after = AlphaSqrtPrice::::get(netuid); let current_price_after = (sqrt_current_price_after * sqrt_current_price_after).to_num::(); assert_abs_diff_eq!( @@ -1321,8 +1285,7 @@ fn test_swap_multiple_positions() { fn test_swap_precision_edge_case() { new_test_ext().execute_with(|| { let netuid = NetUid::from(123); // 123 is netuid with low edge case liquidity - let order_type = OrderType::Sell; - let liquidity = 1_000_000_000_000_000_000; + let order = TaoForAlpha::with_amount(1_000_000_000_000_000_000); let tick_low = TickIndex::MIN; let sqrt_limit_price: SqrtPrice = tick_low.try_to_sqrt_price().unwrap(); @@ -1332,10 +1295,9 @@ fn test_swap_precision_edge_case() { // Swap let swap_result = - Pallet::::do_swap(netuid, order_type, liquidity, sqrt_limit_price, false, true) - .unwrap(); + Pallet::::do_swap(netuid, order, sqrt_limit_price, false, true).unwrap(); - assert!(swap_result.amount_paid_out > 0); + assert!(swap_result.amount_paid_out > TaoCurrency::ZERO); }); } @@ -1413,14 +1375,20 @@ fn test_convert_deltas() { AlphaSqrtPrice::::insert(netuid, sqrt_price); assert_abs_diff_eq!( - Pallet::::convert_deltas(netuid, OrderType::Sell, delta_in), - expected_sell, - epsilon = 2 + BasicSwapStep::::convert_deltas( + netuid, + delta_in.into() + ), + expected_sell.into(), + epsilon = 2.into() ); assert_abs_diff_eq!( - Pallet::::convert_deltas(netuid, OrderType::Buy, delta_in), - expected_buy, - epsilon = 2 + BasicSwapStep::::convert_deltas( + netuid, + delta_in.into() + ), + expected_buy.into(), + epsilon = 2.into() ); } } @@ -1534,8 +1502,7 @@ fn test_swap_fee_correctness() { // Swap buy and swap sell Pallet::::do_swap( netuid, - OrderType::Buy, - liquidity / 10, + AlphaForTao::with_amount(liquidity / 10), u64::MAX.into(), false, false, @@ -1543,8 +1510,7 @@ fn test_swap_fee_correctness() { .unwrap(); Pallet::::do_swap( netuid, - OrderType::Sell, - liquidity / 10, + TaoForAlpha::with_amount(liquidity / 10), 0_u64.into(), false, false, @@ -1641,8 +1607,7 @@ fn test_rollback_works() { assert_eq!( Pallet::::do_swap( netuid, - OrderType::Buy, - 1_000_000, + AlphaForTao::with_amount(1_000_000), u64::MAX.into(), false, true @@ -1650,8 +1615,7 @@ fn test_rollback_works() { .unwrap(), Pallet::::do_swap( netuid, - OrderType::Buy, - 1_000_000, + AlphaForTao::with_amount(1_000_000), u64::MAX.into(), false, false @@ -1695,8 +1659,7 @@ fn test_new_lp_doesnt_get_old_fees() { // Swap buy and swap sell Pallet::::do_swap( netuid, - OrderType::Buy, - liquidity / 10, + AlphaForTao::with_amount(liquidity / 10), u64::MAX.into(), false, false, @@ -1704,8 +1667,7 @@ fn test_new_lp_doesnt_get_old_fees() { .unwrap(); Pallet::::do_swap( netuid, - OrderType::Sell, - liquidity / 10, + TaoForAlpha::with_amount(liquidity / 10), 0_u64.into(), false, false, @@ -1748,7 +1710,7 @@ fn bbox(t: U64F64, a: U64F64, b: U64F64) -> U64F64 { } fn print_current_price(netuid: NetUid) { - let current_sqrt_price = Pallet::::current_price_sqrt(netuid).to_num::(); + let current_sqrt_price = AlphaSqrtPrice::::get(netuid).to_num::(); let current_price = current_sqrt_price * current_sqrt_price; log::trace!("Current price: {current_price:.6}"); } @@ -1776,20 +1738,16 @@ fn test_wrapping_fees() { print_current_price(netuid); - let swap_amt = 800_000_000_u64; - let order_type = OrderType::Sell; + let order = TaoForAlpha::with_amount(800_000_000); let sqrt_limit_price = SqrtPrice::from_num(0.000001); - Pallet::::do_swap(netuid, order_type, swap_amt, sqrt_limit_price, false, false) - .unwrap(); + Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); - let swap_amt = 1_850_000_000_u64; - let order_type = OrderType::Buy; + let order = AlphaForTao::with_amount(1_850_000_000); let sqrt_limit_price = SqrtPrice::from_num(1_000_000.0); print_current_price(netuid); - Pallet::::do_swap(netuid, order_type, swap_amt, sqrt_limit_price, false, false) - .unwrap(); + Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); print_current_price(netuid); @@ -1803,14 +1761,12 @@ fn test_wrapping_fees() { ) .unwrap(); - let swap_amt = 1_800_000_000_u64; - let order_type = OrderType::Sell; + let order = TaoForAlpha::with_amount(1_800_000_000); let sqrt_limit_price = SqrtPrice::from_num(0.000001); - let initial_sqrt_price = Pallet::::current_price_sqrt(netuid); - Pallet::::do_swap(netuid, order_type, swap_amt, sqrt_limit_price, false, false) - .unwrap(); - let final_sqrt_price = Pallet::::current_price_sqrt(netuid); + let initial_sqrt_price = AlphaSqrtPrice::::get(netuid); + Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); + let final_sqrt_price = AlphaSqrtPrice::::get(netuid); print_current_price(netuid); @@ -1876,74 +1832,70 @@ fn test_less_price_movement() { // - Provide liquidity if iteration provides lq // - Buy or sell // - Save end price if iteration doesn't provide lq - [ - (OrderType::Buy, 0_u64), - (OrderType::Buy, 1_000_000_000_000_u64), - (OrderType::Sell, 0_u64), - (OrderType::Sell, 1_000_000_000_000_u64), - ] - .into_iter() - .for_each(|(order_type, provided_liquidity)| { - new_test_ext().execute_with(|| { - // Setup swap - assert_ok!(Pallet::::maybe_initialize_v3(netuid)); + macro_rules! perform_test { + ($order_t:ident, $provided_liquidity:expr, $limit_price:expr, $should_price_shrink:expr) => { + let provided_liquidity = $provided_liquidity; + let should_price_shrink = $should_price_shrink; + let limit_price = $limit_price; + new_test_ext().execute_with(|| { + // Setup swap + assert_ok!(Pallet::::maybe_initialize_v3(netuid)); - // Buy Alpha - assert_ok!(Pallet::::do_swap( - netuid, - OrderType::Buy, - initial_stake_liquidity, - SqrtPrice::from_num(10_000_000_000_u64), - false, - false - )); + // Buy Alpha + assert_ok!(Pallet::::do_swap( + netuid, + AlphaForTao::with_amount(initial_stake_liquidity), + SqrtPrice::from_num(10_000_000_000_u64), + false, + false + )); - // Get current price - let start_price = Pallet::::current_price(netuid); + // Get current price + let start_price = Pallet::::current_price(netuid); - // Add liquidity if this test iteration provides - if provided_liquidity > 0 { - let tick_low = price_to_tick(start_price.to_num::() * 0.5); - let tick_high = price_to_tick(start_price.to_num::() * 1.5); - assert_ok!(Pallet::::do_add_liquidity( + // Add liquidity if this test iteration provides + if provided_liquidity > 0 { + let tick_low = price_to_tick(start_price.to_num::() * 0.5); + let tick_high = price_to_tick(start_price.to_num::() * 1.5); + assert_ok!(Pallet::::do_add_liquidity( + netuid, + &OK_COLDKEY_ACCOUNT_ID, + &OK_HOTKEY_ACCOUNT_ID, + tick_low, + tick_high, + provided_liquidity, + )); + } + + // Swap + let sqrt_limit_price = SqrtPrice::from_num(limit_price); + assert_ok!(Pallet::::do_swap( netuid, - &OK_COLDKEY_ACCOUNT_ID, - &OK_HOTKEY_ACCOUNT_ID, - tick_low, - tick_high, - provided_liquidity, + $order_t::with_amount(swapped_liquidity), + sqrt_limit_price, + false, + false )); - } - - // Swap - let sqrt_limit_price = if order_type == OrderType::Buy { - SqrtPrice::from_num(1000.) - } else { - SqrtPrice::from_num(0.001) - }; - assert_ok!(Pallet::::do_swap( - netuid, - order_type, - swapped_liquidity, - sqrt_limit_price, - false, - false - )); - let end_price = Pallet::::current_price(netuid); + let end_price = Pallet::::current_price(netuid); - // Save end price if iteration doesn't provide or compare with previous end price if it does - if provided_liquidity > 0 { - if order_type == OrderType::Buy { - assert!(end_price < last_end_price); + // Save end price if iteration doesn't provide or compare with previous end price if + // it does + if provided_liquidity > 0 { + assert_eq!(should_price_shrink, end_price < last_end_price); } else { - assert!(end_price > last_end_price); + last_end_price = end_price; } - } else { - last_end_price = end_price; - } - }); - }); + }); + }; + } + + for provided_liquidity in [0, 1_000_000_000_000_u64] { + perform_test!(AlphaForTao, provided_liquidity, 1000.0_f64, true); + } + for provided_liquidity in [0, 1_000_000_000_000_u64] { + perform_test!(TaoForAlpha, provided_liquidity, 0.001_f64, false); + } } #[test] @@ -2021,8 +1973,7 @@ fn test_liquidate_v3_removes_positions_ticks_and_state() { let sqrt_limit_price = SqrtPrice::from_num(1_000_000.0); assert_ok!(Pallet::::do_swap( netuid, - OrderType::Buy, - 1_000_000, + AlphaForTao::with_amount(1_000_000), sqrt_limit_price, false, false @@ -2320,8 +2271,8 @@ fn liquidate_v3_refunds_user_funds_and_clears_state() { need_alpha.into(), ) .expect("decrease ALPHA"); - ::BalanceOps::increase_provided_tao_reserve(netuid.into(), tao_taken); - ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), alpha_taken); + TaoReserve::increase_provided(netuid.into(), tao_taken); + AlphaReserve::increase_provided(netuid.into(), alpha_taken); // Liquidate everything on the subnet. assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); @@ -2385,7 +2336,7 @@ fn refund_alpha_single_provider_exact() { alpha_needed.into(), ) .expect("decrease ALPHA"); - ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), alpha_taken); + AlphaReserve::increase_provided(netuid.into(), alpha_taken); // --- Act: dissolve (calls refund_alpha inside). assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); @@ -2453,12 +2404,12 @@ fn refund_alpha_multiple_providers_proportional_to_principal() { let a1_taken = ::BalanceOps::decrease_stake(&c1, &h1, netuid.into(), a1.into()) .expect("decrease α #1"); - ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), a1_taken); + AlphaReserve::increase_provided(netuid.into(), a1_taken); let a2_taken = ::BalanceOps::decrease_stake(&c2, &h2, netuid.into(), a2.into()) .expect("decrease α #2"); - ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), a2_taken); + AlphaReserve::increase_provided(netuid.into(), a2_taken); // Act assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); @@ -2515,12 +2466,12 @@ fn refund_alpha_same_cold_multiple_hotkeys_conserved_to_owner() { let t1 = ::BalanceOps::decrease_stake(&cold, &hot1, netuid.into(), a1.into()) .expect("decr α #hot1"); - ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), t1); + AlphaReserve::increase_provided(netuid.into(), t1); let t2 = ::BalanceOps::decrease_stake(&cold, &hot2, netuid.into(), a2.into()) .expect("decr α #hot2"); - ::BalanceOps::increase_provided_alpha_reserve(netuid.into(), t2); + AlphaReserve::increase_provided(netuid.into(), t2); // Act assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); From dbc6f663edceecc6f3362c6496e5193ff9fc5412 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Wed, 1 Oct 2025 12:52:47 +0300 Subject: [PATCH 09/14] Refactor SwapHandler --- pallets/swap-interface/src/lib.rs | 45 ++++++++++------------------- pallets/swap-interface/src/order.rs | 8 ++--- pallets/swap/src/mock.rs | 8 ++--- pallets/swap/src/pallet/impls.rs | 44 +++++----------------------- pallets/swap/src/pallet/tests.rs | 44 ++++++++++++++-------------- 5 files changed, 53 insertions(+), 96 deletions(-) diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index b543590836..4646cc4933 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -9,24 +9,29 @@ pub use order::*; mod order; -pub trait SwapHandler { - fn swap( +pub trait SwapEngine: DefaultPriceLimit { + fn swap( netuid: NetUid, order: OrderT, price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result, DispatchError> - where - OrderT: Order, - Self: SwapEngine; - fn sim_swap( + ) -> Result, DispatchError>; + fn sim_swap( netuid: NetUid, order: OrderT, - ) -> Result, DispatchError> - where - OrderT: Order, - Self: DefaultPriceLimit + SwapEngine; + ) -> Result, DispatchError>; +} + +pub trait DefaultPriceLimit +where + PaidIn: Currency, + PaidOut: Currency, +{ + fn default_price_limit() -> C; +} + +pub trait SwapExt { fn approx_fee_amount(netuid: NetUid, amount: T) -> T; fn current_alpha_price(netuid: NetUid) -> U96F32; fn max_price() -> C; @@ -42,24 +47,6 @@ pub trait SwapHandler { fn clear_protocol_liquidity(netuid: NetUid) -> DispatchResult; } -pub trait DefaultPriceLimit -where - PaidIn: Currency, - PaidOut: Currency, -{ - fn default_price_limit() -> C; -} - -pub trait SwapEngine { - 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-interface/src/order.rs b/pallets/swap-interface/src/order.rs index 5fbfad9923..1576283fd5 100644 --- a/pallets/swap-interface/src/order.rs +++ b/pallets/swap-interface/src/order.rs @@ -15,7 +15,7 @@ pub trait Order: Clone { } #[derive(Clone, Default)] -pub struct AlphaForTao +pub struct GetAlphaForTao where ReserveIn: CurrencyReserve, ReserveOut: CurrencyReserve, @@ -24,7 +24,7 @@ where _phantom: PhantomData<(ReserveIn, ReserveOut)>, } -impl Order for AlphaForTao +impl Order for GetAlphaForTao where ReserveIn: CurrencyReserve + Clone, ReserveOut: CurrencyReserve + Clone, @@ -51,7 +51,7 @@ where } #[derive(Clone, Default)] -pub struct TaoForAlpha +pub struct GetTaoForAlpha where ReserveIn: CurrencyReserve, ReserveOut: CurrencyReserve, @@ -60,7 +60,7 @@ where _phantom: PhantomData<(ReserveIn, ReserveOut)>, } -impl Order for TaoForAlpha +impl Order for GetTaoForAlpha where ReserveIn: CurrencyReserve + Clone, ReserveOut: CurrencyReserve + Clone, diff --git a/pallets/swap/src/mock.rs b/pallets/swap/src/mock.rs index feaa3ea56b..5dae51c1db 100644 --- a/pallets/swap/src/mock.rs +++ b/pallets/swap/src/mock.rs @@ -120,8 +120,8 @@ impl CurrencyReserve for AlphaReserve { fn decrease_provided(_: NetUid, _: AlphaCurrency) {} } -pub type AlphaForTao = subtensor_swap_interface::AlphaForTao; -pub type TaoForAlpha = subtensor_swap_interface::TaoForAlpha; +pub type GetAlphaForTao = subtensor_swap_interface::GetAlphaForTao; +pub type GetTaoForAlpha = subtensor_swap_interface::GetTaoForAlpha; pub(crate) trait GlobalFeeInfo: Currency { fn global_fee(&self, netuid: NetUid) -> U64F64; @@ -147,7 +147,7 @@ pub(crate) trait TestExt { ) -> f64; } -impl TestExt for Test { +impl TestExt for Test { fn approx_expected_swap_output( sqrt_current_price: f64, liquidity_before: f64, @@ -159,7 +159,7 @@ impl TestExt for Test { } } -impl TestExt for Test { +impl TestExt for Test { fn approx_expected_swap_output( sqrt_current_price: f64, liquidity_before: f64, diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 600a075934..0e49e82e56 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -10,7 +10,7 @@ use subtensor_runtime_common::{ AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, }; use subtensor_swap_interface::{ - DefaultPriceLimit, Order as OrderT, SwapEngine, SwapHandler, SwapResult, + DefaultPriceLimit, Order as OrderT, SwapEngine, SwapExt, SwapResult, }; use super::pallet::*; @@ -984,6 +984,7 @@ impl DefaultPriceLimit for Pallet { impl SwapEngine for Pallet where Order: OrderT, + Self: DefaultPriceLimit, BasicSwapStep: SwapStep, { fn swap( @@ -1007,49 +1008,16 @@ where ) .map_err(Into::into) } -} - -impl SwapHandler for Pallet { - fn swap( - netuid: NetUid, - order: Order, - price_limit: TaoCurrency, - drop_fees: bool, - should_rollback: bool, - ) -> Result, DispatchError> - where - Order: OrderT, - Self: SwapEngine, - { - >::swap( - NetUid::from(netuid), - order, - price_limit, - drop_fees, - should_rollback, - ) - .map_err(Into::into) - } - fn sim_swap( + fn sim_swap( netuid: NetUid, order: Order, - ) -> Result, DispatchError> - where - Order: OrderT, - Self: DefaultPriceLimit + SwapEngine, - { + ) -> Result, DispatchError> { match T::SubnetInfo::mechanism(netuid) { 1 => { let price_limit = Self::default_price_limit::(); - >::swap::( - netuid, - order, - price_limit, - false, - true, - ) + Self::swap(netuid, order, price_limit, false, true) } _ => { let actual_amount = if T::SubnetInfo::exists(netuid) { @@ -1065,7 +1033,9 @@ impl SwapHandler for Pallet { } } } +} +impl SwapExt for Pallet { fn approx_fee_amount(netuid: NetUid, amount: C) -> C { Self::calculate_fee_amount(netuid, amount, false) } diff --git a/pallets/swap/src/pallet/tests.rs b/pallets/swap/src/pallet/tests.rs index 0c50fa2519..88172c66ac 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -666,7 +666,7 @@ fn test_modify_position_basic() { // Swap to create fees on the position let sqrt_limit_price = SqrtPrice::from_num((limit_price).sqrt()); - let order = AlphaForTao::with_amount(liquidity / 10); + let order = GetAlphaForTao::with_amount(liquidity / 10); Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); // Modify liquidity (also causes claiming of fees) @@ -864,21 +864,21 @@ fn test_swap_basic() { // Test case is (order_type, liquidity, limit_price, output_amount) perform_test( 1.into(), - AlphaForTao::with_amount(1_000), + GetAlphaForTao::with_amount(1_000), 1000.0, 3990, true, ); perform_test( 2.into(), - TaoForAlpha::with_amount(1_000), + GetTaoForAlpha::with_amount(1_000), 0.0001, 250, false, ); perform_test( 3.into(), - AlphaForTao::with_amount(500_000_000), + GetAlphaForTao::with_amount(500_000_000), 1000.0, 2_000_000_000, true, @@ -1105,7 +1105,7 @@ fn test_swap_single_position() { // order_liquidity is represented as a fraction of position_liquidity for liquidity_fraction in [0.0001, 0.001, 0.01, 0.1, 0.2, 0.5] { perform_test!( - AlphaForTao, + GetAlphaForTao, price_low_offset, price_high_offset, position_liquidity, @@ -1114,7 +1114,7 @@ fn test_swap_single_position() { true ); perform_test!( - TaoForAlpha, + GetTaoForAlpha, price_low_offset, price_high_offset, position_liquidity, @@ -1264,8 +1264,8 @@ fn test_swap_multiple_positions() { (1_000_000_000), (10_000_000_000), ] { - perform_test!(AlphaForTao, order_liquidity, 1000.0_f64, true); - perform_test!(TaoForAlpha, order_liquidity, 0.0001_f64, false); + perform_test!(GetAlphaForTao, order_liquidity, 1000.0_f64, true); + perform_test!(GetTaoForAlpha, order_liquidity, 0.0001_f64, false); } // Current price shouldn't be much different from the original @@ -1285,7 +1285,7 @@ fn test_swap_multiple_positions() { fn test_swap_precision_edge_case() { new_test_ext().execute_with(|| { let netuid = NetUid::from(123); // 123 is netuid with low edge case liquidity - let order = TaoForAlpha::with_amount(1_000_000_000_000_000_000); + let order = GetTaoForAlpha::with_amount(1_000_000_000_000_000_000); let tick_low = TickIndex::MIN; let sqrt_limit_price: SqrtPrice = tick_low.try_to_sqrt_price().unwrap(); @@ -1502,7 +1502,7 @@ fn test_swap_fee_correctness() { // Swap buy and swap sell Pallet::::do_swap( netuid, - AlphaForTao::with_amount(liquidity / 10), + GetAlphaForTao::with_amount(liquidity / 10), u64::MAX.into(), false, false, @@ -1510,7 +1510,7 @@ fn test_swap_fee_correctness() { .unwrap(); Pallet::::do_swap( netuid, - TaoForAlpha::with_amount(liquidity / 10), + GetTaoForAlpha::with_amount(liquidity / 10), 0_u64.into(), false, false, @@ -1607,7 +1607,7 @@ fn test_rollback_works() { assert_eq!( Pallet::::do_swap( netuid, - AlphaForTao::with_amount(1_000_000), + GetAlphaForTao::with_amount(1_000_000), u64::MAX.into(), false, true @@ -1615,7 +1615,7 @@ fn test_rollback_works() { .unwrap(), Pallet::::do_swap( netuid, - AlphaForTao::with_amount(1_000_000), + GetAlphaForTao::with_amount(1_000_000), u64::MAX.into(), false, false @@ -1659,7 +1659,7 @@ fn test_new_lp_doesnt_get_old_fees() { // Swap buy and swap sell Pallet::::do_swap( netuid, - AlphaForTao::with_amount(liquidity / 10), + GetAlphaForTao::with_amount(liquidity / 10), u64::MAX.into(), false, false, @@ -1667,7 +1667,7 @@ fn test_new_lp_doesnt_get_old_fees() { .unwrap(); Pallet::::do_swap( netuid, - TaoForAlpha::with_amount(liquidity / 10), + GetTaoForAlpha::with_amount(liquidity / 10), 0_u64.into(), false, false, @@ -1738,11 +1738,11 @@ fn test_wrapping_fees() { print_current_price(netuid); - let order = TaoForAlpha::with_amount(800_000_000); + let order = GetTaoForAlpha::with_amount(800_000_000); let sqrt_limit_price = SqrtPrice::from_num(0.000001); Pallet::::do_swap(netuid, order, sqrt_limit_price, false, false).unwrap(); - let order = AlphaForTao::with_amount(1_850_000_000); + let order = GetAlphaForTao::with_amount(1_850_000_000); let sqrt_limit_price = SqrtPrice::from_num(1_000_000.0); print_current_price(netuid); @@ -1761,7 +1761,7 @@ fn test_wrapping_fees() { ) .unwrap(); - let order = TaoForAlpha::with_amount(1_800_000_000); + let order = GetTaoForAlpha::with_amount(1_800_000_000); let sqrt_limit_price = SqrtPrice::from_num(0.000001); let initial_sqrt_price = AlphaSqrtPrice::::get(netuid); @@ -1844,7 +1844,7 @@ fn test_less_price_movement() { // Buy Alpha assert_ok!(Pallet::::do_swap( netuid, - AlphaForTao::with_amount(initial_stake_liquidity), + GetAlphaForTao::with_amount(initial_stake_liquidity), SqrtPrice::from_num(10_000_000_000_u64), false, false @@ -1891,10 +1891,10 @@ fn test_less_price_movement() { } for provided_liquidity in [0, 1_000_000_000_000_u64] { - perform_test!(AlphaForTao, provided_liquidity, 1000.0_f64, true); + perform_test!(GetAlphaForTao, provided_liquidity, 1000.0_f64, true); } for provided_liquidity in [0, 1_000_000_000_000_u64] { - perform_test!(TaoForAlpha, provided_liquidity, 0.001_f64, false); + perform_test!(GetTaoForAlpha, provided_liquidity, 0.001_f64, false); } } @@ -1972,7 +1972,7 @@ fn test_liquidate_v3_removes_positions_ticks_and_state() { let sqrt_limit_price = SqrtPrice::from_num(1_000_000.0); assert_ok!(Pallet::::do_swap( netuid, - AlphaForTao::with_amount(1_000_000), + GetAlphaForTao::with_amount(1_000_000), sqrt_limit_price, false, false From cb4ba55d94e61f7d7eb22d4b3311d598a8e1ce22 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Wed, 1 Oct 2025 14:25:21 +0300 Subject: [PATCH 10/14] Update swap api in subtensor module --- Cargo.lock | 1 + .../subtensor/src/coinbase/block_emission.rs | 4 +- pallets/subtensor/src/coinbase/root.rs | 6 +- .../subtensor/src/coinbase/run_coinbase.rs | 14 +- pallets/subtensor/src/lib.rs | 59 ++++++--- pallets/subtensor/src/macros/config.rs | 9 +- pallets/subtensor/src/rpc_info/stake_info.rs | 4 +- pallets/subtensor/src/staking/add_stake.rs | 22 ++- pallets/subtensor/src/staking/helpers.rs | 36 +++-- pallets/subtensor/src/staking/move_stake.rs | 11 +- pallets/subtensor/src/staking/remove_stake.rs | 36 +++-- pallets/subtensor/src/staking/stake_utils.rs | 125 +++++++----------- pallets/subtensor/src/subnets/leasing.rs | 4 +- pallets/subtensor/src/subnets/registration.rs | 12 +- pallets/subtensor/src/subnets/subnet.rs | 4 +- pallets/subtensor/src/tests/children.rs | 4 +- pallets/subtensor/src/tests/coinbase.rs | 10 +- pallets/subtensor/src/tests/epoch.rs | 6 +- pallets/subtensor/src/tests/mock.rs | 19 +-- pallets/subtensor/src/tests/move_stake.rs | 69 +++++----- pallets/subtensor/src/tests/networks.rs | 11 +- pallets/subtensor/src/tests/senate.rs | 8 +- pallets/subtensor/src/tests/staking.rs | 92 ++++++------- pallets/subtensor/src/tests/staking2.rs | 35 +++-- pallets/subtensor/src/tests/swap_coldkey.rs | 2 +- pallets/subtensor/src/tests/swap_hotkey.rs | 2 +- pallets/subtensor/src/tests/weights.rs | 8 +- pallets/swap-interface/Cargo.toml | 1 + pallets/swap-interface/src/lib.rs | 13 ++ pallets/transaction-fee/src/lib.rs | 4 +- pallets/transaction-fee/src/tests/mock.rs | 4 +- 31 files changed, 300 insertions(+), 335 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6b5204cb2a..5ccf87a3a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17826,6 +17826,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "substrate-fixed", + "subtensor-macros", "subtensor-runtime-common", ] diff --git a/pallets/subtensor/src/coinbase/block_emission.rs b/pallets/subtensor/src/coinbase/block_emission.rs index 064bab4d2a..5c56b70c36 100644 --- a/pallets/subtensor/src/coinbase/block_emission.rs +++ b/pallets/subtensor/src/coinbase/block_emission.rs @@ -6,7 +6,7 @@ use substrate_fixed::{ types::{I96F32, U96F32}, }; use subtensor_runtime_common::{NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::SwapExt; impl Pallet { /// Calculates the dynamic TAO emission for a given subnet. @@ -40,7 +40,7 @@ impl Pallet { let float_alpha_block_emission: U96F32 = U96F32::saturating_from_num(alpha_block_emission); // Get alpha price for subnet. - let alpha_price = T::SwapInterface::current_alpha_price(netuid.into()); + let alpha_price = T::SwapExt::current_alpha_price(netuid.into()); log::debug!("{netuid:?} - alpha_price: {alpha_price:?}"); // Get initial alpha_in diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 6b09c9ed46..1353fe0f42 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -22,7 +22,7 @@ use safe_math::*; use sp_core::Get; use substrate_fixed::types::{I64F64, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, NetUidStorageIndex, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::SwapExt; impl Pallet { /// Fetches the total count of root network validators @@ -373,9 +373,9 @@ impl Pallet { ); // 2. --- Perform the cleanup before removing the network. - T::SwapInterface::dissolve_all_liquidity_providers(netuid)?; + T::SwapExt::dissolve_all_liquidity_providers(netuid)?; Self::destroy_alpha_in_out_stakes(netuid)?; - T::SwapInterface::clear_protocol_liquidity(netuid)?; + T::SwapExt::clear_protocol_liquidity(netuid)?; T::CommitmentsInterface::purge_netuid(netuid); // 3. --- Remove the network diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 3f2f715df6..f5e57da63b 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -3,7 +3,7 @@ use alloc::collections::BTreeMap; use safe_math::*; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::SwapExt; // Distribute dividends to each hotkey macro_rules! asfloat { @@ -58,7 +58,7 @@ impl Pallet { // Only calculate for subnets that we are emitting to. for netuid_i in subnets_to_emit_to.iter() { // Get subnet price. - let price_i = T::SwapInterface::current_alpha_price((*netuid_i).into()); + let price_i = T::SwapExt::current_alpha_price((*netuid_i).into()); log::debug!("price_i: {price_i:?}"); // Get subnet TAO. let moving_price_i: U96F32 = Self::get_moving_alpha_price(*netuid_i); @@ -91,7 +91,7 @@ impl Pallet { let buy_swap_result = Self::swap_tao_for_alpha( *netuid_i, tou64!(difference_tao).into(), - T::SwapInterface::max_price().into(), + T::SwapExt::max_price(), true, ); if let Ok(buy_swap_result_ok) = buy_swap_result { @@ -159,7 +159,7 @@ impl Pallet { *total = total.saturating_add(tao_in_i.into()); }); // Adjust protocol liquidity based on new reserves - T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i); + T::SwapExt::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i); } // --- 5. Compute owner cuts and remove them from alpha_out remaining. @@ -220,14 +220,14 @@ impl Pallet { let swap_result = Self::swap_alpha_for_tao( *netuid_i, tou64!(root_alpha).into(), - T::SwapInterface::min_price().into(), + T::SwapExt::min_price(), true, ); if let Ok(ok_result) = swap_result { - let root_tao: u64 = ok_result.amount_paid_out; + let root_tao = ok_result.amount_paid_out; // Accumulate root divs for subnet. PendingRootDivs::::mutate(*netuid_i, |total| { - *total = total.saturating_add(root_tao.into()); + *total = total.saturating_add(root_tao); }); } } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index ab0809050f..e4f5e6d701 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -23,7 +23,7 @@ use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::{DispatchError, transaction_validity::TransactionValidityError}; use sp_std::marker::PhantomData; -use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; +use subtensor_runtime_common::{AlphaCurrency, Currency, CurrencyReserve, NetUid, TaoCurrency}; // ============================ // ==== Benchmark Imports ===== @@ -2124,17 +2124,48 @@ impl CollectiveInterface for () { } } -impl> - subtensor_runtime_common::SubnetInfo for Pallet -{ - fn tao_reserve(netuid: NetUid) -> TaoCurrency { +#[derive(Clone)] +pub struct TaoCurrencyReserve(PhantomData); + +impl CurrencyReserve for TaoCurrencyReserve { + fn reserve(netuid: NetUid) -> TaoCurrency { SubnetTAO::::get(netuid).saturating_add(SubnetTaoProvided::::get(netuid)) } - fn alpha_reserve(netuid: NetUid) -> AlphaCurrency { + fn increase_provided(netuid: NetUid, tao: TaoCurrency) { + Pallet::::increase_provided_tao_reserve(netuid, tao); + } + + fn decrease_provided(netuid: NetUid, tao: TaoCurrency) { + Pallet::::decrease_provided_tao_reserve(netuid, tao); + } +} + +#[derive(Clone)] +pub struct AlphaCurrencyReserve(PhantomData); + +impl CurrencyReserve for AlphaCurrencyReserve { + fn reserve(netuid: NetUid) -> AlphaCurrency { SubnetAlphaIn::::get(netuid).saturating_add(SubnetAlphaInProvided::::get(netuid)) } + fn increase_provided(netuid: NetUid, alpha: AlphaCurrency) { + Pallet::::increase_provided_alpha_reserve(netuid, alpha); + } + + fn decrease_provided(netuid: NetUid, alpha: AlphaCurrency) { + Pallet::::decrease_provided_alpha_reserve(netuid, alpha); + } +} + +pub type GetAlphaForTao = + subtensor_swap_interface::GetAlphaForTao, AlphaCurrencyReserve>; +pub type GetTaoForAlpha = + subtensor_swap_interface::GetTaoForAlpha, TaoCurrencyReserve>; + +impl> + subtensor_runtime_common::SubnetInfo for Pallet +{ fn exists(netuid: NetUid) -> bool { Self::if_subnet_exist(netuid) } @@ -2231,22 +2262,6 @@ impl> hotkey, coldkey, netuid, alpha, )) } - - fn increase_provided_tao_reserve(netuid: NetUid, tao: TaoCurrency) { - Self::increase_provided_tao_reserve(netuid, tao); - } - - fn decrease_provided_tao_reserve(netuid: NetUid, tao: TaoCurrency) { - Self::decrease_provided_tao_reserve(netuid, tao); - } - - fn increase_provided_alpha_reserve(netuid: NetUid, alpha: AlphaCurrency) { - Self::increase_provided_alpha_reserve(netuid, alpha); - } - - fn decrease_provided_alpha_reserve(netuid: NetUid, alpha: AlphaCurrency) { - Self::decrease_provided_alpha_reserve(netuid, alpha); - } } /// Enum that defines types of rate limited operations for diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index bcd1bcd56a..f6aeb6450c 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -8,7 +8,7 @@ mod config { use crate::CommitmentsInterface; use pallet_commitments::GetCommitments; - use subtensor_swap_interface::SwapHandler; + use subtensor_swap_interface::{Order as OrderT, SwapEngine, SwapExt}; /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] @@ -54,8 +54,11 @@ mod config { /// the preimage to store the call data. type Preimages: QueryPreimage + StorePreimage; - /// Swap interface. - type SwapInterface: SwapHandler; + /// Implementor of `SwapExt` interface from `subtensor_swap_interface` + type SwapExt: SwapExt; + + /// Implementor of `SwapEngine` interface from `subtensor_swap_interface` + type SwapEngine: SwapEngine; /// Interface to allow interacting with the proxy pallet. type ProxyInterface: crate::ProxyInterface; diff --git a/pallets/subtensor/src/rpc_info/stake_info.rs b/pallets/subtensor/src/rpc_info/stake_info.rs index 84e9e6d6b7..0844a33db7 100644 --- a/pallets/subtensor/src/rpc_info/stake_info.rs +++ b/pallets/subtensor/src/rpc_info/stake_info.rs @@ -3,7 +3,7 @@ extern crate alloc; use codec::Compact; use frame_support::pallet_prelude::{Decode, Encode}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::SwapExt; use super::*; @@ -128,7 +128,7 @@ impl Pallet { 0_u64 } else { let netuid = destination.or(origin).map(|v| v.1).unwrap_or_default(); - T::SwapInterface::approx_fee_amount(netuid.into(), amount) + T::SwapExt::approx_fee_amount(netuid.into(), TaoCurrency::from(amount)).to_u64() } } } diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index 740d42d09e..0b1916c210 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -1,6 +1,6 @@ use substrate_fixed::types::I96F32; use subtensor_runtime_common::{NetUid, TaoCurrency}; -use subtensor_swap_interface::{OrderType, SwapHandler}; +use subtensor_swap_interface::{Order, SwapEngine, SwapExt}; use super::*; @@ -74,7 +74,7 @@ impl Pallet { &coldkey, netuid, tao_staked.saturating_to_num::().into(), - T::SwapInterface::max_price().into(), + T::SwapExt::max_price(), true, false, )?; @@ -193,19 +193,13 @@ impl Pallet { } // Use reverting swap to estimate max limit amount - let result = T::SwapInterface::swap( - netuid.into(), - OrderType::Buy, - u64::MAX, - limit_price.into(), - false, - true, - ) - .map(|r| r.amount_paid_in.saturating_add(r.fee_paid)) - .map_err(|_| Error::ZeroMaxStakeAmount)?; + let order = GetAlphaForTao::::with_amount(u64::MAX); + let result = T::SwapEngine::swap(netuid.into(), order, limit_price, false, true) + .map(|r| r.amount_paid_in.saturating_add(r.fee_paid)) + .map_err(|_| Error::ZeroMaxStakeAmount)?; - if result != 0 { - Ok(result) + if !result.is_zero() { + Ok(result.into()) } else { Err(Error::ZeroMaxStakeAmount) } diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index 1625afa811..5d66aca7c3 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -8,7 +8,7 @@ use frame_support::traits::{ use safe_math::*; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{NetUid, TaoCurrency}; -use subtensor_swap_interface::{OrderType, SwapHandler}; +use subtensor_swap_interface::{Order, SwapEngine, SwapExt}; use super::*; @@ -51,9 +51,8 @@ impl Pallet { let alpha = U96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet( hotkey, netuid, )); - let alpha_price = U96F32::saturating_from_num( - T::SwapInterface::current_alpha_price(netuid.into()), - ); + let alpha_price = + U96F32::saturating_from_num(T::SwapExt::current_alpha_price(netuid.into())); alpha.saturating_mul(alpha_price) }) .sum::() @@ -73,20 +72,15 @@ impl Pallet { let alpha_stake = Self::get_stake_for_hotkey_and_coldkey_on_subnet( hotkey, coldkey, netuid, ); - T::SwapInterface::sim_swap( - netuid.into(), - OrderType::Sell, - alpha_stake.into(), - ) - .map(|r| { - let fee: u64 = U96F32::saturating_from_num(r.fee_paid) - .saturating_mul(T::SwapInterface::current_alpha_price( - netuid.into(), - )) - .saturating_to_num(); - r.amount_paid_out.saturating_add(fee) - }) - .unwrap_or_default() + let order = GetTaoForAlpha::::with_amount(alpha_stake); + T::SwapEngine::sim_swap(netuid.into(), order) + .map(|r| { + let fee: u64 = U96F32::saturating_from_num(r.fee_paid) + .saturating_mul(T::SwapExt::current_alpha_price(netuid.into())) + .saturating_to_num(); + r.amount_paid_out.to_u64().saturating_add(fee) + }) + .unwrap_or_default() }) .sum::() }) @@ -190,7 +184,7 @@ impl Pallet { Self::get_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid); let min_alpha_stake = U96F32::saturating_from_num(Self::get_nominator_min_required_stake()) - .safe_div(T::SwapInterface::current_alpha_price(netuid)) + .safe_div(T::SwapExt::current_alpha_price(netuid)) .saturating_to_num::(); if alpha_stake > 0.into() && alpha_stake < min_alpha_stake.into() { // Log the clearing of a small nomination @@ -202,7 +196,7 @@ impl Pallet { coldkey, netuid, alpha_stake, - T::SwapInterface::min_price().into(), + T::SwapExt::min_price(), false, ); @@ -319,7 +313,7 @@ impl Pallet { } pub fn is_user_liquidity_enabled(netuid: NetUid) -> bool { - T::SwapInterface::is_user_liquidity_enabled(netuid) + T::SwapExt::is_user_liquidity_enabled(netuid) } pub fn recycle_subnet_alpha(netuid: NetUid, amount: AlphaCurrency) { diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index 589da2b4b8..d5b694012c 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -3,7 +3,7 @@ use safe_math::*; use sp_core::Get; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::SwapExt; impl Pallet { /// Moves stake from one hotkey to another across subnets. @@ -360,7 +360,7 @@ impl Pallet { origin_coldkey, origin_netuid, move_amount, - T::SwapInterface::min_price().into(), + T::SwapExt::min_price(), drop_fee_origin, )?; @@ -378,7 +378,7 @@ impl Pallet { destination_coldkey, destination_netuid, tao_unstaked, - T::SwapInterface::max_price().into(), + T::SwapExt::max_price(), set_limit, drop_fee_destination, )?; @@ -499,9 +499,8 @@ impl Pallet { let limit_price_float: U64F64 = U64F64::saturating_from_num(limit_price) .checked_div(U64F64::saturating_from_num(1_000_000_000)) .unwrap_or(U64F64::saturating_from_num(0)); - let current_price = T::SwapInterface::current_alpha_price(origin_netuid.into()).safe_div( - T::SwapInterface::current_alpha_price(destination_netuid.into()), - ); + let current_price = T::SwapExt::current_alpha_price(origin_netuid.into()) + .safe_div(T::SwapExt::current_alpha_price(destination_netuid.into())); if limit_price_float > current_price { return Err(Error::ZeroMaxStakeAmount); } diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index 9d610ea88f..d0a635534b 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -1,8 +1,9 @@ -use subtensor_swap_interface::{OrderType, SwapHandler}; +use subtensor_swap_interface::{SwapEngine, SwapExt}; use super::*; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; +use subtensor_swap_interface::Order; impl Pallet { /// ---- The implementation for the extrinsic remove_stake: Removes stake from a hotkey account and adds it onto a coldkey. @@ -73,7 +74,7 @@ impl Pallet { &coldkey, netuid, alpha_unstaked, - T::SwapInterface::min_price().into(), + T::SwapExt::min_price(), false, )?; @@ -168,7 +169,7 @@ impl Pallet { &coldkey, netuid, alpha_unstaked, - T::SwapInterface::min_price().into(), + T::SwapExt::min_price(), false, )?; @@ -261,7 +262,7 @@ impl Pallet { &coldkey, netuid, alpha_unstaked, - T::SwapInterface::min_price().into(), + T::SwapExt::min_price(), false, )?; @@ -280,7 +281,7 @@ impl Pallet { &coldkey, NetUid::ROOT, total_tao_unstaked, - T::SwapInterface::max_price().into(), + T::SwapExt::max_price(), false, // no limit for Root subnet false, )?; @@ -405,19 +406,13 @@ impl Pallet { } // Use reverting swap to estimate max limit amount - let result = T::SwapInterface::swap( - netuid.into(), - OrderType::Sell, - u64::MAX, - limit_price.into(), - false, - true, - ) - .map(|r| r.amount_paid_in.saturating_add(r.fee_paid)) - .map_err(|_| Error::ZeroMaxStakeAmount)?; + let order = GetTaoForAlpha::::with_amount(u64::MAX); + let result = T::SwapEngine::swap(netuid.into(), order, limit_price.into(), false, true) + .map(|r| r.amount_paid_in.saturating_add(r.fee_paid)) + .map_err(|_| Error::ZeroMaxStakeAmount)?; - if result != 0 { - Ok(result.into()) + if !result.is_zero() { + Ok(result) } else { Err(Error::ZeroMaxStakeAmount) } @@ -469,13 +464,14 @@ impl Pallet { .saturating_to_num::(); let owner_emission_tao: TaoCurrency = if owner_alpha_u64 > 0 { - match T::SwapInterface::sim_swap(netuid.into(), OrderType::Sell, owner_alpha_u64) { - Ok(sim) => TaoCurrency::from(sim.amount_paid_out), + let order = GetTaoForAlpha::::with_amount(owner_alpha_u64); + match T::SwapEngine::sim_swap(netuid.into(), order) { + Ok(sim) => sim.amount_paid_out, Err(e) => { log::debug!( "destroy_alpha_in_out_stakes: sim_swap owner α→τ failed (netuid={netuid:?}, alpha={owner_alpha_u64}, err={e:?}); falling back to price multiply.", ); - let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); + let cur_price: U96F32 = T::SwapExt::current_alpha_price(netuid.into()); let val_u64: u64 = U96F32::from_num(owner_alpha_u64) .saturating_mul(cur_price) .floor() diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index 528289ec0e..58671d6c62 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -4,7 +4,7 @@ use share_pool::{SharePool, SharePoolDataOperations}; use sp_std::ops::Neg; use substrate_fixed::types::{I64F64, I96F32, U64F64, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; -use subtensor_swap_interface::{OrderType, SwapHandler, SwapResult}; +use subtensor_swap_interface::{Order, SwapEngine, SwapExt, SwapResult}; impl Pallet { /// Retrieves the total alpha issuance for a given subnet. @@ -58,8 +58,7 @@ impl Pallet { // We can use unsigned type here: U96F32 let one_minus_alpha: U96F32 = U96F32::saturating_from_num(1.0).saturating_sub(alpha); let current_price: U96F32 = alpha.saturating_mul( - T::SwapInterface::current_alpha_price(netuid.into()) - .min(U96F32::saturating_from_num(1.0)), + T::SwapExt::current_alpha_price(netuid.into()).min(U96F32::saturating_from_num(1.0)), ); let current_moving: U96F32 = one_minus_alpha.saturating_mul(Self::get_moving_alpha_price(netuid)); @@ -579,35 +578,25 @@ impl Pallet { tao: TaoCurrency, price_limit: TaoCurrency, drop_fees: bool, - ) -> Result { + ) -> Result, DispatchError> { // Step 1: Get the mechanism type for the subnet (0 for Stable, 1 for Dynamic) let mechanism_id: u16 = SubnetMechanism::::get(netuid); let swap_result = if mechanism_id == 1 { - T::SwapInterface::swap( - netuid.into(), - OrderType::Buy, - tao.into(), - price_limit.into(), - drop_fees, - false, - )? + let order = GetAlphaForTao::::with_amount(tao); + T::SwapEngine::swap(netuid.into(), order, price_limit.into(), drop_fees, false)? } else { - let abs_delta: u64 = tao.into(); - // Step 3.b.1: Stable mechanism, just return the value 1:1 SwapResult { - amount_paid_in: tao.into(), - amount_paid_out: tao.into(), - fee_paid: 0, - tao_reserve_delta: abs_delta as i64, - alpha_reserve_delta: (abs_delta as i64).neg(), + amount_paid_in: tao, + amount_paid_out: tao.to_u64().into(), + fee_paid: TaoCurrency::ZERO, } }; - let alpha_decrease = AlphaCurrency::from(swap_result.alpha_reserve_delta.unsigned_abs()); + let alpha_decrease = swap_result.paid_out_reserve_delta_i64().unsigned_abs() as u64; // Decrease Alpha reserves. - Self::decrease_provided_alpha_reserve(netuid.into(), alpha_decrease); + Self::decrease_provided_alpha_reserve(netuid.into(), alpha_decrease.into()); // Increase Alpha outstanding. SubnetAlphaOut::::mutate(netuid, |total| { @@ -618,7 +607,8 @@ impl Pallet { // (SubnetTAO + SubnetTaoProvided) in tao_reserve(), so it is irrelevant // which one to increase. SubnetTAO::::mutate(netuid, |total| { - *total = total.saturating_add((swap_result.tao_reserve_delta as u64).into()); + let delta = swap_result.paid_in_reserve_delta_i64().unsigned_abs() as u64; + *total = total.saturating_add(delta.into()); }); // Increase Total Tao reserves. @@ -640,64 +630,46 @@ impl Pallet { alpha: AlphaCurrency, price_limit: TaoCurrency, drop_fees: bool, - ) -> Result { + ) -> Result, DispatchError> { // Step 1: Get the mechanism type for the subnet (0 for Stable, 1 for Dynamic) let mechanism_id: u16 = SubnetMechanism::::get(netuid); // Step 2: Swap alpha and attain tao let swap_result = if mechanism_id == 1 { - T::SwapInterface::swap( - netuid.into(), - OrderType::Sell, - alpha.into(), - price_limit.into(), - drop_fees, - false, - )? + let order = GetTaoForAlpha::::with_amount(alpha); + T::SwapEngine::swap(netuid.into(), order, price_limit.into(), drop_fees, false)? } else { - let abs_delta: u64 = alpha.into(); - // Step 3.b.1: Stable mechanism, just return the value 1:1 SwapResult { - amount_paid_in: alpha.into(), - amount_paid_out: alpha.into(), - fee_paid: 0, - tao_reserve_delta: (abs_delta as i64).neg(), - alpha_reserve_delta: abs_delta as i64, + amount_paid_in: alpha, + amount_paid_out: alpha.to_u64().into(), + fee_paid: AlphaCurrency::ZERO, } }; // Increase only the protocol Alpha reserve. We only use the sum of // (SubnetAlphaIn + SubnetAlphaInProvided) in alpha_reserve(), so it is irrelevant // which one to increase. + let alpha_delta = swap_result.paid_in_reserve_delta_i64().unsigned_abs() as u64; SubnetAlphaIn::::mutate(netuid, |total| { - *total = total.saturating_add((swap_result.alpha_reserve_delta as u64).into()); + *total = total.saturating_add(alpha_delta.into()); }); // Decrease Alpha outstanding. // TODO: Deprecate, not accurate in v3 anymore SubnetAlphaOut::::mutate(netuid, |total| { - *total = total.saturating_sub((swap_result.alpha_reserve_delta as u64).into()); + *total = total.saturating_sub(alpha_delta.into()); }); // Decrease tao reserves. - Self::decrease_provided_tao_reserve( - netuid.into(), - swap_result - .tao_reserve_delta - .abs() - .try_into() - .unwrap_or(0) - .into(), - ); + let tao_delta = swap_result.paid_out_reserve_delta_i64().unsigned_abs() as u64; + Self::decrease_provided_tao_reserve(netuid.into(), tao_delta.into()); // Reduce total TAO reserves. - TotalStake::::mutate(|total| { - *total = total.saturating_sub(swap_result.amount_paid_out.into()) - }); + TotalStake::::mutate(|total| *total = total.saturating_sub(swap_result.amount_paid_out)); // Increase total subnet TAO volume. SubnetVolume::::mutate(netuid, |total| { - *total = total.saturating_add(swap_result.amount_paid_out.into()) + *total = total.saturating_add(swap_result.amount_paid_out.to_u64() as u128) }); // Return the tao received. @@ -751,7 +723,7 @@ impl Pallet { swap_result.amount_paid_out.into(), actual_alpha_decrease, netuid, - swap_result.fee_paid, + swap_result.fee_paid.to_u64(), )); log::debug!( @@ -782,7 +754,10 @@ impl Pallet { // Swap the tao to alpha. let swap_result = Self::swap_tao_for_alpha(netuid, tao, price_limit, drop_fees)?; - ensure!(swap_result.amount_paid_out > 0, Error::::AmountTooLow); + ensure!( + !swap_result.amount_paid_out.is_zero(), + Error::::AmountTooLow + ); ensure!( Self::try_increase_stake_for_hotkey_and_coldkey_on_subnet( @@ -801,7 +776,7 @@ impl Pallet { swap_result.amount_paid_out.into(), ) .is_zero() - || swap_result.amount_paid_out == 0 + || swap_result.amount_paid_out.is_zero() { return Ok(AlphaCurrency::ZERO); } @@ -826,7 +801,7 @@ impl Pallet { tao, swap_result.amount_paid_out.into(), netuid, - swap_result.fee_paid, + swap_result.fee_paid.to_u64(), )); log::debug!( @@ -872,8 +847,7 @@ impl Pallet { // Calculate TAO equivalent based on current price (it is accurate because // there's no slippage in this move) - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()); + let current_price = ::SwapExt::current_alpha_price(netuid.into()); let tao_equivalent: TaoCurrency = current_price .saturating_mul(U96F32::saturating_from_num(actual_alpha_moved)) .saturating_to_num::() @@ -946,9 +920,10 @@ impl Pallet { // Get the minimum balance (and amount) that satisfies the transaction let min_stake = DefaultMinStake::::get(); let min_amount = { - let fee = T::SwapInterface::sim_swap(netuid.into(), OrderType::Buy, min_stake.into()) + let order = GetAlphaForTao::::with_amount(min_stake); + let fee = T::SwapEngine::sim_swap(netuid.into(), order) .map(|res| res.fee_paid) - .unwrap_or(T::SwapInterface::approx_fee_amount( + .unwrap_or(T::SwapExt::approx_fee_amount( netuid.into(), min_stake.into(), )); @@ -978,18 +953,18 @@ impl Pallet { Error::::HotKeyAccountNotExists ); - let swap_result = - T::SwapInterface::sim_swap(netuid.into(), OrderType::Buy, stake_to_be_added.into()) - .map_err(|_| Error::::InsufficientLiquidity)?; + let order = GetAlphaForTao::::with_amount(stake_to_be_added); + let swap_result = T::SwapEngine::sim_swap(netuid.into(), order) + .map_err(|_| Error::::InsufficientLiquidity)?; // Check that actual withdrawn TAO amount is not lower than the minimum stake ensure!( - TaoCurrency::from(swap_result.amount_paid_in) >= min_stake, + swap_result.amount_paid_in >= min_stake, Error::::AmountTooLow ); ensure!( - swap_result.amount_paid_out > 0, + !swap_result.amount_paid_out.is_zero(), Error::::InsufficientLiquidity ); @@ -1029,11 +1004,12 @@ impl Pallet { // Bypass this check if the user unstakes full amount let remaining_alpha_stake = Self::calculate_reduced_stake_on_subnet(hotkey, coldkey, netuid, alpha_unstaked)?; - match T::SwapInterface::sim_swap(netuid.into(), OrderType::Sell, alpha_unstaked.into()) { + let order = GetTaoForAlpha::::with_amount(alpha_unstaked); + match T::SwapEngine::sim_swap(netuid.into(), order) { Ok(res) => { if !remaining_alpha_stake.is_zero() { ensure!( - TaoCurrency::from(res.amount_paid_out) >= DefaultMinStake::::get(), + res.amount_paid_out >= DefaultMinStake::::get(), Error::::AmountTooLow ); } @@ -1166,15 +1142,12 @@ impl Pallet { // If origin and destination netuid are different, do the swap-related checks if origin_netuid != destination_netuid { // Ensure that the stake amount to be removed is above the minimum in tao equivalent. - let tao_equivalent = T::SwapInterface::sim_swap( - origin_netuid.into(), - OrderType::Sell, - alpha_amount.into(), - ) - .map(|res| res.amount_paid_out) - .map_err(|_| Error::::InsufficientLiquidity)?; + let order = GetTaoForAlpha::::with_amount(alpha_amount); + let tao_equivalent = T::SwapEngine::sim_swap(origin_netuid.into(), order) + .map(|res| res.amount_paid_out) + .map_err(|_| Error::::InsufficientLiquidity)?; ensure!( - TaoCurrency::from(tao_equivalent) > DefaultMinStake::::get(), + tao_equivalent > DefaultMinStake::::get(), Error::::AmountTooLow ); diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index 244b9af2e9..930b9453b6 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -25,7 +25,7 @@ use sp_core::blake2_256; use sp_runtime::{Percent, traits::TrailingZeroInput}; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::SwapExt; pub type LeaseId = u32; @@ -310,7 +310,7 @@ impl Pallet { &lease.coldkey, lease.netuid, total_contributors_cut_alpha, - T::SwapInterface::min_price().into(), + T::SwapExt::min_price(), false, ) { Ok(tao_unstaked) => tao_unstaked, diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index bd7bdeed57..59ded2d028 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -3,7 +3,7 @@ use sp_core::{H256, U256}; use sp_io::hashing::{keccak_256, sha2_256}; use sp_runtime::Saturating; use subtensor_runtime_common::{Currency, NetUid}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::SwapExt; use system::pallet_prelude::BlockNumberFor; const LOG_TARGET: &str = "runtime::subtensor::registration"; @@ -133,13 +133,9 @@ impl Pallet { Self::remove_balance_from_coldkey_account(&coldkey, registration_cost.into())?; // Tokens are swapped and then burned. - let burned_alpha = Self::swap_tao_for_alpha( - netuid, - actual_burn_amount, - T::SwapInterface::max_price().into(), - false, - )? - .amount_paid_out; + let burned_alpha = + Self::swap_tao_for_alpha(netuid, actual_burn_amount, T::SwapExt::max_price(), false)? + .amount_paid_out; SubnetAlphaOut::::mutate(netuid, |total| { *total = total.saturating_sub(burned_alpha.into()) }); diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 8439297e14..5d6a9c968e 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -1,7 +1,7 @@ use super::*; use sp_core::Get; use subtensor_runtime_common::{NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::SwapExt; impl Pallet { /// Returns true if the subnetwork exists. @@ -248,7 +248,7 @@ impl Pallet { Self::deposit_event(Event::SubnetIdentitySet(netuid_to_register)); } - T::SwapInterface::toggle_user_liquidity(netuid_to_register, true); + T::SwapExt::toggle_user_liquidity(netuid_to_register, true); // --- 18. Emit the NetworkAdded event. log::info!("NetworkAdded( netuid:{netuid_to_register:?}, mechanism:{mechid:?} )"); Self::deposit_event(Event::NetworkAdded(netuid_to_register, mechid)); diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index 0fee0af2ca..582a9f7246 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -7,7 +7,7 @@ use approx::assert_abs_diff_eq; use frame_support::{assert_err, assert_noop, assert_ok}; use substrate_fixed::types::{I64F64, I96F32, U96F32}; use subtensor_runtime_common::{AlphaCurrency, NetUidStorageIndex, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::{SwapEngine, SwapExt}; use crate::{utils::rate_limiting::TransactionType, *}; use sp_core::U256; @@ -3007,7 +3007,7 @@ fn test_parent_child_chain_emission() { SubtensorModule::swap_tao_for_alpha( netuid, total_tao.to_num::().into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, ) .unwrap() diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index a4ea3988b9..90d037b024 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -10,7 +10,7 @@ use pallet_subtensor_swap::position::PositionId; use sp_core::U256; use substrate_fixed::types::{I64F64, I96F32, U96F32}; use subtensor_runtime_common::{AlphaCurrency, NetUidStorageIndex}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::{SwapEngine, SwapExt}; #[allow(clippy::arithmetic_side_effects)] fn close(value: u64, target: u64, eps: u64) { @@ -488,8 +488,8 @@ fn test_coinbase_alpha_issuance_with_cap_trigger_and_block_emission() { .unwrap(); // Get the prices before the run_coinbase - let price_1_before = ::SwapInterface::current_alpha_price(netuid1); - let price_2_before = ::SwapInterface::current_alpha_price(netuid2); + let price_1_before = ::SwapExt::current_alpha_price(netuid1); + let price_2_before = ::SwapExt::current_alpha_price(netuid2); // Set issuance at 21M SubnetAlphaOut::::insert(netuid1, AlphaCurrency::from(21_000_000_000_000_000)); // Set issuance above 21M @@ -499,8 +499,8 @@ fn test_coinbase_alpha_issuance_with_cap_trigger_and_block_emission() { SubtensorModule::run_coinbase(U96F32::from_num(emission)); // Get the prices after the run_coinbase - let price_1_after = ::SwapInterface::current_alpha_price(netuid1); - let price_2_after = ::SwapInterface::current_alpha_price(netuid2); + let price_1_after = ::SwapExt::current_alpha_price(netuid1); + let price_2_after = ::SwapExt::current_alpha_price(netuid2); // AlphaIn gets decreased beacuse of a buy assert!(u64::from(SubnetAlphaIn::::get(netuid1)) < initial_alpha); diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index 7c23dc2b2c..7f7faf78eb 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -12,7 +12,7 @@ use rand::{Rng, SeedableRng, distributions::Uniform, rngs::StdRng, seq::SliceRan use sp_core::{Get, U256}; use substrate_fixed::types::I32F32; use subtensor_runtime_common::{AlphaCurrency, NetUidStorageIndex, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::{SwapEngine, SwapExt}; use super::mock::*; use crate::epoch::math::{fixed, u16_proportion_to_fixed}; @@ -1322,7 +1322,7 @@ fn test_set_alpha_disabled() { migrations::migrate_create_root_network::migrate_create_root_network::(); SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1_000_000_000_000_000); assert_ok!(SubtensorModule::root_register(signer.clone(), hotkey,)); - let fee = ::SwapInterface::approx_fee_amount( + let fee = ::SwapExt::approx_fee_amount( netuid.into(), DefaultMinStake::::get().into(), ); @@ -2292,7 +2292,7 @@ fn test_get_set_alpha() { assert_ok!(SubtensorModule::register_network(signer.clone(), hotkey)); SubtokenEnabled::::insert(netuid, true); - let fee = ::SwapInterface::approx_fee_amount( + let fee = ::SwapExt::approx_fee_amount( netuid.into(), DefaultMinStake::::get().into(), ); diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 7c9861398d..e38e2a86bb 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -26,7 +26,7 @@ use sp_runtime::{ }; use sp_std::{cell::RefCell, cmp::Ordering}; use subtensor_runtime_common::{NetUid, TaoCurrency}; -use subtensor_swap_interface::{OrderType, SwapHandler}; +use subtensor_swap_interface::{Order as OrderT, SwapEngine, SwapExt}; type Block = frame_system::mocking::MockBlock; @@ -457,7 +457,8 @@ impl crate::Config for Test { type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; type DurationOfStartCall = DurationOfStartCall; - type SwapInterface = Swap; + type SwapExt = Swap; + type SwapEngine = Swap; type KeySwapOnSubnetCost = InitialKeySwapOnSubnetCost; type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval; type ProxyInterface = FakeProxier; @@ -980,7 +981,7 @@ pub fn increase_stake_on_coldkey_hotkey_account( coldkey, netuid, tao_staked, - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1016,11 +1017,11 @@ pub(crate) fn swap_tao_to_alpha(netuid: NetUid, tao: TaoCurrency) -> (AlphaCurre return (tao.to_u64().into(), 0); } - let result = ::SwapInterface::swap( + let result = ::SwapEngine::swap( netuid.into(), OrderType::Buy, tao.into(), - ::SwapInterface::max_price(), + ::SwapExt::max_price(), false, true, ); @@ -1045,15 +1046,15 @@ pub(crate) fn swap_alpha_to_tao_ext( } println!( - "::SwapInterface::min_price() = {:?}", - ::SwapInterface::min_price() + "::SwapExt::min_price() = {:?}", + ::SwapExt::min_price() ); - let result = ::SwapInterface::swap( + let result = ::SwapEngine::swap( netuid.into(), OrderType::Sell, alpha.into(), - ::SwapInterface::min_price(), + ::SwapExt::min_price(), drop_fees, true, ); diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index e49903aa86..54d142b9c5 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -5,7 +5,7 @@ use frame_support::{assert_err, assert_noop, assert_ok}; use sp_core::{Get, U256}; use substrate_fixed::types::{U64F64, U96F32}; use subtensor_runtime_common::TaoCurrency; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::{SwapEngine, SwapExt}; use super::mock; use super::mock::*; @@ -33,7 +33,7 @@ fn test_do_move_success() { &coldkey, netuid.into(), stake_amount, - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -110,7 +110,7 @@ fn test_do_move_different_subnets() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -140,10 +140,8 @@ fn test_do_move_different_subnets() { ), AlphaCurrency::ZERO ); - let fee = ::SwapInterface::approx_fee_amount( - destination_netuid.into(), - alpha.into(), - ); + let fee = + ::SwapExt::approx_fee_amount(destination_netuid.into(), alpha.into()); assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &destination_hotkey, @@ -180,7 +178,7 @@ fn test_do_move_nonexistent_subnet() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -284,7 +282,7 @@ fn test_do_move_nonexistent_destination_hotkey() { &coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -349,7 +347,7 @@ fn test_do_move_partial_stake() { &coldkey, netuid, total_stake.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -418,7 +416,7 @@ fn test_do_move_multiple_times() { &coldkey, netuid, initial_stake.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -490,7 +488,7 @@ fn test_do_move_wrong_origin() { &coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -557,7 +555,7 @@ fn test_do_move_same_hotkey_fails() { &coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -608,7 +606,7 @@ fn test_do_move_event_emission() { &coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -621,8 +619,7 @@ fn test_do_move_event_emission() { // Move stake and capture events System::reset_events(); - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()); + let current_price = ::SwapExt::current_alpha_price(netuid.into()); let tao_equivalent = (current_price * U96F32::from_num(alpha)).to_num::(); // no fee conversion assert_ok!(SubtensorModule::do_move_stake( RuntimeOrigin::signed(coldkey), @@ -669,7 +666,7 @@ fn test_do_move_storage_updates() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -736,7 +733,7 @@ fn test_move_full_amount_same_netuid() { &coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -804,7 +801,7 @@ fn test_do_move_max_values() { &coldkey, netuid, max_stake.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -910,7 +907,7 @@ fn test_do_transfer_success() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1019,7 +1016,7 @@ fn test_do_transfer_insufficient_stake() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1060,7 +1057,7 @@ fn test_do_transfer_wrong_origin() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1098,7 +1095,7 @@ fn test_do_transfer_minimum_stake_check() { &origin_coldkey, netuid, stake_amount, - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1146,7 +1143,7 @@ fn test_do_transfer_different_subnets() { &origin_coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1212,7 +1209,7 @@ fn test_do_swap_success() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1320,7 +1317,7 @@ fn test_do_swap_insufficient_stake() { &coldkey, netuid1, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1355,7 +1352,7 @@ fn test_do_swap_wrong_origin() { &real_coldkey, netuid1, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1393,7 +1390,7 @@ fn test_do_swap_minimum_stake_check() { &coldkey, netuid1, total_stake, - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1429,7 +1426,7 @@ fn test_do_swap_same_subnet() { &coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1474,7 +1471,7 @@ fn test_do_swap_partial_stake() { &coldkey, origin_netuid, total_stake_tao.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1526,7 +1523,7 @@ fn test_do_swap_storage_updates() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1586,7 +1583,7 @@ fn test_do_swap_multiple_times() { &coldkey, netuid1, initial_stake.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1657,7 +1654,7 @@ fn test_do_swap_allows_non_owned_hotkey() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1805,7 +1802,7 @@ fn test_transfer_stake_rate_limited() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), true, false, ) @@ -1850,7 +1847,7 @@ fn test_transfer_stake_doesnt_limit_destination_coldkey() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -1896,7 +1893,7 @@ fn test_swap_stake_limits_destination_netuid() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 42de84f54f..d3ff811683 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -7,7 +7,7 @@ use sp_core::U256; use sp_std::collections::btree_map::BTreeMap; use substrate_fixed::types::{I96F32, U64F64, U96F32}; use subtensor_runtime_common::{NetUidStorageIndex, TaoCurrency}; -use subtensor_swap_interface::{OrderType, SwapHandler}; +use subtensor_swap_interface::{SwapEngine, SwapExt}; #[test] fn test_registration_ok() { @@ -225,8 +225,7 @@ fn dissolve_owner_cut_refund_logic() { .saturating_to_num::(); // Current α→τ price for this subnet. - let price: U96F32 = - ::SwapInterface::current_alpha_price(net.into()); + let price: U96F32 = ::SwapExt::current_alpha_price(net.into()); let owner_emission_tao_u64: u64 = U96F32::from_num(owner_alpha_u64) .saturating_mul(price) .floor() @@ -782,7 +781,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { // Runtime-exact min amount = min_stake + fee let min_amount = { let min_stake = DefaultMinStake::::get(); - let fee = ::SwapInterface::approx_fee_amount( + let fee = ::SwapExt::approx_fee_amount( netuid.into(), min_stake.into(), ); @@ -869,7 +868,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { .floor() .saturating_to_num::(); - let owner_emission_tao_u64: u64 = ::SwapInterface::sim_swap( + let owner_emission_tao_u64: u64 = ::SwapEngine::sim_swap( netuid.into(), OrderType::Sell, owner_alpha_u64, @@ -878,7 +877,7 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { .unwrap_or_else(|_| { // Fallback matches the pallet's fallback let price: U96F32 = - ::SwapInterface::current_alpha_price(netuid.into()); + ::SwapExt::current_alpha_price(netuid.into()); U96F32::from_num(owner_alpha_u64) .saturating_mul(price) .floor() diff --git a/pallets/subtensor/src/tests/senate.rs b/pallets/subtensor/src/tests/senate.rs index 23d3552240..390a54932c 100644 --- a/pallets/subtensor/src/tests/senate.rs +++ b/pallets/subtensor/src/tests/senate.rs @@ -14,7 +14,7 @@ use sp_runtime::{ traits::{BlakeTwo256, Hash}, }; use subtensor_runtime_common::TaoCurrency; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::{SwapEngine, SwapExt}; use super::mock; use super::mock::*; @@ -724,10 +724,8 @@ fn test_adjust_senate_events() { let max_senate_size: u16 = SenateMaxMembers::get() as u16; let stake_threshold = { let default_stake = DefaultMinStake::::get().to_u64(); - let fee = ::SwapInterface::approx_fee_amount( - netuid, - default_stake.into(), - ); + let fee = + ::SwapExt::approx_fee_amount(netuid, default_stake.into()); default_stake + fee }; diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index b36788fa31..714ac3da0f 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -16,7 +16,7 @@ use substrate_fixed::types::{I96F32, I110F18, U64F64, U96F32}; use subtensor_runtime_common::{ AlphaCurrency, Currency as CurrencyT, NetUid, NetUidStorageIndex, TaoCurrency, }; -use subtensor_swap_interface::{OrderType, SwapHandler}; +use subtensor_swap_interface::{SwapEngine, SwapExt}; use super::mock; use super::mock::*; @@ -85,7 +85,7 @@ fn test_add_stake_ok_no_emission() { let (tao_expected, _) = mock::swap_alpha_to_tao(netuid, alpha_staked); let approx_fee = - ::SwapInterface::approx_fee_amount(netuid.into(), amount); + ::SwapExt::approx_fee_amount(netuid.into(), amount); assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), @@ -581,8 +581,7 @@ fn test_add_stake_partial_below_min_stake_fails() { .unwrap(); // Get the current price (should be 1.0) - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()); + let current_price = ::SwapExt::current_alpha_price(netuid.into()); assert_eq!(current_price.to_num::(), 1.0); // Set limit price close to 1.0 so that we hit the limit on adding and the amount is lower than min stake @@ -602,7 +601,7 @@ fn test_add_stake_partial_below_min_stake_fails() { ); let new_current_price = - ::SwapInterface::current_alpha_price(netuid.into()); + ::SwapExt::current_alpha_price(netuid.into()); assert_eq!(new_current_price.to_num::(), 1.0); }); } @@ -715,7 +714,7 @@ fn test_remove_stake_total_balance_no_change() { // Add subnet TAO for the equivalent amount added at price let amount_tao = U96F32::saturating_from_num(amount) - * ::SwapInterface::current_alpha_price(netuid.into()); + * ::SwapExt::current_alpha_price(netuid.into()); SubnetTAO::::mutate(netuid, |v| { *v += amount_tao.saturating_to_num::().into() }); @@ -729,7 +728,7 @@ fn test_remove_stake_total_balance_no_change() { amount.into() )); - let fee = ::SwapInterface::approx_fee_amount(netuid.into(), amount); + let fee = ::SwapExt::approx_fee_amount(netuid.into(), amount); assert_abs_diff_eq!( SubtensorModule::get_coldkey_balance(&coldkey_account_id), amount - fee, @@ -864,7 +863,7 @@ fn test_remove_stake_insufficient_liquidity() { &coldkey, netuid, amount_staked.into(), - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, false, ) @@ -2172,8 +2171,7 @@ fn test_get_total_delegated_stake_after_unstaking() { netuid, unstake_amount_alpha.into() )); - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()); + let current_price = ::SwapExt::current_alpha_price(netuid.into()); // Calculate the expected delegated stake let unstake_amount = @@ -2876,7 +2874,7 @@ fn test_max_amount_add_dynamic() { if !alpha_in.is_zero() { let expected_price = U96F32::from_num(tao_in) / U96F32::from_num(alpha_in); assert_abs_diff_eq!( - ::SwapInterface::current_alpha_price(netuid.into()) + ::SwapExt::current_alpha_price(netuid.into()) .to_num::(), expected_price.to_num::(), epsilon = expected_price.to_num::() / 1_000_f64 @@ -3091,7 +3089,7 @@ fn test_max_amount_remove_dynamic() { if !alpha_in.is_zero() { let expected_price = I96F32::from_num(tao_in) / I96F32::from_num(alpha_in); assert_eq!( - ::SwapInterface::current_alpha_price(netuid.into()), + ::SwapExt::current_alpha_price(netuid.into()), expected_price ); } @@ -3262,7 +3260,7 @@ fn test_max_amount_move_stable_dynamic() { SubnetTAO::::insert(dynamic_netuid, tao_reserve); SubnetAlphaIn::::insert(dynamic_netuid, alpha_in); let current_price = - ::SwapInterface::current_alpha_price(dynamic_netuid.into()); + ::SwapExt::current_alpha_price(dynamic_netuid.into()); assert_eq!(current_price, U96F32::from_num(0.5)); // The tests below just mimic the add_stake_limit tests for reverted price @@ -3362,7 +3360,7 @@ fn test_max_amount_move_dynamic_stable() { SubnetTAO::::insert(dynamic_netuid, tao_reserve); SubnetAlphaIn::::insert(dynamic_netuid, alpha_in); let current_price = - ::SwapInterface::current_alpha_price(dynamic_netuid.into()); + ::SwapExt::current_alpha_price(dynamic_netuid.into()); assert_eq!(current_price, U96F32::from_num(1.5)); // The tests below just mimic the remove_stake_limit tests @@ -3667,9 +3665,9 @@ fn test_max_amount_move_dynamic_dynamic() { if dest_price != 0 { let expected_price = origin_price / dest_price; assert_eq!( - ::SwapInterface::current_alpha_price( + ::SwapExt::current_alpha_price( origin_netuid.into() - ) / ::SwapInterface::current_alpha_price( + ) / ::SwapExt::current_alpha_price( destination_netuid.into() ), expected_price @@ -3706,8 +3704,7 @@ fn test_add_stake_limit_ok() { let tao_reserve = TaoCurrency::from(150_000_000_000); let alpha_in = AlphaCurrency::from(100_000_000_000); mock::setup_reserves(netuid, tao_reserve, alpha_in); - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()); + let current_price = ::SwapExt::current_alpha_price(netuid.into()); assert_eq!(current_price, U96F32::from_num(1.5)); // Give it some $$$ in his coldkey balance @@ -3741,7 +3738,7 @@ fn test_add_stake_limit_ok() { ); // Check that 450 TAO less fees balance still remains free on coldkey - let fee = ::SwapInterface::approx_fee_amount( + let fee = ::SwapExt::approx_fee_amount( netuid.into(), amount / 2, ) as f64; @@ -3753,8 +3750,7 @@ fn test_add_stake_limit_ok() { // Check that price has updated to ~24 = (150+450) / (100 - 75) let exp_price = U96F32::from_num(24.0); - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()); + let current_price = ::SwapExt::current_alpha_price(netuid.into()); assert_abs_diff_eq!( exp_price.to_num::(), current_price.to_num::(), @@ -3778,8 +3774,7 @@ fn test_add_stake_limit_fill_or_kill() { let alpha_in = AlphaCurrency::from(100_000_000_000); SubnetTAO::::insert(netuid, tao_reserve); SubnetAlphaIn::::insert(netuid, alpha_in); - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()); + let current_price = ::SwapExt::current_alpha_price(netuid.into()); // FIXME it's failing because in the swap pallet, the alpha price is set only after an // initial swap assert_eq!(current_price, U96F32::from_num(1.5)); @@ -3885,8 +3880,7 @@ fn test_remove_stake_limit_ok() { ); // Setup limit price to 99% of current price - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()); + let current_price = ::SwapExt::current_alpha_price(netuid.into()); let limit_price = (current_price.to_num::() * 990_000_000_f64) as u64; // Alpha unstaked - calculated using formula from delta_in() @@ -3942,8 +3936,7 @@ fn test_remove_stake_limit_fill_or_kill() { let alpha_in = AlphaCurrency::from(100_000_000_000); SubnetTAO::::insert(netuid, tao_reserve); SubnetAlphaIn::::insert(netuid, alpha_in); - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()); + let current_price = ::SwapExt::current_alpha_price(netuid.into()); assert_eq!(current_price, U96F32::from_num(1.5)); // Setup limit price so that it doesn't drop by more than 10% from current price @@ -4027,11 +4020,11 @@ fn test_add_stake_specific_stake_into_subnet_fail() { // Add stake as new hotkey let expected_alpha = AlphaCurrency::from( - ::SwapInterface::swap( + ::SwapEngine::swap( netuid.into(), OrderType::Buy, tao_staked.into(), - ::SwapInterface::max_price(), + ::SwapExt::max_price(), false, true, ) @@ -4163,7 +4156,7 @@ fn test_remove_99_9989_per_cent_stake_leaves_a_little() { )); // Check that all alpha was unstaked and 99% TAO balance was returned (less fees) - // let fee = ::SwapInterface::approx_fee_amount(netuid.into(), (amount as f64 * 0.99) as u64); + // let fee = ::SwapExt::approx_fee_amount(netuid.into(), (amount as f64 * 0.99) as u64); assert_abs_diff_eq!( SubtensorModule::get_coldkey_balance(&coldkey_account_id), (amount as f64 * 0.99) as u64 - fee, @@ -4220,7 +4213,7 @@ fn test_move_stake_limit_partial() { SubnetTAO::::insert(destination_netuid, tao_reserve * 100_000.into()); SubnetAlphaIn::::insert(destination_netuid, alpha_in * 100_000.into()); let current_price = - ::SwapInterface::current_alpha_price(origin_netuid.into()); + ::SwapExt::current_alpha_price(origin_netuid.into()); assert_eq!(current_price, U96F32::from_num(1.5)); // The relative price between origin and destination subnets is 1. @@ -4463,11 +4456,10 @@ fn test_stake_into_subnet_ok() { let alpha_in = AlphaCurrency::from(1_000_000_000_000); mock::setup_reserves(netuid, tao_reserve, alpha_in); let current_price = - ::SwapInterface::current_alpha_price(netuid.into()) - .to_num::(); + ::SwapExt::current_alpha_price(netuid.into()).to_num::(); // Initialize swap v3 - assert_ok!(::SwapInterface::swap( + assert_ok!(::SwapEngine::swap( netuid.into(), OrderType::Buy, 0, @@ -4517,11 +4509,10 @@ fn test_stake_into_subnet_low_amount() { let alpha_in = AlphaCurrency::from(1_000_000_000_000); mock::setup_reserves(netuid, tao_reserve, alpha_in); let current_price = - ::SwapInterface::current_alpha_price(netuid.into()) - .to_num::(); + ::SwapExt::current_alpha_price(netuid.into()).to_num::(); // Initialize swap v3 - assert_ok!(::SwapInterface::swap( + assert_ok!(::SwapEngine::swap( netuid.into(), OrderType::Buy, 0, @@ -4569,7 +4560,7 @@ fn test_unstake_from_subnet_low_amount() { mock::setup_reserves(netuid, tao_reserve, alpha_in); // Initialize swap v3 - assert_ok!(::SwapInterface::swap( + assert_ok!(::SwapEngine::swap( netuid.into(), OrderType::Buy, 0, @@ -4627,7 +4618,7 @@ fn test_stake_into_subnet_prohibitive_limit() { mock::setup_reserves(netuid, tao_reserve, alpha_in); // Initialize swap v3 - assert_ok!(::SwapInterface::swap( + assert_ok!(::SwapEngine::swap( netuid.into(), OrderType::Buy, 0, @@ -4683,7 +4674,7 @@ fn test_unstake_from_subnet_prohibitive_limit() { mock::setup_reserves(netuid, tao_reserve, alpha_in); // Initialize swap v3 - assert_ok!(::SwapInterface::swap( + assert_ok!(::SwapEngine::swap( netuid.into(), OrderType::Buy, 0, @@ -4759,7 +4750,7 @@ fn test_unstake_full_amount() { mock::setup_reserves(netuid, tao_reserve, alpha_in); // Initialize swap v3 - assert_ok!(::SwapInterface::swap( + assert_ok!(::SwapEngine::swap( netuid.into(), OrderType::Buy, 0, @@ -4884,16 +4875,15 @@ fn test_swap_fees_tao_correctness() { // Add owner coldkey Alpha as concentrated liquidity // between current price current price + 0.01 - let current_price = - ::SwapInterface::current_alpha_price(netuid.into()) - .to_num::() - + 0.0001; + let current_price = ::SwapExt::current_alpha_price(netuid.into()) + .to_num::() + + 0.0001; let limit_price = current_price + 0.01; let tick_low = price_to_tick(current_price); let tick_high = price_to_tick(limit_price); let liquidity = amount; - assert_ok!(::SwapInterface::do_add_liquidity( + assert_ok!(::SwapExt::do_add_liquidity( netuid.into(), &owner_coldkey, &owner_hotkey, @@ -5145,7 +5135,7 @@ fn test_default_min_stake_sufficiency() { let alpha_in = AlphaCurrency::from(21_000_000_000_000_000); mock::setup_reserves(netuid, tao_reserve, alpha_in); let current_price_before = - ::SwapInterface::current_alpha_price(netuid.into()); + ::SwapExt::current_alpha_price(netuid.into()); // Stake and unstake assert_ok!(SubtensorModule::add_stake( @@ -5156,7 +5146,7 @@ fn test_default_min_stake_sufficiency() { )); let fee_stake = (fee_rate * amount as f64) as u64; let current_price_after_stake = - ::SwapInterface::current_alpha_price(netuid.into()); + ::SwapExt::current_alpha_price(netuid.into()); remove_stake_rate_limit_for_tests(&owner_hotkey, &coldkey, netuid); let user_alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &owner_hotkey, @@ -5171,7 +5161,7 @@ fn test_default_min_stake_sufficiency() { )); let fee_unstake = (fee_rate * user_alpha.to_u64() as f64) as u64; let current_price_after_unstake = - ::SwapInterface::current_alpha_price(netuid.into()); + ::SwapExt::current_alpha_price(netuid.into()); assert!(fee_stake > 0); assert!(fee_unstake > 0); @@ -5215,7 +5205,7 @@ fn test_update_position_fees() { // Add owner coldkey Alpha as concentrated liquidity // between current price current price + 0.01 let current_price = - ::SwapInterface::current_alpha_price(netuid.into()) + ::SwapExt::current_alpha_price(netuid.into()) .to_num::() + 0.0001; let limit_price = current_price + 0.001; @@ -5223,7 +5213,7 @@ fn test_update_position_fees() { let tick_high = price_to_tick(limit_price); let liquidity = amount; - let (position_id, _, _) = ::SwapInterface::do_add_liquidity( + let (position_id, _, _) = ::SwapExt::do_add_liquidity( NetUid::from(netuid), &owner_coldkey, &owner_hotkey, diff --git a/pallets/subtensor/src/tests/staking2.rs b/pallets/subtensor/src/tests/staking2.rs index f5d57d02d3..250893fc13 100644 --- a/pallets/subtensor/src/tests/staking2.rs +++ b/pallets/subtensor/src/tests/staking2.rs @@ -7,7 +7,7 @@ use frame_support::{ }; use sp_core::U256; use subtensor_runtime_common::{AlphaCurrency, Currency, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::{SwapEngine, SwapExt}; use super::mock; use super::mock::*; @@ -38,7 +38,7 @@ fn test_stake_base_case() { SubtensorModule::swap_tao_for_alpha( netuid, tao_to_swap, - ::SwapInterface::max_price().into(), + ::SwapExt::max_price().into(), false, ) .unwrap() @@ -699,7 +699,7 @@ fn test_stake_fee_api() { stake_amount, ); let dynamic_fee_0 = - ::SwapInterface::approx_fee_amount(netuid0.into(), stake_amount); + ::SwapExt::approx_fee_amount(netuid0.into(), stake_amount); assert_eq!(stake_fee_0, dynamic_fee_0); // Test stake fee for remove on root @@ -711,7 +711,7 @@ fn test_stake_fee_api() { stake_amount, ); let dynamic_fee_1 = - ::SwapInterface::approx_fee_amount(root_netuid.into(), stake_amount); + ::SwapExt::approx_fee_amount(root_netuid.into(), stake_amount); assert_eq!(stake_fee_1, dynamic_fee_1); // Test stake fee for move from root to non-root @@ -723,7 +723,7 @@ fn test_stake_fee_api() { stake_amount, ); let dynamic_fee_2 = - ::SwapInterface::approx_fee_amount(netuid0.into(), stake_amount); + ::SwapExt::approx_fee_amount(netuid0.into(), stake_amount); assert_eq!(stake_fee_2, dynamic_fee_2); // Test stake fee for move between hotkeys on root @@ -735,7 +735,7 @@ fn test_stake_fee_api() { stake_amount, ); let dynamic_fee_3 = - ::SwapInterface::approx_fee_amount(root_netuid.into(), stake_amount); + ::SwapExt::approx_fee_amount(root_netuid.into(), stake_amount); assert_eq!(stake_fee_3, dynamic_fee_3); // Test stake fee for move between coldkeys on root @@ -747,7 +747,7 @@ fn test_stake_fee_api() { stake_amount, ); let dynamic_fee_4 = - ::SwapInterface::approx_fee_amount(root_netuid.into(), stake_amount); + ::SwapExt::approx_fee_amount(root_netuid.into(), stake_amount); assert_eq!(stake_fee_4, dynamic_fee_4); // Test stake fee for *swap* from non-root to root @@ -759,7 +759,7 @@ fn test_stake_fee_api() { stake_amount, ); let dynamic_fee_5 = - ::SwapInterface::approx_fee_amount(root_netuid.into(), stake_amount); + ::SwapExt::approx_fee_amount(root_netuid.into(), stake_amount); assert_eq!(stake_fee_5, dynamic_fee_5); // Test stake fee for move between hotkeys on non-root @@ -771,7 +771,7 @@ fn test_stake_fee_api() { stake_amount, ); let dynamic_fee_6 = - ::SwapInterface::approx_fee_amount(netuid0.into(), stake_amount); + ::SwapExt::approx_fee_amount(netuid0.into(), stake_amount); assert_eq!(stake_fee_6, dynamic_fee_6); // Test stake fee for move between coldkeys on non-root @@ -783,7 +783,7 @@ fn test_stake_fee_api() { stake_amount, ); let dynamic_fee_7 = - ::SwapInterface::approx_fee_amount(netuid0.into(), stake_amount); + ::SwapExt::approx_fee_amount(netuid0.into(), stake_amount); assert_eq!(stake_fee_7, dynamic_fee_7); // Test stake fee for *swap* from non-root to non-root @@ -795,7 +795,7 @@ fn test_stake_fee_api() { stake_amount, ); let dynamic_fee_8 = - ::SwapInterface::approx_fee_amount(netuid1.into(), stake_amount); + ::SwapExt::approx_fee_amount(netuid1.into(), stake_amount); assert_eq!(stake_fee_8, dynamic_fee_8); }); } @@ -847,32 +847,29 @@ fn test_stake_fee_calculation() { // Test stake fee for add_stake // Default for adding stake - let stake_fee = - ::SwapInterface::approx_fee_amount(netuid0.into(), stake_amount); + let stake_fee = ::SwapExt::approx_fee_amount(netuid0.into(), stake_amount); assert_eq!(stake_fee, default_fee); // Test stake fee for remove on root let stake_fee = - ::SwapInterface::approx_fee_amount(root_netuid.into(), stake_amount); // Default for removing stake from root + ::SwapExt::approx_fee_amount(root_netuid.into(), stake_amount); // Default for removing stake from root assert_eq!(stake_fee, default_fee); // Test stake fee for move from root to non-root // Default for moving stake from root to non-root - let stake_fee = - ::SwapInterface::approx_fee_amount(netuid0.into(), stake_amount); + let stake_fee = ::SwapExt::approx_fee_amount(netuid0.into(), stake_amount); assert_eq!(stake_fee, default_fee); // Test stake fee for move between hotkeys on root let stake_fee = - ::SwapInterface::approx_fee_amount(root_netuid.into(), stake_amount); // Default for moving stake between hotkeys on root + ::SwapExt::approx_fee_amount(root_netuid.into(), stake_amount); // Default for moving stake between hotkeys on root assert_eq!(stake_fee, default_fee); // Test stake fee for *swap* from non-root to non-root // Charged a dynamic fee - let stake_fee = - ::SwapInterface::approx_fee_amount(netuid1.into(), stake_amount); + let stake_fee = ::SwapExt::approx_fee_amount(netuid1.into(), stake_amount); assert_ne!(stake_fee, default_fee); }); } diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 59792d5602..08aec6cd32 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -15,7 +15,7 @@ use sp_runtime::traits::{DispatchInfoOf, TransactionExtension}; use sp_runtime::{DispatchError, traits::TxBaseImplication}; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, SubnetInfo, TaoCurrency}; -use subtensor_swap_interface::{OrderType, SwapHandler}; +use subtensor_swap_interface::{SwapEngine, SwapExt}; use super::mock; use super::mock::*; diff --git a/pallets/subtensor/src/tests/swap_hotkey.rs b/pallets/subtensor/src/tests/swap_hotkey.rs index 5991baf07a..e47c4549ef 100644 --- a/pallets/subtensor/src/tests/swap_hotkey.rs +++ b/pallets/subtensor/src/tests/swap_hotkey.rs @@ -9,7 +9,7 @@ use sp_core::{Get, H160, H256, U256}; use sp_runtime::SaturatedConversion; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaCurrency, NetUidStorageIndex, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::{SwapEngine, SwapExt}; use super::mock; use super::mock::*; diff --git a/pallets/subtensor/src/tests/weights.rs b/pallets/subtensor/src/tests/weights.rs index bc9af5cf07..fa9df72775 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -22,7 +22,7 @@ use sp_runtime::{ use sp_std::collections::vec_deque::VecDeque; use substrate_fixed::types::I32F32; use subtensor_runtime_common::{NetUidStorageIndex, TaoCurrency}; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::{SwapEngine, SwapExt}; use tle::{ curves::drand::TinyBLS381, ibe::fullident::Identity, @@ -281,10 +281,8 @@ fn test_set_weights_validate() { ); // Increase the stake and make it to be equal to the minimum threshold - let fee = ::SwapInterface::approx_fee_amount( - netuid.into(), - min_stake.into(), - ); + let fee = + ::SwapExt::approx_fee_amount(netuid.into(), min_stake.into()); assert_ok!(SubtensorModule::do_add_stake( RuntimeOrigin::signed(hotkey), hotkey, diff --git a/pallets/swap-interface/Cargo.toml b/pallets/swap-interface/Cargo.toml index a5ae9ac75f..e4392c6d67 100644 --- a/pallets/swap-interface/Cargo.toml +++ b/pallets/swap-interface/Cargo.toml @@ -9,6 +9,7 @@ frame-support.workspace = true scale-info.workspace = true substrate-fixed.workspace = true subtensor-runtime-common.workspace = true +subtensor-macros.workspace = true [lints] workspace = true diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index 4646cc4933..d3ab99013d 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -3,6 +3,7 @@ use core::ops::Neg; use frame_support::pallet_prelude::*; use substrate_fixed::types::U96F32; +use subtensor_macros::freeze_struct; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; pub use order::*; @@ -47,6 +48,7 @@ pub trait SwapExt { fn clear_protocol_liquidity(netuid: NetUid) -> DispatchResult; } +#[freeze_struct("d3d0b124fe5a97c8")] #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug, TypeInfo)] pub struct SwapResult where @@ -67,7 +69,18 @@ where self.amount_paid_in.to_u64() as i128 } + pub fn paid_in_reserve_delta_i64(&self) -> i64 { + self.paid_in_reserve_delta() + .clamp(i64::MIN as i128, i64::MAX as i128) as i64 + } + pub fn paid_out_reserve_delta(&self) -> i128 { (self.amount_paid_out.to_u64() as i128).neg() } + + pub fn paid_out_reserve_delta_i64(&self) -> i64 { + (self.amount_paid_out.to_u64() as i128) + .neg() + .clamp(i64::MIN as i128, i64::MAX as i128) as i64 + } } diff --git a/pallets/transaction-fee/src/lib.rs b/pallets/transaction-fee/src/lib.rs index 42ba69c841..2526ed6693 100644 --- a/pallets/transaction-fee/src/lib.rs +++ b/pallets/transaction-fee/src/lib.rs @@ -24,7 +24,7 @@ use sp_runtime::{ use pallet_subtensor::Call as SubtensorCall; use pallet_transaction_payment::Config as PTPConfig; use pallet_transaction_payment::OnChargeTransaction; -use subtensor_swap_interface::SwapHandler; +use subtensor_swap_interface::SwapExt; // Misc use core::marker::PhantomData; @@ -127,7 +127,7 @@ where /// /// If this function returns true, but at the time of execution the Alpha price /// changes and it becomes impossible to pay tx fee with the Alpha balance, - /// the transaction still executes and all Alpha is withdrawn from the account. + /// the transaction still executes and all Alpha is withdrawn from the account. fn can_withdraw_in_alpha( coldkey: &AccountIdOf, alpha_vec: &[(AccountIdOf, NetUid)], diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index 92915aafc0..b7b21c358b 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -615,11 +615,11 @@ pub(crate) fn swap_alpha_to_tao_ext( return (alpha.into(), 0); } - let result = ::SwapInterface::swap( + let result = ::SwapEngine::swap( netuid.into(), OrderType::Sell, alpha.into(), - ::SwapInterface::min_price(), + ::SwapExt::min_price(), drop_fees, true, ); From 3da7e9ce565a7659de6e93ff82e8f1e8602bc9c1 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Fri, 3 Oct 2025 14:28:25 +0300 Subject: [PATCH 11/14] Fix new swap's API integration issues --- pallets/admin-utils/src/tests/mock.rs | 2 + .../subtensor/src/coinbase/block_emission.rs | 4 +- pallets/subtensor/src/coinbase/root.rs | 6 +- .../subtensor/src/coinbase/run_coinbase.rs | 10 +- pallets/subtensor/src/macros/config.rs | 13 +- pallets/subtensor/src/rpc_info/stake_info.rs | 4 +- pallets/subtensor/src/staking/add_stake.rs | 6 +- pallets/subtensor/src/staking/helpers.rs | 19 ++- pallets/subtensor/src/staking/move_stake.rs | 11 +- pallets/subtensor/src/staking/remove_stake.rs | 18 +- pallets/subtensor/src/staking/stake_utils.rs | 30 ++-- pallets/subtensor/src/subnets/leasing.rs | 4 +- pallets/subtensor/src/subnets/registration.rs | 12 +- pallets/subtensor/src/subnets/subnet.rs | 5 +- pallets/subtensor/src/tests/children.rs | 4 +- pallets/subtensor/src/tests/coinbase.rs | 10 +- pallets/subtensor/src/tests/epoch.rs | 14 +- pallets/subtensor/src/tests/mock.rs | 37 ++-- pallets/subtensor/src/tests/move_stake.rs | 67 ++++---- pallets/subtensor/src/tests/networks.rs | 66 ++++---- pallets/subtensor/src/tests/senate.rs | 8 +- pallets/subtensor/src/tests/staking.rs | 159 ++++++++++-------- pallets/subtensor/src/tests/staking2.rs | 84 ++++++--- pallets/subtensor/src/tests/swap_coldkey.rs | 2 +- pallets/subtensor/src/tests/swap_hotkey.rs | 2 +- pallets/subtensor/src/tests/weights.rs | 6 +- pallets/swap-interface/src/lib.rs | 42 +++-- pallets/swap/src/pallet/impls.rs | 53 ++++-- pallets/transaction-fee/src/lib.rs | 2 +- pallets/transaction-fee/src/tests/mock.rs | 18 +- precompiles/src/alpha.rs | 39 ++--- runtime/src/lib.rs | 12 +- 32 files changed, 424 insertions(+), 345 deletions(-) diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 8c708adc9e..66e55533d4 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -344,6 +344,8 @@ impl pallet_subtensor_swap::Config for Test { type SubnetInfo = SubtensorModule; type BalanceOps = SubtensorModule; type ProtocolId = SwapProtocolId; + type TaoReserve = pallet_subtensor::TaoCurrencyReserve; + type AlphaReserve = pallet_subtensor::AlphaCurrencyReserve; type MaxFeeRate = SwapMaxFeeRate; type MaxPositions = SwapMaxPositions; type MinimumLiquidity = SwapMinimumLiquidity; diff --git a/pallets/subtensor/src/coinbase/block_emission.rs b/pallets/subtensor/src/coinbase/block_emission.rs index 5c56b70c36..064bab4d2a 100644 --- a/pallets/subtensor/src/coinbase/block_emission.rs +++ b/pallets/subtensor/src/coinbase/block_emission.rs @@ -6,7 +6,7 @@ use substrate_fixed::{ types::{I96F32, U96F32}, }; use subtensor_runtime_common::{NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapExt; +use subtensor_swap_interface::SwapHandler; impl Pallet { /// Calculates the dynamic TAO emission for a given subnet. @@ -40,7 +40,7 @@ impl Pallet { let float_alpha_block_emission: U96F32 = U96F32::saturating_from_num(alpha_block_emission); // Get alpha price for subnet. - let alpha_price = T::SwapExt::current_alpha_price(netuid.into()); + let alpha_price = T::SwapInterface::current_alpha_price(netuid.into()); log::debug!("{netuid:?} - alpha_price: {alpha_price:?}"); // Get initial alpha_in diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 1353fe0f42..6b09c9ed46 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -22,7 +22,7 @@ use safe_math::*; use sp_core::Get; use substrate_fixed::types::{I64F64, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, NetUidStorageIndex, TaoCurrency}; -use subtensor_swap_interface::SwapExt; +use subtensor_swap_interface::SwapHandler; impl Pallet { /// Fetches the total count of root network validators @@ -373,9 +373,9 @@ impl Pallet { ); // 2. --- Perform the cleanup before removing the network. - T::SwapExt::dissolve_all_liquidity_providers(netuid)?; + T::SwapInterface::dissolve_all_liquidity_providers(netuid)?; Self::destroy_alpha_in_out_stakes(netuid)?; - T::SwapExt::clear_protocol_liquidity(netuid)?; + T::SwapInterface::clear_protocol_liquidity(netuid)?; T::CommitmentsInterface::purge_netuid(netuid); // 3. --- Remove the network diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index f5e57da63b..79c63ad34f 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -3,7 +3,7 @@ use alloc::collections::BTreeMap; use safe_math::*; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapExt; +use subtensor_swap_interface::SwapHandler; // Distribute dividends to each hotkey macro_rules! asfloat { @@ -58,7 +58,7 @@ impl Pallet { // Only calculate for subnets that we are emitting to. for netuid_i in subnets_to_emit_to.iter() { // Get subnet price. - let price_i = T::SwapExt::current_alpha_price((*netuid_i).into()); + let price_i = T::SwapInterface::current_alpha_price((*netuid_i).into()); log::debug!("price_i: {price_i:?}"); // Get subnet TAO. let moving_price_i: U96F32 = Self::get_moving_alpha_price(*netuid_i); @@ -91,7 +91,7 @@ impl Pallet { let buy_swap_result = Self::swap_tao_for_alpha( *netuid_i, tou64!(difference_tao).into(), - T::SwapExt::max_price(), + T::SwapInterface::max_price(), true, ); if let Ok(buy_swap_result_ok) = buy_swap_result { @@ -159,7 +159,7 @@ impl Pallet { *total = total.saturating_add(tao_in_i.into()); }); // Adjust protocol liquidity based on new reserves - T::SwapExt::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i); + T::SwapInterface::adjust_protocol_liquidity(*netuid_i, tao_in_i, alpha_in_i); } // --- 5. Compute owner cuts and remove them from alpha_out remaining. @@ -220,7 +220,7 @@ impl Pallet { let swap_result = Self::swap_alpha_for_tao( *netuid_i, tou64!(root_alpha).into(), - T::SwapExt::min_price(), + T::SwapInterface::min_price(), true, ); if let Ok(ok_result) = swap_result { diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index f6aeb6450c..433599749f 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -6,9 +6,9 @@ use frame_support::pallet_macros::pallet_section; #[pallet_section] mod config { - use crate::CommitmentsInterface; + use crate::{CommitmentsInterface, GetAlphaForTao, GetTaoForAlpha}; use pallet_commitments::GetCommitments; - use subtensor_swap_interface::{Order as OrderT, SwapEngine, SwapExt}; + use subtensor_swap_interface::{SwapEngine, SwapHandler}; /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] @@ -54,11 +54,10 @@ mod config { /// the preimage to store the call data. type Preimages: QueryPreimage + StorePreimage; - /// Implementor of `SwapExt` interface from `subtensor_swap_interface` - type SwapExt: SwapExt; - - /// Implementor of `SwapEngine` interface from `subtensor_swap_interface` - type SwapEngine: SwapEngine; + /// Implementor of `SwapHandler` interface from `subtensor_swap_interface` + type SwapInterface: SwapHandler + + SwapEngine> + + SwapEngine>; /// Interface to allow interacting with the proxy pallet. type ProxyInterface: crate::ProxyInterface; diff --git a/pallets/subtensor/src/rpc_info/stake_info.rs b/pallets/subtensor/src/rpc_info/stake_info.rs index 0844a33db7..13789292da 100644 --- a/pallets/subtensor/src/rpc_info/stake_info.rs +++ b/pallets/subtensor/src/rpc_info/stake_info.rs @@ -3,7 +3,7 @@ extern crate alloc; use codec::Compact; use frame_support::pallet_prelude::{Decode, Encode}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapExt; +use subtensor_swap_interface::SwapHandler; use super::*; @@ -128,7 +128,7 @@ impl Pallet { 0_u64 } else { let netuid = destination.or(origin).map(|v| v.1).unwrap_or_default(); - T::SwapExt::approx_fee_amount(netuid.into(), TaoCurrency::from(amount)).to_u64() + T::SwapInterface::approx_fee_amount(netuid.into(), TaoCurrency::from(amount)).to_u64() } } } diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index 0b1916c210..f8cbf02358 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -1,6 +1,6 @@ use substrate_fixed::types::I96F32; use subtensor_runtime_common::{NetUid, TaoCurrency}; -use subtensor_swap_interface::{Order, SwapEngine, SwapExt}; +use subtensor_swap_interface::{Order, SwapHandler}; use super::*; @@ -74,7 +74,7 @@ impl Pallet { &coldkey, netuid, tao_staked.saturating_to_num::().into(), - T::SwapExt::max_price(), + T::SwapInterface::max_price(), true, false, )?; @@ -194,7 +194,7 @@ impl Pallet { // Use reverting swap to estimate max limit amount let order = GetAlphaForTao::::with_amount(u64::MAX); - let result = T::SwapEngine::swap(netuid.into(), order, limit_price, false, true) + let result = T::SwapInterface::swap(netuid.into(), order, limit_price, false, true) .map(|r| r.amount_paid_in.saturating_add(r.fee_paid)) .map_err(|_| Error::ZeroMaxStakeAmount)?; diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index 5d66aca7c3..cae9579f33 100644 --- a/pallets/subtensor/src/staking/helpers.rs +++ b/pallets/subtensor/src/staking/helpers.rs @@ -8,7 +8,7 @@ use frame_support::traits::{ use safe_math::*; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{NetUid, TaoCurrency}; -use subtensor_swap_interface::{Order, SwapEngine, SwapExt}; +use subtensor_swap_interface::{Order, SwapHandler}; use super::*; @@ -51,8 +51,9 @@ impl Pallet { let alpha = U96F32::saturating_from_num(Self::get_stake_for_hotkey_on_subnet( hotkey, netuid, )); - let alpha_price = - U96F32::saturating_from_num(T::SwapExt::current_alpha_price(netuid.into())); + let alpha_price = U96F32::saturating_from_num( + T::SwapInterface::current_alpha_price(netuid.into()), + ); alpha.saturating_mul(alpha_price) }) .sum::() @@ -73,10 +74,12 @@ impl Pallet { hotkey, coldkey, netuid, ); let order = GetTaoForAlpha::::with_amount(alpha_stake); - T::SwapEngine::sim_swap(netuid.into(), order) + T::SwapInterface::sim_swap(netuid.into(), order) .map(|r| { let fee: u64 = U96F32::saturating_from_num(r.fee_paid) - .saturating_mul(T::SwapExt::current_alpha_price(netuid.into())) + .saturating_mul(T::SwapInterface::current_alpha_price( + netuid.into(), + )) .saturating_to_num(); r.amount_paid_out.to_u64().saturating_add(fee) }) @@ -184,7 +187,7 @@ impl Pallet { Self::get_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid); let min_alpha_stake = U96F32::saturating_from_num(Self::get_nominator_min_required_stake()) - .safe_div(T::SwapExt::current_alpha_price(netuid)) + .safe_div(T::SwapInterface::current_alpha_price(netuid)) .saturating_to_num::(); if alpha_stake > 0.into() && alpha_stake < min_alpha_stake.into() { // Log the clearing of a small nomination @@ -196,7 +199,7 @@ impl Pallet { coldkey, netuid, alpha_stake, - T::SwapExt::min_price(), + T::SwapInterface::min_price(), false, ); @@ -313,7 +316,7 @@ impl Pallet { } pub fn is_user_liquidity_enabled(netuid: NetUid) -> bool { - T::SwapExt::is_user_liquidity_enabled(netuid) + T::SwapInterface::is_user_liquidity_enabled(netuid) } pub fn recycle_subnet_alpha(netuid: NetUid, amount: AlphaCurrency) { diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index d5b694012c..768430b03a 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -3,7 +3,7 @@ use safe_math::*; use sp_core::Get; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapExt; +use subtensor_swap_interface::SwapHandler; impl Pallet { /// Moves stake from one hotkey to another across subnets. @@ -360,7 +360,7 @@ impl Pallet { origin_coldkey, origin_netuid, move_amount, - T::SwapExt::min_price(), + T::SwapInterface::min_price(), drop_fee_origin, )?; @@ -378,7 +378,7 @@ impl Pallet { destination_coldkey, destination_netuid, tao_unstaked, - T::SwapExt::max_price(), + T::SwapInterface::max_price(), set_limit, drop_fee_destination, )?; @@ -499,8 +499,9 @@ impl Pallet { let limit_price_float: U64F64 = U64F64::saturating_from_num(limit_price) .checked_div(U64F64::saturating_from_num(1_000_000_000)) .unwrap_or(U64F64::saturating_from_num(0)); - let current_price = T::SwapExt::current_alpha_price(origin_netuid.into()) - .safe_div(T::SwapExt::current_alpha_price(destination_netuid.into())); + let current_price = T::SwapInterface::current_alpha_price(origin_netuid.into()).safe_div( + T::SwapInterface::current_alpha_price(destination_netuid.into()), + ); if limit_price_float > current_price { return Err(Error::ZeroMaxStakeAmount); } diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index d0a635534b..ea0ccbb752 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -1,9 +1,7 @@ -use subtensor_swap_interface::{SwapEngine, SwapExt}; - use super::*; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; -use subtensor_swap_interface::Order; +use subtensor_swap_interface::{Order, SwapHandler}; impl Pallet { /// ---- The implementation for the extrinsic remove_stake: Removes stake from a hotkey account and adds it onto a coldkey. @@ -74,7 +72,7 @@ impl Pallet { &coldkey, netuid, alpha_unstaked, - T::SwapExt::min_price(), + T::SwapInterface::min_price(), false, )?; @@ -169,7 +167,7 @@ impl Pallet { &coldkey, netuid, alpha_unstaked, - T::SwapExt::min_price(), + T::SwapInterface::min_price(), false, )?; @@ -262,7 +260,7 @@ impl Pallet { &coldkey, netuid, alpha_unstaked, - T::SwapExt::min_price(), + T::SwapInterface::min_price(), false, )?; @@ -281,7 +279,7 @@ impl Pallet { &coldkey, NetUid::ROOT, total_tao_unstaked, - T::SwapExt::max_price(), + T::SwapInterface::max_price(), false, // no limit for Root subnet false, )?; @@ -407,7 +405,7 @@ impl Pallet { // Use reverting swap to estimate max limit amount let order = GetTaoForAlpha::::with_amount(u64::MAX); - let result = T::SwapEngine::swap(netuid.into(), order, limit_price.into(), false, true) + let result = T::SwapInterface::swap(netuid.into(), order, limit_price.into(), false, true) .map(|r| r.amount_paid_in.saturating_add(r.fee_paid)) .map_err(|_| Error::ZeroMaxStakeAmount)?; @@ -465,13 +463,13 @@ impl Pallet { let owner_emission_tao: TaoCurrency = if owner_alpha_u64 > 0 { let order = GetTaoForAlpha::::with_amount(owner_alpha_u64); - match T::SwapEngine::sim_swap(netuid.into(), order) { + match T::SwapInterface::sim_swap(netuid.into(), order) { Ok(sim) => sim.amount_paid_out, Err(e) => { log::debug!( "destroy_alpha_in_out_stakes: sim_swap owner α→τ failed (netuid={netuid:?}, alpha={owner_alpha_u64}, err={e:?}); falling back to price multiply.", ); - let cur_price: U96F32 = T::SwapExt::current_alpha_price(netuid.into()); + let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); let val_u64: u64 = U96F32::from_num(owner_alpha_u64) .saturating_mul(cur_price) .floor() diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index 58671d6c62..7910449e23 100644 --- a/pallets/subtensor/src/staking/stake_utils.rs +++ b/pallets/subtensor/src/staking/stake_utils.rs @@ -4,7 +4,7 @@ use share_pool::{SharePool, SharePoolDataOperations}; use sp_std::ops::Neg; use substrate_fixed::types::{I64F64, I96F32, U64F64, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; -use subtensor_swap_interface::{Order, SwapEngine, SwapExt, SwapResult}; +use subtensor_swap_interface::{Order, SwapHandler, SwapResult}; impl Pallet { /// Retrieves the total alpha issuance for a given subnet. @@ -58,7 +58,8 @@ impl Pallet { // We can use unsigned type here: U96F32 let one_minus_alpha: U96F32 = U96F32::saturating_from_num(1.0).saturating_sub(alpha); let current_price: U96F32 = alpha.saturating_mul( - T::SwapExt::current_alpha_price(netuid.into()).min(U96F32::saturating_from_num(1.0)), + T::SwapInterface::current_alpha_price(netuid.into()) + .min(U96F32::saturating_from_num(1.0)), ); let current_moving: U96F32 = one_minus_alpha.saturating_mul(Self::get_moving_alpha_price(netuid)); @@ -583,7 +584,7 @@ impl Pallet { let mechanism_id: u16 = SubnetMechanism::::get(netuid); let swap_result = if mechanism_id == 1 { let order = GetAlphaForTao::::with_amount(tao); - T::SwapEngine::swap(netuid.into(), order, price_limit.into(), drop_fees, false)? + T::SwapInterface::swap(netuid.into(), order, price_limit.into(), drop_fees, false)? } else { // Step 3.b.1: Stable mechanism, just return the value 1:1 SwapResult { @@ -593,7 +594,7 @@ impl Pallet { } }; - let alpha_decrease = swap_result.paid_out_reserve_delta_i64().unsigned_abs() as u64; + let alpha_decrease = swap_result.paid_out_reserve_delta_i64().unsigned_abs(); // Decrease Alpha reserves. Self::decrease_provided_alpha_reserve(netuid.into(), alpha_decrease.into()); @@ -607,7 +608,7 @@ impl Pallet { // (SubnetTAO + SubnetTaoProvided) in tao_reserve(), so it is irrelevant // which one to increase. SubnetTAO::::mutate(netuid, |total| { - let delta = swap_result.paid_in_reserve_delta_i64().unsigned_abs() as u64; + let delta = swap_result.paid_in_reserve_delta_i64().unsigned_abs(); *total = total.saturating_add(delta.into()); }); @@ -636,7 +637,7 @@ impl Pallet { // Step 2: Swap alpha and attain tao let swap_result = if mechanism_id == 1 { let order = GetTaoForAlpha::::with_amount(alpha); - T::SwapEngine::swap(netuid.into(), order, price_limit.into(), drop_fees, false)? + T::SwapInterface::swap(netuid.into(), order, price_limit.into(), drop_fees, false)? } else { // Step 3.b.1: Stable mechanism, just return the value 1:1 SwapResult { @@ -649,7 +650,7 @@ impl Pallet { // Increase only the protocol Alpha reserve. We only use the sum of // (SubnetAlphaIn + SubnetAlphaInProvided) in alpha_reserve(), so it is irrelevant // which one to increase. - let alpha_delta = swap_result.paid_in_reserve_delta_i64().unsigned_abs() as u64; + let alpha_delta = swap_result.paid_in_reserve_delta_i64().unsigned_abs(); SubnetAlphaIn::::mutate(netuid, |total| { *total = total.saturating_add(alpha_delta.into()); }); @@ -661,7 +662,7 @@ impl Pallet { }); // Decrease tao reserves. - let tao_delta = swap_result.paid_out_reserve_delta_i64().unsigned_abs() as u64; + let tao_delta = swap_result.paid_out_reserve_delta_i64().unsigned_abs(); Self::decrease_provided_tao_reserve(netuid.into(), tao_delta.into()); // Reduce total TAO reserves. @@ -847,7 +848,8 @@ impl Pallet { // Calculate TAO equivalent based on current price (it is accurate because // there's no slippage in this move) - let current_price = ::SwapExt::current_alpha_price(netuid.into()); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); let tao_equivalent: TaoCurrency = current_price .saturating_mul(U96F32::saturating_from_num(actual_alpha_moved)) .saturating_to_num::() @@ -921,9 +923,9 @@ impl Pallet { let min_stake = DefaultMinStake::::get(); let min_amount = { let order = GetAlphaForTao::::with_amount(min_stake); - let fee = T::SwapEngine::sim_swap(netuid.into(), order) + let fee = T::SwapInterface::sim_swap(netuid.into(), order) .map(|res| res.fee_paid) - .unwrap_or(T::SwapExt::approx_fee_amount( + .unwrap_or(T::SwapInterface::approx_fee_amount( netuid.into(), min_stake.into(), )); @@ -954,7 +956,7 @@ impl Pallet { ); let order = GetAlphaForTao::::with_amount(stake_to_be_added); - let swap_result = T::SwapEngine::sim_swap(netuid.into(), order) + let swap_result = T::SwapInterface::sim_swap(netuid.into(), order) .map_err(|_| Error::::InsufficientLiquidity)?; // Check that actual withdrawn TAO amount is not lower than the minimum stake @@ -1005,7 +1007,7 @@ impl Pallet { let remaining_alpha_stake = Self::calculate_reduced_stake_on_subnet(hotkey, coldkey, netuid, alpha_unstaked)?; let order = GetTaoForAlpha::::with_amount(alpha_unstaked); - match T::SwapEngine::sim_swap(netuid.into(), order) { + match T::SwapInterface::sim_swap(netuid.into(), order) { Ok(res) => { if !remaining_alpha_stake.is_zero() { ensure!( @@ -1143,7 +1145,7 @@ impl Pallet { if origin_netuid != destination_netuid { // Ensure that the stake amount to be removed is above the minimum in tao equivalent. let order = GetTaoForAlpha::::with_amount(alpha_amount); - let tao_equivalent = T::SwapEngine::sim_swap(origin_netuid.into(), order) + let tao_equivalent = T::SwapInterface::sim_swap(origin_netuid.into(), order) .map(|res| res.amount_paid_out) .map_err(|_| Error::::InsufficientLiquidity)?; ensure!( diff --git a/pallets/subtensor/src/subnets/leasing.rs b/pallets/subtensor/src/subnets/leasing.rs index 930b9453b6..a33c32e4ef 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -25,7 +25,7 @@ use sp_core::blake2_256; use sp_runtime::{Percent, traits::TrailingZeroInput}; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapExt; +use subtensor_swap_interface::SwapHandler; pub type LeaseId = u32; @@ -310,7 +310,7 @@ impl Pallet { &lease.coldkey, lease.netuid, total_contributors_cut_alpha, - T::SwapExt::min_price(), + T::SwapInterface::min_price(), false, ) { Ok(tao_unstaked) => tao_unstaked, diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index 59ded2d028..5a2ffc77d2 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -3,7 +3,7 @@ use sp_core::{H256, U256}; use sp_io::hashing::{keccak_256, sha2_256}; use sp_runtime::Saturating; use subtensor_runtime_common::{Currency, NetUid}; -use subtensor_swap_interface::SwapExt; +use subtensor_swap_interface::SwapHandler; use system::pallet_prelude::BlockNumberFor; const LOG_TARGET: &str = "runtime::subtensor::registration"; @@ -133,9 +133,13 @@ impl Pallet { Self::remove_balance_from_coldkey_account(&coldkey, registration_cost.into())?; // Tokens are swapped and then burned. - let burned_alpha = - Self::swap_tao_for_alpha(netuid, actual_burn_amount, T::SwapExt::max_price(), false)? - .amount_paid_out; + let burned_alpha = Self::swap_tao_for_alpha( + netuid, + actual_burn_amount, + T::SwapInterface::max_price(), + false, + )? + .amount_paid_out; SubnetAlphaOut::::mutate(netuid, |total| { *total = total.saturating_sub(burned_alpha.into()) }); diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 5d6a9c968e..7676e0c557 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -1,8 +1,7 @@ use super::*; use sp_core::Get; use subtensor_runtime_common::{NetUid, TaoCurrency}; -use subtensor_swap_interface::SwapExt; - +use subtensor_swap_interface::SwapHandler; impl Pallet { /// Returns true if the subnetwork exists. /// @@ -248,7 +247,7 @@ impl Pallet { Self::deposit_event(Event::SubnetIdentitySet(netuid_to_register)); } - T::SwapExt::toggle_user_liquidity(netuid_to_register, true); + T::SwapInterface::toggle_user_liquidity(netuid_to_register, true); // --- 18. Emit the NetworkAdded event. log::info!("NetworkAdded( netuid:{netuid_to_register:?}, mechanism:{mechid:?} )"); Self::deposit_event(Event::NetworkAdded(netuid_to_register, mechid)); diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index 582a9f7246..285788d158 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -7,7 +7,7 @@ use approx::assert_abs_diff_eq; use frame_support::{assert_err, assert_noop, assert_ok}; use substrate_fixed::types::{I64F64, I96F32, U96F32}; use subtensor_runtime_common::{AlphaCurrency, NetUidStorageIndex, TaoCurrency}; -use subtensor_swap_interface::{SwapEngine, SwapExt}; +use subtensor_swap_interface::SwapHandler; use crate::{utils::rate_limiting::TransactionType, *}; use sp_core::U256; @@ -3007,7 +3007,7 @@ fn test_parent_child_chain_emission() { SubtensorModule::swap_tao_for_alpha( netuid, total_tao.to_num::().into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, ) .unwrap() diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index 90d037b024..2345b82b7e 100644 --- a/pallets/subtensor/src/tests/coinbase.rs +++ b/pallets/subtensor/src/tests/coinbase.rs @@ -10,7 +10,7 @@ use pallet_subtensor_swap::position::PositionId; use sp_core::U256; use substrate_fixed::types::{I64F64, I96F32, U96F32}; use subtensor_runtime_common::{AlphaCurrency, NetUidStorageIndex}; -use subtensor_swap_interface::{SwapEngine, SwapExt}; +use subtensor_swap_interface::{SwapEngine, SwapHandler}; #[allow(clippy::arithmetic_side_effects)] fn close(value: u64, target: u64, eps: u64) { @@ -488,8 +488,8 @@ fn test_coinbase_alpha_issuance_with_cap_trigger_and_block_emission() { .unwrap(); // Get the prices before the run_coinbase - let price_1_before = ::SwapExt::current_alpha_price(netuid1); - let price_2_before = ::SwapExt::current_alpha_price(netuid2); + let price_1_before = ::SwapInterface::current_alpha_price(netuid1); + let price_2_before = ::SwapInterface::current_alpha_price(netuid2); // Set issuance at 21M SubnetAlphaOut::::insert(netuid1, AlphaCurrency::from(21_000_000_000_000_000)); // Set issuance above 21M @@ -499,8 +499,8 @@ fn test_coinbase_alpha_issuance_with_cap_trigger_and_block_emission() { SubtensorModule::run_coinbase(U96F32::from_num(emission)); // Get the prices after the run_coinbase - let price_1_after = ::SwapExt::current_alpha_price(netuid1); - let price_2_after = ::SwapExt::current_alpha_price(netuid2); + let price_1_after = ::SwapInterface::current_alpha_price(netuid1); + let price_2_after = ::SwapInterface::current_alpha_price(netuid2); // AlphaIn gets decreased beacuse of a buy assert!(u64::from(SubnetAlphaIn::::get(netuid1)) < initial_alpha); diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index 7f7faf78eb..4d8108ac29 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -12,7 +12,7 @@ use rand::{Rng, SeedableRng, distributions::Uniform, rngs::StdRng, seq::SliceRan use sp_core::{Get, U256}; use substrate_fixed::types::I32F32; use subtensor_runtime_common::{AlphaCurrency, NetUidStorageIndex, TaoCurrency}; -use subtensor_swap_interface::{SwapEngine, SwapExt}; +use subtensor_swap_interface::SwapHandler; use super::mock::*; use crate::epoch::math::{fixed, u16_proportion_to_fixed}; @@ -1322,15 +1322,15 @@ fn test_set_alpha_disabled() { migrations::migrate_create_root_network::migrate_create_root_network::(); SubtensorModule::add_balance_to_coldkey_account(&coldkey, 1_000_000_000_000_000); assert_ok!(SubtensorModule::root_register(signer.clone(), hotkey,)); - let fee = ::SwapExt::approx_fee_amount( + let fee = ::SwapInterface::approx_fee_amount( netuid.into(), - DefaultMinStake::::get().into(), + DefaultMinStake::::get(), ); assert_ok!(SubtensorModule::add_stake( signer.clone(), hotkey, netuid, - (5 * DefaultMinStake::::get().to_u64() + fee).into() + TaoCurrency::from(5) * DefaultMinStake::::get() + fee )); // Only owner can set alpha values assert_ok!(SubtensorModule::register_network(signer.clone(), hotkey)); @@ -2292,16 +2292,16 @@ fn test_get_set_alpha() { assert_ok!(SubtensorModule::register_network(signer.clone(), hotkey)); SubtokenEnabled::::insert(netuid, true); - let fee = ::SwapExt::approx_fee_amount( + let fee = ::SwapInterface::approx_fee_amount( netuid.into(), - DefaultMinStake::::get().into(), + DefaultMinStake::::get(), ); assert_ok!(SubtensorModule::add_stake( signer.clone(), hotkey, netuid, - (DefaultMinStake::::get().to_u64() + fee * 2).into() + DefaultMinStake::::get() + fee * 2.into() )); assert_ok!(SubtensorModule::do_set_alpha_values( diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index e38e2a86bb..1f4b93205d 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -26,7 +26,7 @@ use sp_runtime::{ }; use sp_std::{cell::RefCell, cmp::Ordering}; use subtensor_runtime_common::{NetUid, TaoCurrency}; -use subtensor_swap_interface::{Order as OrderT, SwapEngine, SwapExt}; +use subtensor_swap_interface::{Order, SwapHandler}; type Block = frame_system::mocking::MockBlock; @@ -457,8 +457,7 @@ impl crate::Config for Test { type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; type DurationOfStartCall = DurationOfStartCall; - type SwapExt = Swap; - type SwapEngine = Swap; + type SwapInterface = pallet_subtensor_swap::Pallet; type KeySwapOnSubnetCost = InitialKeySwapOnSubnetCost; type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval; type ProxyInterface = FakeProxier; @@ -483,6 +482,8 @@ impl pallet_subtensor_swap::Config for Test { type SubnetInfo = SubtensorModule; type BalanceOps = SubtensorModule; type ProtocolId = SwapProtocolId; + type TaoReserve = TaoCurrencyReserve; + type AlphaReserve = AlphaCurrencyReserve; type MaxFeeRate = SwapMaxFeeRate; type MaxPositions = SwapMaxPositions; type MinimumLiquidity = SwapMinimumLiquidity; @@ -981,7 +982,7 @@ pub fn increase_stake_on_coldkey_hotkey_account( coldkey, netuid, tao_staked, - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1017,11 +1018,11 @@ pub(crate) fn swap_tao_to_alpha(netuid: NetUid, tao: TaoCurrency) -> (AlphaCurre return (tao.to_u64().into(), 0); } - let result = ::SwapEngine::swap( + let order = GetAlphaForTao::::with_amount(tao); + let result = ::SwapInterface::swap( netuid.into(), - OrderType::Buy, - tao.into(), - ::SwapExt::max_price(), + order, + ::SwapInterface::max_price(), false, true, ); @@ -1031,9 +1032,9 @@ pub(crate) fn swap_tao_to_alpha(netuid: NetUid, tao: TaoCurrency) -> (AlphaCurre let result = result.unwrap(); // we don't want to have silent 0 comparissons in tests - assert!(result.amount_paid_out > 0); + assert!(result.amount_paid_out > AlphaCurrency::ZERO); - (result.amount_paid_out.into(), result.fee_paid) + (result.amount_paid_out, result.fee_paid.into()) } pub(crate) fn swap_alpha_to_tao_ext( @@ -1046,15 +1047,15 @@ pub(crate) fn swap_alpha_to_tao_ext( } println!( - "::SwapExt::min_price() = {:?}", - ::SwapExt::min_price() + "::SwapInterface::min_price() = {:?}", + ::SwapInterface::min_price::() ); - let result = ::SwapEngine::swap( + let order = GetTaoForAlpha::::with_amount(alpha); + let result = ::SwapInterface::swap( netuid.into(), - OrderType::Sell, - alpha.into(), - ::SwapExt::min_price(), + order, + ::SwapInterface::min_price(), drop_fees, true, ); @@ -1064,9 +1065,9 @@ pub(crate) fn swap_alpha_to_tao_ext( let result = result.unwrap(); // we don't want to have silent 0 comparissons in tests - assert!(result.amount_paid_out > 0); + assert!(!result.amount_paid_out.is_zero()); - (result.amount_paid_out.into(), result.fee_paid) + (result.amount_paid_out, result.fee_paid.into()) } pub(crate) fn swap_alpha_to_tao(netuid: NetUid, alpha: AlphaCurrency) -> (TaoCurrency, u64) { diff --git a/pallets/subtensor/src/tests/move_stake.rs b/pallets/subtensor/src/tests/move_stake.rs index 54d142b9c5..dfd9927da4 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -5,7 +5,7 @@ use frame_support::{assert_err, assert_noop, assert_ok}; use sp_core::{Get, U256}; use substrate_fixed::types::{U64F64, U96F32}; use subtensor_runtime_common::TaoCurrency; -use subtensor_swap_interface::{SwapEngine, SwapExt}; +use subtensor_swap_interface::SwapHandler; use super::mock; use super::mock::*; @@ -33,7 +33,7 @@ fn test_do_move_success() { &coldkey, netuid.into(), stake_amount, - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -110,7 +110,7 @@ fn test_do_move_different_subnets() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -141,14 +141,14 @@ fn test_do_move_different_subnets() { AlphaCurrency::ZERO ); let fee = - ::SwapExt::approx_fee_amount(destination_netuid.into(), alpha.into()); + ::SwapInterface::approx_fee_amount(destination_netuid.into(), alpha); assert_abs_diff_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &destination_hotkey, &coldkey, destination_netuid ), - alpha - fee.into(), + alpha - fee, epsilon = alpha / 1000.into() ); }); @@ -178,7 +178,7 @@ fn test_do_move_nonexistent_subnet() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -282,7 +282,7 @@ fn test_do_move_nonexistent_destination_hotkey() { &coldkey, netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -347,7 +347,7 @@ fn test_do_move_partial_stake() { &coldkey, netuid, total_stake.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -416,7 +416,7 @@ fn test_do_move_multiple_times() { &coldkey, netuid, initial_stake.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -488,7 +488,7 @@ fn test_do_move_wrong_origin() { &coldkey, netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -555,7 +555,7 @@ fn test_do_move_same_hotkey_fails() { &coldkey, netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -606,7 +606,7 @@ fn test_do_move_event_emission() { &coldkey, netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -619,7 +619,8 @@ fn test_do_move_event_emission() { // Move stake and capture events System::reset_events(); - let current_price = ::SwapExt::current_alpha_price(netuid.into()); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); let tao_equivalent = (current_price * U96F32::from_num(alpha)).to_num::(); // no fee conversion assert_ok!(SubtensorModule::do_move_stake( RuntimeOrigin::signed(coldkey), @@ -666,7 +667,7 @@ fn test_do_move_storage_updates() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -733,7 +734,7 @@ fn test_move_full_amount_same_netuid() { &coldkey, netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -801,7 +802,7 @@ fn test_do_move_max_values() { &coldkey, netuid, max_stake.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -907,7 +908,7 @@ fn test_do_transfer_success() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1016,7 +1017,7 @@ fn test_do_transfer_insufficient_stake() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1057,7 +1058,7 @@ fn test_do_transfer_wrong_origin() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1095,7 +1096,7 @@ fn test_do_transfer_minimum_stake_check() { &origin_coldkey, netuid, stake_amount, - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1143,7 +1144,7 @@ fn test_do_transfer_different_subnets() { &origin_coldkey, origin_netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1209,7 +1210,7 @@ fn test_do_swap_success() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1317,7 +1318,7 @@ fn test_do_swap_insufficient_stake() { &coldkey, netuid1, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1352,7 +1353,7 @@ fn test_do_swap_wrong_origin() { &real_coldkey, netuid1, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1390,7 +1391,7 @@ fn test_do_swap_minimum_stake_check() { &coldkey, netuid1, total_stake, - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1426,7 +1427,7 @@ fn test_do_swap_same_subnet() { &coldkey, netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1471,7 +1472,7 @@ fn test_do_swap_partial_stake() { &coldkey, origin_netuid, total_stake_tao.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1523,7 +1524,7 @@ fn test_do_swap_storage_updates() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1583,7 +1584,7 @@ fn test_do_swap_multiple_times() { &coldkey, netuid1, initial_stake.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1654,7 +1655,7 @@ fn test_do_swap_allows_non_owned_hotkey() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1802,7 +1803,7 @@ fn test_transfer_stake_rate_limited() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), true, false, ) @@ -1847,7 +1848,7 @@ fn test_transfer_stake_doesnt_limit_destination_coldkey() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1893,7 +1894,7 @@ fn test_swap_stake_limits_destination_netuid() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index d3ff811683..9fb6b0c70b 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -7,7 +7,7 @@ use sp_core::U256; use sp_std::collections::btree_map::BTreeMap; use substrate_fixed::types::{I96F32, U64F64, U96F32}; use subtensor_runtime_common::{NetUidStorageIndex, TaoCurrency}; -use subtensor_swap_interface::{SwapEngine, SwapExt}; +use subtensor_swap_interface::{Order, SwapHandler}; #[test] fn test_registration_ok() { @@ -225,7 +225,8 @@ fn dissolve_owner_cut_refund_logic() { .saturating_to_num::(); // Current α→τ price for this subnet. - let price: U96F32 = ::SwapExt::current_alpha_price(net.into()); + let price: U96F32 = + ::SwapInterface::current_alpha_price(net.into()); let owner_emission_tao_u64: u64 = U96F32::from_num(owner_alpha_u64) .saturating_mul(price) .floor() @@ -781,11 +782,11 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { // Runtime-exact min amount = min_stake + fee let min_amount = { let min_stake = DefaultMinStake::::get(); - let fee = ::SwapExt::approx_fee_amount( + let fee = ::SwapInterface::approx_fee_amount( netuid.into(), - min_stake.into(), + min_stake, ); - min_stake.saturating_add(fee.into()) + min_stake.saturating_add(fee) }; const N: usize = 20; @@ -868,23 +869,22 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { .floor() .saturating_to_num::(); - let owner_emission_tao_u64: u64 = ::SwapEngine::sim_swap( - netuid.into(), - OrderType::Sell, - owner_alpha_u64, - ) - .map(|res| res.amount_paid_out) - .unwrap_or_else(|_| { - // Fallback matches the pallet's fallback - let price: U96F32 = - ::SwapExt::current_alpha_price(netuid.into()); - U96F32::from_num(owner_alpha_u64) - .saturating_mul(price) - .floor() - .saturating_to_num::() - }); - - let expected_refund: u64 = lock.saturating_sub(owner_emission_tao_u64); + let order = GetTaoForAlpha::::with_amount(owner_alpha_u64); + let owner_emission_tao = + ::SwapInterface::sim_swap(netuid.into(), order) + .map(|res| res.amount_paid_out) + .unwrap_or_else(|_| { + // Fallback matches the pallet's fallback + let price: U96F32 = + ::SwapInterface::current_alpha_price(netuid.into()); + U96F32::from_num(owner_alpha_u64) + .saturating_mul(price) + .floor() + .saturating_to_num::() + .into() + }); + + let expected_refund = lock.saturating_sub(owner_emission_tao.to_u64()); // ── 6) run distribution (credits τ to coldkeys, wipes α state) ───── assert_ok!(SubtensorModule::destroy_alpha_in_out_stakes(netuid)); @@ -1978,19 +1978,17 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( pallet_subtensor_swap::AlphaSqrtPrice::::set(net_new, sqrt1); // Compute the exact min stake per the pallet rule: DefaultMinStake + fee(DefaultMinStake). - let min_stake_u64: u64 = DefaultMinStake::::get().into(); - let fee_for_min: u64 = pallet_subtensor_swap::Pallet::::sim_swap( + let min_stake = DefaultMinStake::::get(); + let order = GetAlphaForTao::::with_amount(min_stake); + let fee_for_min = pallet_subtensor_swap::Pallet::::sim_swap( net_new, - subtensor_swap_interface::OrderType::Buy, - min_stake_u64, + order, ) .map(|r| r.fee_paid) .unwrap_or_else(|_e| { - as subtensor_swap_interface::SwapHandler< - ::AccountId, - >>::approx_fee_amount(net_new, min_stake_u64) + as subtensor_swap_interface::SwapHandler>::approx_fee_amount(net_new, min_stake) }); - let min_amount_required: u64 = min_stake_u64.saturating_add(fee_for_min); + let min_amount_required = min_stake.saturating_add(fee_for_min).to_u64(); // Re‑stake from three coldkeys; choose a specific DISTINCT hotkey per cold. for &cold in &cold_lps[0..3] { @@ -2001,10 +1999,10 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( let a_prev: u64 = Alpha::::get((hot1, cold, net_new)).saturating_to_num(); // Expected α for this exact τ, using the same sim path as the pallet. - let expected_alpha_out: u64 = pallet_subtensor_swap::Pallet::::sim_swap( + let order = GetAlphaForTao::::with_amount(min_amount_required); + let expected_alpha_out = pallet_subtensor_swap::Pallet::::sim_swap( net_new, - subtensor_swap_interface::OrderType::Buy, - min_amount_required, + order, ) .map(|r| r.amount_paid_out) .expect("sim_swap must succeed for fresh net and min amount"); @@ -2029,7 +2027,7 @@ fn massive_dissolve_refund_and_reregistration_flow_is_lossless_and_cleans_state( // α minted equals the simulated swap’s net out for that same τ. assert_eq!( - a_delta, expected_alpha_out, + a_delta, expected_alpha_out.to_u64(), "α minted mismatch for cold {cold:?} (hot {hot1:?}) on new net (αΔ {a_delta}, expected {expected_alpha_out})" ); } diff --git a/pallets/subtensor/src/tests/senate.rs b/pallets/subtensor/src/tests/senate.rs index 390a54932c..8ad093d0ae 100644 --- a/pallets/subtensor/src/tests/senate.rs +++ b/pallets/subtensor/src/tests/senate.rs @@ -14,7 +14,7 @@ use sp_runtime::{ traits::{BlakeTwo256, Hash}, }; use subtensor_runtime_common::TaoCurrency; -use subtensor_swap_interface::{SwapEngine, SwapExt}; +use subtensor_swap_interface::SwapHandler; use super::mock; use super::mock::*; @@ -723,10 +723,10 @@ fn test_adjust_senate_events() { let max_senate_size: u16 = SenateMaxMembers::get() as u16; let stake_threshold = { - let default_stake = DefaultMinStake::::get().to_u64(); + let default_stake = DefaultMinStake::::get(); let fee = - ::SwapExt::approx_fee_amount(netuid, default_stake.into()); - default_stake + fee + ::SwapInterface::approx_fee_amount(netuid, default_stake); + (default_stake + fee).to_u64() }; // We will be registering MaxMembers hotkeys and two more to try a replace diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 714ac3da0f..2f142b104d 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -16,7 +16,7 @@ use substrate_fixed::types::{I96F32, I110F18, U64F64, U96F32}; use subtensor_runtime_common::{ AlphaCurrency, Currency as CurrencyT, NetUid, NetUidStorageIndex, TaoCurrency, }; -use subtensor_swap_interface::{SwapEngine, SwapExt}; +use subtensor_swap_interface::{Order, SwapHandler}; use super::mock; use super::mock::*; @@ -84,12 +84,14 @@ fn test_add_stake_ok_no_emission() { )); let (tao_expected, _) = mock::swap_alpha_to_tao(netuid, alpha_staked); - let approx_fee = - ::SwapExt::approx_fee_amount(netuid.into(), amount); + let approx_fee = ::SwapInterface::approx_fee_amount( + netuid.into(), + TaoCurrency::from(amount), + ); assert_abs_diff_eq!( SubtensorModule::get_total_stake_for_hotkey(&hotkey_account_id), - tao_expected + approx_fee.into(), // swap returns value after fee, so we need to compensate it + tao_expected + approx_fee, // swap returns value after fee, so we need to compensate it epsilon = 10000.into(), ); @@ -581,7 +583,8 @@ fn test_add_stake_partial_below_min_stake_fails() { .unwrap(); // Get the current price (should be 1.0) - let current_price = ::SwapExt::current_alpha_price(netuid.into()); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); assert_eq!(current_price.to_num::(), 1.0); // Set limit price close to 1.0 so that we hit the limit on adding and the amount is lower than min stake @@ -601,7 +604,7 @@ fn test_add_stake_partial_below_min_stake_fails() { ); let new_current_price = - ::SwapExt::current_alpha_price(netuid.into()); + ::SwapInterface::current_alpha_price(netuid.into()); assert_eq!(new_current_price.to_num::(), 1.0); }); } @@ -714,7 +717,7 @@ fn test_remove_stake_total_balance_no_change() { // Add subnet TAO for the equivalent amount added at price let amount_tao = U96F32::saturating_from_num(amount) - * ::SwapExt::current_alpha_price(netuid.into()); + * ::SwapInterface::current_alpha_price(netuid.into()); SubnetTAO::::mutate(netuid, |v| { *v += amount_tao.saturating_to_num::().into() }); @@ -728,7 +731,11 @@ fn test_remove_stake_total_balance_no_change() { amount.into() )); - let fee = ::SwapExt::approx_fee_amount(netuid.into(), amount); + let fee = ::SwapInterface::approx_fee_amount( + netuid.into(), + TaoCurrency::from(amount), + ) + .to_u64(); assert_abs_diff_eq!( SubtensorModule::get_coldkey_balance(&coldkey_account_id), amount - fee, @@ -863,7 +870,7 @@ fn test_remove_stake_insufficient_liquidity() { &coldkey, netuid, amount_staked.into(), - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -2171,7 +2178,8 @@ fn test_get_total_delegated_stake_after_unstaking() { netuid, unstake_amount_alpha.into() )); - let current_price = ::SwapExt::current_alpha_price(netuid.into()); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); // Calculate the expected delegated stake let unstake_amount = @@ -2874,7 +2882,7 @@ fn test_max_amount_add_dynamic() { if !alpha_in.is_zero() { let expected_price = U96F32::from_num(tao_in) / U96F32::from_num(alpha_in); assert_abs_diff_eq!( - ::SwapExt::current_alpha_price(netuid.into()) + ::SwapInterface::current_alpha_price(netuid.into()) .to_num::(), expected_price.to_num::(), epsilon = expected_price.to_num::() / 1_000_f64 @@ -3089,7 +3097,7 @@ fn test_max_amount_remove_dynamic() { if !alpha_in.is_zero() { let expected_price = I96F32::from_num(tao_in) / I96F32::from_num(alpha_in); assert_eq!( - ::SwapExt::current_alpha_price(netuid.into()), + ::SwapInterface::current_alpha_price(netuid.into()), expected_price ); } @@ -3260,7 +3268,7 @@ fn test_max_amount_move_stable_dynamic() { SubnetTAO::::insert(dynamic_netuid, tao_reserve); SubnetAlphaIn::::insert(dynamic_netuid, alpha_in); let current_price = - ::SwapExt::current_alpha_price(dynamic_netuid.into()); + ::SwapInterface::current_alpha_price(dynamic_netuid.into()); assert_eq!(current_price, U96F32::from_num(0.5)); // The tests below just mimic the add_stake_limit tests for reverted price @@ -3360,7 +3368,7 @@ fn test_max_amount_move_dynamic_stable() { SubnetTAO::::insert(dynamic_netuid, tao_reserve); SubnetAlphaIn::::insert(dynamic_netuid, alpha_in); let current_price = - ::SwapExt::current_alpha_price(dynamic_netuid.into()); + ::SwapInterface::current_alpha_price(dynamic_netuid.into()); assert_eq!(current_price, U96F32::from_num(1.5)); // The tests below just mimic the remove_stake_limit tests @@ -3665,9 +3673,9 @@ fn test_max_amount_move_dynamic_dynamic() { if dest_price != 0 { let expected_price = origin_price / dest_price; assert_eq!( - ::SwapExt::current_alpha_price( + ::SwapInterface::current_alpha_price( origin_netuid.into() - ) / ::SwapExt::current_alpha_price( + ) / ::SwapInterface::current_alpha_price( destination_netuid.into() ), expected_price @@ -3704,7 +3712,8 @@ fn test_add_stake_limit_ok() { let tao_reserve = TaoCurrency::from(150_000_000_000); let alpha_in = AlphaCurrency::from(100_000_000_000); mock::setup_reserves(netuid, tao_reserve, alpha_in); - let current_price = ::SwapExt::current_alpha_price(netuid.into()); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); assert_eq!(current_price, U96F32::from_num(1.5)); // Give it some $$$ in his coldkey balance @@ -3738,10 +3747,11 @@ fn test_add_stake_limit_ok() { ); // Check that 450 TAO less fees balance still remains free on coldkey - let fee = ::SwapExt::approx_fee_amount( + let fee = ::SwapInterface::approx_fee_amount( netuid.into(), - amount / 2, - ) as f64; + TaoCurrency::from(amount / 2), + ) + .to_u64() as f64; assert_abs_diff_eq!( SubtensorModule::get_coldkey_balance(&coldkey_account_id), amount / 2 - fee as u64, @@ -3750,7 +3760,8 @@ fn test_add_stake_limit_ok() { // Check that price has updated to ~24 = (150+450) / (100 - 75) let exp_price = U96F32::from_num(24.0); - let current_price = ::SwapExt::current_alpha_price(netuid.into()); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); assert_abs_diff_eq!( exp_price.to_num::(), current_price.to_num::(), @@ -3774,7 +3785,8 @@ fn test_add_stake_limit_fill_or_kill() { let alpha_in = AlphaCurrency::from(100_000_000_000); SubnetTAO::::insert(netuid, tao_reserve); SubnetAlphaIn::::insert(netuid, alpha_in); - let current_price = ::SwapExt::current_alpha_price(netuid.into()); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); // FIXME it's failing because in the swap pallet, the alpha price is set only after an // initial swap assert_eq!(current_price, U96F32::from_num(1.5)); @@ -3880,7 +3892,8 @@ fn test_remove_stake_limit_ok() { ); // Setup limit price to 99% of current price - let current_price = ::SwapExt::current_alpha_price(netuid.into()); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); let limit_price = (current_price.to_num::() * 990_000_000_f64) as u64; // Alpha unstaked - calculated using formula from delta_in() @@ -3936,7 +3949,8 @@ fn test_remove_stake_limit_fill_or_kill() { let alpha_in = AlphaCurrency::from(100_000_000_000); SubnetTAO::::insert(netuid, tao_reserve); SubnetAlphaIn::::insert(netuid, alpha_in); - let current_price = ::SwapExt::current_alpha_price(netuid.into()); + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()); assert_eq!(current_price, U96F32::from_num(1.5)); // Setup limit price so that it doesn't drop by more than 10% from current price @@ -4019,18 +4033,16 @@ fn test_add_stake_specific_stake_into_subnet_fail() { ); // Add stake as new hotkey - let expected_alpha = AlphaCurrency::from( - ::SwapEngine::swap( - netuid.into(), - OrderType::Buy, - tao_staked.into(), - ::SwapExt::max_price(), - false, - true, - ) - .map(|v| v.amount_paid_out) - .unwrap_or_default(), - ); + let order = GetAlphaForTao::::with_amount(tao_staked); + let expected_alpha = ::SwapInterface::swap( + netuid.into(), + order, + ::SwapInterface::max_price(), + false, + true, + ) + .map(|v| v.amount_paid_out) + .unwrap_or_default(); assert_ok!(SubtensorModule::add_stake( RuntimeOrigin::signed(coldkey_account_id), hotkey_account_id, @@ -4213,7 +4225,7 @@ fn test_move_stake_limit_partial() { SubnetTAO::::insert(destination_netuid, tao_reserve * 100_000.into()); SubnetAlphaIn::::insert(destination_netuid, alpha_in * 100_000.into()); let current_price = - ::SwapExt::current_alpha_price(origin_netuid.into()); + ::SwapInterface::current_alpha_price(origin_netuid.into()); assert_eq!(current_price, U96F32::from_num(1.5)); // The relative price between origin and destination subnets is 1. @@ -4456,14 +4468,15 @@ fn test_stake_into_subnet_ok() { let alpha_in = AlphaCurrency::from(1_000_000_000_000); mock::setup_reserves(netuid, tao_reserve, alpha_in); let current_price = - ::SwapExt::current_alpha_price(netuid.into()).to_num::(); + ::SwapInterface::current_alpha_price(netuid.into()) + .to_num::(); // Initialize swap v3 - assert_ok!(::SwapEngine::swap( + let order = GetAlphaForTao::::with_amount(0); + assert_ok!(::SwapInterface::swap( netuid.into(), - OrderType::Buy, - 0, - u64::MAX, + order, + TaoCurrency::MAX, false, true )); @@ -4509,14 +4522,15 @@ fn test_stake_into_subnet_low_amount() { let alpha_in = AlphaCurrency::from(1_000_000_000_000); mock::setup_reserves(netuid, tao_reserve, alpha_in); let current_price = - ::SwapExt::current_alpha_price(netuid.into()).to_num::(); + ::SwapInterface::current_alpha_price(netuid.into()) + .to_num::(); // Initialize swap v3 - assert_ok!(::SwapEngine::swap( + let order = GetAlphaForTao::::with_amount(0); + assert_ok!(::SwapInterface::swap( netuid.into(), - OrderType::Buy, - 0, - u64::MAX, + order, + TaoCurrency::MAX, false, true )); @@ -4560,11 +4574,11 @@ fn test_unstake_from_subnet_low_amount() { mock::setup_reserves(netuid, tao_reserve, alpha_in); // Initialize swap v3 - assert_ok!(::SwapEngine::swap( + let order = GetAlphaForTao::::with_amount(0); + assert_ok!(::SwapInterface::swap( netuid.into(), - OrderType::Buy, - 0, - u64::MAX, + order, + TaoCurrency::MAX, false, true )); @@ -4618,11 +4632,11 @@ fn test_stake_into_subnet_prohibitive_limit() { mock::setup_reserves(netuid, tao_reserve, alpha_in); // Initialize swap v3 - assert_ok!(::SwapEngine::swap( + let order = GetAlphaForTao::::with_amount(0); + assert_ok!(::SwapInterface::swap( netuid.into(), - OrderType::Buy, - 0, - u64::MAX, + order, + TaoCurrency::MAX, false, true )); @@ -4674,11 +4688,11 @@ fn test_unstake_from_subnet_prohibitive_limit() { mock::setup_reserves(netuid, tao_reserve, alpha_in); // Initialize swap v3 - assert_ok!(::SwapEngine::swap( + let order = GetAlphaForTao::::with_amount(0); + assert_ok!(::SwapInterface::swap( netuid.into(), - OrderType::Buy, - 0, - u64::MAX, + order, + TaoCurrency::MAX, false, true )); @@ -4750,11 +4764,11 @@ fn test_unstake_full_amount() { mock::setup_reserves(netuid, tao_reserve, alpha_in); // Initialize swap v3 - assert_ok!(::SwapEngine::swap( + let order = GetAlphaForTao::::with_amount(0); + assert_ok!(::SwapInterface::swap( netuid.into(), - OrderType::Buy, - 0, - u64::MAX, + order, + TaoCurrency::MAX, false, true )); @@ -4875,15 +4889,16 @@ fn test_swap_fees_tao_correctness() { // Add owner coldkey Alpha as concentrated liquidity // between current price current price + 0.01 - let current_price = ::SwapExt::current_alpha_price(netuid.into()) - .to_num::() - + 0.0001; + let current_price = + ::SwapInterface::current_alpha_price(netuid.into()) + .to_num::() + + 0.0001; let limit_price = current_price + 0.01; let tick_low = price_to_tick(current_price); let tick_high = price_to_tick(limit_price); let liquidity = amount; - assert_ok!(::SwapExt::do_add_liquidity( + assert_ok!(::SwapInterface::do_add_liquidity( netuid.into(), &owner_coldkey, &owner_hotkey, @@ -5135,7 +5150,7 @@ fn test_default_min_stake_sufficiency() { let alpha_in = AlphaCurrency::from(21_000_000_000_000_000); mock::setup_reserves(netuid, tao_reserve, alpha_in); let current_price_before = - ::SwapExt::current_alpha_price(netuid.into()); + ::SwapInterface::current_alpha_price(netuid.into()); // Stake and unstake assert_ok!(SubtensorModule::add_stake( @@ -5146,7 +5161,7 @@ fn test_default_min_stake_sufficiency() { )); let fee_stake = (fee_rate * amount as f64) as u64; let current_price_after_stake = - ::SwapExt::current_alpha_price(netuid.into()); + ::SwapInterface::current_alpha_price(netuid.into()); remove_stake_rate_limit_for_tests(&owner_hotkey, &coldkey, netuid); let user_alpha = SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( &owner_hotkey, @@ -5161,7 +5176,7 @@ fn test_default_min_stake_sufficiency() { )); let fee_unstake = (fee_rate * user_alpha.to_u64() as f64) as u64; let current_price_after_unstake = - ::SwapExt::current_alpha_price(netuid.into()); + ::SwapInterface::current_alpha_price(netuid.into()); assert!(fee_stake > 0); assert!(fee_unstake > 0); @@ -5205,7 +5220,7 @@ fn test_update_position_fees() { // Add owner coldkey Alpha as concentrated liquidity // between current price current price + 0.01 let current_price = - ::SwapExt::current_alpha_price(netuid.into()) + ::SwapInterface::current_alpha_price(netuid.into()) .to_num::() + 0.0001; let limit_price = current_price + 0.001; @@ -5213,7 +5228,7 @@ fn test_update_position_fees() { let tick_high = price_to_tick(limit_price); let liquidity = amount; - let (position_id, _, _) = ::SwapExt::do_add_liquidity( + let (position_id, _, _) = ::SwapInterface::do_add_liquidity( NetUid::from(netuid), &owner_coldkey, &owner_hotkey, diff --git a/pallets/subtensor/src/tests/staking2.rs b/pallets/subtensor/src/tests/staking2.rs index 250893fc13..7d6cc6ad3c 100644 --- a/pallets/subtensor/src/tests/staking2.rs +++ b/pallets/subtensor/src/tests/staking2.rs @@ -7,7 +7,7 @@ use frame_support::{ }; use sp_core::U256; use subtensor_runtime_common::{AlphaCurrency, Currency, TaoCurrency}; -use subtensor_swap_interface::{SwapEngine, SwapExt}; +use subtensor_swap_interface::SwapHandler; use super::mock; use super::mock::*; @@ -38,7 +38,7 @@ fn test_stake_base_case() { SubtensorModule::swap_tao_for_alpha( netuid, tao_to_swap, - ::SwapExt::max_price().into(), + ::SwapInterface::max_price(), false, ) .unwrap() @@ -698,8 +698,11 @@ fn test_stake_fee_api() { coldkey1, stake_amount, ); - let dynamic_fee_0 = - ::SwapExt::approx_fee_amount(netuid0.into(), stake_amount); + let dynamic_fee_0 = ::SwapInterface::approx_fee_amount( + netuid0.into(), + TaoCurrency::from(stake_amount), + ) + .to_u64(); assert_eq!(stake_fee_0, dynamic_fee_0); // Test stake fee for remove on root @@ -710,8 +713,11 @@ fn test_stake_fee_api() { coldkey1, stake_amount, ); - let dynamic_fee_1 = - ::SwapExt::approx_fee_amount(root_netuid.into(), stake_amount); + let dynamic_fee_1 = ::SwapInterface::approx_fee_amount( + root_netuid.into(), + TaoCurrency::from(stake_amount), + ) + .to_u64(); assert_eq!(stake_fee_1, dynamic_fee_1); // Test stake fee for move from root to non-root @@ -722,8 +728,11 @@ fn test_stake_fee_api() { coldkey1, stake_amount, ); - let dynamic_fee_2 = - ::SwapExt::approx_fee_amount(netuid0.into(), stake_amount); + let dynamic_fee_2 = ::SwapInterface::approx_fee_amount( + netuid0.into(), + TaoCurrency::from(stake_amount), + ) + .to_u64(); assert_eq!(stake_fee_2, dynamic_fee_2); // Test stake fee for move between hotkeys on root @@ -734,8 +743,11 @@ fn test_stake_fee_api() { coldkey1, stake_amount, ); - let dynamic_fee_3 = - ::SwapExt::approx_fee_amount(root_netuid.into(), stake_amount); + let dynamic_fee_3 = ::SwapInterface::approx_fee_amount( + root_netuid.into(), + TaoCurrency::from(stake_amount), + ) + .to_u64(); assert_eq!(stake_fee_3, dynamic_fee_3); // Test stake fee for move between coldkeys on root @@ -746,8 +758,11 @@ fn test_stake_fee_api() { coldkey2, stake_amount, ); - let dynamic_fee_4 = - ::SwapExt::approx_fee_amount(root_netuid.into(), stake_amount); + let dynamic_fee_4 = ::SwapInterface::approx_fee_amount( + root_netuid.into(), + TaoCurrency::from(stake_amount), + ) + .to_u64(); assert_eq!(stake_fee_4, dynamic_fee_4); // Test stake fee for *swap* from non-root to root @@ -758,8 +773,11 @@ fn test_stake_fee_api() { coldkey1, stake_amount, ); - let dynamic_fee_5 = - ::SwapExt::approx_fee_amount(root_netuid.into(), stake_amount); + let dynamic_fee_5 = ::SwapInterface::approx_fee_amount( + root_netuid.into(), + TaoCurrency::from(stake_amount), + ) + .to_u64(); assert_eq!(stake_fee_5, dynamic_fee_5); // Test stake fee for move between hotkeys on non-root @@ -770,8 +788,11 @@ fn test_stake_fee_api() { coldkey1, stake_amount, ); - let dynamic_fee_6 = - ::SwapExt::approx_fee_amount(netuid0.into(), stake_amount); + let dynamic_fee_6 = ::SwapInterface::approx_fee_amount( + netuid0.into(), + TaoCurrency::from(stake_amount), + ) + .to_u64(); assert_eq!(stake_fee_6, dynamic_fee_6); // Test stake fee for move between coldkeys on non-root @@ -782,8 +803,11 @@ fn test_stake_fee_api() { coldkey2, stake_amount, ); - let dynamic_fee_7 = - ::SwapExt::approx_fee_amount(netuid0.into(), stake_amount); + let dynamic_fee_7 = ::SwapInterface::approx_fee_amount( + netuid0.into(), + TaoCurrency::from(stake_amount), + ) + .to_u64(); assert_eq!(stake_fee_7, dynamic_fee_7); // Test stake fee for *swap* from non-root to non-root @@ -794,8 +818,11 @@ fn test_stake_fee_api() { coldkey1, stake_amount, ); - let dynamic_fee_8 = - ::SwapExt::approx_fee_amount(netuid1.into(), stake_amount); + let dynamic_fee_8 = ::SwapInterface::approx_fee_amount( + netuid1.into(), + TaoCurrency::from(stake_amount), + ) + .to_u64(); assert_eq!(stake_fee_8, dynamic_fee_8); }); } @@ -817,9 +844,9 @@ fn test_stake_fee_calculation() { let total_hotkey_alpha = AlphaCurrency::from(100_000_000_000); let tao_in = TaoCurrency::from(100_000_000_000); // 100 TAO let reciprocal_price = 2; // 1 / price - let stake_amount = 100_000_000_000_u64; + let stake_amount = TaoCurrency::from(100_000_000_000); - let default_fee = 0; // FIXME: DefaultStakingFee is deprecated + let default_fee = TaoCurrency::ZERO; // FIXME: DefaultStakingFee is deprecated // Setup alpha out SubnetAlphaOut::::insert(netuid0, AlphaCurrency::from(100_000_000_000)); @@ -847,29 +874,32 @@ fn test_stake_fee_calculation() { // Test stake fee for add_stake // Default for adding stake - let stake_fee = ::SwapExt::approx_fee_amount(netuid0.into(), stake_amount); + let stake_fee = + ::SwapInterface::approx_fee_amount(netuid0.into(), stake_amount); assert_eq!(stake_fee, default_fee); // Test stake fee for remove on root let stake_fee = - ::SwapExt::approx_fee_amount(root_netuid.into(), stake_amount); // Default for removing stake from root + ::SwapInterface::approx_fee_amount(root_netuid.into(), stake_amount); // Default for removing stake from root assert_eq!(stake_fee, default_fee); // Test stake fee for move from root to non-root // Default for moving stake from root to non-root - let stake_fee = ::SwapExt::approx_fee_amount(netuid0.into(), stake_amount); + let stake_fee = + ::SwapInterface::approx_fee_amount(netuid0.into(), stake_amount); assert_eq!(stake_fee, default_fee); // Test stake fee for move between hotkeys on root let stake_fee = - ::SwapExt::approx_fee_amount(root_netuid.into(), stake_amount); // Default for moving stake between hotkeys on root + ::SwapInterface::approx_fee_amount(root_netuid.into(), stake_amount); // Default for moving stake between hotkeys on root assert_eq!(stake_fee, default_fee); // Test stake fee for *swap* from non-root to non-root // Charged a dynamic fee - let stake_fee = ::SwapExt::approx_fee_amount(netuid1.into(), stake_amount); + let stake_fee = + ::SwapInterface::approx_fee_amount(netuid1.into(), stake_amount); assert_ne!(stake_fee, default_fee); }); } diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 08aec6cd32..b65bdfd0f8 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -15,7 +15,7 @@ use sp_runtime::traits::{DispatchInfoOf, TransactionExtension}; use sp_runtime::{DispatchError, traits::TxBaseImplication}; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, SubnetInfo, TaoCurrency}; -use subtensor_swap_interface::{SwapEngine, SwapExt}; +use subtensor_swap_interface::{SwapEngine, SwapHandler}; use super::mock; use super::mock::*; diff --git a/pallets/subtensor/src/tests/swap_hotkey.rs b/pallets/subtensor/src/tests/swap_hotkey.rs index e47c4549ef..56ee243b8f 100644 --- a/pallets/subtensor/src/tests/swap_hotkey.rs +++ b/pallets/subtensor/src/tests/swap_hotkey.rs @@ -9,7 +9,7 @@ use sp_core::{Get, H160, H256, U256}; use sp_runtime::SaturatedConversion; use substrate_fixed::types::U64F64; use subtensor_runtime_common::{AlphaCurrency, NetUidStorageIndex, TaoCurrency}; -use subtensor_swap_interface::{SwapEngine, SwapExt}; +use subtensor_swap_interface::{SwapEngine, SwapHandler}; use super::mock; use super::mock::*; diff --git a/pallets/subtensor/src/tests/weights.rs b/pallets/subtensor/src/tests/weights.rs index fa9df72775..db7a8ee8d4 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -22,7 +22,7 @@ use sp_runtime::{ use sp_std::collections::vec_deque::VecDeque; use substrate_fixed::types::I32F32; use subtensor_runtime_common::{NetUidStorageIndex, TaoCurrency}; -use subtensor_swap_interface::{SwapEngine, SwapExt}; +use subtensor_swap_interface::SwapHandler; use tle::{ curves::drand::TinyBLS381, ibe::fullident::Identity, @@ -282,12 +282,12 @@ fn test_set_weights_validate() { // Increase the stake and make it to be equal to the minimum threshold let fee = - ::SwapExt::approx_fee_amount(netuid.into(), min_stake.into()); + ::SwapInterface::approx_fee_amount(netuid.into(), min_stake); assert_ok!(SubtensorModule::do_add_stake( RuntimeOrigin::signed(hotkey), hotkey, netuid, - min_stake + fee.into() + min_stake + fee )); let min_stake_with_slippage = SubtensorModule::get_total_stake_for_hotkey(&hotkey); diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index d3ab99013d..ae7d375f97 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -10,29 +10,33 @@ pub use order::*; mod order; -pub trait SwapEngine: DefaultPriceLimit { +pub trait SwapEngine: DefaultPriceLimit { fn swap( netuid: NetUid, - order: OrderT, + order: O, price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result, DispatchError>; - fn sim_swap( - netuid: NetUid, - order: OrderT, - ) -> Result, DispatchError>; + ) -> Result, DispatchError>; } -pub trait DefaultPriceLimit -where - PaidIn: Currency, - PaidOut: Currency, -{ - fn default_price_limit() -> C; -} +pub trait SwapHandler { + fn swap( + netuid: NetUid, + order: O, + price_limit: TaoCurrency, + drop_fees: bool, + should_rollback: bool, + ) -> Result, DispatchError> + where + Self: SwapEngine; + fn sim_swap( + netuid: NetUid, + order: O, + ) -> Result, DispatchError> + where + Self: SwapEngine; -pub trait SwapExt { fn approx_fee_amount(netuid: NetUid, amount: T) -> T; fn current_alpha_price(netuid: NetUid) -> U96F32; fn max_price() -> C; @@ -48,6 +52,14 @@ pub trait SwapExt { fn clear_protocol_liquidity(netuid: NetUid) -> DispatchResult; } +pub trait DefaultPriceLimit +where + PaidIn: Currency, + PaidOut: Currency, +{ + fn default_price_limit() -> C; +} + #[freeze_struct("d3d0b124fe5a97c8")] #[derive(Decode, Encode, PartialEq, Eq, Clone, Debug, TypeInfo)] pub struct SwapResult diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 0e49e82e56..a5237369ee 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -10,7 +10,7 @@ use subtensor_runtime_common::{ AlphaCurrency, BalanceOps, Currency, CurrencyReserve, NetUid, SubnetInfo, TaoCurrency, }; use subtensor_swap_interface::{ - DefaultPriceLimit, Order as OrderT, SwapEngine, SwapExt, SwapResult, + DefaultPriceLimit, Order as OrderT, SwapEngine, SwapHandler, SwapResult, }; use super::pallet::*; @@ -172,7 +172,7 @@ impl Pallet { /// - `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. + /// changes. /// /// # Returns /// Returns a [`Result`] with a [`SwapResult`] on success, or a [`DispatchError`] on failure. @@ -185,7 +185,7 @@ impl Pallet { /// 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. + /// reserve. /// /// Use simulation mode to preview the outcome of a swap without modifying the blockchain state. pub(crate) fn do_swap( @@ -981,25 +981,26 @@ impl DefaultPriceLimit for Pallet { } } -impl SwapEngine for Pallet +impl SwapEngine for Pallet where - Order: OrderT, - Self: DefaultPriceLimit, - BasicSwapStep: SwapStep, + T: Config, + O: OrderT, + BasicSwapStep: SwapStep, + Self: DefaultPriceLimit, { fn swap( netuid: NetUid, - order: Order, + order: O, price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result, DispatchError> { + ) -> 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::( + Self::do_swap::( NetUid::from(netuid), order, limit_sqrt_price, @@ -1008,22 +1009,42 @@ where ) .map_err(Into::into) } +} - fn sim_swap( +impl SwapHandler for Pallet { + fn swap( netuid: NetUid, - order: Order, - ) -> Result, DispatchError> { + order: O, + price_limit: TaoCurrency, + drop_fees: bool, + should_rollback: bool, + ) -> Result, DispatchError> + where + O: OrderT, + Self: SwapEngine, + { + >::swap(netuid, order, price_limit, drop_fees, should_rollback) + } + + fn sim_swap( + netuid: NetUid, + order: O, + ) -> Result, DispatchError> + where + O: OrderT, + Self: SwapEngine, + { match T::SubnetInfo::mechanism(netuid) { 1 => { let price_limit = Self::default_price_limit::(); - Self::swap(netuid, order, price_limit, false, true) + >::swap(netuid, order, price_limit, false, true) } _ => { let actual_amount = if T::SubnetInfo::exists(netuid) { order.amount() } else { - Order::PaidIn::ZERO + O::PaidIn::ZERO }; Ok(SwapResult { amount_paid_in: actual_amount, @@ -1033,9 +1054,7 @@ where } } } -} -impl SwapExt for Pallet { fn approx_fee_amount(netuid: NetUid, amount: C) -> C { Self::calculate_fee_amount(netuid, amount, false) } diff --git a/pallets/transaction-fee/src/lib.rs b/pallets/transaction-fee/src/lib.rs index 2526ed6693..89326457f6 100644 --- a/pallets/transaction-fee/src/lib.rs +++ b/pallets/transaction-fee/src/lib.rs @@ -24,7 +24,7 @@ use sp_runtime::{ use pallet_subtensor::Call as SubtensorCall; use pallet_transaction_payment::Config as PTPConfig; use pallet_transaction_payment::OnChargeTransaction; -use subtensor_swap_interface::SwapExt; +use subtensor_swap_interface::SwapHandler; // Misc use core::marker::PhantomData; diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index b7b21c358b..220022a4d4 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -21,8 +21,8 @@ use sp_runtime::{ }; use sp_std::cmp::Ordering; use sp_weights::Weight; -pub use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; -use subtensor_swap_interface::{OrderType, SwapHandler}; +pub use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; +use subtensor_swap_interface::{Order, SwapHandler}; use crate::SubtensorTxFeeHandler; use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier}; @@ -409,6 +409,8 @@ impl pallet_subtensor_swap::Config for Test { type SubnetInfo = SubtensorModule; type BalanceOps = SubtensorModule; type ProtocolId = SwapProtocolId; + type TaoReserve = pallet_subtensor::TaoCurrencyReserve; + type AlphaReserve = pallet_subtensor::AlphaCurrencyReserve; type MaxFeeRate = SwapMaxFeeRate; type MaxPositions = SwapMaxPositions; type MinimumLiquidity = SwapMinimumLiquidity; @@ -615,11 +617,11 @@ pub(crate) fn swap_alpha_to_tao_ext( return (alpha.into(), 0); } - let result = ::SwapEngine::swap( + let order = GetTaoForAlpha::::with_amount(alpha); + let result = ::SwapInterface::swap( netuid.into(), - OrderType::Sell, - alpha.into(), - ::SwapExt::min_price(), + order, + ::SwapInterface::min_price(), drop_fees, true, ); @@ -629,9 +631,9 @@ pub(crate) fn swap_alpha_to_tao_ext( let result = result.unwrap(); // we don't want to have silent 0 comparissons in tests - assert!(result.amount_paid_out > 0); + assert!(!result.amount_paid_out.is_zero()); - (result.amount_paid_out, result.fee_paid) + (result.amount_paid_out.to_u64(), result.fee_paid.to_u64()) } pub(crate) fn swap_alpha_to_tao(netuid: NetUid, alpha: AlphaCurrency) -> (u64, u64) { diff --git a/precompiles/src/alpha.rs b/precompiles/src/alpha.rs index 29f7cab568..9dd0379f10 100644 --- a/precompiles/src/alpha.rs +++ b/precompiles/src/alpha.rs @@ -7,7 +7,7 @@ use sp_core::U256; use sp_std::vec::Vec; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{Currency, NetUid}; -use subtensor_swap_interface::{OrderType, SwapHandler}; +use subtensor_swap_interface::{Order, SwapHandler}; use crate::PrecompileExt; @@ -36,9 +36,7 @@ where #[precompile::view] fn get_alpha_price(_handle: &mut impl PrecompileHandle, netuid: u16) -> EvmResult { let price = - as SwapHandler>::current_alpha_price( - netuid.into(), - ); + as SwapHandler>::current_alpha_price(netuid.into()); let price: SubstrateBalance = price.saturating_to_num::().into(); let price_eth = ::BalanceConverter::into_evm_balance(price) .map(|amount| amount.into_u256()) @@ -104,16 +102,13 @@ where netuid: u16, tao: u64, ) -> EvmResult { + let order = pallet_subtensor::GetAlphaForTao::::with_amount(tao); let swap_result = - as SwapHandler>::sim_swap( - netuid.into(), - OrderType::Buy, - tao, - ) - .map_err(|e| PrecompileFailure::Error { - exit_status: ExitError::Other(Into::<&'static str>::into(e).into()), - })?; - Ok(U256::from(swap_result.amount_paid_out)) + as SwapHandler>::sim_swap(netuid.into(), order) + .map_err(|e| PrecompileFailure::Error { + exit_status: ExitError::Other(Into::<&'static str>::into(e).into()), + })?; + Ok(U256::from(swap_result.amount_paid_out.to_u64())) } #[precompile::public("simSwapAlphaForTao(uint16,uint64)")] @@ -123,16 +118,13 @@ where netuid: u16, alpha: u64, ) -> EvmResult { + let order = pallet_subtensor::GetTaoForAlpha::::with_amount(alpha); let swap_result = - as SwapHandler>::sim_swap( - netuid.into(), - OrderType::Sell, - alpha, - ) - .map_err(|e| PrecompileFailure::Error { - exit_status: ExitError::Other(Into::<&'static str>::into(e).into()), - })?; - Ok(U256::from(swap_result.amount_paid_out)) + as SwapHandler>::sim_swap(netuid.into(), order) + .map_err(|e| PrecompileFailure::Error { + exit_status: ExitError::Other(Into::<&'static str>::into(e).into()), + })?; + Ok(U256::from(swap_result.amount_paid_out.to_u64())) } #[precompile::public("getSubnetMechanism(uint16)")] @@ -201,8 +193,7 @@ where let mut sum_alpha_price: U96F32 = U96F32::from_num(0); for (netuid, _) in netuids { - let price = - as SwapHandler>::current_alpha_price( + let price = as SwapHandler>::current_alpha_price( netuid.into(), ); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index e597728a60..4421136648 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -67,7 +67,7 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; use subtensor_precompiles::Precompiles; use subtensor_runtime_common::{AlphaCurrency, TaoCurrency, time::*, *}; -use subtensor_swap_interface::{OrderType, SwapHandler}; +use subtensor_swap_interface::{Order, SwapHandler}; // A few exports that help ease life for downstream crates. pub use frame_support::{ @@ -1312,6 +1312,8 @@ impl pallet_subtensor_swap::Config for Runtime { type SubnetInfo = SubtensorModule; type BalanceOps = SubtensorModule; type ProtocolId = SwapProtocolId; + type TaoReserve = pallet_subtensor::TaoCurrencyReserve; + type AlphaReserve = pallet_subtensor::AlphaCurrencyReserve; type MaxFeeRate = SwapMaxFeeRate; type MaxPositions = SwapMaxPositions; type MinimumLiquidity = SwapMinimumLiquidity; @@ -2497,10 +2499,10 @@ impl_runtime_apis! { } fn sim_swap_tao_for_alpha(netuid: NetUid, tao: TaoCurrency) -> SimSwapResult { + let order = pallet_subtensor::GetAlphaForTao::::with_amount(tao); pallet_subtensor_swap::Pallet::::sim_swap( netuid.into(), - OrderType::Buy, - tao.into(), + order, ) .map_or_else( |_| SimSwapResult { @@ -2519,10 +2521,10 @@ impl_runtime_apis! { } fn sim_swap_alpha_for_tao(netuid: NetUid, alpha: AlphaCurrency) -> SimSwapResult { + let order = pallet_subtensor::GetTaoForAlpha::::with_amount(alpha); pallet_subtensor_swap::Pallet::::sim_swap( netuid.into(), - OrderType::Sell, - alpha.into(), + order, ) .map_or_else( |_| SimSwapResult { From a99e4977f8199d6fa1e8e8309bf4689d263b48d4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 3 Oct 2025 20:32:38 +0000 Subject: [PATCH 12/14] auto-update benchmark weights --- pallets/subtensor/src/macros/dispatches.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 485dce6498..5ea9f6416d 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -706,7 +706,7 @@ mod dispatches { /// #[pallet::call_index(2)] #[pallet::weight((Weight::from_parts(340_800_000, 0) - .saturating_add(T::DbWeight::get().reads(26)) + .saturating_add(T::DbWeight::get().reads(24_u64)) .saturating_add(T::DbWeight::get().writes(15)), DispatchClass::Normal, Pays::Yes))] pub fn add_stake( origin: OriginFor, @@ -1671,7 +1671,7 @@ mod dispatches { /// - Thrown if key has hit transaction rate limit #[pallet::call_index(84)] #[pallet::weight((Weight::from_parts(358_500_000, 0) - .saturating_add(T::DbWeight::get().reads(38_u64)) + .saturating_add(T::DbWeight::get().reads(36_u64)) .saturating_add(T::DbWeight::get().writes(21_u64)), DispatchClass::Operational, Pays::Yes))] pub fn unstake_all_alpha(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_unstake_all_alpha(origin, hotkey) @@ -1785,7 +1785,7 @@ mod dispatches { #[pallet::call_index(87)] #[pallet::weight(( Weight::from_parts(351_300_000, 0) - .saturating_add(T::DbWeight::get().reads(37_u64)) + .saturating_add(T::DbWeight::get().reads(35_u64)) .saturating_add(T::DbWeight::get().writes(22_u64)), DispatchClass::Normal, Pays::Yes @@ -1850,7 +1850,7 @@ mod dispatches { /// #[pallet::call_index(88)] #[pallet::weight((Weight::from_parts(402_900_000, 0) - .saturating_add(T::DbWeight::get().reads(26)) + .saturating_add(T::DbWeight::get().reads(24_u64)) .saturating_add(T::DbWeight::get().writes(15)), DispatchClass::Normal, Pays::Yes))] pub fn add_stake_limit( origin: OriginFor, @@ -1914,7 +1914,7 @@ mod dispatches { /// #[pallet::call_index(89)] #[pallet::weight((Weight::from_parts(377_400_000, 0) - .saturating_add(T::DbWeight::get().reads(30_u64)) + .saturating_add(T::DbWeight::get().reads(28_u64)) .saturating_add(T::DbWeight::get().writes(14)), DispatchClass::Normal, Pays::Yes))] pub fn remove_stake_limit( origin: OriginFor, @@ -1958,7 +1958,7 @@ mod dispatches { #[pallet::call_index(90)] #[pallet::weight(( Weight::from_parts(411_500_000, 0) - .saturating_add(T::DbWeight::get().reads(37_u64)) + .saturating_add(T::DbWeight::get().reads(35_u64)) .saturating_add(T::DbWeight::get().writes(22_u64)), DispatchClass::Normal, Pays::Yes @@ -2136,7 +2136,7 @@ mod dispatches { /// Without limit_price it remove all the stake similar to `remove_stake` extrinsic #[pallet::call_index(103)] #[pallet::weight((Weight::from_parts(395_300_000, 10142) - .saturating_add(T::DbWeight::get().reads(30_u64)) + .saturating_add(T::DbWeight::get().reads(28_u64)) .saturating_add(T::DbWeight::get().writes(14_u64)), DispatchClass::Normal, Pays::Yes))] pub fn remove_stake_full_limit( origin: T::RuntimeOrigin, From 8b62e98b9df572dde17fd49c2be3946822fff140 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Mon, 6 Oct 2025 15:08:12 +0300 Subject: [PATCH 13/14] Address Loris' comments --- pallets/subtensor/src/tests/mock.rs | 4 ++-- pallets/subtensor/src/tests/staking.rs | 2 +- pallets/transaction-fee/src/tests/mock.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 1f4b93205d..cdb43de1af 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -1031,7 +1031,7 @@ pub(crate) fn swap_tao_to_alpha(netuid: NetUid, tao: TaoCurrency) -> (AlphaCurre let result = result.unwrap(); - // we don't want to have silent 0 comparissons in tests + // we don't want to have silent 0 comparisons in tests assert!(result.amount_paid_out > AlphaCurrency::ZERO); (result.amount_paid_out, result.fee_paid.into()) @@ -1064,7 +1064,7 @@ pub(crate) fn swap_alpha_to_tao_ext( let result = result.unwrap(); - // we don't want to have silent 0 comparissons in tests + // we don't want to have silent 0 comparisons in tests assert!(!result.amount_paid_out.is_zero()); (result.amount_paid_out, result.fee_paid.into()) diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 2f142b104d..be49967589 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -4168,7 +4168,7 @@ fn test_remove_99_9989_per_cent_stake_leaves_a_little() { )); // Check that all alpha was unstaked and 99% TAO balance was returned (less fees) - // let fee = ::SwapExt::approx_fee_amount(netuid.into(), (amount as f64 * 0.99) as u64); + // let fee = ::SwapInterface::approx_fee_amount(netuid.into(), (amount as f64 * 0.99) as u64); assert_abs_diff_eq!( SubtensorModule::get_coldkey_balance(&coldkey_account_id), (amount as f64 * 0.99) as u64 - fee, diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index 220022a4d4..576301e0e9 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -630,7 +630,7 @@ pub(crate) fn swap_alpha_to_tao_ext( let result = result.unwrap(); - // we don't want to have silent 0 comparissons in tests + // we don't want to have silent 0 comparisons in tests assert!(!result.amount_paid_out.is_zero()); (result.amount_paid_out.to_u64(), result.fee_paid.to_u64()) From 7639869f2d144f695fab9387ea38525c1e167547 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Tue, 7 Oct 2025 15:03:45 +0300 Subject: [PATCH 14/14] Remove swap pallet error shadowing in max_amount staking calculators --- pallets/subtensor/src/staking/add_stake.rs | 12 +- pallets/subtensor/src/staking/move_stake.rs | 14 +- pallets/subtensor/src/staking/remove_stake.rs | 9 +- pallets/subtensor/src/tests/staking.rs | 141 ++++++++++-------- 4 files changed, 97 insertions(+), 79 deletions(-) diff --git a/pallets/subtensor/src/staking/add_stake.rs b/pallets/subtensor/src/staking/add_stake.rs index f8cbf02358..ea33912bf1 100644 --- a/pallets/subtensor/src/staking/add_stake.rs +++ b/pallets/subtensor/src/staking/add_stake.rs @@ -180,7 +180,10 @@ impl Pallet { } // Returns the maximum amount of RAO that can be executed with price limit - pub fn get_max_amount_add(netuid: NetUid, limit_price: TaoCurrency) -> Result> { + pub fn get_max_amount_add( + netuid: NetUid, + limit_price: TaoCurrency, + ) -> Result { // Corner case: root and stao // There's no slippage for root or stable subnets, so if limit price is 1e9 rao or // higher, then max_amount equals u64::MAX, otherwise it is 0. @@ -188,20 +191,19 @@ impl Pallet { if limit_price >= 1_000_000_000.into() { return Ok(u64::MAX); } else { - return Err(Error::ZeroMaxStakeAmount); + return Err(Error::::ZeroMaxStakeAmount.into()); } } // Use reverting swap to estimate max limit amount let order = GetAlphaForTao::::with_amount(u64::MAX); let result = T::SwapInterface::swap(netuid.into(), order, limit_price, false, true) - .map(|r| r.amount_paid_in.saturating_add(r.fee_paid)) - .map_err(|_| Error::ZeroMaxStakeAmount)?; + .map(|r| r.amount_paid_in.saturating_add(r.fee_paid))?; if !result.is_zero() { Ok(result.into()) } else { - Err(Error::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) } } } diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index 768430b03a..a1d9b46d5b 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -424,8 +424,8 @@ impl Pallet { origin_netuid: NetUid, destination_netuid: NetUid, limit_price: TaoCurrency, - ) -> Result> { - let tao: U64F64 = U64F64::saturating_from_num(1_000_000_000); + ) -> Result { + let tao = U64F64::saturating_from_num(1_000_000_000); // Corner case: both subnet IDs are root or stao // There's no slippage for root or stable subnets, so slippage is always 0. @@ -434,7 +434,7 @@ impl Pallet { && (destination_netuid.is_root() || SubnetMechanism::::get(destination_netuid) == 0) { if limit_price > tao.saturating_to_num::().into() { - return Err(Error::ZeroMaxStakeAmount); + return Err(Error::::ZeroMaxStakeAmount.into()); } else { return Ok(AlphaCurrency::MAX); } @@ -476,7 +476,7 @@ impl Pallet { let subnet_tao_2 = SubnetTAO::::get(destination_netuid) .saturating_add(SubnetTaoProvided::::get(destination_netuid)); if subnet_tao_1.is_zero() || subnet_tao_2.is_zero() { - return Err(Error::ZeroMaxStakeAmount); + return Err(Error::::ZeroMaxStakeAmount.into()); } let subnet_tao_1_float: U64F64 = U64F64::saturating_from_num(subnet_tao_1); let subnet_tao_2_float: U64F64 = U64F64::saturating_from_num(subnet_tao_2); @@ -487,7 +487,7 @@ impl Pallet { let alpha_in_2 = SubnetAlphaIn::::get(destination_netuid) .saturating_add(SubnetAlphaInProvided::::get(destination_netuid)); if alpha_in_1.is_zero() || alpha_in_2.is_zero() { - return Err(Error::ZeroMaxStakeAmount); + return Err(Error::::ZeroMaxStakeAmount.into()); } let alpha_in_1_float: U64F64 = U64F64::saturating_from_num(alpha_in_1); let alpha_in_2_float: U64F64 = U64F64::saturating_from_num(alpha_in_2); @@ -503,7 +503,7 @@ impl Pallet { T::SwapInterface::current_alpha_price(destination_netuid.into()), ); if limit_price_float > current_price { - return Err(Error::ZeroMaxStakeAmount); + return Err(Error::::ZeroMaxStakeAmount.into()); } // Corner case: limit_price is zero @@ -529,7 +529,7 @@ impl Pallet { if final_result != 0 { Ok(final_result.into()) } else { - Err(Error::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) } } } diff --git a/pallets/subtensor/src/staking/remove_stake.rs b/pallets/subtensor/src/staking/remove_stake.rs index d7a45f79ab..5771029f74 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -391,7 +391,7 @@ impl Pallet { pub fn get_max_amount_remove( netuid: NetUid, limit_price: TaoCurrency, - ) -> Result> { + ) -> Result { // Corner case: root and stao // There's no slippage for root or stable subnets, so if limit price is 1e9 rao or // lower, then max_amount equals u64::MAX, otherwise it is 0. @@ -399,20 +399,19 @@ impl Pallet { if limit_price <= 1_000_000_000.into() { return Ok(AlphaCurrency::MAX); } else { - return Err(Error::ZeroMaxStakeAmount); + return Err(Error::::ZeroMaxStakeAmount.into()); } } // Use reverting swap to estimate max limit amount let order = GetTaoForAlpha::::with_amount(u64::MAX); let result = T::SwapInterface::swap(netuid.into(), order, limit_price.into(), false, true) - .map(|r| r.amount_paid_in.saturating_add(r.fee_paid)) - .map_err(|_| Error::ZeroMaxStakeAmount)?; + .map(|r| r.amount_paid_in.saturating_add(r.fee_paid))?; if !result.is_zero() { Ok(result) } else { - Err(Error::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) } } diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index be49967589..31826b6810 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -2729,13 +2729,13 @@ fn test_max_amount_add_root() { // 0 price on root => max is 0 assert_eq!( SubtensorModule::get_max_amount_add(NetUid::ROOT, TaoCurrency::ZERO), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); // 0.999999... price on root => max is 0 assert_eq!( SubtensorModule::get_max_amount_add(NetUid::ROOT, TaoCurrency::from(999_999_999)), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); // 1.0 price on root => max is u64::MAX @@ -2767,13 +2767,13 @@ fn test_max_amount_add_stable() { // 0 price => max is 0 assert_eq!( SubtensorModule::get_max_amount_add(netuid, TaoCurrency::ZERO), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); // 0.999999... price => max is 0 assert_eq!( SubtensorModule::get_max_amount_add(netuid, TaoCurrency::from(999_999_999)), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); // 1.0 price => max is u64::MAX @@ -2806,7 +2806,9 @@ fn test_max_amount_add_dynamic() { 1_000_000_000, 1_000_000_000, 0, - Err(Error::::ZeroMaxStakeAmount), + Err(DispatchError::from( + pallet_subtensor_swap::Error::::PriceLimitExceeded, + )), ), // Low bounds (100, 100, 1_100_000_000, Ok(4)), @@ -2827,25 +2829,33 @@ fn test_max_amount_add_dynamic() { 150_000_000_000, 100_000_000_000, 0, - Err(Error::::ZeroMaxStakeAmount), + Err(DispatchError::from( + pallet_subtensor_swap::Error::::PriceLimitExceeded, + )), ), ( 150_000_000_000, 100_000_000_000, 100_000_000, - Err(Error::::ZeroMaxStakeAmount), + Err(DispatchError::from( + pallet_subtensor_swap::Error::::PriceLimitExceeded, + )), ), ( 150_000_000_000, 100_000_000_000, 500_000_000, - Err(Error::::ZeroMaxStakeAmount), + Err(DispatchError::from( + pallet_subtensor_swap::Error::::PriceLimitExceeded, + )), ), ( 150_000_000_000, 100_000_000_000, 1_499_999_999, - Err(Error::::ZeroMaxStakeAmount), + Err(DispatchError::from( + pallet_subtensor_swap::Error::::PriceLimitExceeded, + )), ), (150_000_000_000, 100_000_000_000, 1_500_000_000, Ok(5)), (150_000_000_000, 100_000_000_000, 1_500_000_001, Ok(51)), @@ -2934,13 +2944,13 @@ fn test_max_amount_remove_root() { // 1.000...001 price on root => max is 0 assert_eq!( SubtensorModule::get_max_amount_remove(NetUid::ROOT, TaoCurrency::from(1_000_000_001)), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); // 2.0 price on root => max is 0 assert_eq!( SubtensorModule::get_max_amount_remove(NetUid::ROOT, TaoCurrency::from(2_000_000_000)), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); }); } @@ -2972,13 +2982,13 @@ fn test_max_amount_remove_stable() { // 1.000...001 price => max is 0 assert_eq!( SubtensorModule::get_max_amount_remove(netuid, TaoCurrency::from(1_000_000_001)), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); // 2.0 price => max is 0 assert_eq!( SubtensorModule::get_max_amount_remove(netuid, TaoCurrency::from(2_000_000_000)), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); }); } @@ -2998,13 +3008,17 @@ fn test_max_amount_remove_dynamic() { 0, 1_000_000_000, 100, - Err(Error::::ZeroMaxStakeAmount), + Err(DispatchError::from( + pallet_subtensor_swap::Error::::ReservesTooLow, + )), ), ( 1_000_000_000, 0, 100, - Err(Error::::ZeroMaxStakeAmount), + Err(DispatchError::from( + pallet_subtensor_swap::Error::::PriceLimitExceeded, + )), ), (10_000_000_000, 10_000_000_000, 0, Ok(u64::MAX)), // Low bounds (numbers are empirical, it is only important that result @@ -3043,13 +3057,17 @@ fn test_max_amount_remove_dynamic() { 200_000_000_000, 100_000_000_000, 2_000_000_000, - Err(Error::::ZeroMaxStakeAmount), + Err(DispatchError::from( + pallet_subtensor_swap::Error::::PriceLimitExceeded, + )), ), ( 200_000_000_000, 100_000_000_000, 2_000_000_001, - Err(Error::::ZeroMaxStakeAmount), + Err(DispatchError::from( + pallet_subtensor_swap::Error::::PriceLimitExceeded, + )), ), (200_000_000_000, 100_000_000_000, 1_999_999_999, Ok(24)), (200_000_000_000, 100_000_000_000, 1_999_999_990, Ok(252)), @@ -3065,7 +3083,9 @@ fn test_max_amount_remove_dynamic() { 21_000_000_000_000_000, 1_000_000_000_000_000_000, u64::MAX, - Err(Error::::ZeroMaxStakeAmount), + Err(DispatchError::from( + pallet_subtensor_swap::Error::::PriceLimitExceeded, + )), ), ( 21_000_000_000_000_000, @@ -3086,39 +3106,36 @@ fn test_max_amount_remove_dynamic() { Ok(u64::MAX), ), ] - .iter() - .for_each( - |&(tao_in, alpha_in, limit_price, ref expected_max_swappable)| { - let alpha_in = AlphaCurrency::from(alpha_in); - // Forse-set alpha in and tao reserve to achieve relative price of subnets - SubnetTAO::::insert(netuid, TaoCurrency::from(tao_in)); - SubnetAlphaIn::::insert(netuid, alpha_in); - - if !alpha_in.is_zero() { - let expected_price = I96F32::from_num(tao_in) / I96F32::from_num(alpha_in); - assert_eq!( - ::SwapInterface::current_alpha_price(netuid.into()), - expected_price - ); - } + .into_iter() + .for_each(|(tao_in, alpha_in, limit_price, expected_max_swappable)| { + let alpha_in = AlphaCurrency::from(alpha_in); + // Forse-set alpha in and tao reserve to achieve relative price of subnets + SubnetTAO::::insert(netuid, TaoCurrency::from(tao_in)); + SubnetAlphaIn::::insert(netuid, alpha_in); - match expected_max_swappable { - Err(_) => assert_err!( - SubtensorModule::get_max_amount_remove(netuid, limit_price.into()), - Error::::ZeroMaxStakeAmount - ), - Ok(v) => { - let v = AlphaCurrency::from(*v); - assert_abs_diff_eq!( - SubtensorModule::get_max_amount_remove(netuid, limit_price.into()) - .unwrap(), - v, - epsilon = v / 100.into() - ); - } + if !alpha_in.is_zero() { + let expected_price = I96F32::from_num(tao_in) / I96F32::from_num(alpha_in); + assert_eq!( + ::SwapInterface::current_alpha_price(netuid.into()), + expected_price + ); + } + + match expected_max_swappable { + Err(e) => assert_err!( + SubtensorModule::get_max_amount_remove(netuid, limit_price.into()), + DispatchError::from(e) + ), + Ok(v) => { + let v = AlphaCurrency::from(v); + assert_abs_diff_eq!( + SubtensorModule::get_max_amount_remove(netuid, limit_price.into()).unwrap(), + v, + epsilon = v / 100.into() + ); } - }, - ); + } + }); }); } @@ -3169,7 +3186,7 @@ fn test_max_amount_move_root_root() { NetUid::ROOT, TaoCurrency::from(1_000_000_001) ), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); // 2.0 price on (root, root) => max is 0 @@ -3179,7 +3196,7 @@ fn test_max_amount_move_root_root() { NetUid::ROOT, TaoCurrency::from(2_000_000_000) ), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); }); } @@ -3234,7 +3251,7 @@ fn test_max_amount_move_root_stable() { netuid, TaoCurrency::from(1_000_000_001) ), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); // 2.0 price on (root, stable) => max is 0 @@ -3244,7 +3261,7 @@ fn test_max_amount_move_root_stable() { netuid, TaoCurrency::from(2_000_000_000) ), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); }); } @@ -3286,7 +3303,7 @@ fn test_max_amount_move_stable_dynamic() { dynamic_netuid, TaoCurrency::from(2_000_000_000) ), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); // 3.0 price => max is 0 @@ -3296,7 +3313,7 @@ fn test_max_amount_move_stable_dynamic() { dynamic_netuid, TaoCurrency::from(3_000_000_000) ), - Err(Error::::ZeroMaxStakeAmount) + Err(pallet_subtensor_swap::Error::::PriceLimitExceeded.into()) ); // 2x price => max is 1x TAO @@ -3328,7 +3345,7 @@ fn test_max_amount_move_stable_dynamic() { // Max price doesn't panic and returns something meaningful assert_eq!( SubtensorModule::get_max_amount_move(stable_netuid, dynamic_netuid, TaoCurrency::MAX), - Err(Error::::ZeroMaxStakeAmount) + Err(pallet_subtensor_swap::Error::::PriceLimitExceeded.into()) ); assert_eq!( SubtensorModule::get_max_amount_move( @@ -3336,7 +3353,7 @@ fn test_max_amount_move_stable_dynamic() { dynamic_netuid, TaoCurrency::MAX - 1.into() ), - Err(Error::::ZeroMaxStakeAmount) + Err(pallet_subtensor_swap::Error::::PriceLimitExceeded.into()) ); assert_eq!( SubtensorModule::get_max_amount_move( @@ -3344,7 +3361,7 @@ fn test_max_amount_move_stable_dynamic() { dynamic_netuid, TaoCurrency::MAX / 2.into() ), - Err(Error::::ZeroMaxStakeAmount) + Err(pallet_subtensor_swap::Error::::PriceLimitExceeded.into()) ); }); } @@ -3400,7 +3417,7 @@ fn test_max_amount_move_dynamic_stable() { stable_netuid, 1_500_000_001.into() ), - Err(Error::::ZeroMaxStakeAmount) + Err(pallet_subtensor_swap::Error::::PriceLimitExceeded.into()) ); // 1.5 price => max is 0 because of non-zero slippage @@ -3853,7 +3870,7 @@ fn test_add_stake_limit_partial_zero_max_stake_amount_error() { limit_price, true ), - Error::::ZeroMaxStakeAmount + DispatchError::from(pallet_subtensor_swap::Error::::PriceLimitExceeded) ); }); } @@ -4652,7 +4669,7 @@ fn test_stake_into_subnet_prohibitive_limit() { TaoCurrency::ZERO, true, ), - Error::::ZeroMaxStakeAmount + DispatchError::from(pallet_subtensor_swap::Error::::PriceLimitExceeded) ); // Check if stake has NOT increased @@ -4725,7 +4742,7 @@ fn test_unstake_from_subnet_prohibitive_limit() { TaoCurrency::MAX, true, ), - Error::::ZeroMaxStakeAmount + DispatchError::from(pallet_subtensor_swap::Error::::PriceLimitExceeded) ); // Check if stake has NOT decreased