diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index c7a28502aa..481e42f384 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -2432,6 +2432,14 @@ impl> SubnetAlphaIn::::get(netuid) } + fn subnet_exist(netuid: u16) -> bool { + Self::if_subnet_exist(netuid) + } +} + +impl> + subtensor_swap_interface::BalanceOps for Pallet +{ fn tao_balance(account_id: &T::AccountId) -> u64 { pallet_balances::Pallet::::free_balance(account_id) } @@ -2439,4 +2447,44 @@ impl> fn alpha_balance(netuid: u16, coldkey: &T::AccountId, hotkey: &T::AccountId) -> u64 { Self::get_stake_for_hotkey_and_coldkey_on_subnet(hotkey, coldkey, netuid) } + + fn increase_balance(coldkey: &T::AccountId, tao: u64) { + Self::add_balance_to_coldkey_account(&coldkey, tao) + } + + fn decrease_balance(coldkey: &T::AccountId, tao: u64) -> Result { + Self::remove_balance_from_coldkey_account(&coldkey, tao) + } + + fn increase_stake( + coldkey: &T::AccountId, + hotkey: &T::AccountId, + netuid: u16, + alpha: u64, + ) -> Result<(), DispatchError> { + ensure!( + Self::hotkey_account_exists(&hotkey), + Error::::HotKeyAccountNotExists + ); + + Self::increase_stake_for_hotkey_and_coldkey_on_subnet(&hotkey, &coldkey, netuid, alpha); + + Ok(()) + } + + fn decrease_stake( + coldkey: &T::AccountId, + hotkey: &T::AccountId, + netuid: u16, + alpha: u64, + ) -> Result { + ensure!( + Self::hotkey_account_exists(&hotkey), + Error::::HotKeyAccountNotExists + ); + + Ok(Self::decrease_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, alpha, + )) + } } diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index fcc086eb58..7a25cf2b4f 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2023,232 +2023,5 @@ mod dispatches { ) -> DispatchResult { Self::do_burn_alpha(origin, hotkey, amount, netuid) } - - /// Add liquidity to a specific price range for a subnet. - /// - /// Parameters: - /// - origin: The origin of the transaction - /// - netuid: Subnet ID - /// - tick_low: Lower bound of the price range - /// - tick_high: Upper bound of the price range - /// - liquidity: Amount of liquidity to add - /// - /// Emits `Event::LiquidityAdded` on success - #[pallet::call_index(103)] - #[pallet::weight(( - Weight::from_parts(50_000_000, 0) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)), - DispatchClass::Operational, - Pays::Yes - ))] - pub fn add_liquidity( - origin: OriginFor, - hotkey: T::AccountId, - netuid: u16, - tick_low: i32, - tick_high: i32, - liquidity: u64, - ) -> DispatchResult { - let coldkey = ensure_signed(origin)?; - - // Ensure that the subnet exists. - ensure!( - Self::if_subnet_exist(netuid), - Error::::SubNetworkDoesNotExist - ); - - let (position_id, tao, alpha) = T::SwapInterface::add_liquidity( - netuid, &coldkey, &hotkey, tick_low, tick_high, liquidity, - )?; - - // Remove TAO and Alpha balances or fail transaction if they can't be removed exactly - let tao_provided = Self::remove_balance_from_coldkey_account(&coldkey, tao)?; - ensure!(tao_provided == tao, Error::::InsufficientBalance); - - let alpha_provided = Self::decrease_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, &coldkey, netuid, alpha, - ); - ensure!(alpha_provided == alpha, Error::::InsufficientBalance); - - // Emit an event - Self::deposit_event(Event::LiquidityAdded { - coldkey, - hotkey, - netuid, - position_id, - liquidity, - tao, - alpha, - }); - - Ok(()) - } - - /// Remove liquidity from a specific position. - /// - /// Parameters: - /// - origin: The origin of the transaction - /// - netuid: Subnet ID - /// - position_id: ID of the position to remove - /// - /// Emits `Event::LiquidityRemoved` on success - #[pallet::call_index(104)] - #[pallet::weight(( - Weight::from_parts(50_000_000, 0) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)), - DispatchClass::Operational, - Pays::Yes - ))] - pub fn remove_liquidity( - origin: OriginFor, - hotkey: T::AccountId, - netuid: u16, - position_id: u128, - ) -> DispatchResult { - let coldkey = ensure_signed(origin)?; - - // Ensure that the subnet exists. - ensure!( - Self::if_subnet_exist(netuid), - Error::::SubNetworkDoesNotExist - ); - - // Ensure the hotkey account exists - ensure!( - Self::hotkey_account_exists(&hotkey), - Error::::HotKeyAccountNotExists - ); - - // Remove liquidity - let result = T::SwapInterface::remove_liquidity(netuid, &coldkey, position_id)?; - - // Credit the returned tao and alpha to the account - Self::add_balance_to_coldkey_account( - &coldkey, - result.tao.saturating_add(result.fee_tao), - ); - Self::increase_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &coldkey, - netuid, - result.alpha.saturating_add(result.fee_alpha), - ); - - // Emit an event - Self::deposit_event(Event::LiquidityRemoved { - coldkey, - netuid: netuid.into(), - position_id, - tao: result.tao, - alpha: result.alpha, - fee_tao: result.fee_tao, - fee_alpha: result.fee_alpha, - }); - - Ok(()) - } - - /// Modify a liquidity position. - /// - /// Parameters: - /// - origin: The origin of the transaction - /// - netuid: Subnet ID - /// - position_id: ID of the position to remove - /// - liquidity_delta: Liquidity to add (if positive) or remove (if negative) - /// - /// Emits `Event::LiquidityRemoved` on success - #[pallet::call_index(105)] - #[pallet::weight(( - Weight::from_parts(50_000_000, 0) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)), - DispatchClass::Operational, - Pays::Yes - ))] - pub fn modify_position( - origin: OriginFor, - hotkey: T::AccountId, - netuid: u16, - position_id: u128, - liquidity_delta: i64, - ) -> DispatchResult { - let coldkey = ensure_signed(origin)?; - - // Ensure that the subnet exists. - ensure!( - Self::if_subnet_exist(netuid), - Error::::SubNetworkDoesNotExist - ); - - // Ensure the hotkey account exists - ensure!( - Self::hotkey_account_exists(&hotkey), - Error::::HotKeyAccountNotExists - ); - - // Add or remove liquidity - let result = T::SwapInterface::modify_position( - netuid, - &coldkey, - &hotkey, - position_id, - liquidity_delta, - )?; - - if liquidity_delta > 0 { - // Remove TAO and Alpha balances or fail transaction if they can't be removed exactly - let tao_provided = Self::remove_balance_from_coldkey_account(&coldkey, result.tao)?; - ensure!(tao_provided == result.tao, Error::::InsufficientBalance); - - let alpha_provided = Self::decrease_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &coldkey, - netuid, - result.alpha, - ); - ensure!( - alpha_provided == result.alpha, - Error::::InsufficientBalance - ); - - // Emit an event - Self::deposit_event(Event::LiquidityAdded { - coldkey, - hotkey, - netuid, - position_id, - liquidity: liquidity_delta as u64, - tao: result.tao, - alpha: result.alpha, - }); - } else { - // Credit the returned tao and alpha to the account - Self::add_balance_to_coldkey_account( - &coldkey, - result.tao.saturating_add(result.fee_tao), - ); - Self::increase_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, - &coldkey, - netuid, - result.alpha.saturating_add(result.fee_alpha), - ); - - // Emit an event - Self::deposit_event(Event::LiquidityRemoved { - coldkey, - netuid: netuid.into(), - position_id, - tao: result.tao, - alpha: result.alpha, - fee_tao: result.fee_tao, - fee_alpha: result.fee_alpha, - }); - } - - Ok(()) - } - } + } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 90d68ed8ea..8c2e863d0e 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -323,41 +323,5 @@ mod events { /// - **netuid**: The network identifier. /// - **Enabled**: Is Commit-Reveal enabled. CommitRevealEnabled(u16, bool), - - /// Event emitted when liquidity is added to a subnet's liquidity pool. - LiquidityAdded { - /// The coldkey account that owns the position - coldkey: T::AccountId, - /// The hotkey account associated with the position - hotkey: T::AccountId, - /// The subnet identifier - netuid: u16, - /// Unique identifier for the liquidity position - position_id: u128, - /// The amount of liquidity added to the position - liquidity: u64, - /// The amount of TAO tokens committed to the position - tao: u64, - /// The amount of Alpha tokens committed to the position - alpha: u64, - }, - - /// Event emitted when liquidity is removed from a subnet's liquidity pool. - LiquidityRemoved { - /// The coldkey account that owns the position - coldkey: T::AccountId, - /// The subnet identifier - netuid: u16, - /// Unique identifier for the liquidity position - position_id: u128, - /// The amount of TAO tokens returned to the user - tao: u64, - /// The amount of Alpha tokens returned to the user - alpha: u64, - /// The amount of TAO fees earned from the position - fee_tao: u64, - /// The amount of Alpha fees earned from the position - fee_alpha: u64, - }, } } diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index cc6753b552..7e5c437e43 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -432,6 +432,7 @@ impl pallet_subtensor_swap::Config for Test { type RuntimeEvent = RuntimeEvent; type AdminOrigin = EnsureRoot; type LiquidityDataProvider = SubtensorModule; + type BalanceOps = SubtensorModule; type ProtocolId = SwapProtocolId; type MaxFeeRate = SwapMaxFeeRate; type MaxPositions = SwapMaxPositions; diff --git a/pallets/swap-interface/src/lib.rs b/pallets/swap-interface/src/lib.rs index 264219e17f..9d3ca776d5 100644 --- a/pallets/swap-interface/src/lib.rs +++ b/pallets/swap-interface/src/lib.rs @@ -17,26 +17,6 @@ pub trait SwapHandler { price_limit: u64, should_rollback: bool, ) -> Result; - fn add_liquidity( - netuid: u16, - coldkey_account_id: &AccountId, - hotkey_account_id: &AccountId, - tick_low: i32, - tick_high: i32, - liquidity: u64, - ) -> Result<(u128, u64, u64), DispatchError>; - fn remove_liquidity( - netuid: u16, - coldkey_account_id: &AccountId, - position_id: u128, - ) -> Result; - fn modify_position( - netuid: u16, - coldkey_account_id: &AccountId, - hotkey_account_id: &AccountId, - position_id: u128, - liquidity_delta: i64, - ) -> Result; fn approx_fee_amount(netuid: u16, amount: u64) -> u64; fn current_alpha_price(netuid: u16) -> U96F32; fn max_price() -> u64; @@ -63,10 +43,24 @@ pub struct UpdateLiquidityResult { pub trait LiquidityDataProvider { fn tao_reserve(netuid: u16) -> u64; fn alpha_reserve(netuid: u16) -> u64; + fn subnet_exist(netuid: u16) -> bool; +} + +pub trait BalanceOps { fn tao_balance(account_id: &AccountId) -> u64; - fn alpha_balance( + fn alpha_balance(netuid: u16, coldkey: &AccountId, hotkey: &AccountId) -> u64; + fn increase_balance(coldkey: &AccountId, tao: u64); + fn decrease_balance(coldkey: &AccountId, tao: u64) -> Result; + fn increase_stake( + coldkey: &AccountId, + hotkey: &AccountId, + netuid: u16, + alpha: u64, + ) -> Result<(), DispatchError>; + fn decrease_stake( + coldkey: &AccountId, + hotkey: &AccountId, netuid: u16, - coldkey_account_id: &AccountId, - hotkey_account_id: &AccountId, - ) -> u64; + alpha: u64, + ) -> Result; } diff --git a/pallets/swap/src/mock.rs b/pallets/swap/src/mock.rs index 5ad5362e35..4fe12926a4 100644 --- a/pallets/swap/src/mock.rs +++ b/pallets/swap/src/mock.rs @@ -1,6 +1,7 @@ use core::num::NonZeroU64; use frame_support::construct_runtime; +use frame_support::pallet_prelude::*; use frame_support::{ PalletId, parameter_types, traits::{ConstU32, Everything}, @@ -11,7 +12,7 @@ use sp_runtime::{ BuildStorage, traits::{BlakeTwo256, IdentityLookup}, }; -use subtensor_swap_interface::LiquidityDataProvider; +use subtensor_swap_interface::{BalanceOps, LiquidityDataProvider}; construct_runtime!( pub enum Test { @@ -88,6 +89,14 @@ impl LiquidityDataProvider for MockLiquidityProvider { } } + fn subnet_exist(_netuid: u16) -> bool { + true + } +} + +pub struct MockBalanceOps; + +impl BalanceOps for MockBalanceOps { fn tao_balance(account_id: &AccountId) -> u64 { if *account_id == OK_COLDKEY_ACCOUNT_ID { 100_000_000_000_000 @@ -105,12 +114,34 @@ impl LiquidityDataProvider for MockLiquidityProvider { 1_000_000_000 } } + + fn increase_balance(_coldkey: &AccountId, _tao: u64) {} + fn decrease_balance(_coldkey: &AccountId, _tao: u64) -> Result { + Ok(0) + } + fn increase_stake( + _coldkey: &AccountId, + _hotkey: &AccountId, + _netuid: u16, + _alpha: u64, + ) -> Result<(), DispatchError> { + Ok(()) + } + fn decrease_stake( + _coldkey: &AccountId, + _hotkey: &AccountId, + _netuid: u16, + _alpha: u64, + ) -> Result { + Ok(0) + } } impl crate::pallet::Config for Test { type RuntimeEvent = RuntimeEvent; type AdminOrigin = EnsureRoot; type LiquidityDataProvider = MockLiquidityProvider; + type BalanceOps = MockBalanceOps; type ProtocolId = SwapProtocolId; type MaxFeeRate = MaxFeeRate; type MaxPositions = MaxPositions; diff --git a/pallets/swap/src/pallet/impls.rs b/pallets/swap/src/pallet/impls.rs index 15dd1090e7..b1078d2565 100644 --- a/pallets/swap/src/pallet/impls.rs +++ b/pallets/swap/src/pallet/impls.rs @@ -7,7 +7,7 @@ use sp_arithmetic::helpers_128bit; use sp_runtime::traits::AccountIdConversion; use substrate_fixed::types::{U64F64, U96F32}; use subtensor_swap_interface::{ - LiquidityDataProvider, SwapHandler, SwapResult, UpdateLiquidityResult, + BalanceOps, LiquidityDataProvider, SwapHandler, SwapResult, UpdateLiquidityResult, }; use super::pallet::*; @@ -104,13 +104,17 @@ impl SwapStep { // 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.sqrt_price_target, &self.sqrt_price_limit) && self.price_is_closer(&self.sqrt_price_target, &self.sqrt_price_edge) { + if self.price_is_closer(&self.sqrt_price_target, &self.sqrt_price_limit) + && self.price_is_closer(&self.sqrt_price_target, &self.sqrt_price_edge) + { // 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.sqrt_price_target; self.delta_in = self.possible_delta_in; - } else if self.price_is_closer(&self.sqrt_price_limit, &self.sqrt_price_target) && self.price_is_closer(&self.sqrt_price_limit, &self.sqrt_price_edge) { + } else if self.price_is_closer(&self.sqrt_price_limit, &self.sqrt_price_target) + && self.price_is_closer(&self.sqrt_price_limit, &self.sqrt_price_edge) + { // Case 2. lim_quantity is the lowest // The trade also completely happens within one tick, no tick crossing happens. self.action = SwapStepAction::Stop; @@ -137,11 +141,12 @@ impl SwapStep { // 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.sqrt_price_limit, &self.sqrt_price_target) { - self.sqrt_price_limit - } else { - self.sqrt_price_target - }; + let natural_reason_stop_price = + if self.price_is_closer(&self.sqrt_price_limit, &self.sqrt_price_target) { + self.sqrt_price_limit + } else { + self.sqrt_price_target + }; if natural_reason_stop_price == self.sqrt_price_edge { self.action = match self.order_type { OrderType::Buy => SwapStepAction::Crossing, @@ -404,7 +409,9 @@ impl Pallet { let current_price_tick = TickIndex::try_from_sqrt_price(current_price).unwrap_or(fallback_tick); - let roundtrip_current_price = current_price_tick.try_to_sqrt_price().unwrap_or(SqrtPrice::from_num(0)); + let roundtrip_current_price = current_price_tick + .try_to_sqrt_price() + .unwrap_or(SqrtPrice::from_num(0)); (match order_type { OrderType::Buy => { @@ -418,11 +425,16 @@ impl Pallet { } } OrderType::Sell => { - let mut lower_tick = ActiveTickIndexManager::find_closest_lower::(netuid, current_price_tick) - .unwrap_or(TickIndex::MIN); + let mut lower_tick = + ActiveTickIndexManager::find_closest_lower::(netuid, current_price_tick) + .unwrap_or(TickIndex::MIN); if current_price == roundtrip_current_price { - lower_tick = ActiveTickIndexManager::find_closest_lower::(netuid, lower_tick.prev().unwrap_or(TickIndex::MIN)).unwrap_or(TickIndex::MIN); + lower_tick = ActiveTickIndexManager::find_closest_lower::( + netuid, + lower_tick.prev().unwrap_or(TickIndex::MIN), + ) + .unwrap_or(TickIndex::MIN); } lower_tick } @@ -626,7 +638,7 @@ impl Pallet { /// - [`SwapError::InsufficientBalance`] if the account does not have enough balance. /// - [`SwapError::InvalidTickRange`] if `tick_low` is greater than or equal to `tick_high`. /// - Other [`SwapError`] variants as applicable. - pub fn add_liquidity( + pub fn do_add_liquidity( netuid: NetUid, coldkey_account_id: &T::AccountId, hotkey_account_id: &T::AccountId, @@ -644,8 +656,8 @@ impl Pallet { let position_id = position.id; ensure!( - T::LiquidityDataProvider::tao_balance(coldkey_account_id) >= tao - && T::LiquidityDataProvider::alpha_balance( + T::BalanceOps::tao_balance(coldkey_account_id) >= tao + && T::BalanceOps::alpha_balance( netuid.into(), coldkey_account_id, hotkey_account_id @@ -729,7 +741,7 @@ impl Pallet { /// Remove liquidity and credit balances back to (coldkey_account_id, hotkey_account_id) stake /// /// Account ID and Position ID identify position in the storage map - pub fn remove_liquidity( + pub fn do_remove_liquidity( netuid: NetUid, coldkey_account_id: &T::AccountId, position_id: PositionId, @@ -779,7 +791,7 @@ impl Pallet { }) } - pub fn modify_position( + pub fn do_modify_position( netuid: NetUid, coldkey_account_id: &T::AccountId, hotkey_account_id: &T::AccountId, @@ -836,8 +848,8 @@ impl Pallet { if liquidity_delta > 0 { // Check that user has enough balances ensure!( - T::LiquidityDataProvider::tao_balance(coldkey_account_id) >= tao - && T::LiquidityDataProvider::alpha_balance( + T::BalanceOps::tao_balance(coldkey_account_id) >= tao + && T::BalanceOps::alpha_balance( netuid.into(), coldkey_account_id, hotkey_account_id @@ -1030,55 +1042,6 @@ impl SwapHandler for Pallet { .map_err(Into::into) } - fn add_liquidity( - netuid: u16, - coldkey_account_id: &T::AccountId, - hotkey_account_id: &T::AccountId, - tick_low: i32, - tick_high: i32, - liquidity: u64, - ) -> Result<(u128, u64, u64), DispatchError> { - let tick_low = TickIndex::new(tick_low).map_err(|_| Error::::InvalidTickRange)?; - let tick_high = TickIndex::new(tick_high).map_err(|_| Error::::InvalidTickRange)?; - - Self::add_liquidity( - NetUid::from(netuid), - coldkey_account_id, - hotkey_account_id, - tick_low, - tick_high, - liquidity, - ) - .map(|(pid, t, a)| (pid.into(), t, a)) - .map_err(Into::into) - } - - fn remove_liquidity( - netuid: u16, - coldkey_account_id: &T::AccountId, - position_id: u128, - ) -> Result { - Self::remove_liquidity(netuid.into(), coldkey_account_id, position_id.into()) - .map_err(Into::into) - } - - fn modify_position( - netuid: u16, - coldkey_account_id: &T::AccountId, - hotkey_account_id: &T::AccountId, - position_id: u128, - liquidity_delta: i64, - ) -> Result { - Self::modify_position( - netuid.into(), - coldkey_account_id, - hotkey_account_id, - position_id.into(), - liquidity_delta, - ) - .map_err(Into::into) - } - fn approx_fee_amount(netuid: u16, amount: u64) -> u64 { Self::calculate_fee_amount(netuid.into(), amount) } @@ -1332,7 +1295,7 @@ mod tests { let liquidity_before = CurrentLiquidity::::get(netuid); // Add liquidity - let (position_id, tao, alpha) = Pallet::::add_liquidity( + let (position_id, tao, alpha) = Pallet::::do_add_liquidity( netuid, &OK_COLDKEY_ACCOUNT_ID, &OK_HOTKEY_ACCOUNT_ID, @@ -1434,7 +1397,7 @@ mod tests { // Add liquidity assert_err!( - Swap::add_liquidity( + Swap::do_add_liquidity( netuid, &OK_COLDKEY_ACCOUNT_ID, &OK_HOTKEY_ACCOUNT_ID, @@ -1474,7 +1437,7 @@ mod tests { // Add liquidity assert_err!( - Pallet::::add_liquidity( + Pallet::::do_add_liquidity( netuid, &coldkey_account_id, &hotkey_account_id, @@ -1547,7 +1510,7 @@ mod tests { let liquidity_before = CurrentLiquidity::::get(netuid); // Add liquidity - let (position_id, _, _) = Pallet::::add_liquidity( + let (position_id, _, _) = Pallet::::do_add_liquidity( netuid, &OK_COLDKEY_ACCOUNT_ID, &OK_HOTKEY_ACCOUNT_ID, @@ -1559,7 +1522,7 @@ mod tests { // Remove liquidity let remove_result = - Pallet::::remove_liquidity(netuid, &OK_COLDKEY_ACCOUNT_ID, position_id) + Pallet::::do_remove_liquidity(netuid, &OK_COLDKEY_ACCOUNT_ID, position_id) .unwrap(); assert_abs_diff_eq!(remove_result.tao, tao, epsilon = tao / 1000); assert_abs_diff_eq!(remove_result.alpha, alpha, epsilon = alpha / 1000); @@ -1600,7 +1563,7 @@ mod tests { assert_ok!(Pallet::::maybe_initialize_v3(netuid)); // Add liquidity - assert_ok!(Pallet::::add_liquidity( + assert_ok!(Pallet::::do_add_liquidity( netuid, &OK_COLDKEY_ACCOUNT_ID, &OK_HOTKEY_ACCOUNT_ID, @@ -1613,7 +1576,7 @@ mod tests { // Remove liquidity assert_err!( - Pallet::::remove_liquidity( + Pallet::::do_remove_liquidity( netuid, &OK_COLDKEY_ACCOUNT_ID, PositionId::new::() @@ -1656,7 +1619,7 @@ mod tests { assert_ok!(Pallet::::maybe_initialize_v3(netuid)); // Add liquidity - let (position_id, _, _) = Pallet::::add_liquidity( + let (position_id, _, _) = Pallet::::do_add_liquidity( netuid, &OK_COLDKEY_ACCOUNT_ID, &OK_HOTKEY_ACCOUNT_ID, @@ -1679,7 +1642,7 @@ mod tests { // Modify liquidity (also causes claiming of fees) let liquidity_before = CurrentLiquidity::::get(netuid); - let modify_result = Pallet::::modify_position( + let modify_result = Pallet::::do_modify_position( netuid, &OK_COLDKEY_ACCOUNT_ID, &OK_HOTKEY_ACCOUNT_ID, @@ -1708,7 +1671,7 @@ mod tests { assert_eq!(position.tick_high, tick_high); // Modify liquidity again (ensure fees aren't double-collected) - let modify_result = Pallet::::modify_position( + let modify_result = Pallet::::do_modify_position( netuid, &OK_COLDKEY_ACCOUNT_ID, &OK_HOTKEY_ACCOUNT_ID, @@ -1964,7 +1927,7 @@ mod tests { 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::::add_liquidity( + let (_position_id, _tao, _alpha) = Pallet::::do_add_liquidity( netuid, &OK_COLDKEY_ACCOUNT_ID, &OK_HOTKEY_ACCOUNT_ID, @@ -2197,7 +2160,7 @@ mod tests { 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::::add_liquidity( + let (_position_id, _tao, _alpha) = Pallet::::do_add_liquidity( netuid, &OK_COLDKEY_ACCOUNT_ID, &OK_HOTKEY_ACCOUNT_ID, diff --git a/pallets/swap/src/pallet/mod.rs b/pallets/swap/src/pallet/mod.rs index 3a44cb4601..bcf7e16f00 100644 --- a/pallets/swap/src/pallet/mod.rs +++ b/pallets/swap/src/pallet/mod.rs @@ -3,7 +3,7 @@ use core::num::NonZeroU64; use frame_support::{PalletId, pallet_prelude::*, traits::Get}; use frame_system::pallet_prelude::*; use substrate_fixed::types::U64F64; -use subtensor_swap_interface::LiquidityDataProvider; +use subtensor_swap_interface::{BalanceOps, LiquidityDataProvider}; use crate::{ NetUid, @@ -37,6 +37,10 @@ mod pallet { /// [`LiquidityDataProvider`](subtensor_swap_interface::LiquidityDataProvider). type LiquidityDataProvider: LiquidityDataProvider; + /// Implementor of + /// [`BalanceOps`](subtensor_swap_interface::BalanceOps). + type BalanceOps: BalanceOps; + /// This type is used to derive protocol accoun ID. #[pallet::constant] type ProtocolId: Get; @@ -135,6 +139,42 @@ mod pallet { pub enum Event { /// Event emitted when the fee rate has been updated for a subnet FeeRateSet { netuid: NetUid, rate: u16 }, + + /// Event emitted when liquidity is added to a subnet's liquidity pool. + LiquidityAdded { + /// The coldkey account that owns the position + coldkey: T::AccountId, + /// The hotkey account associated with the position + hotkey: T::AccountId, + /// The subnet identifier + netuid: u16, + /// Unique identifier for the liquidity position + position_id: u128, + /// The amount of liquidity added to the position + liquidity: u64, + /// The amount of TAO tokens committed to the position + tao: u64, + /// The amount of Alpha tokens committed to the position + alpha: u64, + }, + + /// Event emitted when liquidity is removed from a subnet's liquidity pool. + LiquidityRemoved { + /// The coldkey account that owns the position + coldkey: T::AccountId, + /// The subnet identifier + netuid: u16, + /// Unique identifier for the liquidity position + position_id: u128, + /// The amount of TAO tokens returned to the user + tao: u64, + /// The amount of Alpha tokens returned to the user + alpha: u64, + /// The amount of TAO fees earned from the position + fee_tao: u64, + /// The amount of Alpha fees earned from the position + fee_alpha: u64, + }, } #[pallet::error] @@ -172,6 +212,9 @@ mod pallet { /// Reserves too low for operation. ReservesTooLow, + + /// The subnet does not exist. + SubNetworkDoesNotExist, } #[pallet::call] @@ -185,6 +228,12 @@ mod pallet { pub fn set_fee_rate(origin: OriginFor, netuid: u16, rate: u16) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; + // Ensure that the subnet exists. + ensure!( + T::LiquidityDataProvider::subnet_exist(netuid), + Error::::SubNetworkDoesNotExist + ); + // using u16 for compatibility let netuid = netuid.into(); @@ -196,6 +245,219 @@ mod pallet { Ok(()) } + + /// Add liquidity to a specific price range for a subnet. + /// + /// Parameters: + /// - origin: The origin of the transaction + /// - netuid: Subnet ID + /// - tick_low: Lower bound of the price range + /// - tick_high: Upper bound of the price range + /// - liquidity: Amount of liquidity to add + /// + /// Emits `Event::LiquidityAdded` on success + #[pallet::call_index(1)] + #[pallet::weight(( + Weight::from_parts(50_000_000, 0) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn add_liquidity( + origin: OriginFor, + hotkey: T::AccountId, + netuid: u16, + tick_low: i32, + tick_high: i32, + liquidity: u64, + ) -> DispatchResult { + let coldkey = ensure_signed(origin)?; + + // Ensure that the subnet exists. + ensure!( + T::LiquidityDataProvider::subnet_exist(netuid), + Error::::SubNetworkDoesNotExist + ); + + let tick_low = TickIndex::new(tick_low).map_err(|_| Error::::InvalidTickRange)?; + let tick_high = TickIndex::new(tick_high).map_err(|_| Error::::InvalidTickRange)?; + let (position_id, tao, alpha) = Self::do_add_liquidity( + netuid.into(), + &coldkey, + &hotkey, + tick_low, + tick_high, + liquidity, + )?; + + // Remove TAO and Alpha balances or fail transaction if they can't be removed exactly + let tao_provided = T::BalanceOps::decrease_balance(&coldkey, tao)?; + ensure!(tao_provided == tao, Error::::InsufficientBalance); + + let alpha_provided = T::BalanceOps::decrease_stake(&coldkey, &hotkey, netuid, alpha)?; + ensure!(alpha_provided == alpha, Error::::InsufficientBalance); + + // Emit an event + Self::deposit_event(Event::LiquidityAdded { + coldkey, + hotkey, + netuid, + position_id: position_id.into(), + liquidity, + tao, + alpha, + }); + + Ok(()) + } + + /// Remove liquidity from a specific position. + /// + /// Parameters: + /// - origin: The origin of the transaction + /// - netuid: Subnet ID + /// - position_id: ID of the position to remove + /// + /// Emits `Event::LiquidityRemoved` on success + #[pallet::call_index(2)] + #[pallet::weight(( + Weight::from_parts(50_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn remove_liquidity( + origin: OriginFor, + hotkey: T::AccountId, + netuid: u16, + position_id: u128, + ) -> DispatchResult { + let coldkey = ensure_signed(origin)?; + + // Ensure that the subnet exists. + ensure!( + T::LiquidityDataProvider::subnet_exist(netuid), + Error::::SubNetworkDoesNotExist + ); + + // Remove liquidity + let result = Self::do_remove_liquidity(netuid.into(), &coldkey, position_id.into())?; + + // Credit the returned tao and alpha to the account + T::BalanceOps::increase_balance(&coldkey, result.tao.saturating_add(result.fee_tao)); + T::BalanceOps::increase_stake( + &coldkey, + &hotkey, + netuid, + result.alpha.saturating_add(result.fee_alpha), + )?; + + // Emit an event + Self::deposit_event(Event::LiquidityRemoved { + coldkey, + netuid: netuid.into(), + position_id, + tao: result.tao, + alpha: result.alpha, + fee_tao: result.fee_tao, + fee_alpha: result.fee_alpha, + }); + + Ok(()) + } + + /// Modify a liquidity position. + /// + /// Parameters: + /// - origin: The origin of the transaction + /// - netuid: Subnet ID + /// - position_id: ID of the position to remove + /// - liquidity_delta: Liquidity to add (if positive) or remove (if negative) + /// + /// Emits `Event::LiquidityRemoved` on success + #[pallet::call_index(3)] + #[pallet::weight(( + Weight::from_parts(50_000_000, 0) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(4)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn modify_position( + origin: OriginFor, + hotkey: T::AccountId, + netuid: u16, + position_id: u128, + liquidity_delta: i64, + ) -> DispatchResult { + let coldkey = ensure_signed(origin)?; + + // Ensure that the subnet exists. + ensure!( + T::LiquidityDataProvider::subnet_exist(netuid), + Error::::SubNetworkDoesNotExist + ); + + // Add or remove liquidity + let result = Self::do_modify_position( + netuid.into(), + &coldkey, + &hotkey, + position_id.into(), + liquidity_delta, + )?; + + if liquidity_delta > 0 { + // Remove TAO and Alpha balances or fail transaction if they can't be removed exactly + let tao_provided = T::BalanceOps::decrease_balance(&coldkey, result.tao)?; + ensure!(tao_provided == result.tao, Error::::InsufficientBalance); + + let alpha_provided = + T::BalanceOps::decrease_stake(&coldkey, &hotkey, netuid, result.alpha)?; + ensure!( + alpha_provided == result.alpha, + Error::::InsufficientBalance + ); + + // Emit an event + Self::deposit_event(Event::LiquidityAdded { + coldkey, + hotkey, + netuid, + position_id, + liquidity: liquidity_delta as u64, + tao: result.tao, + alpha: result.alpha, + }); + } else { + // Credit the returned tao and alpha to the account + T::BalanceOps::increase_balance( + &coldkey, + result.tao.saturating_add(result.fee_tao), + ); + T::BalanceOps::increase_stake( + &coldkey, + &hotkey, + netuid, + result.alpha.saturating_add(result.fee_alpha), + )?; + + // Emit an event + Self::deposit_event(Event::LiquidityRemoved { + coldkey, + netuid: netuid.into(), + position_id, + tao: result.tao, + alpha: result.alpha, + fee_tao: result.fee_tao, + fee_alpha: result.fee_alpha, + }); + } + + Ok(()) + } } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 4bef958ebe..fc32639868 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1132,6 +1132,7 @@ impl pallet_subtensor_swap::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AdminOrigin = EnsureRoot; type LiquidityDataProvider = SubtensorModule; + type BalanceOps = SubtensorModule; type ProtocolId = SwapProtocolId; type MaxFeeRate = SwapMaxFeeRate; type MaxPositions = SwapMaxPositions;