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
2 changes: 2 additions & 0 deletions .github/workflows/typescript-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ jobs:
binary: fast
- test: zombienet_coldkey_swap
binary: fast
- test: zombienet_subnets
binary: fast

name: "typescript-e2e-${{ matrix.test }}"

Expand Down
21 changes: 21 additions & 0 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,12 @@ pub mod pallet {
10u16
}

/// Default value for AutoParentDelegationEnabled.
#[pallet::type_value]
pub fn DefaultAutoParentDelegationEnabled<T: Config>() -> bool {
true
}

#[pallet::storage]
pub type MinActivityCutoff<T: Config> =
StorageValue<_, u16, ValueQuery, DefaultMinActivityCutoff<T>>;
Expand Down Expand Up @@ -2454,6 +2460,21 @@ pub mod pallet {
pub type BurnIncreaseMult<T> =
StorageMap<_, Identity, NetUid, U64F64, ValueQuery, DefaultBurnIncreaseMult<T>>;

/// --- MAP ( hotkey ) --> parent_delegation_enabled
///
/// When `true`, this root validator allows auto parent delegation.
/// Defaults to `true`; validators can opt out at any time
/// by calling `set_auto_parent_delegation_enabled(false)`.
#[pallet::storage]
pub type AutoParentDelegationEnabled<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
bool,
ValueQuery,
DefaultAutoParentDelegationEnabled<T>, // default = true
>;

/// ==================
/// ==== Genesis =====
/// ==================
Expand Down
33 changes: 33 additions & 0 deletions pallets/subtensor/src/macros/dispatches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2506,5 +2506,38 @@ mod dispatches {
) -> DispatchResult {
Self::do_register_limit(origin, netuid, hotkey, limit_price)
}

/// --- Allows a root validator to toggle auto parent delegation
/// for new subnets owner hotkey
#[pallet::call_index(135)]
#[pallet::weight((
Weight::from_parts(21_000_000, 0)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64)),
DispatchClass::Normal,
Pays::Yes
))]
pub fn set_auto_parent_delegation_enabled(
origin: OriginFor<T>,
hotkey: T::AccountId,
enabled: bool,
) -> DispatchResult {
let coldkey = ensure_signed(origin)?;

ensure!(
Self::coldkey_owns_hotkey(&coldkey, &hotkey),
Error::<T>::NonAssociatedColdKey
);

ensure!(
Self::is_hotkey_registered_on_network(NetUid::ROOT, &hotkey),
Error::<T>::HotKeyNotRegisteredInSubNet
);

AutoParentDelegationEnabled::<T>::insert(&hotkey, enabled);

Self::deposit_event(Event::AutoParentDelegationEnabledSet { hotkey, enabled });
Ok(())
}
}
}
8 changes: 8 additions & 0 deletions pallets/subtensor/src/macros/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,5 +562,13 @@ mod events {
/// The burn increase multiplier value for neuron registration.
burn_increase_mult: u64,
},

/// A root validator toggled the "auto parent delegation" flag.
AutoParentDelegationEnabledSet {
/// The validator hotkey.
hotkey: T::AccountId,
/// Whether delegation is now enabled.
enabled: bool,
},
}
}
9 changes: 9 additions & 0 deletions pallets/subtensor/src/staking/set_children.rs
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,10 @@ impl<T: Config> Pallet<T> {
ChildkeyTake::<T>::get(hotkey, netuid)
}

pub fn get_auto_parent_delegation_enabled(root_validator_hotkey: &T::AccountId) -> bool {
AutoParentDelegationEnabled::<T>::get(root_validator_hotkey)
}

////////////////////////////////////////////////////////////
// State cleaners (for use in migration)
// TODO: Deprecate when the state is clean for a while
Expand Down Expand Up @@ -832,6 +836,11 @@ impl<T: Config> Pallet<T> {
continue;
}

// Skip if root validator disabled auto parent delegation via AutoParentDelegationEnabled flag
if !Self::get_auto_parent_delegation_enabled(&root_validator_hotkey) {
continue;
}

// Look up the coldkey that owns this root validator hotkey.
let coldkey = Self::get_owning_coldkey_for_hotkey(&root_validator_hotkey);

Expand Down
130 changes: 130 additions & 0 deletions pallets/subtensor/src/tests/children.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4514,3 +4514,133 @@ fn test_register_network_schedules_root_validators() {
));
});
}

// Test that register_network automatically sets root validators as parents of the
// subnet owner, only if AutoParentDelegationEnabled is enabled (default).
// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_register_network_schedules_root_validators_auto_parent_delegation_flag --exact --show-output --nocapture
#[test]
fn test_register_network_schedules_root_validators_auto_parent_delegation_flag() {
new_test_ext(1).execute_with(|| {
// --- Setup root network and root validators ---
let root_val_coldkey_1 = U256::from(100);
let root_val_hotkey_1 = U256::from(101);
let root_val_coldkey_2 = U256::from(200);
let root_val_hotkey_2 = U256::from(201);

add_network(NetUid::ROOT, 1, 0);

// Root validators need to be registered on some subnet before root_register.
// Create a bootstrap subnet for that purpose.
let bootstrap_netuid = NetUid::from(1);
add_network(bootstrap_netuid, 1, 0);
register_ok_neuron(bootstrap_netuid, root_val_hotkey_1, root_val_coldkey_1, 0);
register_ok_neuron(bootstrap_netuid, root_val_hotkey_2, root_val_coldkey_2, 0);

assert_ok!(SubtensorModule::root_register(
RuntimeOrigin::signed(root_val_coldkey_1),
root_val_hotkey_1,
));
assert_ok!(SubtensorModule::root_register(
RuntimeOrigin::signed(root_val_coldkey_2),
root_val_hotkey_2,
));

// Give root validators significant stake on root and bootstrap subnet
let root_stake = AlphaBalance::from(1_000_000_000);
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
&root_val_hotkey_1,
&root_val_coldkey_1,
NetUid::ROOT,
root_stake,
);
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
&root_val_hotkey_2,
&root_val_coldkey_2,
NetUid::ROOT,
root_stake,
);

// --- Minimize cooldown so pending children activate quickly ---
assert_ok!(SubtensorModule::set_pending_childkey_cooldown(
RuntimeOrigin::root(),
0,
));

// --- Set a high stake threshold ---
let high_threshold = 500_000_000u64;
SubtensorModule::set_stake_threshold(high_threshold);

// --- Register a new subnet (this should automatically call do_set_root_validators_for_subnet) ---
let subnet_owner_coldkey = U256::from(1001);
let subnet_owner_hotkey = U256::from(1002);
let lock_cost = SubtensorModule::get_network_lock_cost();
SubtensorModule::add_balance_to_coldkey_account(&subnet_owner_coldkey, lock_cost.into());
TotalIssuance::<Test>::mutate(|total| {
*total = total.saturating_add(lock_cost);
});

assert_ok!(SubtensorModule::set_auto_parent_delegation_enabled(
RuntimeOrigin::signed(root_val_coldkey_1),
root_val_hotkey_1,
false,
));

assert_ok!(SubtensorModule::register_network(
RuntimeOrigin::signed(subnet_owner_coldkey),
subnet_owner_hotkey,
));

// Determine the netuid that was just created
let netuid: NetUid = (TotalNetworks::<Test>::get().saturating_sub(1)).into();
assert_eq!(
SubnetOwnerHotkey::<Test>::get(netuid),
subnet_owner_hotkey,
"Subnet owner hotkey should be set"
);

// Root validators need stake on the new subnet for child stake inheritance to work
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
&root_val_hotkey_1,
&root_val_coldkey_1,
netuid,
root_stake,
);
SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet(
&root_val_hotkey_2,
&root_val_coldkey_2,
netuid,
root_stake,
);

// --- Verify child keys were applied immediately (SubtokenEnabled is false for new subnets) ---
let children_1 = SubtensorModule::get_children(&root_val_hotkey_1, netuid);
assert_eq!(
children_1,
vec![],
"Root validator 1 not have subnet owner as a child because AutoParentDelegationEnabled is false"
);
let children_2 = SubtensorModule::get_children(&root_val_hotkey_2, netuid);
assert_eq!(
children_2,
vec![(u64::MAX, subnet_owner_hotkey)],
"Root validator 2 should have subnet owner as child"
);

// --- Verify subnet owner can now set weights ---
SubtensorModule::set_weights_set_rate_limit(netuid, 0);
SubtensorModule::set_commit_reveal_weights_enabled(netuid, false);
let version_key = SubtensorModule::get_weights_version_key(netuid);

assert!(
SubtensorModule::check_weights_min_stake(&subnet_owner_hotkey, netuid),
"Subnet owner should have enough inherited stake to set weights"
);
assert_ok!(SubtensorModule::set_weights(
RuntimeOrigin::signed(subnet_owner_hotkey),
netuid,
vec![0],
vec![u16::MAX],
version_key
));
});
}
17 changes: 4 additions & 13 deletions precompiles/src/staking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ use pallet_evm::{
};
use pallet_subtensor_proxy as pallet_proxy;
use precompile_utils::EvmResult;
use precompile_utils::prelude::{RuntimeHelper, revert, Address};
use precompile_utils::prelude::{Address, RuntimeHelper, revert};
use sp_core::{H160, H256, U256};
use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable, StaticLookup, UniqueSaturatedInto};
use sp_std::vec;
Expand All @@ -63,15 +63,12 @@ impl StorageInstance for AllowancesPrefix {

pub type AllowancesStorage = StorageDoubleMap<
AllowancesPrefix,

// For each approver (EVM address as only EVM-natives need the precompile)
Blake2_128Concat,
H160,

// For each pair of (spender, netuid) (EVM address as only EVM-natives need the precompile)
Blake2_128Concat,
(H160, u16),

// Allowed amount
U256,
ValueQuery,
Expand Down Expand Up @@ -627,20 +624,14 @@ where
amount_alpha: U256,
) -> EvmResult<()> {
let spender = handle.context().caller;
let source_address = source_address.0;
let source_address = source_address.0;
let destination_coldkey = R::AccountId::from(destination_coldkey.0);
let hotkey = R::AccountId::from(hotkey.0);
let origin_netuid = try_u16_from_u256(origin_netuid)?;
let destination_netuid = try_u16_from_u256(destination_netuid)?;
let alpha_amount: u64 = amount_alpha.unique_saturated_into();

Self::try_consume_allowance(
handle,
source_address,
spender,
origin_netuid,
amount_alpha,
)?;
Self::try_consume_allowance(handle, source_address, spender, origin_netuid, amount_alpha)?;

let call = pallet_subtensor::Call::<R>::transfer_stake {
destination_coldkey,
Expand All @@ -649,7 +640,7 @@ where
destination_netuid: destination_netuid.into(),
alpha_amount: alpha_amount.into(),
};
let source_id = <R as pallet_evm::Config>::AddressMapping::into_account_id(source_address);
let source_id = <R as pallet_evm::Config>::AddressMapping::into_account_id(source_address);

handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(source_id))
}
Expand Down
25 changes: 25 additions & 0 deletions ts-tests/moonwall.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,31 @@
"endpoints": ["ws://127.0.0.1:9947"]
}
]
}, {
"name": "zombienet_subnets",
"timeout": 600000,
"testFileDir": ["suites/zombienet_subnets"],
"runScripts": [
"generate-types.sh",
"build-spec.sh"
],
"foundation": {
"type": "zombie",
"zombieSpec": {
"configPath": "./configs/zombie_node.json",
"skipBlockCheck": []
}
},
"vitestArgs": {
"bail": 1
},
"connections": [
{
"name": "Node",
"type": "papi",
"endpoints": ["ws://127.0.0.1:9947"]
}
]
}, {
"name": "smoke_mainnet",
"testFileDir": ["suites/smoke"],
Expand Down
Loading
Loading