diff --git a/chain-extensions/src/lib.rs b/chain-extensions/src/lib.rs index eaaee70d27..f395539fab 100644 --- a/chain-extensions/src/lib.rs +++ b/chain-extensions/src/lib.rs @@ -530,6 +530,59 @@ 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)) + } } } } diff --git a/chain-extensions/src/tests.rs b/chain-extensions/src/tests.rs index bd6f46c8ab..7b1d1cfd65 100644 --- a/chain-extensions/src/tests.rs +++ b/chain-extensions/src/tests.rs @@ -1003,3 +1003,155 @@ 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); + + 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); + + 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); + + // Now 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); + + 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); + }); +} diff --git a/chain-extensions/src/types.rs b/chain-extensions/src/types.rs index ee6298ad5b..0b2c3d9f92 100644 --- a/chain-extensions/src/types.rs +++ b/chain-extensions/src/types.rs @@ -21,6 +21,10 @@ pub enum FunctionId { AddProxyV1 = 13, RemoveProxyV1 = 14, GetAlphaPriceV1 = 15, + GetVotingPowerV1 = 16, + GetTotalVotingPowerV1 = 17, + IsVotingPowerTrackingEnabledV1 = 18, + GetVotingPowerDisableAtBlockV1 = 19, } #[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, Debug)] diff --git a/docs/wasm-contracts.md b/docs/wasm-contracts.md index ed6e9ecdd3..75784deb5e 100644 --- a/docs/wasm-contracts.md +++ b/docs/wasm-contracts.md @@ -43,6 +43,11 @@ 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` | Example usage in your ink! contract: ```rust