From c322fdbfedbc56b29f0a5b23a3ca9c1f5ccc7013 Mon Sep 17 00:00:00 2001 From: jeremyp-tao <259353334+jeremyp-tao@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:01:56 +0100 Subject: [PATCH 1/5] add approval/transfer_from to staking precompile --- precompiles/src/solidity/stakingV2.sol | 70 ++++++++++++ precompiles/src/staking.rs | 148 ++++++++++++++++++++++++- 2 files changed, 217 insertions(+), 1 deletion(-) diff --git a/precompiles/src/solidity/stakingV2.sol b/precompiles/src/solidity/stakingV2.sol index fefca82fd9..5a89980a49 100644 --- a/precompiles/src/solidity/stakingV2.sol +++ b/precompiles/src/solidity/stakingV2.sol @@ -310,4 +310,74 @@ interface IStaking { uint256 amount, uint256 netuid ) external payable; + + /** + * @dev Set how much the caller approves the spender to use the provided amount of subnet tokens + * on its behalf in a later call. + * + * This is similar to ERC20 approve, and then allows smart contract to transfer with permission from + * other accounts during their execution. They can then act as escrows while knowing from whom + * the funds comes from, which is not possible if the spender called `transfer` towards the contract + * (no callback). + * + * @param spenderColdkey Coldkey of the address allowed to spend funds from the caller. + * @param netuid The approved subnet token. + * @param absoluteAmount New approval amount, will overwrite previous value. + */ + function setApproval( + bytes32 spenderColdkey, + uint256 netuid, + uint256 absoluteAmount + ) external; + + /** + * @dev Increase much the caller approves the spender to use the provided amount of subnet tokens + * on its behalf in a later call. + * + * This is similar to ERC20 approve, and then allows smart contract to transfer with permission from + * other accounts during their execution. They can then act as escrows while knowing from whom + * the funds comes from, which is not possible if the spender called `transfer` towards the contract + * (no callback). + * + * @param spenderColdkey Coldkey of the address allowed to spend funds from the caller. + * @param netuid The approved subnet token. + * @param increaseAmount How much the approval amount should be increased. + */ + function approve( + bytes32 spenderColdkey, + uint256 netuid, + uint256 increaseAmount + ) external; + + /** + * @dev Transfer a subtensor stake `amount` associated with the `source_coldkey` to a different coldkey + * `destination_coldkey`. The `source_coldkey` must have approved beforehand the transaction signer + * (spender) to spend at least the `amount` (allowance). The allowance towards that spender will be + * decreased by this amount. + * + * This function allows external accounts and contracts to transfer staked TAO to another coldkey, + * which effectively calls `transfer_stake` on the subtensor pallet with specified destination + * coldkey as a parameter being the hashed address mapping of H160 sender address to Substrate ss58 + * address as implemented in Frontier HashedAddressMapping: + * https://github.com/polkadot-evm/frontier/blob/2e219e17a526125da003e64ef22ec037917083fa/frame/evm/src/lib.rs#L739 + * + * @param source_coldkey The source coldkey public key (32 bytes). + * @param destination_coldkey The destination coldkey public key (32 bytes). + * @param hotkey The hotkey public key (32 bytes). + * @param origin_netuid The subnet to move stake from (uint256). + * @param destination_netuid The subnet to move stake to (uint256). + * @param amount The amount to move in rao. + * + * Requirements: + * - `origin_hotkey` and `destination_hotkey` must be valid hotkeys registered on the network, ensuring + * that the stake is correctly attributed. + */ + function transferStakeFrom( + bytes32 source_coldkey + bytes32 destination_coldkey, + bytes32 hotkey, + uint256 origin_netuid, + uint256 destination_netuid, + uint256 amount + ) external; } diff --git a/precompiles/src/staking.rs b/precompiles/src/staking.rs index c276c32e60..6437910d6a 100644 --- a/precompiles/src/staking.rs +++ b/precompiles/src/staking.rs @@ -27,8 +27,10 @@ use alloc::vec::Vec; use core::marker::PhantomData; +use frame_support::Blake2_128Concat; use frame_support::dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo}; -use frame_support::traits::IsSubType; +use frame_support::pallet_prelude::{StorageDoubleMap, ValueQuery}; +use frame_support::traits::{IsSubType, StorageInstance}; use frame_system::RawOrigin; use pallet_evm::{ AddressMapping, BalanceConverter, EvmBalance, ExitError, PrecompileFailure, PrecompileHandle, @@ -36,6 +38,7 @@ use pallet_evm::{ }; use pallet_subtensor_proxy as pallet_proxy; use precompile_utils::EvmResult; +use precompile_utils::prelude::{RuntimeHelper, revert}; use sp_core::{H256, U256}; use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable, StaticLookup, UniqueSaturatedInto}; use sp_std::vec; @@ -43,6 +46,29 @@ use subtensor_runtime_common::{Currency, NetUid, ProxyType}; use crate::{PrecompileExt, PrecompileHandleExt}; +/// Prefix for the Approves map in Substrate storage. +pub struct ApprovesPrefix; +impl StorageInstance for ApprovesPrefix { + const STORAGE_PREFIX: &'static str = "Approves"; + + fn pallet_prefix() -> &'static str { + "EvmPrecompileStaking" + } +} + +pub type ApprovesStorage = StorageDoubleMap< + ApprovesPrefix, + // For each approver + Blake2_128Concat, + ::AccountId, + // For each pair of (spender, netuid) + Blake2_128Concat, + (::AccountId, u16), + // Allowed amount + U256, + ValueQuery, +>; + // Old StakingPrecompile had ETH-precision in values, which was not alligned with Substrate API. So // it's kinda deprecated, but exists for backward compatibility. Eventually, we should remove it // to stop supporting both precompiles. @@ -447,6 +473,126 @@ where Ok(stake.to_u64().into()) } + + #[precompile::public("setApproval(bytes32,bytes32,uint256)")] + fn set_approval( + handle: &mut impl PrecompileHandle, + spender_coldkey: H256, + origin_netuid: U256, + amount_alpha: U256, + ) -> EvmResult<()> { + // ApprovesStorage write + handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; + + let approver = handle.caller_account_id::(); + let spender = R::AccountId::from(spender_coldkey.0); + let netuid = try_u16_from_u256(origin_netuid)?; + + if new_amount.is_zero() { + ApprovesStorage::::remove(approver, &approval_key); + } else { + ApprovesStorage::::insert(approver, &approval_key, new_amount); + } + + Ok(()) + } + + #[precompile::public("approve(bytes32,bytes32,uint256)")] + fn approve( + handle: &mut impl PrecompileHandle, + spender_coldkey: H256, + origin_netuid: U256, + amount_alpha_increase: U256, + ) -> EvmResult<()> { + if amount_alpha_increase.is_zero() { + return Ok(()); + } + + // ApprovesStorage read + write + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; + + let approver = handle.caller_account_id::(); + let spender = R::AccountId::from(spender_coldkey.0); + let netuid = try_u16_from_u256(origin_netuid)?; + + let approval_key = (spender, netuid); + + let current_amount = ApprovesStorage::::get(&approver, &approval_key); + let new_amount = current_amount.saturating_add(amount_alpha_increase); + + ApprovesStorage::::insert(approver, &approval_key, new_amount); + + Ok(()) + } + + fn try_decrease_approval( + handle: &mut impl PrecompileHandle, + approver: R::AccountId, + spender: R::AccountId, + netuid: u16, + amount: U256, + ) -> EvmResult<()> { + if amount.is_zero() { + return Ok(()); + } + + // ApprovesStorage read + write + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; + + let approval_key = (spender, netuid); + + let current_amount = ApprovesStorage::::get(&approver, &approval_key); + let Some(new_amount) = current_amount.checked_sub(amount) else { + return Err(revert("trying to spend more than allowed")); + }; + + if new_amount.is_zero() { + ApprovesStorage::::remove(approver, &approval_key); + } else { + ApprovesStorage::::insert(approver, &approval_key, new_amount); + } + + Ok(()) + } + + #[precompile::public("transferStakeFrom(bytes32,bytes32,bytes32,uint256,uint256,uint256)")] + fn transfer_stake_from( + handle: &mut impl PrecompileHandle, + source_coldkey: H256, + destination_coldkey: H256, + hotkey: H256, + origin_netuid: U256, + destination_netuid: U256, + amount_alpha: U256, + ) -> EvmResult<()> { + let spender_id = handle.caller_account_id::(); + let source_id = R::AccountId::from(source_coldkey.0); + let destination_coldkey = R::AccountId::from(destination_coldkey.0); + let hotkey = R::AccountId::from(hotkey.0); + let origin_netuid = try_u16_from_u256(origin_netuid)?; + let destination_netuid = try_u16_from_u256(destination_netuid)?; + let alpha_amount: u64 = amount_alpha.unique_saturated_into(); + + Self::try_decrease_approval( + handle, + source_id.clone(), + spender_id, + origin_netuid, + amount_alpha, + )?; + + let call = pallet_subtensor::Call::::transfer_stake { + destination_coldkey, + hotkey, + origin_netuid: origin_netuid.into(), + destination_netuid: destination_netuid.into(), + alpha_amount: alpha_amount.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(source_id)) + } } // Deprecated, exists for backward compatibility. From cd7dad9dbc5f909f20a15c1e4e800641126bdb55 Mon Sep 17 00:00:00 2001 From: jeremyp-tao <259353334+jeremyp-tao@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:44:22 +0100 Subject: [PATCH 2/5] make names more aligned with ERC20 version + decrease + view function --- precompiles/src/solidity/stakingV2.sol | 40 +++++++++-- precompiles/src/staking.rs | 97 ++++++++++++++++++++------ 2 files changed, 110 insertions(+), 27 deletions(-) diff --git a/precompiles/src/solidity/stakingV2.sol b/precompiles/src/solidity/stakingV2.sol index 5a89980a49..7b14f6a041 100644 --- a/precompiles/src/solidity/stakingV2.sol +++ b/precompiles/src/solidity/stakingV2.sol @@ -324,17 +324,30 @@ interface IStaking { * @param netuid The approved subnet token. * @param absoluteAmount New approval amount, will overwrite previous value. */ - function setApproval( + function approve( bytes32 spenderColdkey, uint256 netuid, uint256 absoluteAmount ) external; /** - * @dev Increase much the caller approves the spender to use the provided amount of subnet tokens + * @dev Get how much the source allows the spender to use their subnet tokens + * + * @param spenderColdkey Coldkey of the source making the allowance. + * @param spenderColdkey Coldkey of the address allowed to spend funds from the source. + * @param netuid The approved subnet token. + */ + function allowance( + bytes32 sourceColdkey, + bytes32 spenderColdkey, + uint256 netuid, + ) external view returns (uint256); + + /** + * @dev Increase how much the caller approves the spender to use the provided amount of subnet tokens * on its behalf in a later call. * - * This is similar to ERC20 approve, and then allows smart contract to transfer with permission from + * This is similar to ERC20 increaseAllowance, and then allows smart contract to transfer with permission from * other accounts during their execution. They can then act as escrows while knowing from whom * the funds comes from, which is not possible if the spender called `transfer` towards the contract * (no callback). @@ -343,12 +356,31 @@ interface IStaking { * @param netuid The approved subnet token. * @param increaseAmount How much the approval amount should be increased. */ - function approve( + function increaseAllowance( bytes32 spenderColdkey, uint256 netuid, uint256 increaseAmount ) external; + /** + * @dev Decrease how much the caller approves the spender to use the provided amount of subnet tokens + * on its behalf in a later call. + * + * This is similar to ERC20 decreaseAllowance, and then allows smart contract to transfer with permission from + * other accounts during their execution. They can then act as escrows while knowing from whom + * the funds comes from, which is not possible if the spender called `transfer` towards the contract + * (no callback). + * + * @param spenderColdkey Coldkey of the address allowed to spend funds from the caller. + * @param netuid The approved subnet token. + * @param increaseAmount How much the approval amount should be decrease. + */ + function decreaseAllowance( + bytes32 spenderColdkey, + uint256 netuid, + uint256 decreaseAmount + ) external; + /** * @dev Transfer a subtensor stake `amount` associated with the `source_coldkey` to a different coldkey * `destination_coldkey`. The `source_coldkey` must have approved beforehand the transaction signer diff --git a/precompiles/src/staking.rs b/precompiles/src/staking.rs index 6437910d6a..d119906452 100644 --- a/precompiles/src/staking.rs +++ b/precompiles/src/staking.rs @@ -46,18 +46,18 @@ use subtensor_runtime_common::{Currency, NetUid, ProxyType}; use crate::{PrecompileExt, PrecompileHandleExt}; -/// Prefix for the Approves map in Substrate storage. -pub struct ApprovesPrefix; -impl StorageInstance for ApprovesPrefix { - const STORAGE_PREFIX: &'static str = "Approves"; +/// Prefix for the Allowances map in Substrate storage. +pub struct AllowancesPrefix; +impl StorageInstance for AllowancesPrefix { + const STORAGE_PREFIX: &'static str = "Allowances"; fn pallet_prefix() -> &'static str { "EvmPrecompileStaking" } } -pub type ApprovesStorage = StorageDoubleMap< - ApprovesPrefix, +pub type AllowancesStorage = StorageDoubleMap< + AllowancesPrefix, // For each approver Blake2_128Concat, ::AccountId, @@ -474,31 +474,49 @@ where Ok(stake.to_u64().into()) } - #[precompile::public("setApproval(bytes32,bytes32,uint256)")] - fn set_approval( + #[precompile::public("approve(bytes32,bytes32,uint256)")] + fn approve( handle: &mut impl PrecompileHandle, spender_coldkey: H256, origin_netuid: U256, amount_alpha: U256, ) -> EvmResult<()> { - // ApprovesStorage write + // AllowancesStorage write handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; let approver = handle.caller_account_id::(); let spender = R::AccountId::from(spender_coldkey.0); let netuid = try_u16_from_u256(origin_netuid)?; - if new_amount.is_zero() { - ApprovesStorage::::remove(approver, &approval_key); + if amount_alpha.is_zero() { + AllowancesStorage::::remove(approver, (spender, netuid)); } else { - ApprovesStorage::::insert(approver, &approval_key, new_amount); + AllowancesStorage::::insert(approver, (spender, netuid), amount_alpha); } Ok(()) } - #[precompile::public("approve(bytes32,bytes32,uint256)")] - fn approve( + #[precompile::public("allowance(bytes32,bytes32,bytes32)")] + #[precompile::view] + fn allowance( + handle: &mut impl PrecompileHandle, + source_coldkey: H256, + spender_coldkey: H256, + origin_netuid: U256, + ) -> EvmResult { + // AllowancesStorage read + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + let source = R::AccountId::from(source_coldkey.0); + let spender = R::AccountId::from(spender_coldkey.0); + let netuid = try_u16_from_u256(origin_netuid)?; + + Ok(AllowancesStorage::::get(source, (spender, netuid))) + } + + #[precompile::public("increaseAllowance(bytes32,bytes32,uint256)")] + fn increase_allowance( handle: &mut impl PrecompileHandle, spender_coldkey: H256, origin_netuid: U256, @@ -508,7 +526,7 @@ where return Ok(()); } - // ApprovesStorage read + write + // AllowancesStorage read + write handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; @@ -518,15 +536,48 @@ where let approval_key = (spender, netuid); - let current_amount = ApprovesStorage::::get(&approver, &approval_key); + let current_amount = AllowancesStorage::::get(&approver, &approval_key); let new_amount = current_amount.saturating_add(amount_alpha_increase); - ApprovesStorage::::insert(approver, &approval_key, new_amount); + AllowancesStorage::::insert(approver, &approval_key, new_amount); + + Ok(()) + } + + #[precompile::public("decreaseAllowance(bytes32,bytes32,uint256)")] + fn decrease_allowance( + handle: &mut impl PrecompileHandle, + spender_coldkey: H256, + origin_netuid: U256, + amount_alpha_decrease: U256, + ) -> EvmResult<()> { + if amount_alpha_decrease.is_zero() { + return Ok(()); + } + + // AllowancesStorage read + write + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; + + let approver = handle.caller_account_id::(); + let spender = R::AccountId::from(spender_coldkey.0); + let netuid = try_u16_from_u256(origin_netuid)?; + + let approval_key = (spender, netuid); + + let current_amount = AllowancesStorage::::get(&approver, &approval_key); + let new_amount = current_amount.saturating_sub(amount_alpha_decrease); + + if new_amount.is_zero() { + AllowancesStorage::::remove(approver, &approval_key); + } else { + AllowancesStorage::::insert(approver, &approval_key, new_amount); + } Ok(()) } - fn try_decrease_approval( + fn try_consume_allowance( handle: &mut impl PrecompileHandle, approver: R::AccountId, spender: R::AccountId, @@ -537,21 +588,21 @@ where return Ok(()); } - // ApprovesStorage read + write + // AllowancesStorage read + write handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; let approval_key = (spender, netuid); - let current_amount = ApprovesStorage::::get(&approver, &approval_key); + let current_amount = AllowancesStorage::::get(&approver, &approval_key); let Some(new_amount) = current_amount.checked_sub(amount) else { return Err(revert("trying to spend more than allowed")); }; if new_amount.is_zero() { - ApprovesStorage::::remove(approver, &approval_key); + AllowancesStorage::::remove(approver, &approval_key); } else { - ApprovesStorage::::insert(approver, &approval_key, new_amount); + AllowancesStorage::::insert(approver, &approval_key, new_amount); } Ok(()) @@ -575,7 +626,7 @@ where let destination_netuid = try_u16_from_u256(destination_netuid)?; let alpha_amount: u64 = amount_alpha.unique_saturated_into(); - Self::try_decrease_approval( + Self::try_consume_allowance( handle, source_id.clone(), spender_id, From f1d254ab700354a48f4a948384b54d5ac37cf9c0 Mon Sep 17 00:00:00 2001 From: jeremyp-tao <259353334+jeremyp-tao@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:13:23 +0100 Subject: [PATCH 3/5] update abis + test setup --- contract-tests/src/contracts/staking.ts | 1038 ++++++++++------- .../test/staking.precompile.approval.test.ts | 92 ++ precompiles/src/solidity/stakingV2.abi | 136 +++ 3 files changed, 815 insertions(+), 451 deletions(-) create mode 100644 contract-tests/test/staking.precompile.approval.test.ts diff --git a/contract-tests/src/contracts/staking.ts b/contract-tests/src/contracts/staking.ts index 32957342b4..d63d21bc3f 100644 --- a/contract-tests/src/contracts/staking.ts +++ b/contract-tests/src/contracts/staking.ts @@ -2,457 +2,593 @@ export const ISTAKING_ADDRESS = "0x0000000000000000000000000000000000000801"; export const ISTAKING_V2_ADDRESS = "0x0000000000000000000000000000000000000805"; export const IStakingABI = [ - { - inputs: [ - { - internalType: "bytes32", - name: "delegate", - type: "bytes32", - }, - ], - name: "addProxy", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "hotkey", - type: "bytes32", - }, - { - internalType: "uint256", - name: "netuid", - type: "uint256", - }, - ], - name: "addStake", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "delegate", - type: "bytes32", - }, - ], - name: "removeProxy", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "hotkey", - type: "bytes32", - }, - { - internalType: "bytes32", - name: "coldkey", - type: "bytes32", - }, - { - internalType: "uint256", - name: "netuid", - type: "uint256", - }, - ], - name: "getStake", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "hotkey", - type: "bytes32", - }, - { - internalType: "uint256", - name: "amount", - type: "uint256", - }, - { - internalType: "uint256", - name: "netuid", - type: "uint256", - }, - ], - name: "removeStake", - outputs: [], - stateMutability: "payable", - type: "function", - }, + { + inputs: [ + { + internalType: "bytes32", + name: "delegate", + type: "bytes32", + }, + ], + name: "addProxy", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "hotkey", + type: "bytes32", + }, + { + internalType: "uint256", + name: "netuid", + type: "uint256", + }, + ], + name: "addStake", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "delegate", + type: "bytes32", + }, + ], + name: "removeProxy", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "hotkey", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "coldkey", + type: "bytes32", + }, + { + internalType: "uint256", + name: "netuid", + type: "uint256", + }, + ], + name: "getStake", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "hotkey", + type: "bytes32", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "uint256", + name: "netuid", + type: "uint256", + }, + ], + name: "removeStake", + outputs: [], + stateMutability: "payable", + type: "function", + }, ]; export const IStakingV2ABI = [ - { - "inputs": [ - { - "internalType": "bytes32", - "name": "delegate", - "type": "bytes32" - } - ], - "name": "addProxy", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "addStake", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "getAlphaStakedValidators", - "outputs": [ - { - "internalType": "uint256[]", - "name": "", - "type": "uint256[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "coldkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "getStake", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "getTotalAlphaStaked", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "coldkey", - "type": "bytes32" - } - ], - "name": "getTotalColdkeyStake", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "coldkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "getTotalColdkeyStakeOnSubnet", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - } - ], - "name": "getTotalHotkeyStake", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getNominatorMinRequiredStake", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "delegate", - "type": "bytes32" - } - ], - "name": "removeProxy", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "removeStake", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit_price", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "allow_partial", - "type": "bool" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "addStakeLimit", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit_price", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "allow_partial", - "type": "bool" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "removeStakeLimit", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - }, - ], - "name": "removeStakeFull", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit_price", - "type": "uint256" - } - ], - "name": "removeStakeFullLimit", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "burnAlpha", - "outputs": [], - "stateMutability": "payable", - "type": "function" - } -]; \ No newline at end of file + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + } + ], + "name": "addProxy", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "addStake", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "getAlphaStakedValidators", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "coldkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "getStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "getTotalAlphaStaked", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "coldkey", + "type": "bytes32" + } + ], + "name": "getTotalColdkeyStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "coldkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "getTotalColdkeyStakeOnSubnet", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + } + ], + "name": "getTotalHotkeyStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNominatorMinRequiredStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + } + ], + "name": "removeProxy", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "removeStake", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit_price", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "allow_partial", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "addStakeLimit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit_price", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "allow_partial", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "removeStakeLimit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + }, + ], + "name": "removeStakeFull", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit_price", + "type": "uint256" + } + ], + "name": "removeStakeFullLimit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "burnAlpha", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "spenderColdkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "absoluteAmount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "sourceColdkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "spenderColdkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "spenderColdkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "increaseAmount", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [], + "stateMutability": "", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "spenderColdkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "decreaseAmount", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [], + "stateMutability": "", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "source_coldkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "destination_coldkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "origin_netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "destination_netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferStakeFrom", + "outputs": [], + "stateMutability": "", + "type": "function" + } +]; diff --git a/contract-tests/test/staking.precompile.approval.test.ts b/contract-tests/test/staking.precompile.approval.test.ts new file mode 100644 index 0000000000..8485977b98 --- /dev/null +++ b/contract-tests/test/staking.precompile.approval.test.ts @@ -0,0 +1,92 @@ +import * as assert from "assert"; +import { getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { devnet } from "@polkadot-api/descriptors" +import { PolkadotSigner, TypedApi } from "polkadot-api"; +import { convertPublicKeyToSs58, convertH160ToSS58 } from "../src/address-utils" +import { raoToEth, tao } from "../src/balance-math" +import { ethers } from "ethers" +import { generateRandomEthersWallet, getPublicClient } from "../src/utils" +import { convertH160ToPublicKey } from "../src/address-utils" +import { + forceSetBalanceToEthAddress, forceSetBalanceToSs58Address, addNewSubnetwork, burnedRegister, + sendProxyCall, + startCall, +} from "../src/subtensor" +import { ETH_LOCAL_URL } from "../src/config"; +import { ISTAKING_ADDRESS, ISTAKING_V2_ADDRESS, IStakingABI, IStakingV2ABI } from "../src/contracts/staking" +import { PublicClient } from "viem"; + +describe("Test approval in staking precompile", () => { + // init eth part + const wallet1 = generateRandomEthersWallet(); + const wallet2 = generateRandomEthersWallet(); + let publicClient: PublicClient; + // init substrate part + const hotkey = getRandomSubstrateKeypair(); + const coldkey = getRandomSubstrateKeypair(); + const proxy = getRandomSubstrateKeypair(); + + let api: TypedApi + let stakeNetuid: number; + + // sudo account alice as signer + let alice: PolkadotSigner; + before(async () => { + publicClient = await getPublicClient(ETH_LOCAL_URL) + // init variables got from await and async + api = await getDevnetApi() + + // await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(alice.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(coldkey.publicKey)) + await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(proxy.publicKey)) + await forceSetBalanceToEthAddress(api, wallet1.address) + await forceSetBalanceToEthAddress(api, wallet2.address) + let netuid = await addNewSubnetwork(api, hotkey, coldkey) + await startCall(api, netuid, coldkey) + + console.log("test the case on subnet ", netuid) + + await burnedRegister(api, netuid, convertH160ToSS58(wallet1.address), coldkey) + await burnedRegister(api, netuid, convertH160ToSS58(wallet2.address), coldkey) + + // add stake as wallet1 + { + stakeNetuid = (await api.query.SubtensorModule.TotalNetworks.getValue()) - 1 + // the unit in V2 is RAO, not ETH + let stakeBalance = tao(20) + const stakeBefore = await api.query.SubtensorModule.Alpha.getValue(convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet1.address), stakeNetuid) + const contract = new ethers.Contract(ISTAKING_V2_ADDRESS, IStakingV2ABI, wallet1); + const tx = await contract.addStake(hotkey.publicKey, stakeBalance.toString(), stakeNetuid) + await tx.wait() + + const stakeFromContract = BigInt( + await contract.getStake(hotkey.publicKey, convertH160ToPublicKey(wallet1.address), stakeNetuid) + ); + + assert.ok(stakeFromContract > stakeBefore) + const stakeAfter = await api.query.SubtensorModule.Alpha.getValue(convertPublicKeyToSs58(hotkey.publicKey), convertH160ToSS58(wallet1.address), stakeNetuid) + assert.ok(stakeAfter > stakeBefore) + } + }) + + it("Can't transfer from account without approval ", async () => { + try { + // wallet2 tries to transfer from wallet1 + const contract = new ethers.Contract(ISTAKING_V2_ADDRESS, IStakingV2ABI, wallet2); + const tx = await contract.transferStakeFrom( + convertH160ToPublicKey(wallet1.address), // source + convertH160ToPublicKey(wallet2.address), // distination + hotkey.publicKey, + stakeNetuid, + stakeNetuid, + 1 + ) + await tx.wait(); + + assert.fail("should have reverted due to missing allowance"); + } catch (e) { + assert.equal(e.reason, "trying to spend more than allowed", "wrong revert message"); + } + }) +}) diff --git a/precompiles/src/solidity/stakingV2.abi b/precompiles/src/solidity/stakingV2.abi index 40a5acc1d9..14140912b4 100644 --- a/precompiles/src/solidity/stakingV2.abi +++ b/precompiles/src/solidity/stakingV2.abi @@ -394,5 +394,141 @@ "outputs": [], "stateMutability": "payable", "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "spenderColdkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "absoluteAmount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "sourceColdkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "spenderColdkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "spenderColdkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "increaseAmount", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [], + "stateMutability": "", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "spenderColdkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "decreaseAmount", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [], + "stateMutability": "", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "source_coldkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "destination_coldkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "origin_netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "destination_netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferStakeFrom", + "outputs": [], + "stateMutability": "", + "type": "function" } ] From 38dc14a570f0beabee2141f282f401029e9b2b39 Mon Sep 17 00:00:00 2001 From: jeremyp-tao <259353334+jeremyp-tao@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:44:58 +0100 Subject: [PATCH 4/5] fix selectors + fully test precompile --- .../test/staking.precompile.approval.test.ts | 151 +++++++++++++++++- precompiles/src/staking.rs | 12 +- 2 files changed, 156 insertions(+), 7 deletions(-) diff --git a/contract-tests/test/staking.precompile.approval.test.ts b/contract-tests/test/staking.precompile.approval.test.ts index 8485977b98..b6dfc9e6b5 100644 --- a/contract-tests/test/staking.precompile.approval.test.ts +++ b/contract-tests/test/staking.precompile.approval.test.ts @@ -29,6 +29,8 @@ describe("Test approval in staking precompile", () => { let api: TypedApi let stakeNetuid: number; + let expectedAllowance = BigInt(0); + // sudo account alice as signer let alice: PolkadotSigner; before(async () => { @@ -70,7 +72,7 @@ describe("Test approval in staking precompile", () => { } }) - it("Can't transfer from account without approval ", async () => { + it("Can't transfer from account without approval", async () => { try { // wallet2 tries to transfer from wallet1 const contract = new ethers.Contract(ISTAKING_V2_ADDRESS, IStakingV2ABI, wallet2); @@ -89,4 +91,151 @@ describe("Test approval in staking precompile", () => { assert.equal(e.reason, "trying to spend more than allowed", "wrong revert message"); } }) + + it("Can approve some amount", async () => { + const contract = new ethers.Contract(ISTAKING_V2_ADDRESS, IStakingV2ABI, wallet1); + + { + let allowance = BigInt( + await contract.allowance( + convertH160ToPublicKey(wallet1.address), // source + convertH160ToPublicKey(wallet2.address), // destination + stakeNetuid, + ) + ); + assert.equal(allowance, expectedAllowance, "default allowance should be 0"); + } + + { + const tx = await contract.approve( + convertH160ToPublicKey(wallet2.address), // destination + stakeNetuid, + tao(10) + ) + await tx.wait(); + + expectedAllowance += BigInt(tao(10)); + + let allowance = BigInt( + await contract.allowance( + convertH160ToPublicKey(wallet1.address), // source + convertH160ToPublicKey(wallet2.address), // destination + stakeNetuid, + ) + ); + assert.equal(allowance, expectedAllowance, "should have set allowance"); + } + }) + + it("Can now use transfer from", async () => { + const contract = new ethers.Contract(ISTAKING_V2_ADDRESS, IStakingV2ABI, wallet2); + + // wallet2 transfer from wallet1 + const tx = await contract.transferStakeFrom( + convertH160ToPublicKey(wallet1.address), // source + convertH160ToPublicKey(wallet2.address), // distination + hotkey.publicKey, + stakeNetuid, + stakeNetuid, + tao(5) + ) + await tx.wait(); + + expectedAllowance -= BigInt(tao(5)); + + { + let allowance = BigInt( + await contract.allowance( + convertH160ToPublicKey(wallet1.address), // source + convertH160ToPublicKey(wallet2.address), // destination + stakeNetuid, + ) + ); + assert.equal(allowance, expectedAllowance, "allowance should now be 500"); + } + }) + + it("Can't use transfer from with amount too high", async () => { + try { + // wallet2 tries to transfer from wallet1 + const contract = new ethers.Contract(ISTAKING_V2_ADDRESS, IStakingV2ABI, wallet2); + const tx = await contract.transferStakeFrom( + convertH160ToPublicKey(wallet1.address), // source + convertH160ToPublicKey(wallet2.address), // distination + hotkey.publicKey, + stakeNetuid, + stakeNetuid, + expectedAllowance + BigInt(1) + ) + await tx.wait(); + + assert.fail("should have reverted due to missing allowance"); + } catch (e) { + assert.equal(e.reason, "trying to spend more than allowed", "wrong revert message"); + } + }) + + it("Approval functions works as expected", async () => { + const contract = new ethers.Contract(ISTAKING_V2_ADDRESS, IStakingV2ABI, wallet1); + + { + const tx = await contract.increaseAllowance( + convertH160ToPublicKey(wallet2.address), // destination + stakeNetuid, + tao(10) + ) + await tx.wait(); + + expectedAllowance += BigInt(tao(10)); + + let allowance = BigInt( + await contract.allowance( + convertH160ToPublicKey(wallet1.address), // source + convertH160ToPublicKey(wallet2.address), // destination + stakeNetuid, + ) + ); + assert.equal(allowance, expectedAllowance, "allowance have been increased correctly"); + } + + { + const tx = await contract.decreaseAllowance( + convertH160ToPublicKey(wallet2.address), // destination + stakeNetuid, + tao(2) + ) + await tx.wait(); + + expectedAllowance -= BigInt(tao(2)); + + let allowance = BigInt( + await contract.allowance( + convertH160ToPublicKey(wallet1.address), // source + convertH160ToPublicKey(wallet2.address), // destination + stakeNetuid, + ) + ); + assert.equal(allowance, expectedAllowance, "allowance have been decreased correctly"); + } + + { + const tx = await contract.approve( + convertH160ToPublicKey(wallet2.address), // destination + stakeNetuid, + 0 + ) + await tx.wait(); + + expectedAllowance = BigInt(0); + + let allowance = BigInt( + await contract.allowance( + convertH160ToPublicKey(wallet1.address), // source + convertH160ToPublicKey(wallet2.address), // destination + stakeNetuid, + ) + ); + assert.equal(allowance, expectedAllowance, "allowance have been overwritten correctly"); + } + }) }) diff --git a/precompiles/src/staking.rs b/precompiles/src/staking.rs index d119906452..b3b8cad5ab 100644 --- a/precompiles/src/staking.rs +++ b/precompiles/src/staking.rs @@ -474,7 +474,7 @@ where Ok(stake.to_u64().into()) } - #[precompile::public("approve(bytes32,bytes32,uint256)")] + #[precompile::public("approve(bytes32,uint256,uint256)")] fn approve( handle: &mut impl PrecompileHandle, spender_coldkey: H256, @@ -497,11 +497,11 @@ where Ok(()) } - #[precompile::public("allowance(bytes32,bytes32,bytes32)")] - #[precompile::view] + #[precompile::public("allowance(bytes32,bytes32,uint256)")] + #[precompile::view] fn allowance( handle: &mut impl PrecompileHandle, - source_coldkey: H256, + source_coldkey: H256, spender_coldkey: H256, origin_netuid: U256, ) -> EvmResult { @@ -515,7 +515,7 @@ where Ok(AllowancesStorage::::get(source, (spender, netuid))) } - #[precompile::public("increaseAllowance(bytes32,bytes32,uint256)")] + #[precompile::public("increaseAllowance(bytes32,uint256,uint256)")] fn increase_allowance( handle: &mut impl PrecompileHandle, spender_coldkey: H256, @@ -544,7 +544,7 @@ where Ok(()) } - #[precompile::public("decreaseAllowance(bytes32,bytes32,uint256)")] + #[precompile::public("decreaseAllowance(bytes32,uint256,uint256)")] fn decrease_allowance( handle: &mut impl PrecompileHandle, spender_coldkey: H256, From d2acf47b89a2cf94854536c29527e21aa05f81ec Mon Sep 17 00:00:00 2001 From: jeremyp-tao <259353334+jeremyp-tao@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:46:38 +0100 Subject: [PATCH 5/5] fix format in ts file --- contract-tests/src/contracts/staking.ts | 1172 +++++++++++------------ 1 file changed, 586 insertions(+), 586 deletions(-) diff --git a/contract-tests/src/contracts/staking.ts b/contract-tests/src/contracts/staking.ts index d63d21bc3f..5202909849 100644 --- a/contract-tests/src/contracts/staking.ts +++ b/contract-tests/src/contracts/staking.ts @@ -2,593 +2,593 @@ export const ISTAKING_ADDRESS = "0x0000000000000000000000000000000000000801"; export const ISTAKING_V2_ADDRESS = "0x0000000000000000000000000000000000000805"; export const IStakingABI = [ - { - inputs: [ - { - internalType: "bytes32", - name: "delegate", - type: "bytes32", - }, - ], - name: "addProxy", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "hotkey", - type: "bytes32", - }, - { - internalType: "uint256", - name: "netuid", - type: "uint256", - }, - ], - name: "addStake", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "delegate", - type: "bytes32", - }, - ], - name: "removeProxy", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "hotkey", - type: "bytes32", - }, - { - internalType: "bytes32", - name: "coldkey", - type: "bytes32", - }, - { - internalType: "uint256", - name: "netuid", - type: "uint256", - }, - ], - name: "getStake", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "hotkey", - type: "bytes32", - }, - { - internalType: "uint256", - name: "amount", - type: "uint256", - }, - { - internalType: "uint256", - name: "netuid", - type: "uint256", - }, - ], - name: "removeStake", - outputs: [], - stateMutability: "payable", - type: "function", - }, + { + inputs: [ + { + internalType: "bytes32", + name: "delegate", + type: "bytes32", + }, + ], + name: "addProxy", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "hotkey", + type: "bytes32", + }, + { + internalType: "uint256", + name: "netuid", + type: "uint256", + }, + ], + name: "addStake", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "delegate", + type: "bytes32", + }, + ], + name: "removeProxy", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "hotkey", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "coldkey", + type: "bytes32", + }, + { + internalType: "uint256", + name: "netuid", + type: "uint256", + }, + ], + name: "getStake", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes32", + name: "hotkey", + type: "bytes32", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + { + internalType: "uint256", + name: "netuid", + type: "uint256", + }, + ], + name: "removeStake", + outputs: [], + stateMutability: "payable", + type: "function", + }, ]; export const IStakingV2ABI = [ - { - "inputs": [ - { - "internalType": "bytes32", - "name": "delegate", - "type": "bytes32" - } - ], - "name": "addProxy", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "addStake", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "getAlphaStakedValidators", - "outputs": [ - { - "internalType": "uint256[]", - "name": "", - "type": "uint256[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "coldkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "getStake", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "getTotalAlphaStaked", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "coldkey", - "type": "bytes32" - } - ], - "name": "getTotalColdkeyStake", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "coldkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "getTotalColdkeyStakeOnSubnet", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - } - ], - "name": "getTotalHotkeyStake", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getNominatorMinRequiredStake", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "delegate", - "type": "bytes32" - } - ], - "name": "removeProxy", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "removeStake", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit_price", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "allow_partial", - "type": "bool" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "addStakeLimit", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit_price", - "type": "uint256" - }, - { - "internalType": "bool", - "name": "allow_partial", - "type": "bool" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "removeStakeLimit", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - }, - ], - "name": "removeStakeFull", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "limit_price", - "type": "uint256" - } - ], - "name": "removeStakeFullLimit", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "burnAlpha", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "spenderColdkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "absoluteAmount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [], - "stateMutability": "", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "sourceColdkey", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "spenderColdkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "spenderColdkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "increaseAmount", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [], - "stateMutability": "", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "spenderColdkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "netuid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "decreaseAmount", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [], - "stateMutability": "", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "source_coldkey", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "destination_coldkey", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "hotkey", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "origin_netuid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "destination_netuid", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferStakeFrom", - "outputs": [], - "stateMutability": "", - "type": "function" - } + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + } + ], + "name": "addProxy", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "addStake", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "getAlphaStakedValidators", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "coldkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "getStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "getTotalAlphaStaked", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "coldkey", + "type": "bytes32" + } + ], + "name": "getTotalColdkeyStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "coldkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "getTotalColdkeyStakeOnSubnet", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + } + ], + "name": "getTotalHotkeyStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNominatorMinRequiredStake", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + } + ], + "name": "removeProxy", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "removeStake", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit_price", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "allow_partial", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "addStakeLimit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit_price", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "allow_partial", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "removeStakeLimit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + }, + ], + "name": "removeStakeFull", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit_price", + "type": "uint256" + } + ], + "name": "removeStakeFullLimit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "burnAlpha", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "spenderColdkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "absoluteAmount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "sourceColdkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "spenderColdkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "spenderColdkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "increaseAmount", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [], + "stateMutability": "", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "spenderColdkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "decreaseAmount", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [], + "stateMutability": "", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "source_coldkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "destination_coldkey", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "hotkey", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "origin_netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "destination_netuid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferStakeFrom", + "outputs": [], + "stateMutability": "", + "type": "function" + } ];