diff --git a/docs/guides/evm/precompiles/subnet.mdx b/docs/guides/evm/precompiles/subnet.mdx
index 0caa4dd9f6..b3d678a821 100644
--- a/docs/guides/evm/precompiles/subnet.mdx
+++ b/docs/guides/evm/precompiles/subnet.mdx
@@ -38,6 +38,7 @@ getCommitRevealWeightsInterval(uint16)
getDifficulty(uint16)
getImmunityPeriod(uint16)
getKappa(uint16)
+getLiquidAlphaConsensusMode(uint16)
getLiquidAlphaEnabled(uint16)
getMaxBurn(uint16)
getMaxDifficulty(uint16)
@@ -75,6 +76,9 @@ netuid. It increments on every successful registration, allowing a contract to
distinguish a reused netuid even when it did not retain the previous
registration block.
+`getLiquidAlphaConsensusMode` returns `0` for `Current`, `1` for `Previous`,
+and `2` for `Auto` (the default).
+
`getSubnetDissolutionStatus` returns `(isDissolving, cleanupInProgress,
cleanupPhase)`. Phase `0` means that detailed cleanup has not started. Active
cleanup uses stable, append-only phase codes:
@@ -126,6 +130,7 @@ setCommitRevealWeightsInterval(uint16,uint64)
setDifficulty(uint16,uint64)
setImmunityPeriod(uint16,uint16)
setKappa(uint16,uint16)
+setLiquidAlphaConsensusMode(uint16,uint8)
setLiquidAlphaEnabled(uint16,bool)
setMaxDifficulty(uint16,uint64)
setMinAllowedWeights(uint16,uint16)
@@ -140,6 +145,12 @@ setYuma3Enabled(uint16,bool)
toggleTransfers(uint16,bool)
```
+`setLiquidAlphaConsensusMode` uses the same mode encoding as the getter and
+reverts for values above `2`. It dispatches
+`AdminUtils.sudo_set_liquid_alpha_consensus_mode` with the mapped EVM caller,
+so subnet-owner authorization, the admin window, and the hyperparameter rate
+limit remain enforced by the runtime.
+
## Legacy no-op functions
These released selectors remain routed but intentionally do not change state:
diff --git a/docs/hyperparameters/liquid-alpha-enabled.mdx b/docs/hyperparameters/liquid-alpha-enabled.mdx
index 9098f46793..1249e698db 100644
--- a/docs/hyperparameters/liquid-alpha-enabled.mdx
+++ b/docs/hyperparameters/liquid-alpha-enabled.mdx
@@ -24,7 +24,7 @@ by liquid alpha:
Subnet owners can change the mode during the subnet's admin window. The call
has the same owner/root authorization and per-hyperparameter rate limiting as
-other owner-settable parameters, but it is currently exposed as a raw call:
+other owner-settable parameters. It is available as a raw call:
```bash
btcli call AdminUtils.sudo_set_liquid_alpha_consensus_mode \
@@ -41,6 +41,13 @@ mode = await client.query(
)
```
+EVM contracts can use the Subnet precompile at `0x0803` through
+`getLiquidAlphaConsensusMode(uint16)` and
+`setLiquidAlphaConsensusMode(uint16,uint8)`. The EVM encoding is `0` for
+`Current`, `1` for `Previous`, and `2` for `Auto`; other values revert. The
+setter exposes the signed subnet-owner path and retains the runtime's admin
+window and rate-limit checks.
+
One important dependency: `compute_bonds` only runs on the Yuma3 path. If [`yuma3_enabled`](/docs/hyperparameters/yuma3-enabled) is off, the classic bond code ignores this toggle entirely and always uses [`bonds_moving_avg`](/docs/hyperparameters/bonds-moving-avg). Setting `alpha_low`/`alpha_high` also requires this flag to be on first ([`LiquidAlphaDisabled`](/code/pallets/subtensor/src/macros/errors.rs#L134) error otherwise).
diff --git a/precompiles/src/lib.rs b/precompiles/src/lib.rs
index fe098221e0..ac819f92e8 100644
--- a/precompiles/src/lib.rs
+++ b/precompiles/src/lib.rs
@@ -564,6 +564,7 @@ mod address_and_selector_tests {
"setSubnetIdentity(uint16,string,string,string,string,string,string,string,string)",
"updateSubnetSymbol(uint16,string)",
"triggerEpoch(uint16)",
+ "setLiquidAlphaConsensusMode(uint16,uint8)",
"setBondsPenalty(uint16,uint16)",
"setMaxAllowedUids(uint16,uint16)",
"setMaxBurnV2(uint16,uint64)",
@@ -680,6 +681,7 @@ mod address_and_selector_tests {
for signature in [
"getRegisteredSubnetCounter(uint16)",
+ "getLiquidAlphaConsensusMode(uint16)",
"getSubnetDissolutionStatus(uint16)",
"getSubnetMetadata(uint16)",
"getSubnetCapacityConfig(uint16)",
diff --git a/precompiles/src/solidity/subnet.abi b/precompiles/src/solidity/subnet.abi
index a21dbd1c2e..76eef31175 100644
--- a/precompiles/src/solidity/subnet.abi
+++ b/precompiles/src/solidity/subnet.abi
@@ -251,6 +251,25 @@
"stateMutability": "view",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "uint16",
+ "name": "netuid",
+ "type": "uint16"
+ }
+ ],
+ "name": "getLiquidAlphaConsensusMode",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
{
"inputs": [
{
@@ -871,6 +890,24 @@
"stateMutability": "payable",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "uint16",
+ "name": "netuid",
+ "type": "uint16"
+ },
+ {
+ "internalType": "uint8",
+ "name": "mode",
+ "type": "uint8"
+ }
+ ],
+ "name": "setLiquidAlphaConsensusMode",
+ "outputs": [],
+ "stateMutability": "payable",
+ "type": "function"
+ },
{
"inputs": [
{
diff --git a/precompiles/src/solidity/subnet.sol b/precompiles/src/solidity/subnet.sol
index 23582d2f0f..4136d79ad6 100644
--- a/precompiles/src/solidity/subnet.sol
+++ b/precompiles/src/solidity/subnet.sol
@@ -200,6 +200,14 @@ interface ISubnet {
function getLiquidAlphaEnabled(uint16 netuid) external view returns (bool);
+ /**
+ * @dev Returns the liquid-alpha consensus source: 0 Current, 1 Previous,
+ * 2 Auto.
+ */
+ function getLiquidAlphaConsensusMode(
+ uint16 netuid
+ ) external view returns (uint8);
+
function isSubnetDissolving(uint16 netuid) external view returns (bool);
/**
@@ -235,6 +243,15 @@ interface ISubnet {
bool liquidAlphaEnabled
) external payable;
+ /**
+ * @dev Sets the liquid-alpha consensus source: 0 Current, 1 Previous,
+ * 2 Auto. Reverts for any other value.
+ */
+ function setLiquidAlphaConsensusMode(
+ uint16 netuid,
+ uint8 mode
+ ) external payable;
+
function getYuma3Enabled(uint16 netuid) external view returns (bool);
function setYuma3Enabled(
diff --git a/precompiles/src/subnet.rs b/precompiles/src/subnet.rs
index e138c39241..99e1f00939 100644
--- a/precompiles/src/subnet.rs
+++ b/precompiles/src/subnet.rs
@@ -7,7 +7,7 @@ use frame_system::RawOrigin;
use pallet_evm::{AddressMapping, PrecompileHandle};
use precompile_utils::{
EvmResult,
- prelude::{BoundedString, BoundedVec, UnboundedBytes},
+ prelude::{BoundedString, BoundedVec, UnboundedBytes, revert},
};
use sp_core::H256;
use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable, UniqueSaturatedInto};
@@ -772,6 +772,46 @@ where
)
}
+ #[precompile::public("getLiquidAlphaConsensusMode(uint16)")]
+ #[precompile::view]
+ fn get_liquid_alpha_consensus_mode(
+ handle: &mut impl PrecompileHandle,
+ netuid: u16,
+ ) -> EvmResult {
+ handle.record_db_reads::(1)?;
+ let mode = match pallet_subtensor::LiquidAlphaConsensusMode::::get(NetUid::from(netuid))
+ {
+ pallet_subtensor::ConsensusMode::Current => 0,
+ pallet_subtensor::ConsensusMode::Previous => 1,
+ pallet_subtensor::ConsensusMode::Auto => 2,
+ };
+ Ok(mode)
+ }
+
+ #[precompile::public("setLiquidAlphaConsensusMode(uint16,uint8)")]
+ #[precompile::payable]
+ fn set_liquid_alpha_consensus_mode(
+ handle: &mut impl PrecompileHandle,
+ netuid: u16,
+ mode: u8,
+ ) -> EvmResult<()> {
+ let mode = match mode {
+ 0 => pallet_subtensor::ConsensusMode::Current,
+ 1 => pallet_subtensor::ConsensusMode::Previous,
+ 2 => pallet_subtensor::ConsensusMode::Auto,
+ _ => return Err(revert("invalid liquid alpha consensus mode")),
+ };
+ let call = pallet_admin_utils::Call::::sudo_set_liquid_alpha_consensus_mode {
+ netuid: netuid.into(),
+ mode,
+ };
+
+ handle.try_dispatch_runtime_call::(
+ call,
+ RawOrigin::Signed(handle.caller_account_id::()),
+ )
+ }
+
#[precompile::public("getYuma3Enabled(uint16)")]
#[precompile::view]
fn get_yuma3_enabled(handle: &mut impl PrecompileHandle, netuid: u16) -> EvmResult {
@@ -1847,6 +1887,97 @@ mod tests {
});
}
+ #[test]
+ fn liquid_alpha_consensus_mode_preserves_encoding_and_owner_authorization() {
+ new_test_ext().execute_with(|| {
+ let owner = addr_from_index(0x5012);
+ let non_owner = addr_from_index(0x5013);
+ let netuid = setup_owner_subnet(owner);
+ let precompiles = precompiles::>();
+ let address = addr_from_index(SubnetPrecompile::::INDEX);
+ let get_input = encode_with_selector(
+ selector_u32("getLiquidAlphaConsensusMode(uint16)"),
+ (TEST_NETUID_U16,),
+ );
+
+ assert!(!pallet_subtensor::LiquidAlphaConsensusMode::::contains_key(netuid));
+ precompiles
+ .prepare_test(owner, address, get_input.clone())
+ .with_static_call(true)
+ .expect_cost(
+ precompile_utils::prelude::RuntimeHelper::::db_read_gas_cost(),
+ )
+ .execute_returns(2_u8);
+
+ for (encoded, expected) in [
+ (0_u8, pallet_subtensor::ConsensusMode::Current),
+ (1_u8, pallet_subtensor::ConsensusMode::Previous),
+ (2_u8, pallet_subtensor::ConsensusMode::Auto),
+ ] {
+ precompiles
+ .prepare_test(
+ owner,
+ address,
+ encode_with_selector(
+ selector_u32("setLiquidAlphaConsensusMode(uint16,uint8)"),
+ (TEST_NETUID_U16, encoded),
+ ),
+ )
+ .execute_returns(());
+ assert_eq!(
+ pallet_subtensor::LiquidAlphaConsensusMode::::get(netuid),
+ expected
+ );
+ precompiles
+ .prepare_test(owner, address, get_input.clone())
+ .with_static_call(true)
+ .expect_cost(
+ precompile_utils::prelude::RuntimeHelper::::db_read_gas_cost(),
+ )
+ .execute_returns(encoded);
+ }
+
+ precompiles
+ .prepare_test(
+ owner,
+ address,
+ encode_with_selector(
+ selector_u32("setLiquidAlphaConsensusMode(uint16,uint8)"),
+ (TEST_NETUID_U16, 3_u8),
+ ),
+ )
+ .execute_reverts(|output| output == b"invalid liquid alpha consensus mode");
+ assert_eq!(
+ pallet_subtensor::LiquidAlphaConsensusMode::::get(netuid),
+ pallet_subtensor::ConsensusMode::Auto
+ );
+
+ let set_previous = encode_with_selector(
+ selector_u32("setLiquidAlphaConsensusMode(uint16,uint8)"),
+ (TEST_NETUID_U16, 1_u8),
+ );
+ let rejected = execute_precompile(
+ &precompiles,
+ address,
+ non_owner,
+ set_previous.clone(),
+ U256::zero(),
+ );
+ assert!(matches!(rejected, Some(Err(_))));
+ assert_eq!(
+ pallet_subtensor::LiquidAlphaConsensusMode::::get(netuid),
+ pallet_subtensor::ConsensusMode::Auto
+ );
+
+ precompiles
+ .prepare_test(owner, address, set_previous)
+ .with_static_call(true)
+ .execute_reverts(|output| {
+ output == b"Can't call non-static function in static context"
+ });
+ });
+ }
+
#[test]
fn subnet_precompile_gets_network_registered_block() {
new_test_ext().execute_with(|| {
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index d3fb8ccf9c..241a0b2946 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -235,7 +235,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// `spec_version`, and `authoring_version` are the same between Wasm and native.
// This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use
// the compatible custom types.
- spec_version: 448,
+ spec_version: 449,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
diff --git a/sdk/python/bittensor/evm/abi/subnet.json b/sdk/python/bittensor/evm/abi/subnet.json
index a21dbd1c2e..76eef31175 100644
--- a/sdk/python/bittensor/evm/abi/subnet.json
+++ b/sdk/python/bittensor/evm/abi/subnet.json
@@ -251,6 +251,25 @@
"stateMutability": "view",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "uint16",
+ "name": "netuid",
+ "type": "uint16"
+ }
+ ],
+ "name": "getLiquidAlphaConsensusMode",
+ "outputs": [
+ {
+ "internalType": "uint8",
+ "name": "",
+ "type": "uint8"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
{
"inputs": [
{
@@ -871,6 +890,24 @@
"stateMutability": "payable",
"type": "function"
},
+ {
+ "inputs": [
+ {
+ "internalType": "uint16",
+ "name": "netuid",
+ "type": "uint16"
+ },
+ {
+ "internalType": "uint8",
+ "name": "mode",
+ "type": "uint8"
+ }
+ ],
+ "name": "setLiquidAlphaConsensusMode",
+ "outputs": [],
+ "stateMutability": "payable",
+ "type": "function"
+ },
{
"inputs": [
{
diff --git a/sdk/python/tests/unit/test_evm.py b/sdk/python/tests/unit/test_evm.py
index 503a7ef44e..f3f9a0ec16 100644
--- a/sdk/python/tests/unit/test_evm.py
+++ b/sdk/python/tests/unit/test_evm.py
@@ -109,6 +109,18 @@ def test_new_bounded_array_calls_encode(self):
["[1, 2]", ["0x" + "11" * 32, "0x" + "22" * 32]],
).startswith("0x")
+ def test_liquid_alpha_consensus_mode_calls_encode(self):
+ subnet = precompiles.get_precompile("subnet")
+ getter = subnet.function("getLiquidAlphaConsensusMode")
+ setter = subnet.function("setLiquidAlphaConsensusMode")
+
+ assert precompiles.encode_call(getter, [1]).startswith("0x")
+ assert precompiles.encode_call(setter, [1, 2]).startswith("0x")
+ assert getter["outputs"] == [
+ {"internalType": "uint8", "name": "", "type": "uint8"}
+ ]
+ assert setter["stateMutability"] == "payable"
+
# The vendored ABIs in bittensor/evm/abi must stay in sync with the canonical
# .abi artifacts in precompiles/src/solidity (see the bittensor.evm.precompiles