diff --git a/Cargo.lock b/Cargo.lock index ee3fa03e95..6d9637088a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18000,6 +18000,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "substrate-fixed", + "subtensor-macros", "subtensor-runtime-common", ] 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 a5d09ad974..590eb7fee3 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -169,8 +169,6 @@ 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; @@ -180,6 +178,12 @@ pub trait SubnetInfo { fn hotkey_of_uid(netuid: NetUid, uid: u16) -> Option; } +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; @@ -200,10 +204,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/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index aca6b7570e..50b9ccd282 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -341,6 +341,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/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 8748fe8eec..6aa156c8c6 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -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::SwapInterface::max_price(), true, ); if let Ok(buy_swap_result_ok) = buy_swap_result { @@ -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::SwapInterface::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 e33598472b..776f76cb3a 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 ===== @@ -2131,17 +2131,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) } @@ -2238,22 +2269,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 ea04fe07e2..d86e743e43 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::SwapHandler; + use subtensor_swap_interface::{SwapEngine, SwapHandler}; /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] @@ -51,8 +51,10 @@ mod config { /// the preimage to store the call data. type Preimages: QueryPreimage + StorePreimage; - /// Swap interface. - type SwapInterface: SwapHandler; + /// 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/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 2517217647..59c4edcebc 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, diff --git a/pallets/subtensor/src/rpc_info/stake_info.rs b/pallets/subtensor/src/rpc_info/stake_info.rs index 84e9e6d6b7..13789292da 100644 --- a/pallets/subtensor/src/rpc_info/stake_info.rs +++ b/pallets/subtensor/src/rpc_info/stake_info.rs @@ -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::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 740d42d09e..ea33912bf1 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, SwapHandler}; use super::*; @@ -74,7 +74,7 @@ impl Pallet { &coldkey, netuid, tao_staked.saturating_to_num::().into(), - T::SwapInterface::max_price().into(), + T::SwapInterface::max_price(), true, false, )?; @@ -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,26 +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 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::SwapInterface::swap(netuid.into(), order, limit_price, false, true) + .map(|r| r.amount_paid_in.saturating_add(r.fee_paid))?; - if result != 0 { - Ok(result) + if !result.is_zero() { + Ok(result.into()) } else { - Err(Error::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) } } } diff --git a/pallets/subtensor/src/staking/helpers.rs b/pallets/subtensor/src/staking/helpers.rs index 1625afa811..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::{OrderType, SwapHandler}; +use subtensor_swap_interface::{Order, SwapHandler}; use super::*; @@ -73,20 +73,17 @@ 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::SwapInterface::sim_swap(netuid.into(), order) + .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.to_u64().saturating_add(fee) + }) + .unwrap_or_default() }) .sum::() }) @@ -202,7 +199,7 @@ impl Pallet { coldkey, netuid, alpha_stake, - T::SwapInterface::min_price().into(), + T::SwapInterface::min_price(), false, ); diff --git a/pallets/subtensor/src/staking/move_stake.rs b/pallets/subtensor/src/staking/move_stake.rs index 589da2b4b8..a1d9b46d5b 100644 --- a/pallets/subtensor/src/staking/move_stake.rs +++ b/pallets/subtensor/src/staking/move_stake.rs @@ -360,7 +360,7 @@ impl Pallet { origin_coldkey, origin_netuid, move_amount, - T::SwapInterface::min_price().into(), + T::SwapInterface::min_price(), drop_fee_origin, )?; @@ -378,7 +378,7 @@ impl Pallet { destination_coldkey, destination_netuid, tao_unstaked, - T::SwapInterface::max_price().into(), + T::SwapInterface::max_price(), set_limit, drop_fee_destination, )?; @@ -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 99e4a52be1..5771029f74 100644 --- a/pallets/subtensor/src/staking/remove_stake.rs +++ b/pallets/subtensor/src/staking/remove_stake.rs @@ -1,8 +1,7 @@ -use subtensor_swap_interface::{OrderType, SwapHandler}; - use super::*; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; +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. @@ -73,7 +72,7 @@ impl Pallet { &coldkey, netuid, alpha_unstaked, - T::SwapInterface::min_price().into(), + T::SwapInterface::min_price(), false, )?; @@ -168,7 +167,7 @@ impl Pallet { &coldkey, netuid, alpha_unstaked, - T::SwapInterface::min_price().into(), + T::SwapInterface::min_price(), false, )?; @@ -261,7 +260,7 @@ impl Pallet { &coldkey, netuid, alpha_unstaked, - T::SwapInterface::min_price().into(), + T::SwapInterface::min_price(), false, )?; @@ -280,7 +279,7 @@ impl Pallet { &coldkey, NetUid::ROOT, total_tao_unstaked, - T::SwapInterface::max_price().into(), + T::SwapInterface::max_price(), false, // no limit for Root subnet false, )?; @@ -392,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. @@ -400,26 +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 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::SwapInterface::swap(netuid.into(), order, limit_price.into(), false, true) + .map(|r| r.amount_paid_in.saturating_add(r.fee_paid))?; - if result != 0 { - Ok(result.into()) + if !result.is_zero() { + Ok(result) } else { - Err(Error::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) } } @@ -459,7 +451,7 @@ impl Pallet { // - sum emitted α, // - apply owner fraction to get owner α, // - price that α using a *simulated* AMM swap. - let mut owner_emission_tao: TaoCurrency = TaoCurrency::ZERO; + let mut owner_emission_tao = TaoCurrency::ZERO; if should_refund_owner && !lock_cost.is_zero() { let total_emitted_alpha_u128: u128 = Emission::::get(netuid) @@ -471,17 +463,14 @@ impl Pallet { if total_emitted_alpha_u128 > 0 { let owner_fraction: U96F32 = Self::get_float_subnet_owner_cut(); - let owner_alpha_u64: u64 = U96F32::from_num(total_emitted_alpha_u128) + let owner_alpha_u64 = U96F32::from_num(total_emitted_alpha_u128) .saturating_mul(owner_fraction) .floor() .saturating_to_num::(); owner_emission_tao = if owner_alpha_u64 > 0 { - match T::SwapInterface::sim_swap( - netuid.into(), - OrderType::Sell, - owner_alpha_u64, - ) { + let order = GetTaoForAlpha::with_amount(owner_alpha_u64); + match T::SwapInterface::sim_swap(netuid.into(), order) { Ok(sim) => TaoCurrency::from(sim.amount_paid_out), Err(e) => { log::debug!( @@ -489,7 +478,7 @@ impl Pallet { ); let cur_price: U96F32 = T::SwapInterface::current_alpha_price(netuid.into()); - let val_u64: u64 = U96F32::from_num(owner_alpha_u64) + let val_u64 = U96F32::from_num(owner_alpha_u64) .saturating_mul(cur_price) .floor() .saturating_to_num::(); diff --git a/pallets/subtensor/src/staking/stake_utils.rs b/pallets/subtensor/src/staking/stake_utils.rs index 528289ec0e..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::{OrderType, SwapHandler, SwapResult}; +use subtensor_swap_interface::{Order, SwapHandler, SwapResult}; impl Pallet { /// Retrieves the total alpha issuance for a given subnet. @@ -579,35 +579,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::SwapInterface::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(); // 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 +608,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(); + *total = total.saturating_add(delta.into()); }); // Increase Total Tao reserves. @@ -640,64 +631,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::SwapInterface::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(); 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(); + 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 +724,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 +755,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 +777,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 +802,7 @@ impl Pallet { tao, swap_result.amount_paid_out.into(), netuid, - swap_result.fee_paid, + swap_result.fee_paid.to_u64(), )); log::debug!( @@ -946,7 +922,8 @@ 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::SwapInterface::sim_swap(netuid.into(), order) .map(|res| res.fee_paid) .unwrap_or(T::SwapInterface::approx_fee_amount( netuid.into(), @@ -978,18 +955,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::SwapInterface::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 +1006,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::SwapInterface::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 +1144,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::SwapInterface::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..a33c32e4ef 100644 --- a/pallets/subtensor/src/subnets/leasing.rs +++ b/pallets/subtensor/src/subnets/leasing.rs @@ -310,7 +310,7 @@ impl Pallet { &lease.coldkey, lease.netuid, total_contributors_cut_alpha, - T::SwapInterface::min_price().into(), + 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 418c9cbde9..b71aa68a0a 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -136,7 +136,7 @@ impl Pallet { let burned_alpha = Self::swap_tao_for_alpha( netuid, actual_burn_amount, - T::SwapInterface::max_price().into(), + T::SwapInterface::max_price(), false, )? .amount_paid_out; diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 8439297e14..7676e0c557 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -2,7 +2,6 @@ use super::*; use sp_core::Get; use subtensor_runtime_common::{NetUid, TaoCurrency}; use subtensor_swap_interface::SwapHandler; - impl Pallet { /// Returns true if the subnetwork exists. /// diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index 0fee0af2ca..285788d158 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -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(), + ::SwapInterface::max_price(), false, ) .unwrap() diff --git a/pallets/subtensor/src/tests/coinbase.rs b/pallets/subtensor/src/tests/coinbase.rs index b53308ae9f..39a964a067 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, SwapHandler}; #[allow(clippy::arithmetic_side_effects)] fn close(value: u64, target: u64, eps: u64) { diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index 7c23dc2b2c..4d8108ac29 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -1324,13 +1324,13 @@ fn test_set_alpha_disabled() { assert_ok!(SubtensorModule::root_register(signer.clone(), hotkey,)); 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)); @@ -2294,14 +2294,14 @@ fn test_get_set_alpha() { 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 219e210d07..7c731b604c 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, SwapHandler}; type Block = frame_system::mocking::MockBlock; @@ -454,7 +454,7 @@ impl crate::Config for Test { type InitialTaoWeight = InitialTaoWeight; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod; type DurationOfStartCall = DurationOfStartCall; - type SwapInterface = Swap; + type SwapInterface = pallet_subtensor_swap::Pallet; type KeySwapOnSubnetCost = InitialKeySwapOnSubnetCost; type HotkeySwapOnSubnetInterval = HotkeySwapOnSubnetInterval; type ProxyInterface = FakeProxier; @@ -478,6 +478,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; @@ -973,7 +975,7 @@ pub fn increase_stake_on_coldkey_hotkey_account( coldkey, netuid, tao_staked, - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1009,10 +1011,10 @@ pub(crate) fn swap_tao_to_alpha(netuid: NetUid, tao: TaoCurrency) -> (AlphaCurre return (tao.to_u64().into(), 0); } + let order = GetAlphaForTao::::with_amount(tao); let result = ::SwapInterface::swap( netuid.into(), - OrderType::Buy, - tao.into(), + order, ::SwapInterface::max_price(), false, true, @@ -1022,10 +1024,10 @@ 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); + // we don't want to have silent 0 comparisons in tests + 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( @@ -1039,13 +1041,13 @@ pub(crate) fn swap_alpha_to_tao_ext( println!( "::SwapInterface::min_price() = {:?}", - ::SwapInterface::min_price() + ::SwapInterface::min_price::() ); + let order = GetTaoForAlpha::::with_amount(alpha); let result = ::SwapInterface::swap( netuid.into(), - OrderType::Sell, - alpha.into(), + order, ::SwapInterface::min_price(), drop_fees, true, @@ -1055,10 +1057,10 @@ 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); + // we don't want to have silent 0 comparisons in tests + 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 e49903aa86..dfd9927da4 100644 --- a/pallets/subtensor/src/tests/move_stake.rs +++ b/pallets/subtensor/src/tests/move_stake.rs @@ -33,7 +33,7 @@ fn test_do_move_success() { &coldkey, netuid.into(), stake_amount, - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -110,7 +110,7 @@ fn test_do_move_different_subnets() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -140,17 +140,15 @@ fn test_do_move_different_subnets() { ), AlphaCurrency::ZERO ); - let fee = ::SwapInterface::approx_fee_amount( - destination_netuid.into(), - alpha.into(), - ); + let fee = + ::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() ); }); @@ -180,7 +178,7 @@ fn test_do_move_nonexistent_subnet() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -284,7 +282,7 @@ fn test_do_move_nonexistent_destination_hotkey() { &coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -349,7 +347,7 @@ fn test_do_move_partial_stake() { &coldkey, netuid, total_stake.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -418,7 +416,7 @@ fn test_do_move_multiple_times() { &coldkey, netuid, initial_stake.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -490,7 +488,7 @@ fn test_do_move_wrong_origin() { &coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -557,7 +555,7 @@ fn test_do_move_same_hotkey_fails() { &coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -608,7 +606,7 @@ fn test_do_move_event_emission() { &coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -669,7 +667,7 @@ fn test_do_move_storage_updates() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -736,7 +734,7 @@ fn test_move_full_amount_same_netuid() { &coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -804,7 +802,7 @@ fn test_do_move_max_values() { &coldkey, netuid, max_stake.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -910,7 +908,7 @@ fn test_do_transfer_success() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1019,7 +1017,7 @@ fn test_do_transfer_insufficient_stake() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1060,7 +1058,7 @@ fn test_do_transfer_wrong_origin() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1098,7 +1096,7 @@ fn test_do_transfer_minimum_stake_check() { &origin_coldkey, netuid, stake_amount, - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1146,7 +1144,7 @@ fn test_do_transfer_different_subnets() { &origin_coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1212,7 +1210,7 @@ fn test_do_swap_success() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1320,7 +1318,7 @@ fn test_do_swap_insufficient_stake() { &coldkey, netuid1, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1355,7 +1353,7 @@ fn test_do_swap_wrong_origin() { &real_coldkey, netuid1, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1393,7 +1391,7 @@ fn test_do_swap_minimum_stake_check() { &coldkey, netuid1, total_stake, - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1429,7 +1427,7 @@ fn test_do_swap_same_subnet() { &coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1474,7 +1472,7 @@ fn test_do_swap_partial_stake() { &coldkey, origin_netuid, total_stake_tao.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1526,7 +1524,7 @@ fn test_do_swap_storage_updates() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1586,7 +1584,7 @@ fn test_do_swap_multiple_times() { &coldkey, netuid1, initial_stake.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1657,7 +1655,7 @@ fn test_do_swap_allows_non_owned_hotkey() { &coldkey, origin_netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1805,7 +1803,7 @@ fn test_transfer_stake_rate_limited() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), true, false, ) @@ -1850,7 +1848,7 @@ fn test_transfer_stake_doesnt_limit_destination_coldkey() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -1896,7 +1894,7 @@ fn test_swap_stake_limits_destination_netuid() { &origin_coldkey, netuid, stake_amount.into(), - ::SwapInterface::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 e24c1a53c7..94069c633d 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::{Order, SwapHandler}; #[test] fn test_registration_ok() { @@ -804,9 +804,9 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { let min_stake = DefaultMinStake::::get(); 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; @@ -889,23 +889,22 @@ fn destroy_alpha_out_many_stakers_complex_distribution() { .floor() .saturating_to_num::(); - let owner_emission_tao_u64: u64 = ::SwapInterface::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 = - ::SwapInterface::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)); @@ -968,20 +967,18 @@ fn destroy_alpha_out_refund_gating_by_registration_block() { .saturating_to_num::(); // Prefer sim_swap; fall back to current price if unavailable. - let owner_emission_tao_u64: u64 = ::SwapInterface::sim_swap( - netuid.into(), - OrderType::Sell, - owner_alpha_u64, - ) - .map(|res| res.amount_paid_out) - .unwrap_or_else(|_| { - let price: U96F32 = - ::SwapInterface::current_alpha_price(netuid.into()); - U96F32::from_num(owner_alpha_u64) - .saturating_mul(price) - .floor() - .saturating_to_num::() - }); + let order = GetTaoForAlpha::::with_amount(owner_alpha_u64); + let owner_emission_tao_u64 = + ::SwapInterface::sim_swap(netuid.into(), order) + .map(|res| res.amount_paid_out.to_u64()) + .unwrap_or_else(|_| { + let price: U96F32 = + ::SwapInterface::current_alpha_price(netuid.into()); + U96F32::from_num(owner_alpha_u64) + .saturating_mul(price) + .floor() + .saturating_to_num::() + }); let expected_refund: u64 = lock_u64.saturating_sub(owner_emission_tao_u64); @@ -2134,19 +2131,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] { @@ -2157,10 +2152,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"); @@ -2185,7 +2180,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 23d3552240..8ad093d0ae 100644 --- a/pallets/subtensor/src/tests/senate.rs +++ b/pallets/subtensor/src/tests/senate.rs @@ -723,12 +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 fee = ::SwapInterface::approx_fee_amount( - netuid, - default_stake.into(), - ); - default_stake + fee + let default_stake = DefaultMinStake::::get(); + let 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 b36788fa31..31826b6810 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::{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 = - ::SwapInterface::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(), ); @@ -729,7 +731,11 @@ fn test_remove_stake_total_balance_no_change() { amount.into() )); - let fee = ::SwapInterface::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, @@ -864,7 +870,7 @@ fn test_remove_stake_insufficient_liquidity() { &coldkey, netuid, amount_staked.into(), - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, false, ) @@ -2723,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 @@ -2761,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 @@ -2800,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)), @@ -2821,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)), @@ -2928,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()) ); }); } @@ -2966,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()) ); }); } @@ -2992,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 @@ -3037,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)), @@ -3059,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, @@ -3080,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() + ); } - }, - ); + } + }); }); } @@ -3163,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 @@ -3173,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()) ); }); } @@ -3228,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 @@ -3238,7 +3261,7 @@ fn test_max_amount_move_root_stable() { netuid, TaoCurrency::from(2_000_000_000) ), - Err(Error::::ZeroMaxStakeAmount) + Err(Error::::ZeroMaxStakeAmount.into()) ); }); } @@ -3280,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 @@ -3290,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 @@ -3322,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( @@ -3330,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( @@ -3338,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()) ); }); } @@ -3394,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 @@ -3743,8 +3766,9 @@ fn test_add_stake_limit_ok() { // Check that 450 TAO less fees balance still remains free on coldkey 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, @@ -3846,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) ); }); } @@ -4026,18 +4050,16 @@ fn test_add_stake_specific_stake_into_subnet_fail() { ); // Add stake as new hotkey - let expected_alpha = AlphaCurrency::from( - ::SwapInterface::swap( - netuid.into(), - OrderType::Buy, - tao_staked.into(), - ::SwapInterface::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, @@ -4467,11 +4489,11 @@ fn test_stake_into_subnet_ok() { .to_num::(); // Initialize swap v3 + let order = GetAlphaForTao::::with_amount(0); assert_ok!(::SwapInterface::swap( netuid.into(), - OrderType::Buy, - 0, - u64::MAX, + order, + TaoCurrency::MAX, false, true )); @@ -4521,11 +4543,11 @@ fn test_stake_into_subnet_low_amount() { .to_num::(); // Initialize swap v3 + let order = GetAlphaForTao::::with_amount(0); assert_ok!(::SwapInterface::swap( netuid.into(), - OrderType::Buy, - 0, - u64::MAX, + order, + TaoCurrency::MAX, false, true )); @@ -4569,11 +4591,11 @@ fn test_unstake_from_subnet_low_amount() { mock::setup_reserves(netuid, tao_reserve, alpha_in); // Initialize swap v3 + let order = GetAlphaForTao::::with_amount(0); assert_ok!(::SwapInterface::swap( netuid.into(), - OrderType::Buy, - 0, - u64::MAX, + order, + TaoCurrency::MAX, false, true )); @@ -4627,11 +4649,11 @@ fn test_stake_into_subnet_prohibitive_limit() { mock::setup_reserves(netuid, tao_reserve, alpha_in); // Initialize swap v3 + let order = GetAlphaForTao::::with_amount(0); assert_ok!(::SwapInterface::swap( netuid.into(), - OrderType::Buy, - 0, - u64::MAX, + order, + TaoCurrency::MAX, false, true )); @@ -4647,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 @@ -4683,11 +4705,11 @@ fn test_unstake_from_subnet_prohibitive_limit() { mock::setup_reserves(netuid, tao_reserve, alpha_in); // Initialize swap v3 + let order = GetAlphaForTao::::with_amount(0); assert_ok!(::SwapInterface::swap( netuid.into(), - OrderType::Buy, - 0, - u64::MAX, + order, + TaoCurrency::MAX, false, true )); @@ -4720,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 @@ -4759,11 +4781,11 @@ fn test_unstake_full_amount() { mock::setup_reserves(netuid, tao_reserve, alpha_in); // Initialize swap v3 + let order = GetAlphaForTao::::with_amount(0); assert_ok!(::SwapInterface::swap( netuid.into(), - OrderType::Buy, - 0, - u64::MAX, + order, + TaoCurrency::MAX, false, true )); diff --git a/pallets/subtensor/src/tests/staking2.rs b/pallets/subtensor/src/tests/staking2.rs index f5d57d02d3..7d6cc6ad3c 100644 --- a/pallets/subtensor/src/tests/staking2.rs +++ b/pallets/subtensor/src/tests/staking2.rs @@ -38,7 +38,7 @@ fn test_stake_base_case() { SubtensorModule::swap_tao_for_alpha( netuid, tao_to_swap, - ::SwapInterface::max_price().into(), + ::SwapInterface::max_price(), false, ) .unwrap() @@ -698,8 +698,11 @@ fn test_stake_fee_api() { coldkey1, stake_amount, ); - let dynamic_fee_0 = - ::SwapInterface::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 = - ::SwapInterface::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 = - ::SwapInterface::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 = - ::SwapInterface::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 = - ::SwapInterface::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 = - ::SwapInterface::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 = - ::SwapInterface::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 = - ::SwapInterface::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 = - ::SwapInterface::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)); diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 59792d5602..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::{OrderType, SwapHandler}; +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 5991baf07a..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::SwapHandler; +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 bc9af5cf07..db7a8ee8d4 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -281,15 +281,13 @@ 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 = + ::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/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 4998bbe379..ae7d375f97 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -1,33 +1,46 @@ #![cfg_attr(not(feature = "std"), no_std)] +use core::ops::Neg; use frame_support::pallet_prelude::*; use substrate_fixed::types::U96F32; -use subtensor_runtime_common::{AlphaCurrency, NetUid, TaoCurrency}; +use subtensor_macros::freeze_struct; +use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum OrderType { - Sell, - Buy, -} +pub use order::*; + +mod order; -pub trait SwapHandler { +pub trait SwapEngine: DefaultPriceLimit { fn swap( netuid: NetUid, - order_t: OrderType, - amount: u64, - price_limit: u64, + order: O, + price_limit: TaoCurrency, + drop_fees: bool, + should_rollback: bool, + ) -> Result, DispatchError>; +} + +pub trait SwapHandler { + fn swap( + netuid: NetUid, + order: O, + price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result; - fn sim_swap( + ) -> Result, DispatchError> + where + Self: SwapEngine; + fn sim_swap( netuid: NetUid, - order_t: OrderType, - amount: u64, - ) -> Result; - fn approx_fee_amount(netuid: NetUid, amount: u64) -> u64; + order: O, + ) -> Result, DispatchError> + where + Self: SwapEngine; + + 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, @@ -39,12 +52,47 @@ pub trait SwapHandler { fn clear_protocol_liquidity(netuid: NetUid) -> DispatchResult; } -#[derive(Debug, PartialEq)] -pub struct SwapResult { - pub amount_paid_in: u64, - pub amount_paid_out: u64, - pub fee_paid: u64, - // For calculation of new tao/alpha reserves - pub tao_reserve_delta: i64, - pub alpha_reserve_delta: i64, +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 +where + PaidIn: Currency, + PaidOut: Currency, +{ + pub amount_paid_in: PaidIn, + pub amount_paid_out: PaidOut, + pub fee_paid: PaidIn, +} + +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_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/swap-interface/src/order.rs b/pallets/swap-interface/src/order.rs new file mode 100644 index 0000000000..1576283fd5 --- /dev/null +++ b/pallets/swap-interface/src/order.rs @@ -0,0 +1,87 @@ +use core::marker::PhantomData; + +use substrate_fixed::types::U64F64; +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: 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, Default)] +pub struct GetAlphaForTao +where + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, +{ + amount: TaoCurrency, + _phantom: PhantomData<(ReserveIn, ReserveOut)>, +} + +impl Order for GetAlphaForTao +where + ReserveIn: CurrencyReserve + Clone, + ReserveOut: CurrencyReserve + Clone, +{ + type PaidIn = TaoCurrency; + type PaidOut = AlphaCurrency; + type ReserveIn = ReserveIn; + type ReserveOut = ReserveOut; + + fn with_amount(amount: impl Into) -> Self { + Self { + amount: amount.into(), + _phantom: PhantomData, + } + } + + 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 + } +} + +#[derive(Clone, Default)] +pub struct GetTaoForAlpha +where + ReserveIn: CurrencyReserve, + ReserveOut: CurrencyReserve, +{ + amount: AlphaCurrency, + _phantom: PhantomData<(ReserveIn, ReserveOut)>, +} + +impl Order for GetTaoForAlpha +where + ReserveIn: CurrencyReserve + Clone, + ReserveOut: CurrencyReserve + Clone, +{ + type PaidIn = AlphaCurrency; + type PaidOut = TaoCurrency; + type ReserveIn = ReserveIn; + type ReserveOut = ReserveOut; + + fn with_amount(amount: impl Into) -> Self { + Self { + amount: amount.into(), + _phantom: PhantomData, + } + } + + 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/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/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/mock.rs b/pallets/swap/src/mock.rs index cbe71ec020..aacdf90835 100644 --- a/pallets/swap/src/mock.rs +++ b/pallets/swap/src/mock.rs @@ -14,9 +14,13 @@ use sp_runtime::{ BuildStorage, Vec, traits::{BlakeTwo256, IdentityLookup}, }; -use subtensor_runtime_common::{AlphaCurrency, BalanceOps, NetUid, SubnetInfo, TaoCurrency}; +use substrate_fixed::types::U64F64; +use subtensor_runtime_common::{ + 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 { @@ -83,11 +87,11 @@ parameter_types! { pub const MinimumReserves: NonZeroU64 = NonZeroU64::new(1).unwrap(); } -// Mock implementor of SubnetInfo trait -pub struct MockLiquidityProvider; +#[derive(Clone)] +pub 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 +100,15 @@ impl SubnetInfo for MockLiquidityProvider { .into() } - fn alpha_reserve(netuid: NetUid) -> AlphaCurrency { + fn increase_provided(_: NetUid, _: TaoCurrency) {} + fn decrease_provided(_: NetUid, _: TaoCurrency) {} +} + +#[derive(Clone)] +pub 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 +116,65 @@ impl SubnetInfo for MockLiquidityProvider { } } + fn increase_provided(_: NetUid, _: AlphaCurrency) {} + fn decrease_provided(_: NetUid, _: AlphaCurrency) {} +} + +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; +} + +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 +pub struct MockLiquidityProvider; + +impl SubnetInfo for MockLiquidityProvider { fn exists(netuid: NetUid) -> bool { netuid != NON_EXISTENT_NETUID.into() } @@ -197,15 +268,12 @@ 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 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 9a41283426..a5237369ee 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}; @@ -8,13 +7,16 @@ use sp_arithmetic::helpers_128bit; use sp_runtime::{DispatchResult, Vec, 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::{ + DefaultPriceLimit, Order as OrderT, SwapEngine, SwapHandler, SwapResult, }; -use subtensor_swap_interface::{SwapHandler, SwapResult}; use super::pallet::*; +use super::swap_step::{BasicSwapStep, SwapStep, SwapStepAction}; use crate::{ - OrderType, SqrtPrice, + SqrtPrice, position::{Position, PositionId}, tick::{ActiveTickIndexManager, Tick, TickIndex}, }; @@ -42,247 +44,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 { @@ -292,8 +53,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)) @@ -306,10 +67,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) { @@ -317,9 +74,10 @@ 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()); + // 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()); // Set price let price = U64F64::saturating_from_num(tao_reserve) @@ -372,7 +130,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 @@ -413,7 +171,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. @@ -425,25 +184,26 @@ 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( + pub(crate) fn do_swap( netuid: NetUid, - order_type: OrderType, - amount: u64, + order: Order, limit_sqrt_price: SqrtPrice, drop_fees: bool, simulate: bool, - ) -> Result { + ) -> Result, DispatchError> + where + Order: OrderT, + BasicSwapStep: SwapStep, + { 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 = Order::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, limit_sqrt_price, drop_fees) + .map_err(Into::into); if simulate || result.is_err() { // Simulation only @@ -453,13 +213,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,51 +225,40 @@ impl Pallet { }) } - fn swap_inner( + fn swap_inner( netuid: NetUid, - order_type: OrderType, - amount: u64, + order: Order, 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 + Order: OrderT, + BasicSwapStep: SwapStep, + { + ensure!( + Order::ReserveOut::reserve(netuid).to_u64() >= T::MinimumReserve::get().get(), + Error::::ReservesTooLow + ); 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_paid_out: u64 = 0; + let mut amount_remaining = order.amount(); + let mut amount_paid_out = Order::PaidOut::ZERO; let mut iteration_counter: u16 = 0; - let mut in_acc: u64 = 0; - let mut fee_acc: u64 = 0; + let mut in_acc = Order::PaidIn::ZERO; + let mut fee_acc = Order::PaidIn::ZERO; log::trace!("======== Start Swap ========"); 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: {}", @@ -520,9 +266,8 @@ impl Pallet { ); // Create and execute a swap step - let mut swap_step = SwapStep::::new( + let mut swap_step = BasicSwapStep::::new( netuid, - order_type, amount_remaining, limit_sqrt_price, drop_fees, @@ -535,13 +280,13 @@ impl Pallet { 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 { - amount_remaining = 0; + if swap_step.action() == SwapStepAction::Stop { + amount_remaining = Order::PaidIn::ZERO; } // The swap step didn't exchange anything - if swap_result.amount_to_take == 0 { - amount_remaining = 0; + if swap_result.amount_to_take.is_zero() { + amount_remaining = Order::PaidIn::ZERO; } iteration_counter = iteration_counter.saturating_add(1); @@ -555,235 +300,38 @@ 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 i64, (amount_paid_out as i64).neg()), - OrderType::Sell => ((amount_paid_out as i64).neg(), in_acc as i64), - }; - Ok(SwapResult { amount_paid_in: in_acc, amount_paid_out, fee_paid: fee_acc, - tao_reserve_delta, - alpha_reserve_delta, }) } - /// 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, - } + return C::ZERO; } - } - - /// 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) + 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() } - }; - - 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()), - OrderType::Buy => SqrtPrice::saturating_from_num(Self::max_price()), - }; - } - - 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), + _ => 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) - } - } - .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 { ActiveTickIndexManager::::find_closest_lower(netuid, index) .and_then(|ti| Ticks::::get(netuid, ti)) @@ -795,7 +343,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()), ) @@ -906,7 +454,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); @@ -985,7 +533,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() @@ -1213,6 +761,23 @@ impl Pallet { 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() + } + /// Dissolve all LPs and clean state. pub fn do_dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult { if SwapV3Initialized::::get(netuid) { @@ -1269,7 +834,7 @@ impl Pallet { if rm.tao > TaoCurrency::ZERO { T::BalanceOps::increase_balance(&owner, rm.tao); user_refunded_tao = user_refunded_tao.saturating_add(rm.tao); - T::BalanceOps::decrease_provided_tao_reserve(netuid, rm.tao); + T::TaoReserve::decrease_provided(netuid, rm.tao); } // 2) Stake ALL withdrawn α (principal + fees) to the best permitted validator. @@ -1303,10 +868,7 @@ impl Pallet { ); } - T::BalanceOps::decrease_provided_alpha_reserve( - netuid, - alpha_total_from_pool, - ); + T::AlphaReserve::decrease_provided(netuid, alpha_total_from_pool); } } Err(e) => { @@ -1407,83 +969,106 @@ impl Pallet { } } -impl SwapHandler for 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 SwapEngine for Pallet +where + T: Config, + O: OrderT, + BasicSwapStep: SwapStep, + Self: DefaultPriceLimit, +{ fn swap( netuid: NetUid, - order_t: OrderType, - amount: u64, - price_limit: u64, + order: O, + price_limit: TaoCurrency, drop_fees: bool, should_rollback: bool, - ) -> Result { - let limit_sqrt_price = SqrtPrice::saturating_from_num(price_limit) + ) -> 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_t, - amount, + order, limit_sqrt_price, drop_fees, should_rollback, ) .map_err(Into::into) } +} + +impl SwapHandler for Pallet { + fn swap( + netuid: NetUid, + 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( + fn sim_swap( netuid: NetUid, - order_t: OrderType, - amount: u64, - ) -> Result { + order: O, + ) -> Result, DispatchError> + where + O: OrderT, + Self: SwapEngine, + { match T::SubnetInfo::mechanism(netuid) { 1 => { - let price_limit = match order_t { - OrderType::Buy => Self::max_price(), - OrderType::Sell => Self::min_price(), - }; + let price_limit = Self::default_price_limit::(); - Self::swap(netuid, order_t, amount, price_limit, false, true) + >::swap(netuid, order, price_limit, false, true) } _ => { let actual_amount = if T::SubnetInfo::exists(netuid) { - amount + order.amount() } else { - 0 + O::PaidIn::ZERO }; Ok(SwapResult { amount_paid_in: actual_amount, - amount_paid_out: actual_amount, - fee_paid: 0, - tao_reserve_delta: 0, - alpha_reserve_delta: 0, + amount_paid_out: actual_amount.to_u64().into(), + fee_paid: 0.into(), }) } } } - 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, false) } fn current_alpha_price(netuid: NetUid) -> U96F32 { Self::current_price(netuid.into()) } - fn min_price() -> u64 { - TickIndex::min_sqrt_price() - .saturating_mul(TickIndex::min_sqrt_price()) - .saturating_mul(SqrtPrice::saturating_from_num(1_000_000_000)) - .saturating_to_num() + fn min_price() -> C { + Self::min_price_inner() } - fn max_price() -> u64 { - 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() + fn max_price() -> C { + Self::max_price_inner() } fn adjust_protocol_liquidity( @@ -1507,17 +1092,3 @@ impl SwapHandler for Pallet { Self::do_clear_protocol_liquidity(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 af4cbdc3cb..c8df6f262d 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::{ @@ -17,6 +17,7 @@ use crate::{ pub use pallet::*; mod impls; +mod swap_step; #[cfg(test)] mod tests; @@ -36,6 +37,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; @@ -391,8 +398,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 { @@ -447,8 +454,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 { diff --git a/pallets/swap/src/pallet/swap_step.rs b/pallets/swap/src/pallet/swap_step.rs new file mode 100644 index 0000000000..6791835b1a --- /dev/null +++ b/pallets/swap/src/pallet/swap_step.rs @@ -0,0 +1,562 @@ +use core::marker::PhantomData; + +use safe_math::*; +use substrate_fixed::types::{I64F64, U64F64}; +use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; + +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, +{ + // Input parameters + netuid: NetUid, + 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: PhantomData<(T, PaidIn, PaidOut)>, +} + +impl BasicSwapStep +where + T: Config, + PaidIn: Currency, + PaidOut: Currency, + Self: SwapStep, +{ + /// Creates and initializes a new swap step + pub(crate) fn new( + netuid: NetUid, + 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, + 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, + } + } + + /// 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, + }) + } + + pub(crate) fn action(&self) -> SwapStepAction { + self.action + } +} + +impl SwapStep + for BasicSwapStep +{ + 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 +{ + 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, +{ + /// 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, +{ + 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)] +pub enum SwapStepAction { + Crossing, + Stop, +} diff --git a/pallets/swap/src/pallet/tests.rs b/pallets/swap/src/pallet/tests.rs index 72c33d698f..88172c66ac 100644 --- a/pallets/swap/src/pallet/tests.rs +++ b/pallets/swap/src/pallet/tests.rs @@ -8,9 +8,11 @@ use sp_arithmetic::helpers_128bit; use sp_runtime::DispatchError; use substrate_fixed::types::U96F32; use subtensor_runtime_common::NetUid; +use subtensor_swap_interface::Order as OrderT; use super::*; -use crate::{OrderType, SqrtPrice, mock::*}; +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 // testing, all the implementation logic is based on sqrt prices @@ -153,8 +155,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,15 +666,8 @@ 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( - netuid, - OrderType::Buy, - liquidity / 10, - sqrt_limit_price, - false, - false, - ) - .unwrap(); + 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) let liquidity_before = CurrentLiquidity::::get(netuid); @@ -754,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(), + GetAlphaForTao::with_amount(1_000), + 1000.0, + 3990, + true, + ); + perform_test( + 2.into(), + GetTaoForAlpha::with_amount(1_000), + 0.0001, + 250, + false, + ); + perform_test( + 3.into(), + GetAlphaForTao::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; @@ -832,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 @@ -945,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!( + GetAlphaForTao, + price_low_offset, + price_high_offset, + position_liquidity, + liquidity_fraction, + 1000.0_f64, + true + ); + perform_test!( + GetTaoForAlpha, + price_low_offset, + price_high_offset, + position_liquidity, + liquidity_fraction, + 0.0001_f64, + false + ); + } }, ); } @@ -1209,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!(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 - 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!( @@ -1320,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 = 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(); @@ -1331,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); }); } @@ -1412,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() ); } } @@ -1533,8 +1502,7 @@ fn test_swap_fee_correctness() { // Swap buy and swap sell Pallet::::do_swap( netuid, - OrderType::Buy, - liquidity / 10, + GetAlphaForTao::with_amount(liquidity / 10), u64::MAX.into(), false, false, @@ -1542,8 +1510,7 @@ fn test_swap_fee_correctness() { .unwrap(); Pallet::::do_swap( netuid, - OrderType::Sell, - liquidity / 10, + GetTaoForAlpha::with_amount(liquidity / 10), 0_u64.into(), false, false, @@ -1640,8 +1607,7 @@ fn test_rollback_works() { assert_eq!( Pallet::::do_swap( netuid, - OrderType::Buy, - 1_000_000, + GetAlphaForTao::with_amount(1_000_000), u64::MAX.into(), false, true @@ -1649,8 +1615,7 @@ fn test_rollback_works() { .unwrap(), Pallet::::do_swap( netuid, - OrderType::Buy, - 1_000_000, + GetAlphaForTao::with_amount(1_000_000), u64::MAX.into(), false, false @@ -1694,8 +1659,7 @@ fn test_new_lp_doesnt_get_old_fees() { // Swap buy and swap sell Pallet::::do_swap( netuid, - OrderType::Buy, - liquidity / 10, + GetAlphaForTao::with_amount(liquidity / 10), u64::MAX.into(), false, false, @@ -1703,8 +1667,7 @@ fn test_new_lp_doesnt_get_old_fees() { .unwrap(); Pallet::::do_swap( netuid, - OrderType::Sell, - liquidity / 10, + GetTaoForAlpha::with_amount(liquidity / 10), 0_u64.into(), false, false, @@ -1747,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}"); } @@ -1775,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 = GetTaoForAlpha::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 = GetAlphaForTao::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); @@ -1802,14 +1761,12 @@ fn test_wrapping_fees() { ) .unwrap(); - let swap_amt = 1_800_000_000_u64; - let order_type = OrderType::Sell; + let order = GetTaoForAlpha::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); @@ -1875,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, + GetAlphaForTao::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!(GetAlphaForTao, provided_liquidity, 1000.0_f64, true); + } + for provided_liquidity in [0, 1_000_000_000_000_u64] { + perform_test!(GetTaoForAlpha, provided_liquidity, 0.001_f64, false); + } } #[test] @@ -2019,8 +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, - OrderType::Buy, - 1_000_000, + GetAlphaForTao::with_amount(1_000_000), sqrt_limit_price, false, false @@ -2320,8 +2272,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); // Users‑only liquidation. assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); @@ -2387,7 +2339,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: users‑only dissolve. assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); @@ -2458,12 +2410,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)); @@ -2520,12 +2472,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)); @@ -2615,8 +2567,8 @@ fn test_dissolve_v3_green_path_refund_tao_stake_alpha_and_clear_state() { ) .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); // --- Act: dissolve (GREEN PATH: permitted validators exist) --- assert_ok!(Pallet::::do_dissolve_all_liquidity_providers(netuid)); diff --git a/pallets/transaction-fee/src/lib.rs b/pallets/transaction-fee/src/lib.rs index 42ba69c841..89326457f6 100644 --- a/pallets/transaction-fee/src/lib.rs +++ b/pallets/transaction-fee/src/lib.rs @@ -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 0f690bfd82..de5d13e346 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}; @@ -406,6 +406,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; @@ -611,10 +613,10 @@ pub(crate) fn swap_alpha_to_tao_ext( return (alpha.into(), 0); } + let order = GetTaoForAlpha::::with_amount(alpha); let result = ::SwapInterface::swap( netuid.into(), - OrderType::Sell, - alpha.into(), + order, ::SwapInterface::min_price(), drop_fees, true, @@ -624,10 +626,10 @@ 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); + // 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) + (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 a05e97cda1..c9c37a1e79 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -68,7 +68,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::{ @@ -1305,6 +1305,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; @@ -2493,10 +2495,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 { @@ -2515,10 +2517,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 {