Skip to content

Commit 287f625

Browse files
committed
Merge branch 'devnet-ready' into remove-gov-deadcode
2 parents 514b354 + 98c500b commit 287f625

File tree

10 files changed

+989
-663
lines changed

10 files changed

+989
-663
lines changed

Cargo.lock

Lines changed: 462 additions & 434 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 218 additions & 217 deletions
Large diffs are not rendered by default.

pallets/subtensor/src/coinbase/run_coinbase.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,9 @@ impl<T: Config> Pallet<T> {
256256
log::warn!("Failed to reveal commits for subnet {netuid} due to error: {e:?}");
257257
};
258258
// Pass on subnets that have not reached their tempo.
259-
if Self::should_run_epoch(netuid, current_block) {
259+
if Self::should_run_epoch(netuid, current_block)
260+
&& Self::is_epoch_input_state_consistent(netuid)
261+
{
260262
// Restart counters.
261263
BlocksSinceLastStep::<T>::insert(netuid, 0);
262264
LastMechansimStepBlock::<T>::insert(netuid, current_block);

pallets/subtensor/src/epoch/run_epoch.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::*;
22
use crate::epoch::math::*;
3-
use alloc::collections::BTreeMap;
3+
use alloc::collections::{BTreeMap, BTreeSet};
44
use frame_support::IterableStorageDoubleMap;
55
use safe_math::*;
66
use sp_std::collections::btree_map::IntoIter;
@@ -1625,4 +1625,20 @@ impl<T: Config> Pallet<T> {
16251625

16261626
Ok(())
16271627
}
1628+
1629+
/// This function ensures major assumptions made by epoch function:
1630+
/// 1. Keys map has no duplicate hotkeys
1631+
///
1632+
pub fn is_epoch_input_state_consistent(netuid: NetUid) -> bool {
1633+
// Check if Keys map has duplicate hotkeys or uids
1634+
let mut hotkey_set: BTreeSet<T::AccountId> = BTreeSet::new();
1635+
// `iter_prefix` over a double map yields (uid, value) for the given first key.
1636+
for (_uid, hotkey) in Keys::<T>::iter_prefix(netuid) {
1637+
if !hotkey_set.insert(hotkey) {
1638+
log::error!("Duplicate hotkeys detected for netuid {netuid}");
1639+
return false;
1640+
}
1641+
}
1642+
true
1643+
}
16281644
}

pallets/subtensor/src/macros/dispatches.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,7 @@ mod dispatches {
10511051
/// The extrinsic for user to change its hotkey in subnet or all subnets.
10521052
#[pallet::call_index(70)]
10531053
#[pallet::weight((Weight::from_parts(275_300_000, 0)
1054-
.saturating_add(T::DbWeight::get().reads(49_u64))
1054+
.saturating_add(T::DbWeight::get().reads(53_u64))
10551055
.saturating_add(T::DbWeight::get().writes(37)), DispatchClass::Normal, Pays::No))]
10561056
pub fn swap_hotkey(
10571057
origin: OriginFor<T>,
@@ -1125,7 +1125,7 @@ mod dispatches {
11251125
///
11261126
#[pallet::call_index(75)]
11271127
#[pallet::weight((
1128-
Weight::from_parts(45_360_000, 0)
1128+
Weight::from_parts(66_450_000, 0)
11291129
.saturating_add(T::DbWeight::get().reads(5))
11301130
.saturating_add(T::DbWeight::get().writes(2)),
11311131
DispatchClass::Normal,

pallets/subtensor/src/tests/epoch.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3884,3 +3884,55 @@ fn test_last_update_size_mismatch() {
38843884
assert_eq!(SubtensorModule::get_dividends_for_uid(netuid, uid), 0);
38853885
});
38863886
}
3887+
3888+
#[test]
3889+
fn empty_ok() {
3890+
new_test_ext(1).execute_with(|| {
3891+
let netuid: NetUid = 155.into();
3892+
assert!(Pallet::<Test>::is_epoch_input_state_consistent(netuid));
3893+
});
3894+
}
3895+
3896+
#[test]
3897+
fn unique_hotkeys_and_uids_ok() {
3898+
new_test_ext(1).execute_with(|| {
3899+
let netuid: NetUid = 155.into();
3900+
3901+
// (netuid, uid) -> hotkey (AccountId = U256)
3902+
Keys::<Test>::insert(netuid, 0u16, U256::from(1u64));
3903+
Keys::<Test>::insert(netuid, 1u16, U256::from(2u64));
3904+
Keys::<Test>::insert(netuid, 2u16, U256::from(3u64));
3905+
3906+
assert!(Pallet::<Test>::is_epoch_input_state_consistent(netuid));
3907+
});
3908+
}
3909+
3910+
#[test]
3911+
fn duplicate_hotkey_within_same_netuid_fails() {
3912+
new_test_ext(1).execute_with(|| {
3913+
let netuid: NetUid = 155.into();
3914+
3915+
// Same hotkey mapped from two different UIDs in the SAME netuid
3916+
let hk = U256::from(42u64);
3917+
Keys::<Test>::insert(netuid, 0u16, hk);
3918+
Keys::<Test>::insert(netuid, 1u16, U256::from(42u64)); // duplicate hotkey
3919+
3920+
assert!(!Pallet::<Test>::is_epoch_input_state_consistent(netuid));
3921+
});
3922+
}
3923+
3924+
#[test]
3925+
fn same_hotkey_across_different_netuids_is_ok() {
3926+
new_test_ext(1).execute_with(|| {
3927+
let net_a: NetUid = 10.into();
3928+
let net_b: NetUid = 11.into();
3929+
3930+
// Same hotkey appears once in each netuid — each net checks independently.
3931+
let hk = U256::from(777u64);
3932+
Keys::<Test>::insert(net_a, 0u16, hk);
3933+
Keys::<Test>::insert(net_b, 0u16, hk);
3934+
3935+
assert!(Pallet::<Test>::is_epoch_input_state_consistent(net_a));
3936+
assert!(Pallet::<Test>::is_epoch_input_state_consistent(net_b));
3937+
});
3938+
}

pallets/swap/src/pallet/impls.rs

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,41 @@ impl<T: Config> Pallet<T> {
113113
Ok(())
114114
}
115115

116+
pub(crate) fn get_proportional_alpha_tao_and_remainders(
117+
sqrt_alpha_price: U64F64,
118+
amount_tao: TaoCurrency,
119+
amount_alpha: AlphaCurrency,
120+
) -> (TaoCurrency, AlphaCurrency, TaoCurrency, AlphaCurrency) {
121+
let price = sqrt_alpha_price.saturating_mul(sqrt_alpha_price);
122+
let tao_equivalent: u64 = U64F64::saturating_from_num(u64::from(amount_alpha))
123+
.saturating_mul(price)
124+
.saturating_to_num();
125+
let amount_tao_u64 = u64::from(amount_tao);
126+
127+
if tao_equivalent <= amount_tao_u64 {
128+
// Too much or just enough TAO
129+
(
130+
tao_equivalent.into(),
131+
amount_alpha,
132+
amount_tao.saturating_sub(TaoCurrency::from(tao_equivalent)),
133+
0.into(),
134+
)
135+
} else {
136+
// Too much Alpha
137+
let alpha_equivalent: u64 = U64F64::saturating_from_num(u64::from(amount_tao))
138+
.safe_div(price)
139+
.saturating_to_num();
140+
(
141+
amount_tao,
142+
alpha_equivalent.into(),
143+
0.into(),
144+
u64::from(amount_alpha)
145+
.saturating_sub(alpha_equivalent)
146+
.into(),
147+
)
148+
}
149+
}
150+
116151
/// Adjusts protocol liquidity with new values of TAO and Alpha reserve
117152
pub(super) fn adjust_protocol_liquidity(
118153
netuid: NetUid,
@@ -129,17 +164,31 @@ impl<T: Config> Pallet<T> {
129164
// Claim protocol fees and add them to liquidity
130165
let (tao_fees, alpha_fees) = position.collect_fees();
131166

132-
// Adjust liquidity
167+
// Add fee reservoirs and get proportional amounts
133168
let current_sqrt_price = AlphaSqrtPrice::<T>::get(netuid);
169+
let tao_reservoir = ScrapReservoirTao::<T>::get(netuid);
170+
let alpha_reservoir = ScrapReservoirAlpha::<T>::get(netuid);
171+
let (corrected_tao_delta, corrected_alpha_delta, tao_scrap, alpha_scrap) =
172+
Self::get_proportional_alpha_tao_and_remainders(
173+
current_sqrt_price,
174+
tao_delta
175+
.saturating_add(TaoCurrency::from(tao_fees))
176+
.saturating_add(tao_reservoir),
177+
alpha_delta
178+
.saturating_add(AlphaCurrency::from(alpha_fees))
179+
.saturating_add(alpha_reservoir),
180+
);
181+
182+
// Update scrap reservoirs
183+
ScrapReservoirTao::<T>::insert(netuid, tao_scrap);
184+
ScrapReservoirAlpha::<T>::insert(netuid, alpha_scrap);
185+
186+
// Adjust liquidity
134187
let maybe_token_amounts = position.to_token_amounts(current_sqrt_price);
135188
if let Ok((tao, alpha)) = maybe_token_amounts {
136189
// Get updated reserves, calculate liquidity
137-
let new_tao_reserve = tao
138-
.saturating_add(tao_delta.to_u64())
139-
.saturating_add(tao_fees);
140-
let new_alpha_reserve = alpha
141-
.saturating_add(alpha_delta.to_u64())
142-
.saturating_add(alpha_fees);
190+
let new_tao_reserve = tao.saturating_add(corrected_tao_delta.to_u64());
191+
let new_alpha_reserve = alpha.saturating_add(corrected_alpha_delta.to_u64());
143192
let new_liquidity = helpers_128bit::sqrt(
144193
(new_tao_reserve as u128).saturating_mul(new_alpha_reserve as u128),
145194
) as u64;

pallets/swap/src/pallet/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ mod pallet {
147147
ValueQuery,
148148
>;
149149

150+
/// TAO reservoir for scraps of protocol claimed fees.
151+
#[pallet::storage]
152+
pub type ScrapReservoirTao<T> = StorageMap<_, Twox64Concat, NetUid, TaoCurrency, ValueQuery>;
153+
154+
/// Alpha reservoir for scraps of protocol claimed fees.
155+
#[pallet::storage]
156+
pub type ScrapReservoirAlpha<T> =
157+
StorageMap<_, Twox64Concat, NetUid, AlphaCurrency, ValueQuery>;
158+
150159
#[pallet::event]
151160
#[pallet::generate_deposit(pub(super) fn deposit_event)]
152161
pub enum Event<T: Config> {

pallets/swap/src/pallet/tests.rs

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2744,3 +2744,172 @@ fn test_clear_protocol_liquidity_green_path() {
27442744
assert!(!SwapV3Initialized::<Test>::contains_key(netuid));
27452745
});
27462746
}
2747+
2748+
fn as_tuple(
2749+
(t_used, a_used, t_rem, a_rem): (TaoCurrency, AlphaCurrency, TaoCurrency, AlphaCurrency),
2750+
) -> (u64, u64, u64, u64) {
2751+
(
2752+
u64::from(t_used),
2753+
u64::from(a_used),
2754+
u64::from(t_rem),
2755+
u64::from(a_rem),
2756+
)
2757+
}
2758+
2759+
#[test]
2760+
fn proportional_when_price_is_one_and_tao_is_plenty() {
2761+
// sqrt_price = 1.0 => price = 1.0
2762+
let sqrt = U64F64::from_num(1u64);
2763+
let amount_tao: TaoCurrency = 10u64.into();
2764+
let amount_alpha: AlphaCurrency = 3u64.into();
2765+
2766+
// alpha * price = 3 * 1 = 3 <= amount_tao(10)
2767+
let out =
2768+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2769+
assert_eq!(as_tuple(out), (3, 3, 7, 0));
2770+
}
2771+
2772+
#[test]
2773+
fn proportional_when_price_is_one_and_alpha_is_excess() {
2774+
// sqrt_price = 1.0 => price = 1.0
2775+
let sqrt = U64F64::from_num(1u64);
2776+
let amount_tao: TaoCurrency = 5u64.into();
2777+
let amount_alpha: AlphaCurrency = 10u64.into();
2778+
2779+
// tao is limiting: alpha_equiv = floor(5 / 1) = 5
2780+
let out =
2781+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2782+
assert_eq!(as_tuple(out), (5, 5, 0, 5));
2783+
}
2784+
2785+
#[test]
2786+
fn proportional_with_higher_price_and_alpha_limiting() {
2787+
// Choose sqrt_price = 2.0 => price = 4.0 (since implementation squares it)
2788+
let sqrt = U64F64::from_num(2u64);
2789+
let amount_tao: TaoCurrency = 85u64.into();
2790+
let amount_alpha: AlphaCurrency = 20u64.into();
2791+
2792+
// tao_equivalent = alpha * price = 20 * 4 = 80 < 85 => alpha limits tao
2793+
// remainders: tao 5, alpha 0
2794+
let out =
2795+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2796+
assert_eq!(as_tuple(out), (80, 20, 5, 0));
2797+
}
2798+
2799+
#[test]
2800+
fn proportional_with_higher_price_and_tao_limiting() {
2801+
// Choose sqrt_price = 2.0 => price = 4.0 (since implementation squares it)
2802+
let sqrt = U64F64::from_num(2u64);
2803+
let amount_tao: TaoCurrency = 50u64.into();
2804+
let amount_alpha: AlphaCurrency = 20u64.into();
2805+
2806+
// tao_equivalent = alpha * price = 20 * 4 = 80 > 50 => tao limits alpha
2807+
// alpha_equivalent = floor(50 / 4) = 12
2808+
// remainders: tao 0, alpha 20 - 12 = 8
2809+
let out =
2810+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2811+
assert_eq!(as_tuple(out), (50, 12, 0, 8));
2812+
}
2813+
2814+
#[test]
2815+
fn zero_price_uses_no_tao_and_all_alpha() {
2816+
// sqrt_price = 0 => price = 0
2817+
let sqrt = U64F64::from_num(0u64);
2818+
let amount_tao: TaoCurrency = 42u64.into();
2819+
let amount_alpha: AlphaCurrency = 17u64.into();
2820+
2821+
// tao_equivalent = 17 * 0 = 0 <= 42
2822+
let out =
2823+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2824+
assert_eq!(as_tuple(out), (0, 17, 42, 0));
2825+
}
2826+
2827+
#[test]
2828+
fn rounding_down_behavior_when_dividing_by_price() {
2829+
// sqrt_price = 2.0 => price = 4.0
2830+
let sqrt = U64F64::from_num(2u64);
2831+
let amount_tao: TaoCurrency = 13u64.into();
2832+
let amount_alpha: AlphaCurrency = 100u64.into();
2833+
2834+
// tao is limiting; alpha_equiv = floor(13 / 4) = 3
2835+
// remainders: tao 0, alpha 100 - 3 = 97
2836+
let out =
2837+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2838+
assert_eq!(as_tuple(out), (13, 3, 0, 97));
2839+
}
2840+
2841+
#[test]
2842+
fn exact_fit_when_tao_matches_alpha_times_price() {
2843+
// sqrt_price = 1.0 => price = 1.0
2844+
let sqrt = U64F64::from_num(1u64);
2845+
let amount_tao: TaoCurrency = 9u64.into();
2846+
let amount_alpha: AlphaCurrency = 9u64.into();
2847+
2848+
let out =
2849+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2850+
assert_eq!(as_tuple(out), (9, 9, 0, 0));
2851+
}
2852+
2853+
#[test]
2854+
fn handles_zero_balances() {
2855+
let sqrt = U64F64::from_num(1u64);
2856+
2857+
// Zero TAO, some alpha
2858+
let out =
2859+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, 0u64.into(), 7u64.into());
2860+
// tao limits; alpha_equiv = floor(0 / 1) = 0
2861+
assert_eq!(as_tuple(out), (0, 0, 0, 7));
2862+
2863+
// Some TAO, zero alpha
2864+
let out =
2865+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, 7u64.into(), 0u64.into());
2866+
// tao_equiv = 0 * 1 = 0 <= 7
2867+
assert_eq!(as_tuple(out), (0, 0, 7, 0));
2868+
2869+
// Both zero
2870+
let out =
2871+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, 0u64.into(), 0u64.into());
2872+
assert_eq!(as_tuple(out), (0, 0, 0, 0));
2873+
}
2874+
2875+
#[test]
2876+
fn adjust_protocol_liquidity_uses_and_sets_scrap_reservoirs() {
2877+
new_test_ext().execute_with(|| {
2878+
// --- Arrange
2879+
let netuid: NetUid = 1u16.into();
2880+
// Price = 1.0 (since sqrt_price^2 = 1), so proportional match is 1:1
2881+
AlphaSqrtPrice::<Test>::insert(netuid, U64F64::saturating_from_num(1u64));
2882+
2883+
// Start with some non-zero scrap reservoirs
2884+
ScrapReservoirTao::<Test>::insert(netuid, TaoCurrency::from(7u64));
2885+
ScrapReservoirAlpha::<Test>::insert(netuid, AlphaCurrency::from(5u64));
2886+
2887+
// Create a minimal protocol position so the function’s body executes.
2888+
let protocol = Pallet::<Test>::protocol_account_id();
2889+
let position = Position::new(
2890+
PositionId::from(0),
2891+
netuid,
2892+
TickIndex::MIN,
2893+
TickIndex::MAX,
2894+
0,
2895+
);
2896+
// Ensure collect_fees() returns (0,0) via zeroed fees in `position` (default).
2897+
Positions::<Test>::insert((netuid, protocol, position.id), position.clone());
2898+
2899+
// --- Act
2900+
// No external deltas or fees; only reservoirs should be considered.
2901+
// With price=1, the exact proportional pair uses 5 alpha and 5 tao,
2902+
// leaving tao scrap = 7 - 5 = 2, alpha scrap = 5 - 5 = 0.
2903+
Pallet::<Test>::adjust_protocol_liquidity(netuid, 0u64.into(), 0u64.into());
2904+
2905+
// --- Assert: reservoirs were READ (used in proportional calc) and then SET (updated)
2906+
assert_eq!(
2907+
ScrapReservoirTao::<Test>::get(netuid),
2908+
TaoCurrency::from(2u64)
2909+
);
2910+
assert_eq!(
2911+
ScrapReservoirAlpha::<Test>::get(netuid),
2912+
AlphaCurrency::from(0u64)
2913+
);
2914+
});
2915+
}

pallets/utility/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ pub mod pallet {
456456
} else {
457457
Self::deposit_event(Event::BatchCompleted);
458458
}
459-
let base_weight = T::WeightInfo::batch(calls_len as u32);
459+
let base_weight = T::WeightInfo::force_batch(calls_len as u32);
460460
Ok(Some(base_weight.saturating_add(weight)).into())
461461
}
462462

0 commit comments

Comments
 (0)