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
53 changes: 53 additions & 0 deletions chain-extensions/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<T>::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::<T>::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::<T>::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::<T>::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))
}
}
}
}
Expand Down
152 changes: 152 additions & 0 deletions chain-extensions/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<mock::Test>::insert(netuid, hotkey, expected_voting_power);

let mut env = MockEnv::new(
FunctionId::GetVotingPowerV1,
caller,
(netuid, hotkey).encode(),
);

let ret = SubtensorChainExtension::<mock::Test>::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::<mock::Test>::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::<mock::Test>::insert(netuid, hotkey1, power1);
pallet_subtensor::VotingPower::<mock::Test>::insert(netuid, hotkey2, power2);
pallet_subtensor::VotingPower::<mock::Test>::insert(netuid, hotkey3, power3);

let mut env = MockEnv::new(FunctionId::GetTotalVotingPowerV1, caller, netuid.encode());

let ret = SubtensorChainExtension::<mock::Test>::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::<mock::Test>::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::<mock::Test>::insert(netuid, true);

let mut env = MockEnv::new(
FunctionId::IsVotingPowerTrackingEnabledV1,
caller,
netuid.encode(),
);

let ret = SubtensorChainExtension::<mock::Test>::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::<mock::Test>::insert(netuid, expected_block);

let mut env = MockEnv::new(
FunctionId::GetVotingPowerDisableAtBlockV1,
caller,
netuid.encode(),
);

let ret = SubtensorChainExtension::<mock::Test>::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);
});
}
4 changes: 4 additions & 0 deletions chain-extensions/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
5 changes: 5 additions & 0 deletions docs/wasm-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<BlockNumber>` |

Example usage in your ink! contract:
```rust
Expand Down