Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions chain-extensions/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ impl pallet_subtensor::Config for Test {
type GetCommitments = ();
type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage;
type CommitmentsInterface = CommitmentsI;
type PrecompileCleanupInterface = PrecompileCleanupI;
type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit;
type AuthorshipProvider = MockAuthorshipProvider;
type WeightInfo = ();
Expand Down Expand Up @@ -463,6 +464,11 @@ impl CommitmentsInterface for CommitmentsI {
fn purge_netuid(_netuid: NetUid) {}
}

pub struct PrecompileCleanupI;
impl pallet_subtensor::PrecompileCleanupInterface for PrecompileCleanupI {
fn purge_netuid(_netuid: NetUid) {}
}

parameter_types! {
pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) *
BlockWeights::get().max_block;
Expand Down
6 changes: 6 additions & 0 deletions eco-tests/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ impl pallet_subtensor::Config for Test {
type GetCommitments = ();
type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage;
type CommitmentsInterface = CommitmentsI;
type PrecompileCleanupInterface = PrecompileCleanupI;
type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit;
type AuthorshipProvider = MockAuthorshipProvider;
type WeightInfo = ();
Expand Down Expand Up @@ -346,6 +347,11 @@ impl CommitmentsInterface for CommitmentsI {
fn purge_netuid(_netuid: NetUid) {}
}

pub struct PrecompileCleanupI;
impl pallet_subtensor::PrecompileCleanupInterface for PrecompileCleanupI {
fn purge_netuid(_netuid: NetUid) {}
}

parameter_types! {
pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) *
BlockWeights::get().max_block;
Expand Down
6 changes: 6 additions & 0 deletions pallets/admin-utils/src/tests/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ impl pallet_subtensor::Config for Test {
type GetCommitments = ();
type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage;
type CommitmentsInterface = CommitmentsI;
type PrecompileCleanupInterface = PrecompileCleanupI;
type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit;
type AuthorshipProvider = MockAuthorshipProvider;
type WeightInfo = ();
Expand Down Expand Up @@ -366,6 +367,11 @@ impl pallet_subtensor::CommitmentsInterface for CommitmentsI {
fn purge_netuid(_netuid: NetUid) {}
}

pub struct PrecompileCleanupI;
impl pallet_subtensor::PrecompileCleanupInterface for PrecompileCleanupI {
fn purge_netuid(_netuid: NetUid) {}
}

pub struct GrandpaInterfaceImpl;
impl crate::GrandpaInterface<Test> for GrandpaInterfaceImpl {
fn schedule_change(
Expand Down
20 changes: 19 additions & 1 deletion pallets/subtensor/src/coinbase/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
// DEALINGS IN THE SOFTWARE.

use super::*;
use crate::CommitmentsInterface;
use crate::{CommitmentsInterface, PrecompileCleanupInterface};
use safe_math::*;
use substrate_fixed::types::{I64F64, U96F32};
use subtensor_runtime_common::{AlphaBalance, NetUid, NetUidStorageIndex, TaoBalance, Token};
Expand Down Expand Up @@ -218,6 +218,7 @@ impl<T: Config> Pallet<T> {
Self::destroy_alpha_in_out_stakes(netuid)?;
T::SwapInterface::clear_protocol_liquidity(netuid)?;
T::CommitmentsInterface::purge_netuid(netuid);
T::PrecompileCleanupInterface::purge_netuid(netuid);

// --- Remove the network
Self::remove_network(netuid);
Expand Down Expand Up @@ -282,9 +283,14 @@ impl<T: Config> Pallet<T> {
Kappa::<T>::remove(netuid);
Difficulty::<T>::remove(netuid);
MaxAllowedUids::<T>::remove(netuid);
MinAllowedUids::<T>::remove(netuid);
ImmunityPeriod::<T>::remove(netuid);
ActivityCutoff::<T>::remove(netuid);
MinAllowedWeights::<T>::remove(netuid);
MaxWeightsLimit::<T>::remove(netuid);
AdjustmentAlpha::<T>::remove(netuid);
AdjustmentInterval::<T>::remove(netuid);
MinNonImmuneUids::<T>::remove(netuid);
RegistrationsThisInterval::<T>::remove(netuid);
POWRegistrationsThisInterval::<T>::remove(netuid);
BurnRegistrationsThisInterval::<T>::remove(netuid);
Expand All @@ -300,6 +306,11 @@ impl<T: Config> Pallet<T> {
SubnetEmaTaoFlow::<T>::remove(netuid);
SubnetTaoProvided::<T>::remove(netuid);

// --- 12. Root / emission split parameters.
RootProp::<T>::remove(netuid);
RecycleOrBurn::<T>::remove(netuid);
RootClaimableThreshold::<T>::remove(netuid);

// --- 13. Token / mechanism / registration toggles.
TokenSymbol::<T>::remove(netuid);
SubnetMechanism::<T>::remove(netuid);
Expand Down Expand Up @@ -362,12 +373,19 @@ impl<T: Config> Pallet<T> {
StakeWeight::<T>::remove(netuid);
LoadedEmission::<T>::remove(netuid);

// --- 18b. Voting power.
let _ = VotingPower::<T>::clear_prefix(netuid, u32::MAX, None);
VotingPowerTrackingEnabled::<T>::remove(netuid);
VotingPowerDisableAtBlock::<T>::remove(netuid);
VotingPowerEmaAlpha::<T>::remove(netuid);

// --- 19. DMAPs where netuid is the FIRST key: clear by prefix.
let _ = BlockAtRegistration::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = Axons::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = NeuronCertificates::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = Prometheus::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = AlphaDividendsPerSubnet::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = RootAlphaDividendsPerSubnet::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = PendingChildKeys::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = AssociatedEvmAddress::<T>::clear_prefix(netuid, u32::MAX, None);

Expand Down
5 changes: 5 additions & 0 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2747,3 +2747,8 @@ impl<T> ProxyInterface<T> for () {
pub trait CommitmentsInterface {
fn purge_netuid(netuid: NetUid);
}

/// EVM precompiles that hold per-subnet state implement this to clean up on subnet deregistration.
pub trait PrecompileCleanupInterface {
fn purge_netuid(netuid: NetUid);
}
5 changes: 4 additions & 1 deletion pallets/subtensor/src/macros/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use frame_support::pallet_macros::pallet_section;
#[pallet_section]
mod config {

use crate::{CommitmentsInterface, GetAlphaForTao, GetTaoForAlpha};
use crate::{CommitmentsInterface, GetAlphaForTao, GetTaoForAlpha, PrecompileCleanupInterface};
use pallet_commitments::GetCommitments;
use subtensor_runtime_common::AuthorshipInfo;
use subtensor_swap_interface::{SwapEngine, SwapHandler};
Expand Down Expand Up @@ -60,6 +60,9 @@ mod config {
/// Interface to clean commitments on network dissolution.
type CommitmentsInterface: CommitmentsInterface;

/// Interface to clean EVM precompile state (e.g. allowances) on network dissolution.
type PrecompileCleanupInterface: PrecompileCleanupInterface;

/// Rate limit for associating an EVM key.
type EvmKeyAssociateRateLimit: Get<u64>;

Expand Down
6 changes: 6 additions & 0 deletions pallets/subtensor/src/tests/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ impl crate::Config for Test {
type GetCommitments = ();
type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage;
type CommitmentsInterface = CommitmentsI;
type PrecompileCleanupInterface = PrecompileCleanupI;
type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit;
type AuthorshipProvider = MockAuthorshipProvider;
type WeightInfo = ();
Expand Down Expand Up @@ -361,6 +362,11 @@ impl CommitmentsInterface for CommitmentsI {
fn purge_netuid(_netuid: NetUid) {}
}

pub struct PrecompileCleanupI;
impl crate::PrecompileCleanupInterface for PrecompileCleanupI {
fn purge_netuid(_netuid: NetUid) {}
}

parameter_types! {
pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) *
BlockWeights::get().max_block;
Expand Down
68 changes: 68 additions & 0 deletions pallets/subtensor/src/tests/networks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2242,6 +2242,74 @@ fn dissolve_clears_all_mechanism_scoped_maps_for_all_mechanisms() {
});
}

#[test]
fn test_dissolve_network_clears_orphaned_storage() {
new_test_ext(0).execute_with(|| {
let cold = U256::from(1);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add the check AllowancesStorage is removed after subnet de-reg

let hot = U256::from(2);
let net = add_dynamic_network(&hot, &cold);

let hotkey1 = U256::from(100);
let hotkey2 = U256::from(200);

// Seed RootAlphaDividendsPerSubnet (DMAP, netuid is first key).
RootAlphaDividendsPerSubnet::<Test>::insert(net, hotkey1, AlphaBalance::from(5_000u64));
RootAlphaDividendsPerSubnet::<Test>::insert(net, hotkey2, AlphaBalance::from(10_000u64));

// Seed VotingPower (DMAP, netuid is first key).
VotingPower::<Test>::insert(net, hotkey1, 42u64);

// Seed simple StorageMaps that were previously missed.
MinAllowedUids::<Test>::insert(net, 4u16);
MaxWeightsLimit::<Test>::insert(net, 100u16);
AdjustmentAlpha::<Test>::insert(net, 999u64);
AdjustmentInterval::<Test>::insert(net, 50u16);
MinNonImmuneUids::<Test>::insert(net, 2u16);
RecycleOrBurn::<Test>::insert(net, RecycleOrBurnEnum::Recycle);
VotingPowerTrackingEnabled::<Test>::insert(net, true);
VotingPowerDisableAtBlock::<Test>::insert(net, 1000u64);
VotingPowerEmaAlpha::<Test>::insert(net, 500u64);

// Seed a different netuid to ensure it's untouched.
let other_net = add_dynamic_network(&U256::from(3), &U256::from(4));
RootAlphaDividendsPerSubnet::<Test>::insert(
other_net,
hotkey1,
AlphaBalance::from(7_000u64),
);
VotingPower::<Test>::insert(other_net, hotkey1, 99u64);

// Dissolve the network.
assert_ok!(SubtensorModule::do_dissolve_network(net));

// Double-map entries for the dissolved netuid should be gone.
assert!(!RootAlphaDividendsPerSubnet::<Test>::contains_key(
net, hotkey1
));
assert!(!RootAlphaDividendsPerSubnet::<Test>::contains_key(
net, hotkey2
));
assert!(!VotingPower::<Test>::contains_key(net, hotkey1));

// Simple maps for the dissolved netuid should be gone.
assert!(!MinAllowedUids::<Test>::contains_key(net));
assert!(!MaxWeightsLimit::<Test>::contains_key(net));
assert!(!AdjustmentAlpha::<Test>::contains_key(net));
assert!(!AdjustmentInterval::<Test>::contains_key(net));
assert!(!MinNonImmuneUids::<Test>::contains_key(net));
assert!(!RecycleOrBurn::<Test>::contains_key(net));
assert!(!VotingPowerTrackingEnabled::<Test>::contains_key(net));
assert!(!VotingPowerDisableAtBlock::<Test>::contains_key(net));
assert!(!VotingPowerEmaAlpha::<Test>::contains_key(net));

// Entries for the other netuid should remain.
assert!(RootAlphaDividendsPerSubnet::<Test>::contains_key(
other_net, hotkey1
));
assert!(VotingPower::<Test>::contains_key(other_net, hotkey1));
});
}

fn owner_alpha_from_lock_and_price(lock_cost_u64: u64, price: U96F32) -> u64 {
let alpha = (U96F32::from_num(lock_cost_u64)
.checked_div(price)
Expand Down
6 changes: 6 additions & 0 deletions pallets/transaction-fee/src/tests/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ impl pallet_subtensor::Config for Test {
type GetCommitments = ();
type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage;
type CommitmentsInterface = CommitmentsI;
type PrecompileCleanupInterface = PrecompileCleanupI;
type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit;
type AuthorshipProvider = MockAuthorshipProvider;
type WeightInfo = ();
Expand Down Expand Up @@ -438,6 +439,11 @@ impl pallet_subtensor::CommitmentsInterface for CommitmentsI {
fn purge_netuid(_netuid: NetUid) {}
}

pub struct PrecompileCleanupI;
impl pallet_subtensor::PrecompileCleanupInterface for PrecompileCleanupI {
fn purge_netuid(_netuid: NetUid) {}
}

parameter_types! {
pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) *
BlockWeights::get().max_block;
Expand Down
12 changes: 11 additions & 1 deletion precompiles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub use metagraph::MetagraphPrecompile;
pub use neuron::NeuronPrecompile;
pub use proxy::ProxyPrecompile;
pub use sr25519::Sr25519Verify;
pub use staking::{StakingPrecompile, StakingPrecompileV2};
pub use staking::{StakingPrecompile, StakingPrecompileV2, purge_allowances_for_netuid};
pub use storage_query::StorageQueryPrecompile;
pub use subnet::SubnetPrecompile;
pub use uid_lookup::UidLookupPrecompile;
Expand Down Expand Up @@ -285,6 +285,16 @@ where
}
}

/// Implementation of [`pallet_subtensor::PrecompileCleanupInterface`] that cleans up
/// EVM precompile storage (e.g. staking allowances) when a subnet is deregistered.
pub struct PrecompileCleanup;

impl pallet_subtensor::PrecompileCleanupInterface for PrecompileCleanup {
fn purge_netuid(netuid: subtensor_runtime_common::NetUid) {
purge_allowances_for_netuid(netuid.into());
}
}

fn hash(a: u64) -> H160 {
H160::from_low_u64_be(a)
}
Expand Down
59 changes: 59 additions & 0 deletions precompiles/src/staking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,25 @@ pub type AllowancesStorage = StorageDoubleMap<
ValueQuery,
>;

/// Remove all allowance entries for the given `netuid`.
///
/// Because `netuid` is embedded in the second key `(spender, netuid)`, we must iterate
/// all entries and filter. Called during subnet deregistration.
pub fn purge_allowances_for_netuid(netuid: u16) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach is already mentioned at #2478

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the context! Yes, the concern about unbounded iteration was raised in #2478. In practice though, remove_network already does the same iterate-and-filter pattern for several other DMAPs where netuid is not the first key (ChildkeyTake, ChildKeys, ParentKeys, LastHotkeyEmissionOnNetuid, TransactionKeyLastBlock, StakingOperationRateLimiter), so this is consistent with the existing approach.

The number of allowance entries per netuid should also be small in practice since each requires an explicit approve call from an EVM user.

let to_rm: Vec<(H160, H160)> = AllowancesStorage::iter()
.filter_map(|(approver, (spender, n), _)| {
if n == netuid {
Some((approver, spender))
} else {
None
}
})
.collect();
for (approver, spender) in to_rm {
AllowancesStorage::remove(approver, (spender, netuid));
}
}

// Old StakingPrecompile had ETH-precision in values, which was not alligned with Substrate API. So
// it's kinda deprecated, but exists for backward compatibility. Eventually, we should remove it
// to stop supporting both precompiles.
Expand Down Expand Up @@ -895,3 +914,43 @@ fn try_u64_from_u256(value: U256) -> Result<u64, PrecompileFailure> {
exit_status: ExitError::Other("the value is outside of u64 bounds".into()),
})
}

#[cfg(test)]
mod tests {
use super::*;
use sp_core::H160;

fn addr(n: u64) -> H160 {
H160::from_low_u64_be(n)
}

#[test]
fn purge_allowances_for_netuid_removes_matching_entries() {
sp_io::TestExternalities::default().execute_with(|| {
let approver1 = addr(1);
let approver2 = addr(2);
let spender1 = addr(10);
let spender2 = addr(20);

// Insert entries for netuid 1 and netuid 2.
AllowancesStorage::insert(approver1, (spender1, 1u16), U256::from(100u64));
AllowancesStorage::insert(approver1, (spender2, 1u16), U256::from(200u64));
AllowancesStorage::insert(approver2, (spender1, 1u16), U256::from(300u64));
// This one belongs to a different netuid and must not be touched.
AllowancesStorage::insert(approver1, (spender1, 2u16), U256::from(999u64));

purge_allowances_for_netuid(1u16);

// All netuid-1 entries should be gone.
assert!(AllowancesStorage::get(approver1, (spender1, 1u16)).is_zero());
assert!(AllowancesStorage::get(approver1, (spender2, 1u16)).is_zero());
assert!(AllowancesStorage::get(approver2, (spender1, 1u16)).is_zero());

// Netuid-2 entry should be untouched.
assert_eq!(
AllowancesStorage::get(approver1, (spender1, 2u16)),
U256::from(999u64)
);
});
}
}
1 change: 1 addition & 0 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,7 @@ impl pallet_subtensor::Config for Runtime {
type GetCommitments = GetCommitmentsStruct;
type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage;
type CommitmentsInterface = CommitmentsI;
type PrecompileCleanupInterface = subtensor_precompiles::PrecompileCleanup;
type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit;
type AuthorshipProvider = BlockAuthorFromAura<Aura>;
type WeightInfo = pallet_subtensor::weights::SubstrateWeight<Runtime>;
Expand Down
Loading