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