From c8e8ba184e1cf7b995abad8bfaed07bc3398067b Mon Sep 17 00:00:00 2001 From: Landyn Date: Tue, 27 Jan 2026 17:03:15 -0600 Subject: [PATCH 1/4] added voting power chain extensions --- chain-extensions/src/lib.rs | 65 ++++++++++++ chain-extensions/src/tests.rs | 183 ++++++++++++++++++++++++++++++++++ chain-extensions/src/types.rs | 5 + 3 files changed, 253 insertions(+) diff --git a/chain-extensions/src/lib.rs b/chain-extensions/src/lib.rs index eaaee70d27..e580eeef64 100644 --- a/chain-extensions/src/lib.rs +++ b/chain-extensions/src/lib.rs @@ -530,6 +530,71 @@ where Ok(RetVal::Converging(Output::Success as u32)) } + FunctionId::GetVotingPowerV1 => { + let (netuid, hotkey): (NetUid, T::AccountId) = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let voting_power = pallet_subtensor::Pallet::::get_voting_power(netuid, &hotkey); + + env.write_output(&voting_power.encode()) + .map_err(|_| DispatchError::Other("Failed to write output"))?; + + Ok(RetVal::Converging(Output::Success as u32)) + } + FunctionId::GetTotalVotingPowerV1 => { + let netuid: NetUid = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let total_voting_power: u64 = + pallet_subtensor::VotingPower::::iter_prefix(netuid) + .map(|(_, power)| power) + .sum(); + + env.write_output(&total_voting_power.encode()) + .map_err(|_| DispatchError::Other("Failed to write output"))?; + + Ok(RetVal::Converging(Output::Success as u32)) + } + FunctionId::IsVotingPowerTrackingEnabledV1 => { + let netuid: NetUid = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let enabled = + pallet_subtensor::Pallet::::get_voting_power_tracking_enabled(netuid); + + env.write_output(&enabled.encode()) + .map_err(|_| DispatchError::Other("Failed to write output"))?; + + Ok(RetVal::Converging(Output::Success as u32)) + } + FunctionId::GetVotingPowerDisableAtBlockV1 => { + let netuid: NetUid = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let disable_at_block = + pallet_subtensor::Pallet::::get_voting_power_disable_at_block(netuid); + + env.write_output(&disable_at_block.encode()) + .map_err(|_| DispatchError::Other("Failed to write output"))?; + + Ok(RetVal::Converging(Output::Success as u32)) + } + FunctionId::GetVotingPowerEmaAlphaV1 => { + let netuid: NetUid = env + .read_as() + .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; + + let ema_alpha = pallet_subtensor::Pallet::::get_voting_power_ema_alpha(netuid); + + env.write_output(&ema_alpha.encode()) + .map_err(|_| DispatchError::Other("Failed to write output"))?; + + Ok(RetVal::Converging(Output::Success as u32)) + } } } } diff --git a/chain-extensions/src/tests.rs b/chain-extensions/src/tests.rs index bd6f46c8ab..48209b7e1f 100644 --- a/chain-extensions/src/tests.rs +++ b/chain-extensions/src/tests.rs @@ -1003,3 +1003,186 @@ fn get_alpha_price_returns_encoded_price() { ); }); } + +#[test] +fn get_voting_power_returns_encoded_value() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(9001); + let owner_coldkey = U256::from(9002); + let hotkey = U256::from(9003); + let caller = U256::from(9004); + + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + + // Set voting power directly + let expected_voting_power: u64 = 1_000_000_000_000; + pallet_subtensor::VotingPower::::insert(netuid, hotkey, expected_voting_power); + + let mut env = MockEnv::new( + FunctionId::GetVotingPowerV1, + caller, + (netuid, hotkey).encode(), + ); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + assert!(env.charged_weight().is_none()); + + let output_power: u64 = Decode::decode(&mut &env.output()[..]).unwrap(); + assert_eq!(output_power, expected_voting_power); + }); +} + +#[test] +fn get_voting_power_returns_zero_when_not_set() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(9101); + let owner_coldkey = U256::from(9102); + let hotkey = U256::from(9103); + let caller = U256::from(9104); + + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + + // Don't set any voting power + + let mut env = MockEnv::new( + FunctionId::GetVotingPowerV1, + caller, + (netuid, hotkey).encode(), + ); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + assert!(env.charged_weight().is_none()); + + let output_power: u64 = Decode::decode(&mut &env.output()[..]).unwrap(); + assert_eq!(output_power, 0); + }); +} + +#[test] +fn get_total_voting_power_returns_sum() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(9201); + let owner_coldkey = U256::from(9202); + let hotkey1 = U256::from(9203); + let hotkey2 = U256::from(9204); + let hotkey3 = U256::from(9205); + let caller = U256::from(9206); + + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + + // Set voting power for multiple hotkeys + let power1: u64 = 1_000_000_000_000; + let power2: u64 = 2_000_000_000_000; + let power3: u64 = 3_000_000_000_000; + pallet_subtensor::VotingPower::::insert(netuid, hotkey1, power1); + pallet_subtensor::VotingPower::::insert(netuid, hotkey2, power2); + pallet_subtensor::VotingPower::::insert(netuid, hotkey3, power3); + + let mut env = MockEnv::new(FunctionId::GetTotalVotingPowerV1, caller, netuid.encode()); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + assert!(env.charged_weight().is_none()); + + let total_power: u64 = Decode::decode(&mut &env.output()[..]).unwrap(); + assert_eq!(total_power, power1 + power2 + power3); + }); +} + +#[test] +fn is_voting_power_tracking_enabled_returns_status() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(9301); + let owner_coldkey = U256::from(9302); + let caller = U256::from(9303); + + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + + // Initially should be false + let mut env = MockEnv::new( + FunctionId::IsVotingPowerTrackingEnabledV1, + caller, + netuid.encode(), + ); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + assert!(env.charged_weight().is_none()); + + let enabled: bool = Decode::decode(&mut &env.output()[..]).unwrap(); + assert!(!enabled); + + // Enable tracking + pallet_subtensor::VotingPowerTrackingEnabled::::insert(netuid, true); + + let mut env = MockEnv::new( + FunctionId::IsVotingPowerTrackingEnabledV1, + caller, + netuid.encode(), + ); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + + let enabled: bool = Decode::decode(&mut &env.output()[..]).unwrap(); + assert!(enabled); + }); +} + +#[test] +fn get_voting_power_disable_at_block_returns_value() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(9401); + let owner_coldkey = U256::from(9402); + let caller = U256::from(9403); + + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + + // Set disable at block + let expected_block: u64 = 123_456; + pallet_subtensor::VotingPowerDisableAtBlock::::insert(netuid, expected_block); + + let mut env = MockEnv::new( + FunctionId::GetVotingPowerDisableAtBlockV1, + caller, + netuid.encode(), + ); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + assert!(env.charged_weight().is_none()); + + let disable_at_block: u64 = Decode::decode(&mut &env.output()[..]).unwrap(); + assert_eq!(disable_at_block, expected_block); + }); +} + +#[test] +fn get_voting_power_ema_alpha_returns_value() { + mock::new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(9501); + let owner_coldkey = U256::from(9502); + let caller = U256::from(9503); + + let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); + + // Set EMA alpha + let expected_alpha: u64 = 500_000_000_000_000_000; // 0.5 in 18 decimal precision + pallet_subtensor::VotingPowerEmaAlpha::::insert(netuid, expected_alpha); + + let mut env = MockEnv::new( + FunctionId::GetVotingPowerEmaAlphaV1, + caller, + netuid.encode(), + ); + + let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); + assert_success(ret); + assert!(env.charged_weight().is_none()); + + let ema_alpha: u64 = Decode::decode(&mut &env.output()[..]).unwrap(); + assert_eq!(ema_alpha, expected_alpha); + }); +} diff --git a/chain-extensions/src/types.rs b/chain-extensions/src/types.rs index ee6298ad5b..3a28ddfdcb 100644 --- a/chain-extensions/src/types.rs +++ b/chain-extensions/src/types.rs @@ -21,6 +21,11 @@ pub enum FunctionId { AddProxyV1 = 13, RemoveProxyV1 = 14, GetAlphaPriceV1 = 15, + GetVotingPowerV1 = 16, + GetTotalVotingPowerV1 = 17, + IsVotingPowerTrackingEnabledV1 = 18, + GetVotingPowerDisableAtBlockV1 = 19, + GetVotingPowerEmaAlphaV1 = 20, } #[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, Debug)] From 24045d63c162aa10e88129f52b89520bdf9d9bce Mon Sep 17 00:00:00 2001 From: Landyn Date: Tue, 27 Jan 2026 18:13:05 -0600 Subject: [PATCH 2/4] comment clean up during review --- chain-extensions/src/tests.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/chain-extensions/src/tests.rs b/chain-extensions/src/tests.rs index 48209b7e1f..64f6fe58e6 100644 --- a/chain-extensions/src/tests.rs +++ b/chain-extensions/src/tests.rs @@ -1014,7 +1014,6 @@ fn get_voting_power_returns_encoded_value() { let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); - // Set voting power directly let expected_voting_power: u64 = 1_000_000_000_000; pallet_subtensor::VotingPower::::insert(netuid, hotkey, expected_voting_power); @@ -1072,7 +1071,6 @@ fn get_total_voting_power_returns_sum() { let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); - // Set voting power for multiple hotkeys let power1: u64 = 1_000_000_000_000; let power2: u64 = 2_000_000_000_000; let power3: u64 = 3_000_000_000_000; @@ -1114,7 +1112,7 @@ fn is_voting_power_tracking_enabled_returns_status() { let enabled: bool = Decode::decode(&mut &env.output()[..]).unwrap(); assert!(!enabled); - // Enable tracking + // Now enable tracking pallet_subtensor::VotingPowerTrackingEnabled::::insert(netuid, true); let mut env = MockEnv::new( @@ -1140,7 +1138,6 @@ fn get_voting_power_disable_at_block_returns_value() { let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); - // Set disable at block let expected_block: u64 = 123_456; pallet_subtensor::VotingPowerDisableAtBlock::::insert(netuid, expected_block); @@ -1168,7 +1165,6 @@ fn get_voting_power_ema_alpha_returns_value() { let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); - // Set EMA alpha let expected_alpha: u64 = 500_000_000_000_000_000; // 0.5 in 18 decimal precision pallet_subtensor::VotingPowerEmaAlpha::::insert(netuid, expected_alpha); From bb9481168a45e2e100fcfa9db94cede85ac23b38 Mon Sep 17 00:00:00 2001 From: Landyn Date: Tue, 27 Jan 2026 20:35:54 -0600 Subject: [PATCH 3/4] Updated wasm contracts chain extension documentation with new voting power funcs --- docs/wasm-contracts.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/wasm-contracts.md b/docs/wasm-contracts.md index ed6e9ecdd3..b726632d7d 100644 --- a/docs/wasm-contracts.md +++ b/docs/wasm-contracts.md @@ -43,6 +43,12 @@ Subtensor provides a custom chain extension that allows smart contracts to inter | 12 | `set_coldkey_auto_stake_hotkey` | Configure automatic stake destination | `(NetUid, AccountId)` | Error code | | 13 | `add_proxy` | Add a staking proxy for the caller | `(AccountId)` | Error code | | 14 | `remove_proxy` | Remove a staking proxy for the caller | `(AccountId)` | Error code | +| 15 | `get_alpha_price` | Get the current alpha price for a subnet | `(NetUid)` | `u64` (price × 10⁹) | +| 16 | `get_voting_power` | Get voting power for a hotkey in a subnet | `(NetUid, AccountId)` | `u64` | +| 17 | `get_total_voting_power` | Get total voting power across all hotkeys in a subnet | `(NetUid)` | `u64` | +| 18 | `is_voting_power_tracking_enabled` | Check if voting power tracking is enabled for a subnet | `(NetUid)` | `bool` | +| 19 | `get_voting_power_disable_at_block` | Get the block number at which voting power tracking will be disabled | `(NetUid)` | `Option` | +| 20 | `get_voting_power_ema_alpha` | Get the EMA alpha value used for voting power calculations | `(NetUid)` | `u32` | Example usage in your ink! contract: ```rust From 14ab9670d6912d050ad22d1416d1b9d86d54f215 Mon Sep 17 00:00:00 2001 From: Landyn Date: Wed, 28 Jan 2026 17:49:26 -0600 Subject: [PATCH 4/4] Removed get_voting_power_ema_alpha chain ext func, unnecessary use for smart ctx --- chain-extensions/src/lib.rs | 12 ------------ chain-extensions/src/tests.rs | 27 --------------------------- chain-extensions/src/types.rs | 1 - docs/wasm-contracts.md | 1 - 4 files changed, 41 deletions(-) diff --git a/chain-extensions/src/lib.rs b/chain-extensions/src/lib.rs index e580eeef64..f395539fab 100644 --- a/chain-extensions/src/lib.rs +++ b/chain-extensions/src/lib.rs @@ -583,18 +583,6 @@ where Ok(RetVal::Converging(Output::Success as u32)) } - FunctionId::GetVotingPowerEmaAlphaV1 => { - let netuid: NetUid = env - .read_as() - .map_err(|_| DispatchError::Other("Failed to decode input parameters"))?; - - let ema_alpha = pallet_subtensor::Pallet::::get_voting_power_ema_alpha(netuid); - - env.write_output(&ema_alpha.encode()) - .map_err(|_| DispatchError::Other("Failed to write output"))?; - - Ok(RetVal::Converging(Output::Success as u32)) - } } } } diff --git a/chain-extensions/src/tests.rs b/chain-extensions/src/tests.rs index 64f6fe58e6..7b1d1cfd65 100644 --- a/chain-extensions/src/tests.rs +++ b/chain-extensions/src/tests.rs @@ -1155,30 +1155,3 @@ fn get_voting_power_disable_at_block_returns_value() { assert_eq!(disable_at_block, expected_block); }); } - -#[test] -fn get_voting_power_ema_alpha_returns_value() { - mock::new_test_ext(1).execute_with(|| { - let owner_hotkey = U256::from(9501); - let owner_coldkey = U256::from(9502); - let caller = U256::from(9503); - - let netuid = mock::add_dynamic_network(&owner_hotkey, &owner_coldkey); - - let expected_alpha: u64 = 500_000_000_000_000_000; // 0.5 in 18 decimal precision - pallet_subtensor::VotingPowerEmaAlpha::::insert(netuid, expected_alpha); - - let mut env = MockEnv::new( - FunctionId::GetVotingPowerEmaAlphaV1, - caller, - netuid.encode(), - ); - - let ret = SubtensorChainExtension::::dispatch(&mut env).unwrap(); - assert_success(ret); - assert!(env.charged_weight().is_none()); - - let ema_alpha: u64 = Decode::decode(&mut &env.output()[..]).unwrap(); - assert_eq!(ema_alpha, expected_alpha); - }); -} diff --git a/chain-extensions/src/types.rs b/chain-extensions/src/types.rs index 3a28ddfdcb..0b2c3d9f92 100644 --- a/chain-extensions/src/types.rs +++ b/chain-extensions/src/types.rs @@ -25,7 +25,6 @@ pub enum FunctionId { GetTotalVotingPowerV1 = 17, IsVotingPowerTrackingEnabledV1 = 18, GetVotingPowerDisableAtBlockV1 = 19, - GetVotingPowerEmaAlphaV1 = 20, } #[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, Debug)] diff --git a/docs/wasm-contracts.md b/docs/wasm-contracts.md index b726632d7d..75784deb5e 100644 --- a/docs/wasm-contracts.md +++ b/docs/wasm-contracts.md @@ -48,7 +48,6 @@ Subtensor provides a custom chain extension that allows smart contracts to inter | 17 | `get_total_voting_power` | Get total voting power across all hotkeys in a subnet | `(NetUid)` | `u64` | | 18 | `is_voting_power_tracking_enabled` | Check if voting power tracking is enabled for a subnet | `(NetUid)` | `bool` | | 19 | `get_voting_power_disable_at_block` | Get the block number at which voting power tracking will be disabled | `(NetUid)` | `Option` | -| 20 | `get_voting_power_ema_alpha` | Get the EMA alpha value used for voting power calculations | `(NetUid)` | `u32` | Example usage in your ink! contract: ```rust