From 3e31bb9c755deea79da9e8c973a27ebb07f47220 Mon Sep 17 00:00:00 2001
From: Pawel Polewicz
Date: Fri, 27 Mar 2026 21:23:01 +0000
Subject: [PATCH] Add Validate proxy type and validator flow tests
---
Cargo.lock | 1 +
common/src/lib.rs | 33 ++
.../test/pure-proxy.precompile.test.ts | 45 ++
pallets/subtensor/src/utils/evm.rs | 2 +-
runtime/Cargo.toml | 2 +
runtime/src/lib.rs | 28 +
runtime/tests/pallet_proxy.rs | 543 +++++++++++++++++-
7 files changed, 651 insertions(+), 3 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index c582dd22cc..0f73b45361 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8391,6 +8391,7 @@ dependencies = [
"frame-system-rpc-runtime-api",
"frame-try-runtime",
"hex",
+ "libsecp256k1",
"log",
"pallet-admin-utils",
"pallet-aura",
diff --git a/common/src/lib.rs b/common/src/lib.rs
index 70fa42c32b..c0baa443a0 100644
--- a/common/src/lib.rs
+++ b/common/src/lib.rs
@@ -165,6 +165,7 @@ pub enum ProxyType {
SwapHotkey,
SubnetLeaseBeneficiary, // Used to operate the leased subnet
RootClaim,
+ Validate,
}
impl TryFrom for ProxyType {
@@ -190,6 +191,7 @@ impl TryFrom for ProxyType {
15 => Ok(Self::SwapHotkey),
16 => Ok(Self::SubnetLeaseBeneficiary),
17 => Ok(Self::RootClaim),
+ 18 => Ok(Self::Validate),
_ => Err(()),
}
}
@@ -216,6 +218,7 @@ impl From for u8 {
ProxyType::SwapHotkey => 15,
ProxyType::SubnetLeaseBeneficiary => 16,
ProxyType::RootClaim => 17,
+ ProxyType::Validate => 18,
}
}
}
@@ -453,4 +456,34 @@ mod tests {
fn netuid_has_u16_bin_repr() {
assert_eq!(NetUid(5).encode(), 5u16.encode());
}
+
+ #[test]
+ fn proxy_type_ids_remain_stable_and_validate_roundtrips() {
+ let expected_ids = [
+ (ProxyType::Any, 0u8),
+ (ProxyType::Owner, 1u8),
+ (ProxyType::NonCritical, 2u8),
+ (ProxyType::NonTransfer, 3u8),
+ (ProxyType::Senate, 4u8),
+ (ProxyType::NonFungible, 5u8),
+ (ProxyType::Triumvirate, 6u8),
+ (ProxyType::Governance, 7u8),
+ (ProxyType::Staking, 8u8),
+ (ProxyType::Registration, 9u8),
+ (ProxyType::Transfer, 10u8),
+ (ProxyType::SmallTransfer, 11u8),
+ (ProxyType::RootWeights, 12u8),
+ (ProxyType::ChildKeys, 13u8),
+ (ProxyType::SudoUncheckedSetCode, 14u8),
+ (ProxyType::SwapHotkey, 15u8),
+ (ProxyType::SubnetLeaseBeneficiary, 16u8),
+ (ProxyType::RootClaim, 17u8),
+ (ProxyType::Validate, 18u8),
+ ];
+
+ for (proxy_type, id) in expected_ids {
+ assert_eq!(>::from(proxy_type), id);
+ assert_eq!(ProxyType::try_from(id), Ok(proxy_type));
+ }
+ }
}
diff --git a/contract-tests/test/pure-proxy.precompile.test.ts b/contract-tests/test/pure-proxy.precompile.test.ts
index f893b6d77a..885e186013 100644
--- a/contract-tests/test/pure-proxy.precompile.test.ts
+++ b/contract-tests/test/pure-proxy.precompile.test.ts
@@ -28,6 +28,12 @@ async function getTransferCallCode(api: TypedApi, receiver: KeyPa
return [...data]
}
+async function getRemarkCallCode(api: TypedApi) {
+ const unsignedTx = api.tx.System.remark({ remark: new Uint8Array([1, 2, 3]) });
+ const encodedCallDataBytes = await unsignedTx.getEncodedData();
+ return [...encodedCallDataBytes.asBytes()];
+}
+
async function getProxies(api: TypedApi, address: string) {
const entries = await api.query.Proxy.Proxies.getEntries()
const result = []
@@ -207,4 +213,43 @@ describe("Test pure proxy precompile", () => {
assert.equal(Number(proxyInfo[2]), delay, "delay should match")
}
})
+
+ it("Call createPureProxy with Validate type", async () => {
+ const validateType = 18;
+ const validateWallet = generateRandomEthersWallet();
+ await forceSetBalanceToEthAddress(api, validateWallet.address);
+
+ const proxiesBefore = await getProxies(api, convertH160ToSS58(validateWallet.address));
+ const contract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, validateWallet);
+
+ const tx = await contract.createPureProxy(validateType, 0, 0);
+ await tx.wait();
+
+ const proxiesAfter = await getProxies(api, convertH160ToSS58(validateWallet.address));
+ assert.equal(proxiesAfter.length, proxiesBefore.length + 1, "validate pure proxy should be created");
+ })
+
+ it("Call addProxy with Validate type, then reject non-Validate call", async () => {
+ const validateType = 18;
+ const ownerWallet = generateRandomEthersWallet();
+ const delegateWallet = generateRandomEthersWallet();
+ await forceSetBalanceToEthAddress(api, ownerWallet.address);
+ await forceSetBalanceToEthAddress(api, delegateWallet.address);
+
+ const ownerContract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, ownerWallet);
+ const addProxyTx = await ownerContract.addProxy(convertH160ToPublicKey(delegateWallet.address), validateType, 0);
+ await addProxyTx.wait();
+
+ const delegateContract = new ethers.Contract(IPROXY_ADDRESS, IProxyABI, delegateWallet);
+ const remarkCall = await getRemarkCallCode(api);
+
+ await assert.rejects(
+ async () => {
+ const tx = await delegateContract.proxyCall(convertH160ToPublicKey(ownerWallet.address), [validateType], remarkCall);
+ await tx.wait();
+ },
+ undefined,
+ "validate proxy should reject a remark call"
+ );
+ })
});
diff --git a/pallets/subtensor/src/utils/evm.rs b/pallets/subtensor/src/utils/evm.rs
index 1ac5c65b24..2d1fa6e74f 100644
--- a/pallets/subtensor/src/utils/evm.rs
+++ b/pallets/subtensor/src/utils/evm.rs
@@ -10,7 +10,7 @@ use subtensor_runtime_common::NetUid;
const MESSAGE_PREFIX: &str = "\x19Ethereum Signed Message:\n";
impl Pallet {
- pub(crate) fn hash_message_eip191>(message: M) -> [u8; 32] {
+ pub fn hash_message_eip191>(message: M) -> [u8; 32] {
let msg_len = message.as_ref().len().to_string();
keccak_256(
&[
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index 2d7b2250d6..020650f701 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -158,6 +158,7 @@ ethereum.workspace = true
[dev-dependencies]
frame-metadata.workspace = true
+libsecp256k1.workspace = true
sp-io.workspace = true
sp-tracing.workspace = true
precompile-utils = { workspace = true, features = ["testing"] }
@@ -280,6 +281,7 @@ std = [
"pallet-shield/std",
"stp-shield/std",
"sp-weights/std",
+ "libsecp256k1/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index ecb8944aa9..7f564f436e 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -810,6 +810,34 @@ impl InstanceFilter for ProxyType {
c,
RuntimeCall::SubtensorModule(pallet_subtensor::Call::claim_root { .. })
),
+ ProxyType::Validate => {
+ matches!(
+ c,
+ RuntimeCall::SubtensorModule(
+ pallet_subtensor::Call::serve_axon { .. }
+ | pallet_subtensor::Call::serve_axon_tls { .. }
+ | pallet_subtensor::Call::associate_evm_key { .. }
+ | pallet_subtensor::Call::set_weights { .. }
+ | pallet_subtensor::Call::set_mechanism_weights { .. }
+ | pallet_subtensor::Call::batch_set_weights { .. }
+ | pallet_subtensor::Call::commit_weights { .. }
+ | pallet_subtensor::Call::commit_mechanism_weights { .. }
+ | pallet_subtensor::Call::batch_commit_weights { .. }
+ | pallet_subtensor::Call::reveal_weights { .. }
+ | pallet_subtensor::Call::reveal_mechanism_weights { .. }
+ | pallet_subtensor::Call::batch_reveal_weights { .. }
+ | pallet_subtensor::Call::commit_timelocked_weights { .. }
+ | pallet_subtensor::Call::commit_crv3_mechanism_weights { .. }
+ | pallet_subtensor::Call::commit_timelocked_mechanism_weights { .. }
+ ) | RuntimeCall::Commitments(pallet_commitments::Call::set_commitment { .. })
+ ) || matches!(
+ c,
+ RuntimeCall::Proxy(pallet_subtensor_proxy::Call::add_proxy {
+ proxy_type: ProxyType::Validate,
+ ..
+ })
+ )
+ }
}
}
fn is_superset(&self, o: &Self) -> bool {
diff --git a/runtime/tests/pallet_proxy.rs b/runtime/tests/pallet_proxy.rs
index 1da1c4cdcc..1e8902a7b4 100644
--- a/runtime/tests/pallet_proxy.rs
+++ b/runtime/tests/pallet_proxy.rs
@@ -1,16 +1,22 @@
#![allow(clippy::unwrap_used)]
+#![allow(clippy::expect_used)]
+use codec::Encode;
use frame_support::{assert_ok, traits::InstanceFilter};
use node_subtensor_runtime::{
- BalancesCall, BuildStorage, Proxy, Runtime, RuntimeCall, RuntimeEvent, RuntimeGenesisConfig,
- RuntimeOrigin, SubtensorModule, System, SystemCall,
+ BalancesCall, BuildStorage, Commitments, Proxy, Runtime, RuntimeCall, RuntimeEvent,
+ RuntimeGenesisConfig, RuntimeOrigin, SubtensorModule, System, SystemCall,
};
+use pallet_commitments::{CommitmentInfo, Data};
use pallet_subtensor_proxy as pallet_proxy;
+use sp_core::{H160, H256, Pair, ecdsa, keccak_256};
+use sp_runtime::BoundedVec;
use subtensor_runtime_common::{AccountId, NetUid, ProxyType, TaoBalance};
const ACCOUNT: [u8; 32] = [1_u8; 32];
const DELEGATE: [u8; 32] = [2_u8; 32];
const OTHER_ACCOUNT: [u8; 32] = [3_u8; 32];
+const THIRD_ACCOUNT: [u8; 32] = [4_u8; 32];
type SystemError = frame_system::Error;
@@ -23,6 +29,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
(AccountId::from(ACCOUNT), amount),
(AccountId::from(DELEGATE), amount),
(AccountId::from(OTHER_ACCOUNT), amount),
+ (AccountId::from(THIRD_ACCOUNT), amount),
],
dev_accounts: None,
},
@@ -137,6 +144,234 @@ fn call_register() -> RuntimeCall {
})
}
+fn call_swap_hotkey() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::swap_hotkey {
+ hotkey: AccountId::from(ACCOUNT),
+ new_hotkey: AccountId::from(OTHER_ACCOUNT),
+ netuid: Some(NetUid::from(1)),
+ })
+}
+
+fn call_set_children() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::set_children {
+ hotkey: AccountId::from(ACCOUNT),
+ netuid: NetUid::from(1),
+ children: vec![(1, AccountId::from(OTHER_ACCOUNT))],
+ })
+}
+
+fn call_serve_axon() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::serve_axon {
+ netuid: NetUid::from(1),
+ version: 1,
+ ip: 0,
+ port: 8080,
+ ip_type: 4,
+ protocol: 0,
+ placeholder1: 0,
+ placeholder2: 0,
+ })
+}
+
+fn call_serve_axon_tls() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::serve_axon_tls {
+ netuid: NetUid::from(1),
+ version: 1,
+ ip: 0,
+ port: 8080,
+ ip_type: 4,
+ protocol: 0,
+ placeholder1: 0,
+ placeholder2: 0,
+ certificate: b"CERT".to_vec(),
+ })
+}
+
+fn call_associate_evm_key() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::associate_evm_key {
+ netuid: NetUid::from(1),
+ evm_key: H160::repeat_byte(1),
+ block_number: 1,
+ signature: ecdsa::Signature::from_raw([0u8; 65]),
+ })
+}
+
+fn call_set_weights() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::set_weights {
+ netuid: NetUid::from(1),
+ dests: vec![0],
+ weights: vec![u16::MAX],
+ version_key: 0,
+ })
+}
+
+fn call_set_mechanism_weights() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::set_mechanism_weights {
+ netuid: NetUid::from(1),
+ mecid: 0u8.into(),
+ dests: vec![0],
+ weights: vec![u16::MAX],
+ version_key: 0,
+ })
+}
+
+fn call_batch_set_weights() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::batch_set_weights {
+ netuids: vec![NetUid::from(1).into()],
+ weights: vec![vec![(0u16.into(), u16::MAX.into())]],
+ version_keys: vec![0u64.into()],
+ })
+}
+
+fn call_commit_weights() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::commit_weights {
+ netuid: NetUid::from(1),
+ commit_hash: H256::repeat_byte(1),
+ })
+}
+
+fn call_commit_mechanism_weights() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::commit_mechanism_weights {
+ netuid: NetUid::from(1),
+ mecid: 0u8.into(),
+ commit_hash: H256::repeat_byte(2),
+ })
+}
+
+fn call_batch_commit_weights() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::batch_commit_weights {
+ netuids: vec![NetUid::from(1).into()],
+ commit_hashes: vec![H256::repeat_byte(3)],
+ })
+}
+
+fn call_reveal_weights() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::reveal_weights {
+ netuid: NetUid::from(1),
+ uids: vec![0],
+ values: vec![u16::MAX],
+ salt: vec![1, 2, 3],
+ version_key: 0,
+ })
+}
+
+fn call_reveal_mechanism_weights() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::reveal_mechanism_weights {
+ netuid: NetUid::from(1),
+ mecid: 0u8.into(),
+ uids: vec![0],
+ values: vec![u16::MAX],
+ salt: vec![1, 2, 3],
+ version_key: 0,
+ })
+}
+
+fn call_batch_reveal_weights() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::batch_reveal_weights {
+ netuid: NetUid::from(1),
+ uids_list: vec![vec![0]],
+ values_list: vec![vec![u16::MAX]],
+ salts_list: vec![vec![1, 2, 3]],
+ version_keys: vec![0],
+ })
+}
+
+fn call_commit_timelocked_weights() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::commit_timelocked_weights {
+ netuid: NetUid::from(1),
+ commit: vec![1, 2, 3].try_into().unwrap(),
+ reveal_round: 10,
+ commit_reveal_version: 4,
+ })
+}
+
+fn call_commit_crv3_mechanism_weights() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(pallet_subtensor::Call::commit_crv3_mechanism_weights {
+ netuid: NetUid::from(1),
+ mecid: 0u8.into(),
+ commit: vec![1, 2, 3].try_into().unwrap(),
+ reveal_round: 10,
+ })
+}
+
+fn call_commit_timelocked_mechanism_weights() -> RuntimeCall {
+ RuntimeCall::SubtensorModule(
+ pallet_subtensor::Call::commit_timelocked_mechanism_weights {
+ netuid: NetUid::from(1),
+ mecid: 0u8.into(),
+ commit: vec![1, 2, 3].try_into().unwrap(),
+ reveal_round: 10,
+ commit_reveal_version: 4,
+ },
+ )
+}
+
+fn plain_commitment_info() -> Box::MaxFields>>
+{
+ Box::new(CommitmentInfo {
+ fields: BoundedVec::try_from(vec![Data::Raw(
+ BoundedVec::try_from(b"knowledge".to_vec()).unwrap(),
+ )])
+ .unwrap(),
+ })
+}
+
+fn timelocked_commitment_info()
+-> Box::MaxFields>> {
+ Box::new(CommitmentInfo {
+ fields: BoundedVec::try_from(vec![Data::TimelockEncrypted {
+ encrypted: BoundedVec::try_from(vec![1, 2, 3]).unwrap(),
+ reveal_round: 10,
+ }])
+ .unwrap(),
+ })
+}
+
+fn call_set_commitment() -> RuntimeCall {
+ RuntimeCall::Commitments(pallet_commitments::Call::set_commitment {
+ netuid: NetUid::from(1),
+ info: plain_commitment_info(),
+ })
+}
+
+fn add_proxy_delegate(proxy_type: ProxyType) {
+ assert_ok!(Proxy::add_proxy(
+ RuntimeOrigin::signed(AccountId::from(ACCOUNT)),
+ AccountId::from(DELEGATE).into(),
+ proxy_type,
+ 0
+ ));
+}
+
+fn setup_hotkey_on_network(netuid: NetUid, hotkey: AccountId, coldkey: AccountId) -> u16 {
+ if !SubtensorModule::if_subnet_exist(netuid) {
+ SubtensorModule::init_new_network(netuid, 13);
+ SubtensorModule::set_network_registration_allowed(netuid, true);
+ SubtensorModule::set_network_pow_registration_allowed(netuid, true);
+ }
+
+ SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey);
+ if SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey).is_err() {
+ SubtensorModule::append_neuron(
+ netuid,
+ &hotkey,
+ SubtensorModule::get_current_block_as_u64(),
+ );
+ }
+ SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey).unwrap()
+}
+
+fn sign_evm_message(pair: &ecdsa::Pair, hotkey: &AccountId, block_number: u64) -> ecdsa::Signature {
+ let hashed_block_number = keccak_256(block_number.encode().as_ref());
+ let message = [hotkey.encode().as_slice(), hashed_block_number.as_slice()].concat();
+ let hash = pallet_subtensor::Pallet::::hash_message_eip191(message);
+ let mut signature = pair.sign_prehashed(&hash);
+ if let Some(v) = signature.0.get_mut(64) {
+ *v = v.wrapping_add(27);
+ }
+ signature
+}
+
fn verify_call_with_proxy_type(proxy_type: &ProxyType, call: &RuntimeCall) {
assert_ok!(Proxy::proxy(
RuntimeOrigin::signed(AccountId::from(DELEGATE)),
@@ -171,6 +406,7 @@ fn test_proxy_pallet() {
ProxyType::NonFungible,
ProxyType::Staking,
ProxyType::Registration,
+ ProxyType::Validate,
];
let calls = [
@@ -266,3 +502,306 @@ fn test_owner_type_can_set_subnet_identity_and_update_symbol() {
verify_call_with_proxy_type(&ProxyType::Owner, &call_update_symbol());
});
}
+
+#[test]
+fn test_validate_proxy_allows_expected_calls() {
+ let allowed_calls = [
+ call_serve_axon,
+ call_serve_axon_tls,
+ call_associate_evm_key,
+ call_set_weights,
+ call_set_mechanism_weights,
+ call_batch_set_weights,
+ call_commit_weights,
+ call_commit_mechanism_weights,
+ call_batch_commit_weights,
+ call_reveal_weights,
+ call_reveal_mechanism_weights,
+ call_batch_reveal_weights,
+ call_commit_timelocked_weights,
+ call_commit_crv3_mechanism_weights,
+ call_commit_timelocked_mechanism_weights,
+ call_set_commitment,
+ ];
+
+ for call in allowed_calls {
+ new_test_ext().execute_with(|| {
+ add_proxy_delegate(ProxyType::Validate);
+ verify_call_with_proxy_type(&ProxyType::Validate, &call());
+ });
+ }
+}
+
+#[test]
+fn test_validate_proxy_filters_disallowed_calls() {
+ let denied_calls = [
+ call_transfer,
+ call_remark,
+ call_owner_util,
+ call_root_register,
+ call_add_stake,
+ call_register,
+ call_swap_hotkey,
+ call_set_children,
+ ];
+
+ for call in denied_calls {
+ new_test_ext().execute_with(|| {
+ add_proxy_delegate(ProxyType::Validate);
+ verify_call_with_proxy_type(&ProxyType::Validate, &call());
+ });
+ }
+}
+
+#[test]
+fn test_validate_proxy_hierarchy_and_escalation_rules() {
+ new_test_ext().execute_with(|| {
+ add_proxy_delegate(ProxyType::Validate);
+
+ let add_validate_proxy = RuntimeCall::Proxy(pallet_proxy::Call::add_proxy {
+ delegate: AccountId::from(OTHER_ACCOUNT).into(),
+ proxy_type: ProxyType::Validate,
+ delay: 0,
+ });
+ assert_ok!(Proxy::proxy(
+ RuntimeOrigin::signed(AccountId::from(DELEGATE)),
+ AccountId::from(ACCOUNT).into(),
+ None,
+ Box::new(add_validate_proxy),
+ ));
+ assert!(
+ Proxy::proxies(AccountId::from(ACCOUNT))
+ .0
+ .iter()
+ .any(|proxy| proxy.delegate == AccountId::from(OTHER_ACCOUNT)
+ && proxy.proxy_type == ProxyType::Validate)
+ );
+
+ let add_any_proxy = RuntimeCall::Proxy(pallet_proxy::Call::add_proxy {
+ delegate: AccountId::from(THIRD_ACCOUNT).into(),
+ proxy_type: ProxyType::Any,
+ delay: 0,
+ });
+ assert_ok!(Proxy::proxy(
+ RuntimeOrigin::signed(AccountId::from(DELEGATE)),
+ AccountId::from(ACCOUNT).into(),
+ None,
+ Box::new(add_any_proxy),
+ ));
+ System::assert_last_event(
+ pallet_proxy::Event::ProxyExecuted {
+ result: Err(SystemError::CallFiltered.into()),
+ }
+ .into(),
+ );
+
+ let remove_all = RuntimeCall::Proxy(pallet_proxy::Call::remove_proxies {});
+ assert_ok!(Proxy::proxy(
+ RuntimeOrigin::signed(AccountId::from(DELEGATE)),
+ AccountId::from(ACCOUNT).into(),
+ None,
+ Box::new(remove_all),
+ ));
+ System::assert_last_event(
+ pallet_proxy::Event::ProxyExecuted {
+ result: Err(SystemError::CallFiltered.into()),
+ }
+ .into(),
+ );
+ });
+}
+
+#[test]
+fn test_validate_proxy_can_set_weights_statefully() {
+ new_test_ext().execute_with(|| {
+ let netuid = NetUid::from(1);
+ let hotkey = AccountId::from(ACCOUNT);
+ let coldkey = AccountId::from(OTHER_ACCOUNT);
+ let peer_hotkey = AccountId::from(THIRD_ACCOUNT);
+
+ let neuron_uid = setup_hotkey_on_network(netuid, hotkey.clone(), coldkey.clone());
+ let peer_uid = setup_hotkey_on_network(netuid, peer_hotkey, coldkey);
+ SubtensorModule::set_validator_permit_for_uid(netuid, neuron_uid, true);
+ SubtensorModule::set_weights_set_rate_limit(netuid, 0);
+ SubtensorModule::set_commit_reveal_weights_enabled(netuid, false);
+
+ add_proxy_delegate(ProxyType::Validate);
+ let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::set_weights {
+ netuid,
+ dests: vec![peer_uid],
+ weights: vec![u16::MAX],
+ version_key: 0,
+ });
+
+ assert_ok!(Proxy::proxy(
+ RuntimeOrigin::signed(AccountId::from(DELEGATE)),
+ hotkey.into(),
+ None,
+ Box::new(call),
+ ));
+
+ assert!(System::events().iter().any(|record| {
+ record.event
+ == RuntimeEvent::SubtensorModule(pallet_subtensor::Event::WeightsSet(
+ SubtensorModule::get_mechanism_storage_index(netuid, 0u8.into()),
+ neuron_uid,
+ ))
+ }));
+ });
+}
+
+#[test]
+fn test_validate_proxy_can_set_plain_commitment_statefully() {
+ new_test_ext().execute_with(|| {
+ let netuid = NetUid::from(1);
+ let hotkey = AccountId::from(ACCOUNT);
+ let coldkey = AccountId::from(OTHER_ACCOUNT);
+
+ setup_hotkey_on_network(netuid, hotkey.clone(), coldkey);
+ add_proxy_delegate(ProxyType::Validate);
+
+ let call = RuntimeCall::Commitments(pallet_commitments::Call::set_commitment {
+ netuid,
+ info: plain_commitment_info(),
+ });
+
+ assert_ok!(Proxy::proxy(
+ RuntimeOrigin::signed(AccountId::from(DELEGATE)),
+ hotkey.clone().into(),
+ None,
+ Box::new(call),
+ ));
+
+ assert!(Commitments::commitment_of(netuid, hotkey).is_some());
+ });
+}
+
+#[test]
+fn test_validate_proxy_can_set_timelocked_commitment_statefully() {
+ new_test_ext().execute_with(|| {
+ let netuid = NetUid::from(1);
+ let hotkey = AccountId::from(ACCOUNT);
+ let coldkey = AccountId::from(OTHER_ACCOUNT);
+
+ setup_hotkey_on_network(netuid, hotkey.clone(), coldkey);
+ add_proxy_delegate(ProxyType::Validate);
+
+ let call = RuntimeCall::Commitments(pallet_commitments::Call::set_commitment {
+ netuid,
+ info: timelocked_commitment_info(),
+ });
+
+ assert_ok!(Proxy::proxy(
+ RuntimeOrigin::signed(AccountId::from(DELEGATE)),
+ hotkey.clone().into(),
+ None,
+ Box::new(call),
+ ));
+
+ assert!(System::events().iter().any(|record| {
+ record.event
+ == RuntimeEvent::Commitments(pallet_commitments::Event::TimelockCommitment {
+ netuid,
+ who: hotkey.clone(),
+ reveal_round: 10,
+ })
+ }));
+ });
+}
+
+#[test]
+fn test_validate_proxy_can_serve_axon_tls_statefully() {
+ new_test_ext().execute_with(|| {
+ let netuid = NetUid::from(1);
+ let hotkey = AccountId::from(ACCOUNT);
+ let coldkey = AccountId::from(OTHER_ACCOUNT);
+ let certificate = b"CERT".to_vec();
+
+ setup_hotkey_on_network(netuid, hotkey.clone(), coldkey);
+ add_proxy_delegate(ProxyType::Validate);
+
+ let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::serve_axon_tls {
+ netuid,
+ version: 1,
+ ip: 0,
+ port: 8080,
+ ip_type: 4,
+ protocol: 0,
+ placeholder1: 0,
+ placeholder2: 0,
+ certificate: certificate.clone(),
+ });
+
+ assert_ok!(Proxy::proxy(
+ RuntimeOrigin::signed(AccountId::from(DELEGATE)),
+ hotkey.clone().into(),
+ None,
+ Box::new(call),
+ ));
+
+ let stored = pallet_subtensor::NeuronCertificates::::get(netuid, hotkey.clone())
+ .expect("certificate should be stored");
+ assert_eq!(
+ stored.public_key.into_inner(),
+ certificate.get(1..).unwrap().to_vec()
+ );
+ assert!(System::events().iter().any(|record| {
+ record.event
+ == RuntimeEvent::SubtensorModule(pallet_subtensor::Event::AxonServed(
+ netuid,
+ hotkey.clone(),
+ ))
+ }));
+ });
+}
+
+#[test]
+fn test_validate_proxy_can_associate_evm_key_statefully() {
+ new_test_ext().execute_with(|| {
+ let netuid = NetUid::from(1);
+ let hotkey = AccountId::from(ACCOUNT);
+ let coldkey = AccountId::from(OTHER_ACCOUNT);
+
+ let neuron_uid = setup_hotkey_on_network(netuid, hotkey.clone(), coldkey);
+ add_proxy_delegate(ProxyType::Validate);
+
+ let pair = ecdsa::Pair::generate().0;
+ let public = pair.public();
+ let uncompressed = libsecp256k1::PublicKey::parse_compressed(&public.0)
+ .unwrap()
+ .serialize();
+ let uncompressed_body = uncompressed.get(1..).unwrap_or_default();
+ let hashed = keccak_256(uncompressed_body);
+ let evm_key = H160::from_slice(hashed.get(12..).unwrap_or_default());
+ let block_number = 1u64;
+ let signature = sign_evm_message(&pair, &hotkey, block_number);
+
+ let call = RuntimeCall::SubtensorModule(pallet_subtensor::Call::associate_evm_key {
+ netuid,
+ evm_key,
+ block_number,
+ signature,
+ });
+
+ assert_ok!(Proxy::proxy(
+ RuntimeOrigin::signed(AccountId::from(DELEGATE)),
+ hotkey.clone().into(),
+ None,
+ Box::new(call),
+ ));
+
+ assert_eq!(
+ pallet_subtensor::AssociatedEvmAddress::::get(netuid, neuron_uid),
+ Some((evm_key, block_number))
+ );
+ assert!(System::events().iter().any(|record| {
+ record.event
+ == RuntimeEvent::SubtensorModule(pallet_subtensor::Event::EvmKeyAssociated {
+ netuid,
+ hotkey: hotkey.clone(),
+ evm_key,
+ block_associated: block_number,
+ })
+ }));
+ });
+}