diff --git a/precompiles/src/balance_transfer.rs b/precompiles/src/balance_transfer.rs index 6cf76a1110..03e667695f 100644 --- a/precompiles/src/balance_transfer.rs +++ b/precompiles/src/balance_transfer.rs @@ -24,11 +24,11 @@ where ::Balance: TryFrom, { const INDEX: u64 = 2048; - const ADDRESS_SS58: [u8; 32] = [ + const ADDRESS_SS58: Option<[u8; 32]> = Some([ 0x07, 0xec, 0x71, 0x2a, 0x5d, 0x38, 0x43, 0x4d, 0xdd, 0x03, 0x3f, 0x8f, 0x02, 0x4e, 0xcd, 0xfc, 0x4b, 0xb5, 0x95, 0x1c, 0x13, 0xc3, 0x08, 0x5c, 0x39, 0x9c, 0x8a, 0x5f, 0x62, 0x93, 0x70, 0x5d, - ]; + ]); } #[precompile_utils::precompile] @@ -60,6 +60,11 @@ where value: amount_sub.unique_saturated_into(), }; - handle.try_dispatch_runtime_call::(call, contract_to_origin(&Self::ADDRESS_SS58)?) + handle.try_dispatch_runtime_call::( + call, + contract_to_origin( + &Self::ADDRESS_SS58.expect("ADDRESS_SS58 is defined for BalanceTransferPrecompile"), + )?, + ) } } diff --git a/precompiles/src/ed25519.rs b/precompiles/src/ed25519.rs index e3791e2485..f1e9aa1103 100644 --- a/precompiles/src/ed25519.rs +++ b/precompiles/src/ed25519.rs @@ -12,7 +12,7 @@ pub(crate) struct Ed25519Verify; impl PrecompileExt for Ed25519Verify { const INDEX: u64 = 1026; - const ADDRESS_SS58: [u8; 32] = [0; 32]; + const ADDRESS_SS58: Option<[u8; 32]> = None; } impl LinearCostPrecompile for Ed25519Verify { diff --git a/precompiles/src/lib.rs b/precompiles/src/lib.rs index 499b3a5f0d..f5338853e3 100644 --- a/precompiles/src/lib.rs +++ b/precompiles/src/lib.rs @@ -86,7 +86,7 @@ where Self(Default::default()) } - pub fn used_addresses() -> [H160; 13] { + pub fn used_addresses() -> [H160; 14] { [ hash(1), hash(2), @@ -101,6 +101,7 @@ where hash(SubnetPrecompile::::INDEX), hash(MetagraphPrecompile::::INDEX), hash(NeuronPrecompile::::INDEX), + hash(StakingPrecompileV2::::INDEX), ] } } @@ -152,9 +153,16 @@ where Some(StakingPrecompile::::execute(handle)) } else { Some(Err(PrecompileFailure::Error { - exit_status: ExitError::Other( - "Precompile Balance Transfer is disabled".into(), - ), + exit_status: ExitError::Other("Precompile Staking is disabled".into()), + })) + } + } + a if a == hash(StakingPrecompileV2::::INDEX) => { + if PrecompileEnable::::get(PrecompileEnum::Staking) { + Some(StakingPrecompileV2::::execute(handle)) + } else { + Some(Err(PrecompileFailure::Error { + exit_status: ExitError::Other("Precompile Staking is disabled".into()), })) } } @@ -302,5 +310,5 @@ trait PrecompileExt: Precompile { const INDEX: u64; // ss58 public key i.e., the contract sends funds it received to the destination address from // the method parameter. - const ADDRESS_SS58: [u8; 32]; + const ADDRESS_SS58: Option<[u8; 32]>; } diff --git a/precompiles/src/metagraph.rs b/precompiles/src/metagraph.rs index a5a70e12f3..061c41afb6 100644 --- a/precompiles/src/metagraph.rs +++ b/precompiles/src/metagraph.rs @@ -16,7 +16,7 @@ where R::AccountId: ByteArray, { const INDEX: u64 = 2050; - const ADDRESS_SS58: [u8; 32] = [0; 32]; + const ADDRESS_SS58: Option<[u8; 32]> = None; } #[precompile_utils::precompile] diff --git a/precompiles/src/neuron.rs b/precompiles/src/neuron.rs index 9091aa8694..e1fc65274a 100644 --- a/precompiles/src/neuron.rs +++ b/precompiles/src/neuron.rs @@ -23,11 +23,11 @@ where ::AddressMapping: AddressMapping, { const INDEX: u64 = 2052; - const ADDRESS_SS58: [u8; 32] = [ + const ADDRESS_SS58: Option<[u8; 32]> = Some([ 0xbc, 0x46, 0x35, 0x79, 0xbc, 0x99, 0xf9, 0xee, 0x7c, 0x59, 0xed, 0xee, 0x20, 0x61, 0xa3, 0x09, 0xd2, 0x1e, 0x68, 0xd5, 0x39, 0xb6, 0x40, 0xec, 0x66, 0x46, 0x90, 0x30, 0xab, 0x74, 0xc1, 0xdb, - ]; + ]); } #[precompile_utils::precompile] diff --git a/precompiles/src/solidity/stakingV2.abi b/precompiles/src/solidity/stakingV2.abi new file mode 100644 index 0000000000..21dd2761e4 --- /dev/null +++ b/precompiles/src/solidity/stakingV2.abi @@ -0,0 +1,141 @@ +[ + { + "inputs": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + } + ], + "name": "addProxy", + "outputs": [], + "stateMutability": "nonpayable", + "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": "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": "coldkey", + "type": "bytes32" + } + ], + "name": "getTotalColdkeyStake", + "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": [ + { + "internalType": "bytes32", + "name": "delegate", + "type": "bytes32" + } + ], + "name": "removeProxy", + "outputs": [], + "stateMutability": "nonpayable", + "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": "nonpayable", + "type": "function" + } +] diff --git a/precompiles/src/solidity/stakingV2.sol b/precompiles/src/solidity/stakingV2.sol new file mode 100644 index 0000000000..67ac0cb129 --- /dev/null +++ b/precompiles/src/solidity/stakingV2.sol @@ -0,0 +1,103 @@ +pragma solidity ^0.8.0; + +address constant ISTAKING_ADDRESS = 0x0000000000000000000000000000000000000805; + +interface IStaking { + /** + * @dev Adds a subtensor stake `amount` associated with the `hotkey`. + * + * This function allows external accounts and contracts to stake TAO into the subtensor pallet, + * which effectively calls `add_stake` on the subtensor pallet with specified hotkey as a parameter + * and coldkey 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 hotkey The hotkey public key (32 bytes). + * @param amount The amount to stake in rao. + * @param netuid The subnet to stake to (uint256). + * + * Requirements: + * - `hotkey` must be a valid hotkey registered on the network, ensuring that the stake is + * correctly attributed. + */ + function addStake(bytes32 hotkey, uint256 amount, uint256 netuid) external payable; + + /** + * @dev Removes a subtensor stake `amount` from the specified `hotkey`. + * + * This function allows external accounts and contracts to unstake TAO from the subtensor pallet, + * which effectively calls `remove_stake` on the subtensor pallet with specified hotkey as a parameter + * and coldkey 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 hotkey The hotkey public key (32 bytes). + * @param amount The amount to unstake in alpha. + * @param netuid The subnet to stake to (uint256). + * + * Requirements: + * - `hotkey` must be a valid hotkey registered on the network, ensuring that the stake is + * correctly attributed. + * - The existing stake amount must be not lower than specified amount + */ + function removeStake( + bytes32 hotkey, + uint256 amount, + uint256 netuid + ) external; + + /** + * @dev Returns the amount of RAO staked by the coldkey. + * + * This function allows external accounts and contracts to query the amount of RAO staked by the coldkey + * which effectively calls `get_total_coldkey_stake` on the subtensor pallet with + * specified coldkey as a parameter. + * + * @param coldkey The coldkey public key (32 bytes). + * @return The amount of RAO staked by the coldkey. + */ + function getTotalColdkeyStake(bytes32 coldkey) external view returns (uint256); + + /** + * @dev Returns the total amount of stake under a hotkey (delegative or otherwise) + * + * This function allows external accounts and contracts to query the total amount of RAO staked under a hotkey + * which effectively calls `get_total_hotkey_stake` on the subtensor pallet with + * specified hotkey as a parameter. + * + * @param hotkey The hotkey public key (32 bytes). + * @return The total amount of RAO staked under the hotkey. + */ + function getTotalHotkeyStake(bytes32 hotkey) external view returns (uint256); + + /** + * @dev Returns the stake amount associated with the specified `hotkey` and `coldkey`. + * + * This function retrieves the current stake amount linked to a specific hotkey and coldkey pair. + * It is a view function, meaning it does not modify the state of the contract and is free to call. + * + * @param hotkey The hotkey public key (32 bytes). + * @param coldkey The coldkey public key (32 bytes). + * @param netuid The subnet the stake is on (uint256). + * @return The current stake amount in uint256 format. + */ + function getStake( + bytes32 hotkey, + bytes32 coldkey, + uint256 netuid + ) external view returns (uint256); + + /** + * @dev Delegates staking to a proxy account. + * + * @param delegate The public key (32 bytes) of the delegate. + */ + function addProxy(bytes32 delegate) external; + + /** + * @dev Removes staking proxy account. + * + * @param delegate The public key (32 bytes) of the delegate. + */ + function removeProxy(bytes32 delegate) external; +} diff --git a/precompiles/src/staking.rs b/precompiles/src/staking.rs index e9bebcf67c..03e8587f9a 100644 --- a/precompiles/src/staking.rs +++ b/precompiles/src/staking.rs @@ -40,6 +40,157 @@ use subtensor_runtime_common::ProxyType; use crate::parser::{parse_pubkey, try_u16_from_u256}; use crate::{PrecompileExt, PrecompileHandleExt}; +// 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. +// +// All the future extensions should happen in StakingPrecompileV2. +pub(crate) struct StakingPrecompileV2(PhantomData); + +impl PrecompileExt for StakingPrecompileV2 +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_proxy::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, + <::Lookup as StaticLookup>::Source: From, +{ + const INDEX: u64 = 2053; + const ADDRESS_SS58: Option<[u8; 32]> = None; +} + +#[precompile_utils::precompile] +impl StakingPrecompileV2 +where + R: frame_system::Config + + pallet_evm::Config + + pallet_subtensor::Config + + pallet_proxy::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: From> + + From> + + GetDispatchInfo + + Dispatchable, + ::AddressMapping: AddressMapping, + <::Lookup as StaticLookup>::Source: From, +{ + #[precompile::public("addStake(bytes32,uint256,uint256)")] + #[precompile::payable] + fn add_stake( + handle: &mut impl PrecompileHandle, + address: H256, + amount_rao: U256, + netuid: U256, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let amount_staked = amount_rao.unique_saturated_into(); + let (hotkey, _) = parse_pubkey(address.as_bytes())?; + let netuid = try_u16_from_u256(netuid)?; + let call = pallet_subtensor::Call::::add_stake { + hotkey, + netuid, + amount_staked, + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("removeStake(bytes32,uint256,uint256)")] + fn remove_stake( + handle: &mut impl PrecompileHandle, + address: H256, + amount_alpha: U256, + netuid: U256, + ) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let (hotkey, _) = parse_pubkey(address.as_bytes())?; + let netuid = try_u16_from_u256(netuid)?; + let amount_unstaked = amount_alpha.unique_saturated_into(); + let call = pallet_subtensor::Call::::remove_stake { + hotkey, + netuid, + amount_unstaked, + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("getTotalColdkeyStake(bytes32)")] + fn get_total_coldkey_stake( + _handle: &mut impl PrecompileHandle, + coldkey: H256, + ) -> EvmResult { + let (coldkey, _) = parse_pubkey(coldkey.as_bytes())?; + let stake = pallet_subtensor::Pallet::::get_total_stake_for_coldkey(&coldkey); + + Ok(stake.into()) + } + + #[precompile::public("getTotalHotkeyStake(bytes32)")] + fn get_total_hotkey_stake( + _handle: &mut impl PrecompileHandle, + hotkey_h256: H256, + ) -> EvmResult { + let (hotkey, _) = parse_pubkey(hotkey_h256.as_bytes())?; + let stake = pallet_subtensor::Pallet::::get_total_stake_for_hotkey(&hotkey); + + Ok(stake.into()) + } + + #[precompile::public("getStake(bytes32,bytes32,uint256)")] + #[precompile::view] + fn get_stake( + _: &mut impl PrecompileHandle, + hotkey: H256, + coldkey: H256, + netuid: U256, + ) -> EvmResult { + let (hotkey, _) = parse_pubkey(hotkey.as_bytes())?; + let (coldkey, _) = parse_pubkey(coldkey.as_bytes())?; + let netuid = try_u16_from_u256(netuid)?; + let stake = pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, + ); + + Ok(stake.into()) + } + + #[precompile::public("addProxy(bytes32)")] + fn add_proxy(handle: &mut impl PrecompileHandle, delegate: H256) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let (delegate, _) = parse_pubkey(delegate.as_bytes())?; + let delegate = ::Lookup::unlookup(delegate); + let call = pallet_proxy::Call::::add_proxy { + delegate, + proxy_type: ProxyType::Staking, + delay: 0u32.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } + + #[precompile::public("removeProxy(bytes32)")] + fn remove_proxy(handle: &mut impl PrecompileHandle, delegate: H256) -> EvmResult<()> { + let account_id = handle.caller_account_id::(); + let (delegate, _) = parse_pubkey(delegate.as_bytes())?; + let delegate = ::Lookup::unlookup(delegate); + let call = pallet_proxy::Call::::remove_proxy { + delegate, + proxy_type: ProxyType::Staking, + delay: 0u32.into(), + }; + + handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) + } +} + +// Deprecated, exists for backward compatibility. pub(crate) struct StakingPrecompile(PhantomData); impl PrecompileExt for StakingPrecompile @@ -60,11 +211,11 @@ where <::Lookup as StaticLookup>::Source: From, { const INDEX: u64 = 2049; - const ADDRESS_SS58: [u8; 32] = [ + const ADDRESS_SS58: Option<[u8; 32]> = Some([ 0x26, 0xf4, 0x10, 0x1e, 0x52, 0xb7, 0x57, 0x34, 0x33, 0x24, 0x5b, 0xc3, 0x0a, 0xe1, 0x8b, 0x63, 0x99, 0x53, 0xd8, 0x41, 0x79, 0x33, 0x03, 0x61, 0x4d, 0xfa, 0xcf, 0xf0, 0x37, 0xf7, 0x12, 0x94, - ]; + ]); } #[precompile_utils::precompile] @@ -117,7 +268,10 @@ where let account_id = handle.caller_account_id::(); let (hotkey, _) = parse_pubkey(address.as_bytes())?; let netuid = try_u16_from_u256(netuid)?; - let amount_unstaked = amount.unique_saturated_into(); + let amount_unstaked = + ::BalanceConverter::into_substrate_balance(amount) + .ok_or(ExitError::OutOfFund)?; + let amount_unstaked = amount_unstaked.unique_saturated_into(); let call = pallet_subtensor::Call::::remove_stake { hotkey, netuid, @@ -161,6 +315,27 @@ where Ok(stake_eth) } + #[precompile::public("getStake(bytes32,bytes32,uint256)")] + #[precompile::view] + fn get_stake( + _: &mut impl PrecompileHandle, + hotkey: H256, + coldkey: H256, + netuid: U256, + ) -> EvmResult { + let (hotkey, _) = parse_pubkey(hotkey.as_bytes())?; + let (coldkey, _) = parse_pubkey(coldkey.as_bytes())?; + let netuid = try_u16_from_u256(netuid)?; + let stake = pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( + &hotkey, &coldkey, netuid, + ); + let stake = U256::from(stake); + let stake = ::BalanceConverter::into_evm_balance(stake) + .ok_or(ExitError::InvalidRange)?; + + Ok(stake) + } + #[precompile::public("addProxy(bytes32)")] fn add_proxy(handle: &mut impl PrecompileHandle, delegate: H256) -> EvmResult<()> { let account_id = handle.caller_account_id::(); @@ -189,29 +364,13 @@ where handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id)) } - #[precompile::public("getStake(bytes32,bytes32,uint256)")] - #[precompile::view] - fn get_stake( - _: &mut impl PrecompileHandle, - hotkey: H256, - coldkey: H256, - netuid: U256, - ) -> EvmResult { - let (hotkey, _) = parse_pubkey(hotkey.as_bytes())?; - let (coldkey, _) = parse_pubkey(coldkey.as_bytes())?; - let netuid = try_u16_from_u256(netuid)?; - let stake = pallet_subtensor::Pallet::::get_stake_for_hotkey_and_coldkey_on_subnet( - &hotkey, &coldkey, netuid, - ); - - Ok(stake.into()) - } - fn transfer_back_to_caller( account_id: &::AccountId, amount: U256, ) -> Result<(), PrecompileFailure> { - let smart_contract_account_id = R::AccountId::from(Self::ADDRESS_SS58); + let smart_contract_account_id = R::AccountId::from( + Self::ADDRESS_SS58.expect("ADDRESS_SS58 is defined for StakingPrecompile"), + ); let amount_sub = ::BalanceConverter::into_substrate_balance(amount) .ok_or(ExitError::OutOfFund)?; diff --git a/precompiles/src/subnet.rs b/precompiles/src/subnet.rs index 1a6bfcf058..d3ee07d5c3 100644 --- a/precompiles/src/subnet.rs +++ b/precompiles/src/subnet.rs @@ -29,11 +29,11 @@ where // <::Lookup as StaticLookup>::Source: From, { const INDEX: u64 = 2051; - const ADDRESS_SS58: [u8; 32] = [ + const ADDRESS_SS58: Option<[u8; 32]> = Some([ 0x3a, 0x86, 0x18, 0xfb, 0xbb, 0x1b, 0xbc, 0x47, 0x86, 0x64, 0xff, 0x53, 0x46, 0x18, 0x0c, 0x35, 0xd0, 0x9f, 0xac, 0x26, 0xf2, 0x02, 0x70, 0x85, 0xb3, 0x1c, 0x56, 0xc1, 0x06, 0x3c, 0x1c, 0xd3, - ]; + ]); } #[precompile_utils::precompile]