Skip to content

Commit 3a266cb

Browse files
authored
Merge pull request #2071 from opentensor/convert-a-to-t-during-LP-dissolution
LP Dissolution validator selection
2 parents ac358c6 + 44cf573 commit 3a266cb

File tree

8 files changed

+450
-62
lines changed

8 files changed

+450
-62
lines changed

common/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use runtime_common::prod_or_fast;
99
use scale_info::TypeInfo;
1010
use serde::{Deserialize, Serialize};
1111
use sp_runtime::{
12-
MultiSignature,
12+
MultiSignature, Vec,
1313
traits::{IdentifyAccount, Verify},
1414
};
1515
use subtensor_macros::freeze_struct;
@@ -175,6 +175,9 @@ pub trait SubnetInfo<AccountId> {
175175
fn mechanism(netuid: NetUid) -> u16;
176176
fn is_owner(account_id: &AccountId, netuid: NetUid) -> bool;
177177
fn is_subtoken_enabled(netuid: NetUid) -> bool;
178+
fn get_validator_trust(netuid: NetUid) -> Vec<u16>;
179+
fn get_validator_permit(netuid: NetUid) -> Vec<bool>;
180+
fn hotkey_of_uid(netuid: NetUid, uid: u16) -> Option<AccountId>;
178181
}
179182

180183
pub trait BalanceOps<AccountId> {

pallets/subtensor/src/coinbase/root.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ impl<T: Config> Pallet<T> {
375375
// 2. --- Perform the cleanup before removing the network.
376376
T::SwapInterface::dissolve_all_liquidity_providers(netuid)?;
377377
Self::destroy_alpha_in_out_stakes(netuid)?;
378+
T::SwapInterface::clear_protocol_liquidity(netuid)?;
378379
T::CommitmentsInterface::purge_netuid(netuid);
379380

380381
// 3. --- Remove the network

pallets/subtensor/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2146,6 +2146,18 @@ impl<T: Config + pallet_balances::Config<Balance = u64>>
21462146
fn is_subtoken_enabled(netuid: NetUid) -> bool {
21472147
SubtokenEnabled::<T>::get(netuid)
21482148
}
2149+
2150+
fn get_validator_trust(netuid: NetUid) -> Vec<u16> {
2151+
ValidatorTrust::<T>::get(netuid)
2152+
}
2153+
2154+
fn get_validator_permit(netuid: NetUid) -> Vec<bool> {
2155+
ValidatorPermit::<T>::get(netuid)
2156+
}
2157+
2158+
fn hotkey_of_uid(netuid: NetUid, uid: u16) -> Option<T::AccountId> {
2159+
Keys::<T>::try_get(netuid, uid).ok()
2160+
}
21492161
}
21502162

21512163
impl<T: Config + pallet_balances::Config<Balance = u64>>

pallets/swap-interface/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub trait SwapHandler<AccountId> {
3636
fn is_user_liquidity_enabled(netuid: NetUid) -> bool;
3737
fn dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult;
3838
fn toggle_user_liquidity(netuid: NetUid, enabled: bool);
39+
fn clear_protocol_liquidity(netuid: NetUid) -> DispatchResult;
3940
}
4041

4142
#[derive(Debug, PartialEq)]

pallets/swap/src/mock.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,26 @@ impl SubnetInfo<AccountId> for MockLiquidityProvider {
120120
fn is_subtoken_enabled(netuid: NetUid) -> bool {
121121
netuid.inner() != SUBTOKEN_DISABLED_NETUID
122122
}
123+
124+
fn get_validator_trust(netuid: NetUid) -> Vec<u16> {
125+
match netuid.into() {
126+
123u16 => vec![4000, 3000, 2000, 1000],
127+
WRAPPING_FEES_NETUID => vec![8000, 7000, 6000, 5000],
128+
_ => vec![1000, 800, 600, 400],
129+
}
130+
}
131+
132+
fn get_validator_permit(netuid: NetUid) -> Vec<bool> {
133+
match netuid.into() {
134+
123u16 => vec![true, true, false, true],
135+
WRAPPING_FEES_NETUID => vec![true, true, true, true],
136+
_ => vec![true, true, true, true],
137+
}
138+
}
139+
140+
fn hotkey_of_uid(_netuid: NetUid, uid: u16) -> Option<AccountId> {
141+
Some(uid as AccountId)
142+
}
123143
}
124144

125145
pub struct MockBalanceOps;

pallets/swap/src/pallet/impls.rs

Lines changed: 136 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use frame_support::storage::{TransactionOutcome, transactional};
55
use frame_support::{ensure, pallet_prelude::DispatchError, traits::Get};
66
use safe_math::*;
77
use sp_arithmetic::helpers_128bit;
8-
use sp_runtime::{DispatchResult, traits::AccountIdConversion};
8+
use sp_runtime::{DispatchResult, Vec, traits::AccountIdConversion};
99
use substrate_fixed::types::{I64F64, U64F64, U96F32};
1010
use subtensor_runtime_common::{
1111
AlphaCurrency, BalanceOps, Currency, NetUid, SubnetInfo, TaoCurrency,
@@ -1216,80 +1216,176 @@ impl<T: Config> Pallet<T> {
12161216
/// Dissolve all LPs and clean state.
12171217
pub fn do_dissolve_all_liquidity_providers(netuid: NetUid) -> DispatchResult {
12181218
if SwapV3Initialized::<T>::get(netuid) {
1219-
// 1) Snapshot (owner, position_id).
1219+
// 1) Snapshot only *non‑protocol* positions: (owner, position_id).
12201220
struct CloseItem<A> {
12211221
owner: A,
12221222
pos_id: PositionId,
12231223
}
1224+
let protocol_account = Self::protocol_account_id();
1225+
12241226
let mut to_close: sp_std::vec::Vec<CloseItem<T::AccountId>> = sp_std::vec::Vec::new();
12251227
for ((owner, pos_id), _pos) in Positions::<T>::iter_prefix((netuid,)) {
1226-
to_close.push(CloseItem { owner, pos_id });
1228+
if owner != protocol_account {
1229+
to_close.push(CloseItem { owner, pos_id });
1230+
}
12271231
}
12281232

1229-
let protocol_account = Self::protocol_account_id();
1233+
if to_close.is_empty() {
1234+
log::debug!(
1235+
"dissolve_all_lp: no user positions; netuid={netuid:?}, protocol liquidity untouched"
1236+
);
1237+
return Ok(());
1238+
}
12301239

1231-
// Non‑protocol first
1232-
to_close
1233-
.sort_by(|a, b| (a.owner == protocol_account).cmp(&(b.owner == protocol_account)));
1240+
let mut user_refunded_tao = TaoCurrency::ZERO;
1241+
let mut user_staked_alpha = AlphaCurrency::ZERO;
1242+
1243+
let trust: Vec<u16> = T::SubnetInfo::get_validator_trust(netuid.into());
1244+
let permit: Vec<bool> = T::SubnetInfo::get_validator_permit(netuid.into());
1245+
1246+
// Helper: pick target validator uid, only among permitted validators, by highest trust.
1247+
let pick_target_uid = |trust: &Vec<u16>, permit: &Vec<bool>| -> Option<u16> {
1248+
let mut best_uid: Option<usize> = None;
1249+
let mut best_trust: u16 = 0;
1250+
for (i, (&t, &p)) in trust.iter().zip(permit.iter()).enumerate() {
1251+
if p && (best_uid.is_none() || t > best_trust) {
1252+
best_uid = Some(i);
1253+
best_trust = t;
1254+
}
1255+
}
1256+
best_uid.map(|i| i as u16)
1257+
};
12341258

12351259
for CloseItem { owner, pos_id } in to_close.into_iter() {
12361260
match Self::do_remove_liquidity(netuid, &owner, pos_id) {
12371261
Ok(rm) => {
1262+
// α withdrawn from the pool = principal + accrued fees
1263+
let alpha_total_from_pool: AlphaCurrency =
1264+
rm.alpha.saturating_add(rm.fee_alpha);
1265+
1266+
// ---------------- USER: refund τ and convert α → stake ----------------
1267+
1268+
// 1) Refund τ principal directly.
12381269
if rm.tao > TaoCurrency::ZERO {
12391270
T::BalanceOps::increase_balance(&owner, rm.tao);
1240-
}
1241-
if owner != protocol_account {
1271+
user_refunded_tao = user_refunded_tao.saturating_add(rm.tao);
12421272
T::BalanceOps::decrease_provided_tao_reserve(netuid, rm.tao);
1243-
let alpha_burn = rm.alpha.saturating_add(rm.fee_alpha);
1244-
if alpha_burn > AlphaCurrency::ZERO {
1245-
T::BalanceOps::decrease_provided_alpha_reserve(netuid, alpha_burn);
1273+
}
1274+
1275+
// 2) Stake ALL withdrawn α (principal + fees) to the best permitted validator.
1276+
if alpha_total_from_pool > AlphaCurrency::ZERO {
1277+
if let Some(target_uid) = pick_target_uid(&trust, &permit) {
1278+
let validator_hotkey: T::AccountId =
1279+
T::SubnetInfo::hotkey_of_uid(netuid.into(), target_uid).ok_or(
1280+
sp_runtime::DispatchError::Other(
1281+
"validator_hotkey_missing",
1282+
),
1283+
)?;
1284+
1285+
// Stake α from LP owner (coldkey) to chosen validator (hotkey).
1286+
T::BalanceOps::increase_stake(
1287+
&owner,
1288+
&validator_hotkey,
1289+
netuid,
1290+
alpha_total_from_pool,
1291+
)?;
1292+
1293+
user_staked_alpha =
1294+
user_staked_alpha.saturating_add(alpha_total_from_pool);
1295+
1296+
log::debug!(
1297+
"dissolve_all_lp: user dissolved & staked α: netuid={netuid:?}, owner={owner:?}, pos_id={pos_id:?}, α_staked={alpha_total_from_pool:?}, target_uid={target_uid}"
1298+
);
1299+
} else {
1300+
// No permitted validators; burn to avoid balance drift.
1301+
log::debug!(
1302+
"dissolve_all_lp: no permitted validators; α burned: netuid={netuid:?}, owner={owner:?}, pos_id={pos_id:?}, α_total={alpha_total_from_pool:?}"
1303+
);
12461304
}
1305+
1306+
T::BalanceOps::decrease_provided_alpha_reserve(
1307+
netuid,
1308+
alpha_total_from_pool,
1309+
);
12471310
}
12481311
}
12491312
Err(e) => {
12501313
log::debug!(
1251-
"dissolve_all_lp: force-closing failed position: netuid={netuid:?}, owner={owner:?}, pos_id={pos_id:?}, err={e:?}"
1314+
"dissolve_all_lp: force-close failed: netuid={netuid:?}, owner={owner:?}, pos_id={pos_id:?}, err={e:?}"
12521315
);
12531316
continue;
12541317
}
12551318
}
12561319
}
12571320

1258-
// 3) Clear active tick index entries, then all swap state.
1259-
let active_ticks: sp_std::vec::Vec<TickIndex> =
1260-
Ticks::<T>::iter_prefix(netuid).map(|(ti, _)| ti).collect();
1261-
for ti in active_ticks {
1262-
ActiveTickIndexManager::<T>::remove(netuid, ti);
1263-
}
1321+
log::debug!(
1322+
"dissolve_all_liquidity_providers (users-only): netuid={netuid:?}, users_refunded_total_τ={user_refunded_tao:?}, users_staked_total_α={user_staked_alpha:?}; protocol liquidity untouched"
1323+
);
12641324

1265-
let _ = Positions::<T>::clear_prefix((netuid,), u32::MAX, None);
1266-
let _ = Ticks::<T>::clear_prefix(netuid, u32::MAX, None);
1325+
return Ok(());
1326+
}
12671327

1268-
FeeGlobalTao::<T>::remove(netuid);
1269-
FeeGlobalAlpha::<T>::remove(netuid);
1270-
CurrentLiquidity::<T>::remove(netuid);
1271-
CurrentTick::<T>::remove(netuid);
1272-
AlphaSqrtPrice::<T>::remove(netuid);
1273-
SwapV3Initialized::<T>::remove(netuid);
1328+
log::debug!(
1329+
"dissolve_all_liquidity_providers: netuid={netuid:?}, mode=V2-or-nonV3, leaving all liquidity/state intact"
1330+
);
12741331

1275-
let _ = TickIndexBitmapWords::<T>::clear_prefix((netuid,), u32::MAX, None);
1276-
FeeRate::<T>::remove(netuid);
1277-
EnabledUserLiquidity::<T>::remove(netuid);
1332+
Ok(())
1333+
}
12781334

1279-
log::debug!(
1280-
"dissolve_all_liquidity_providers: netuid={netuid:?}, mode=V3, positions closed; τ principal refunded; α burned; state cleared"
1281-
);
1335+
/// Clear **protocol-owned** liquidity and wipe all swap state for `netuid`.
1336+
pub fn do_clear_protocol_liquidity(netuid: NetUid) -> DispatchResult {
1337+
let protocol_account = Self::protocol_account_id();
12821338

1283-
return Ok(());
1339+
// 1) Force-close only protocol positions, burning proceeds.
1340+
let mut burned_tao = TaoCurrency::ZERO;
1341+
let mut burned_alpha = AlphaCurrency::ZERO;
1342+
1343+
// Collect protocol position IDs first to avoid mutating while iterating.
1344+
let protocol_pos_ids: sp_std::vec::Vec<PositionId> = Positions::<T>::iter_prefix((netuid,))
1345+
.filter_map(|((owner, pos_id), _)| {
1346+
if owner == protocol_account {
1347+
Some(pos_id)
1348+
} else {
1349+
None
1350+
}
1351+
})
1352+
.collect();
1353+
1354+
for pos_id in protocol_pos_ids {
1355+
match Self::do_remove_liquidity(netuid, &protocol_account, pos_id) {
1356+
Ok(rm) => {
1357+
let alpha_total_from_pool: AlphaCurrency =
1358+
rm.alpha.saturating_add(rm.fee_alpha);
1359+
let tao = rm.tao;
1360+
1361+
if tao > TaoCurrency::ZERO {
1362+
burned_tao = burned_tao.saturating_add(tao);
1363+
}
1364+
if alpha_total_from_pool > AlphaCurrency::ZERO {
1365+
burned_alpha = burned_alpha.saturating_add(alpha_total_from_pool);
1366+
}
1367+
1368+
log::debug!(
1369+
"clear_protocol_liquidity: burned protocol pos: netuid={netuid:?}, pos_id={pos_id:?}, τ={tao:?}, α_total={alpha_total_from_pool:?}"
1370+
);
1371+
}
1372+
Err(e) => {
1373+
log::debug!(
1374+
"clear_protocol_liquidity: force-close failed: netuid={netuid:?}, pos_id={pos_id:?}, err={e:?}"
1375+
);
1376+
continue;
1377+
}
1378+
}
12841379
}
12851380

1286-
// V2 / non‑V3: ensure V3 residues are cleared (safe no‑ops).
1287-
let _ = Positions::<T>::clear_prefix((netuid,), u32::MAX, None);
1381+
// 2) Clear active tick index entries, then all swap state (idempotent even if empty/non‑V3).
12881382
let active_ticks: sp_std::vec::Vec<TickIndex> =
12891383
Ticks::<T>::iter_prefix(netuid).map(|(ti, _)| ti).collect();
12901384
for ti in active_ticks {
12911385
ActiveTickIndexManager::<T>::remove(netuid, ti);
12921386
}
1387+
1388+
let _ = Positions::<T>::clear_prefix((netuid,), u32::MAX, None);
12931389
let _ = Ticks::<T>::clear_prefix(netuid, u32::MAX, None);
12941390

12951391
FeeGlobalTao::<T>::remove(netuid);
@@ -1300,12 +1396,11 @@ impl<T: Config> Pallet<T> {
13001396
SwapV3Initialized::<T>::remove(netuid);
13011397

13021398
let _ = TickIndexBitmapWords::<T>::clear_prefix((netuid,), u32::MAX, None);
1303-
13041399
FeeRate::<T>::remove(netuid);
13051400
EnabledUserLiquidity::<T>::remove(netuid);
13061401

13071402
log::debug!(
1308-
"dissolve_all_liquidity_providers: netuid={netuid:?}, mode=V2-or-nonV3, state_cleared"
1403+
"clear_protocol_liquidity: netuid={netuid:?}, protocol_burned: τ={burned_tao:?}, α={burned_alpha:?}; state cleared"
13091404
);
13101405

13111406
Ok(())
@@ -1408,6 +1503,9 @@ impl<T: Config> SwapHandler<T::AccountId> for Pallet<T> {
14081503
fn toggle_user_liquidity(netuid: NetUid, enabled: bool) {
14091504
EnabledUserLiquidity::<T>::insert(netuid, enabled)
14101505
}
1506+
fn clear_protocol_liquidity(netuid: NetUid) -> DispatchResult {
1507+
Self::do_clear_protocol_liquidity(netuid)
1508+
}
14111509
}
14121510

14131511
#[derive(Debug, PartialEq)]

0 commit comments

Comments
 (0)