Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
06032d5
Merge pull request #2769 from opentensor/testnet
sam0x17 Jun 25, 2026
14cd19c
reduce balancer exp precision from 1024 to 512
gztensor Jun 26, 2026
bc5c024
reduce balancer exp precision from 512 to 128
gztensor Jun 26, 2026
503170f
Increase fuzzy test output frequency
gztensor Jun 26, 2026
98151ec
cap exp_scaled at 1, set balancer exp precision to 256
gztensor Jun 26, 2026
593de8a
Forbid swaps that are too large compared to available liauidity
gztensor Jun 26, 2026
536c893
spec bump
gztensor Jun 26, 2026
c4c77a2
chore: auditor auto-fix
Jun 26, 2026
8bc8ef6
Merge branch 'fix/balancer-performance' of github.com:opentensor/subt…
gztensor Jun 26, 2026
566fb8c
Update pallets/swap/src/pallet/balancer.rs
gztensor Jun 26, 2026
d005de3
fix tests
gztensor Jun 26, 2026
132b9cc
Fix tests, enforce atomic add-stake recycle/burn operations by rollin…
gztensor Jun 26, 2026
1ad1688
Apply the 1000x swap input cap to simulations and limit-path max-amou…
gztensor Jun 26, 2026
bb51677
Merge pull request #2803 from opentensor/fix/balancer-performance
sam0x17 Jun 26, 2026
156eeca
Allow locked alpha transfers in coldkey swaps
gztensor Jun 29, 2026
34e1def
Merge pull request #2810 from opentensor/fix/coldkey-swap-locked-alpha
sam0x17 Jun 29, 2026
b749a63
Apply dispatch extension conditionally
l0r1s Jun 29, 2026
ebe1ac6
Apply subtensor extension conditionally
l0r1s Jun 29, 2026
6d81084
Merge pull request #2812 from opentensor/conditional-ext
sam0x17 Jun 29, 2026
cb26860
Merge branch 'main' into hotfixes-29-06-2026
l0r1s Jun 29, 2026
22842e4
Merge branch 'devnet-ready' into hotfixes-29-06-2026
l0r1s Jun 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 30 additions & 8 deletions chain-extensions/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1297,15 +1297,26 @@ fn add_stake_recycle_rollback_on_recycle_failure() {

let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey);

// Set up very low reserves so recycle will fail with InsufficientLiquidity
mock::register_ok_neuron(netuid, hotkey, coldkey, 0);
pallet_subtensor::Pallet::<mock::Test>::insert_lock_state(
&coldkey,
netuid,
&hotkey,
pallet_subtensor::staking::lock::LockState {
locked_mass: AlphaBalance::from(u64::MAX / 4),
conviction: U64F64::saturating_from_num(0),
last_update: pallet_subtensor::Pallet::<mock::Test>::get_current_block_as_u64(),
},
);

// Leave enough input-side liquidity for add_stake to pass the 1000x swap input cap.
// The lock above makes the recycle leg fail, exercising atomic rollback.
mock::setup_reserves(
netuid,
TaoBalance::from(1_000_u64),
TaoBalance::from(tao_amount_raw / 1000 + 1),
AlphaBalance::from(1_000_u64),
);

mock::register_ok_neuron(netuid, hotkey, coldkey, 0);

add_balance_to_coldkey_account(
&coldkey,
TaoBalance::from(tao_amount_raw.saturating_add(1_000_000_000)),
Expand Down Expand Up @@ -1368,15 +1379,26 @@ fn add_stake_burn_rollback_on_burn_failure() {

let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey);

// Set up very low reserves so burn will fail with InsufficientLiquidity
mock::register_ok_neuron(netuid, hotkey, coldkey, 0);
pallet_subtensor::Pallet::<mock::Test>::insert_lock_state(
&coldkey,
netuid,
&hotkey,
pallet_subtensor::staking::lock::LockState {
locked_mass: AlphaBalance::from(u64::MAX / 4),
conviction: U64F64::saturating_from_num(0),
last_update: pallet_subtensor::Pallet::<mock::Test>::get_current_block_as_u64(),
},
);

// Leave enough input-side liquidity for add_stake to pass the 1000x swap input cap.
// The lock above makes the burn leg fail, exercising atomic rollback.
mock::setup_reserves(
netuid,
TaoBalance::from(1_000_u64),
TaoBalance::from(tao_amount_raw / 1000 + 1),
AlphaBalance::from(1_000_u64),
);

mock::register_ok_neuron(netuid, hotkey, coldkey, 0);

add_balance_to_coldkey_account(
&coldkey,
TaoBalance::from(tao_amount_raw.saturating_add(1_000_000_000)),
Expand Down
26 changes: 17 additions & 9 deletions pallets/subtensor/src/extensions/subtensor.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
Call, CheckColdkeySwap, CheckDelegateTake, CheckEvmKeyAssociation, CheckRateLimits,
CheckServingEndpoints, CheckWeights, Config, Error,
CheckServingEndpoints, CheckWeights, Config, Error, guards::applicable_call,
};
use codec::{Decode, DecodeWithMemTracking, Encode};
use frame_support::{
Expand Down Expand Up @@ -89,15 +89,23 @@ impl<T: Config + Send + Sync + TypeInfo> SubtensorTransactionExtension<T> {

CheckColdkeySwap::<T>::check(who, call)?;

let Some(call) = call.is_sub_type() else {
return Ok(());
};
if let Some(call) = applicable_call(call, CheckWeights::<T>::applies_to) {
CheckWeights::<T>::check(who, call)?;
}
if let Some(call) = applicable_call(call, CheckRateLimits::<T>::applies_to) {
CheckRateLimits::<T>::check(who, call)?;
}
if let Some(call) = applicable_call(call, CheckDelegateTake::<T>::applies_to) {
CheckDelegateTake::<T>::check(who, call)?;
}
if let Some(call) = applicable_call(call, CheckServingEndpoints::<T>::applies_to) {
CheckServingEndpoints::<T>::check(who, call)?;
}
if let Some(call) = applicable_call(call, CheckEvmKeyAssociation::<T>::applies_to) {
CheckEvmKeyAssociation::<T>::check(who, call)?;
}

CheckWeights::<T>::check(who, call)?;
CheckRateLimits::<T>::check(who, call)?;
CheckDelegateTake::<T>::check(who, call)?;
CheckServingEndpoints::<T>::check(who, call)?;
CheckEvmKeyAssociation::<T>::check(who, call)
Ok(())
}
}

Expand Down
25 changes: 20 additions & 5 deletions pallets/subtensor/src/guards/check_coldkey_swap.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::{CallOf, DispatchableOriginOf};
use crate::weights::WeightInfo;
use crate::{Call, ColdkeySwapAnnouncements, ColdkeySwapDisputes, Config, Error};
use frame_support::{
Expand All @@ -8,9 +9,6 @@ use frame_support::{
use sp_runtime::traits::Dispatchable;
use sp_std::marker::PhantomData;

type CallOf<T> = <T as frame_system::Config>::RuntimeCall;
type DispatchableOriginOf<T> = <CallOf<T> as Dispatchable>::RuntimeOrigin;

/// Dispatch extension that blocks most calls when a coldkey swap is active.
///
/// When a coldkey swap has been announced for the signing account:
Expand Down Expand Up @@ -96,9 +94,14 @@ where
#[allow(clippy::expect_used, clippy::unwrap_used)]
mod tests {
use super::CheckColdkeySwap;
use crate::{ColdkeySwapAnnouncements, ColdkeySwapDisputes, Error, tests::mock::*};
use crate::{
ColdkeySwapAnnouncements, ColdkeySwapDisputes, Error, tests::mock::*,
weights::WeightInfo as _,
};
use frame_support::{
BoundedVec, assert_ok, dispatch::DispatchResultWithPostInfo, traits::ExtendedDispatchable,
BoundedVec, assert_ok,
dispatch::{DispatchExtension, DispatchResultWithPostInfo},
traits::ExtendedDispatchable,
};
use frame_system::Call as SystemCall;
use pallet_subtensor_proxy::Call as ProxyCall;
Expand Down Expand Up @@ -176,6 +179,18 @@ mod tests {
)
}

#[test]
fn weight_charges_all_calls_because_swap_state_can_block_any_signed_call() {
let expected = <Test as crate::Config>::WeightInfo::check_coldkey_swap_extension();

for call in forbidden_calls().into_iter().chain(authorized_calls()) {
assert_eq!(
<CheckColdkeySwap<Test> as DispatchExtension<RuntimeCall>>::weight(&call),
expected
);
}
}

#[test]
fn no_active_swap_allows_calls() {
new_test_ext(1).execute_with(|| {
Expand Down
57 changes: 50 additions & 7 deletions pallets/subtensor/src/guards/check_delegate_take.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::{CallOf, DispatchableOriginOf, applicable_call};
use crate::weights::WeightInfo;
use crate::{Call, Config, Error, Pallet};
use frame_support::{
Expand All @@ -8,16 +9,20 @@ use frame_support::{
use sp_runtime::traits::Dispatchable;
use sp_std::marker::PhantomData;

type CallOf<T> = <T as frame_system::Config>::RuntimeCall;
type DispatchableOriginOf<T> = <CallOf<T> as Dispatchable>::RuntimeOrigin;

/// Dispatch extension for delegate-take bounds and ownership preconditions.
///
/// Signed increase/decrease take calls are checked before dispatch; unrelated
/// calls and non-signed origins pass through.
pub struct CheckDelegateTake<T: Config>(PhantomData<T>);

impl<T: Config> CheckDelegateTake<T> {
pub(crate) fn applies_to(call: &Call<T>) -> bool {
matches!(
call,
Call::increase_take { .. } | Call::decrease_take { .. }
)
}

pub fn check(who: &T::AccountId, call: &Call<T>) -> Result<(), Error<T>> {
match call {
Call::increase_take { hotkey, take } | Call::decrease_take { hotkey, take } => {
Expand All @@ -42,8 +47,10 @@ where
{
type Pre = ();

fn weight(_call: &CallOf<T>) -> Weight {
<T as Config>::WeightInfo::check_delegate_take_extension()
fn weight(call: &CallOf<T>) -> Weight {
applicable_call(call, Self::applies_to)
.map(|_| <T as Config>::WeightInfo::check_delegate_take_extension())
.unwrap_or(Weight::zero())
}

fn pre_dispatch(
Expand All @@ -54,7 +61,7 @@ where
return Ok(());
};

let Some(call) = call.is_sub_type() else {
let Some(call) = applicable_call(call, Self::applies_to) else {
return Ok(());
};

Expand All @@ -68,7 +75,10 @@ mod tests {
use super::*;
use crate::{Error, tests::mock::*};
use frame_support::{
assert_ok, dispatch::DispatchResultWithPostInfo, traits::ExtendedDispatchable,
assert_ok,
dispatch::{DispatchExtension, DispatchResultWithPostInfo},
traits::ExtendedDispatchable,
weights::Weight,
};
use sp_core::U256;
use sp_runtime::DispatchError;
Expand All @@ -91,6 +101,39 @@ mod tests {
result.unwrap_err().error
}

fn add_stake_call() -> RuntimeCall {
RuntimeCall::SubtensorModule(SubtensorCall::add_stake {
hotkey: U256::from(1),
netuid: 1u16.into(),
amount_staked: 1_000u64.into(),
})
}

#[test]
fn weight_only_charges_delegate_take_calls() {
let expected = <Test as crate::Config>::WeightInfo::check_delegate_take_extension();

for call in [
RuntimeCall::System(frame_system::Call::remark { remark: vec![] }),
add_stake_call(),
] {
assert_eq!(
<CheckDelegateTake<Test> as DispatchExtension<RuntimeCall>>::weight(&call),
Weight::zero()
);
}

for call in [
increase_take_call(U256::from(1), 0),
decrease_take_call(U256::from(1), 0),
] {
assert_eq!(
<CheckDelegateTake<Test> as DispatchExtension<RuntimeCall>>::weight(&call),
expected
);
}
}

#[test]
fn accepts_owner_with_valid_take() {
new_test_ext(0).execute_with(|| {
Expand Down
54 changes: 46 additions & 8 deletions pallets/subtensor/src/guards/check_evm_key_association.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::{CallOf, DispatchableOriginOf, applicable_call};
use crate::weights::WeightInfo;
use crate::{Call, Config, Error, Pallet};
use frame_support::{
Expand All @@ -8,16 +9,17 @@ use frame_support::{
use sp_runtime::traits::Dispatchable;
use sp_std::marker::PhantomData;

type CallOf<T> = <T as frame_system::Config>::RuntimeCall;
type DispatchableOriginOf<T> = <CallOf<T> as Dispatchable>::RuntimeOrigin;

/// Dispatch extension for EVM-key association preconditions.
///
/// Signed EVM-key association calls are checked for subnet registration and
/// cooldown before dispatch; unrelated calls and non-signed origins pass through.
pub struct CheckEvmKeyAssociation<T: Config>(PhantomData<T>);

impl<T: Config> CheckEvmKeyAssociation<T> {
pub(crate) fn applies_to(call: &Call<T>) -> bool {
matches!(call, Call::associate_evm_key { .. })
}

pub fn check(who: &T::AccountId, call: &Call<T>) -> Result<(), Error<T>> {
match call {
Call::associate_evm_key { netuid, .. } => {
Expand All @@ -40,8 +42,10 @@ where
{
type Pre = ();

fn weight(_call: &CallOf<T>) -> Weight {
<T as Config>::WeightInfo::check_evm_key_association_extension()
fn weight(call: &CallOf<T>) -> Weight {
applicable_call(call, Self::applies_to)
.map(|_| <T as Config>::WeightInfo::check_evm_key_association_extension())
.unwrap_or(Weight::zero())
}

fn pre_dispatch(
Expand All @@ -52,7 +56,7 @@ where
return Ok(());
};

let Some(call) = call.is_sub_type() else {
let Some(call) = applicable_call(call, Self::applies_to) else {
return Ok(());
};

Expand All @@ -64,10 +68,13 @@ where
#[allow(clippy::unwrap_used, clippy::arithmetic_side_effects)]
mod tests {
use super::CheckEvmKeyAssociation;
use crate::{AssociatedEvmAddress, Error, tests::mock::*};
use crate::{AssociatedEvmAddress, Error, tests::mock::*, weights::WeightInfo as _};
use codec::Encode;
use frame_support::{
assert_ok, dispatch::DispatchResultWithPostInfo, traits::ExtendedDispatchable,
assert_ok,
dispatch::{DispatchExtension, DispatchResultWithPostInfo},
traits::ExtendedDispatchable,
weights::Weight,
};
use frame_system::Call as SystemCall;
use sp_core::{H160, Pair, U256, ecdsa, keccak_256};
Expand Down Expand Up @@ -139,6 +146,37 @@ mod tests {
)
}

fn add_stake_call() -> RuntimeCall {
RuntimeCall::SubtensorModule(SubtensorCall::add_stake {
hotkey: U256::from(1),
netuid: 1u16.into(),
amount_staked: 1_000u64.into(),
})
}

#[test]
fn weight_only_charges_evm_key_association_calls() {
let netuid = NetUid::from(1);
let expected = <Test as crate::Config>::WeightInfo::check_evm_key_association_extension();

for call in [
RuntimeCall::System(SystemCall::remark { remark: vec![] }),
add_stake_call(),
] {
assert_eq!(
<CheckEvmKeyAssociation<Test> as DispatchExtension<RuntimeCall>>::weight(&call),
Weight::zero()
);
}

assert_eq!(
<CheckEvmKeyAssociation<Test> as DispatchExtension<RuntimeCall>>::weight(
&dummy_associate_call(netuid)
),
expected
);
}

#[test]
fn unrelated_calls_pass_through() {
new_test_ext(0).execute_with(|| {
Expand Down
Loading
Loading