diff --git a/Cargo.lock b/Cargo.lock index b0c56ffb2e..1ec256be62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6235,6 +6235,18 @@ dependencies = [ "scale-info", ] +[[package]] +name = "pallet-evm-precompile-dispatch" +version = "2.0.0-dev" +source = "git+https://github.com/opentensor/frontier?rev=cd6bca14a3#cd6bca14a366cc7cb1c3b1b1d7bc8213667e4126" +dependencies = [ + "fp-evm", + "frame-support", + "pallet-evm", + "parity-scale-codec", + "sp-runtime", +] + [[package]] name = "pallet-evm-precompile-modexp" version = "2.0.0-dev" @@ -11070,6 +11082,7 @@ dependencies = [ "pallet-admin-utils", "pallet-balances", "pallet-evm", + "pallet-evm-precompile-dispatch", "pallet-evm-precompile-modexp", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", @@ -11077,6 +11090,7 @@ dependencies = [ "pallet-subtensor", "precompile-utils", "sp-core", + "sp-io", "sp-runtime", "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2409-7)", "subtensor-runtime-common", diff --git a/Cargo.toml b/Cargo.toml index b429ac9341..8641f907ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -208,6 +208,7 @@ pallet-base-fee = { git = "https://github.com/opentensor/frontier", rev = "cd6bc pallet-dynamic-fee = { git = "https://github.com/opentensor/frontier", rev = "cd6bca14a3", default-features = false } pallet-ethereum = { git = "https://github.com/opentensor/frontier", rev = "cd6bca14a3", default-features = false } pallet-evm = { git = "https://github.com/opentensor/frontier", rev = "cd6bca14a3", default-features = false } +pallet-evm-precompile-dispatch = { git = "https://github.com/opentensor/frontier", rev = "cd6bca14a3", default-features = false } pallet-evm-chain-id = { git = "https://github.com/opentensor/frontier", rev = "cd6bca14a3", default-features = false } pallet-evm-precompile-modexp = { git = "https://github.com/opentensor/frontier", rev = "cd6bca14a3", default-features = false } pallet-evm-precompile-sha3fips = { git = "https://github.com/opentensor/frontier", rev = "cd6bca14a3", default-features = false } diff --git a/evm-tests/src/config.ts b/evm-tests/src/config.ts index 00b942f802..7fb40e67cb 100644 --- a/evm-tests/src/config.ts +++ b/evm-tests/src/config.ts @@ -35,4 +35,8 @@ export const IBalanceTransferABI = [ stateMutability: "payable", type: "function", }, -]; \ No newline at end of file +]; + +export const IDISPATCH_ADDRESS = "0x0000000000000000000000000000000000000006"; + +export const ISTORAGE_QUERY_ADDRESS = "0x0000000000000000000000000000000000000807"; \ No newline at end of file diff --git a/evm-tests/src/subtensor.ts b/evm-tests/src/subtensor.ts index 3111d90544..e3d5526268 100644 --- a/evm-tests/src/subtensor.ts +++ b/evm-tests/src/subtensor.ts @@ -343,4 +343,12 @@ export async function startCall(api: TypedApi, netuid: number, ke const callStarted = await api.query.SubtensorModule.FirstEmissionBlockNumber .getValue(netuid); assert.notEqual(callStarted, undefined); +} + +export async function setMaxChildkeyTake(api: TypedApi, take: number) { + const alice = getAliceSigner() + const internalCall = api.tx.SubtensorModule.sudo_set_max_childkey_take({ take }) + const tx = api.tx.Sudo.sudo({ call: internalCall.decodedCall }) + + await waitForTransactionWithRetry(api, tx, alice) } \ No newline at end of file diff --git a/evm-tests/test/runtime.call.precompile.test.ts b/evm-tests/test/runtime.call.precompile.test.ts new file mode 100644 index 0000000000..4cd7690724 --- /dev/null +++ b/evm-tests/test/runtime.call.precompile.test.ts @@ -0,0 +1,75 @@ +import * as assert from "assert"; +import { getAliceSigner, getDevnetApi } from "../src/substrate" +import { generateRandomEthersWallet, getPublicClient } from "../src/utils"; +import { IDISPATCH_ADDRESS, ISTORAGE_QUERY_ADDRESS, ETH_LOCAL_URL } from "../src/config"; +import { devnet, MultiAddress } from "@polkadot-api/descriptors" +import { hexToNumber, PublicClient } from "viem"; +import { PolkadotSigner, TypedApi } from "polkadot-api"; +import { convertPublicKeyToSs58 } from "../src/address-utils" +import { forceSetBalanceToEthAddress, setMaxChildkeyTake } from "../src/subtensor"; +import { xxhashAsU8a } from '@polkadot/util-crypto'; +import { u8aToHex } from '@polkadot/util'; + +describe("Test the dispatch precompile", () => { + let publicClient: PublicClient; + const wallet1 = generateRandomEthersWallet(); + let api: TypedApi + let alice: PolkadotSigner; + + before(async () => { + publicClient = await getPublicClient(ETH_LOCAL_URL) + api = await getDevnetApi() + alice = await getAliceSigner() + await forceSetBalanceToEthAddress(api, wallet1.address) + }) + + it("Dispatch transfer call via precompile contract works correctly", async () => { + // call for transfer 1 token to alice + const transferAmount = BigInt(1000000000); + + const unsignedTx = api.tx.Balances.transfer_keep_alive({ + dest: MultiAddress.Id(convertPublicKeyToSs58(alice.publicKey)), + value: transferAmount, + }); + const encodedCallDataBytes = await unsignedTx.getEncodedData(); + + // encoded call should be 0x050300d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d02286bee + const transferCall = encodedCallDataBytes.asHex() + + const aliceBalance = (await api.query.System.Account.getValue( convertPublicKeyToSs58(alice.publicKey))).data.free + const txResponse = await wallet1.sendTransaction({ + to: IDISPATCH_ADDRESS, + data: transferCall, + }) + await txResponse.wait() + + const aliceBalanceAfterTransfer = (await api.query.System.Account.getValue( convertPublicKeyToSs58(alice.publicKey))).data.free + + assert.equal(aliceBalance + transferAmount, aliceBalanceAfterTransfer) + }) + + + it("Storage query call via precompile contract works correctly", async () => { + const palletPrefixBytes = xxhashAsU8a("SubtensorModule", 128); + const storageItemPrefixBytes = xxhashAsU8a("MaxChildkeyTake", 128); + const fullStorageKeyBytes = new Uint8Array([...palletPrefixBytes, ...storageItemPrefixBytes]); + // 0x658faa385070e074c85bf6b568cf0555dba018859cab7e989f77669457b394be + // key for max child key take + const fullStorageKeyHex = u8aToHex(fullStorageKeyBytes); + + let maxChildkeyTake = 257; + await setMaxChildkeyTake(api, maxChildkeyTake) + + api.query.SubtensorModule.MaxChildkeyTake.getValue(); + const rawCallResponse = await publicClient.call({ + to: ISTORAGE_QUERY_ADDRESS, + data: fullStorageKeyHex, + }) + const rawResultData = rawCallResponse.data; + if (rawResultData === undefined) { + throw new Error("rawResultData is undefined"); + } + let value = hexToNumber(rawResultData); + assert.equal(value, maxChildkeyTake, "value should be 257") + }) +}); diff --git a/precompiles/Cargo.toml b/precompiles/Cargo.toml index ec46e6aee2..f19e8f2d1e 100644 --- a/precompiles/Cargo.toml +++ b/precompiles/Cargo.toml @@ -18,12 +18,14 @@ frame-system = { workspace = true } log = { workspace = true } pallet-balances = { workspace = true } pallet-evm = { workspace = true } +pallet-evm-precompile-dispatch = { workspace = true } pallet-evm-precompile-modexp = { workspace = true } pallet-evm-precompile-sha3fips = { workspace = true } pallet-evm-precompile-simple = { workspace = true } pallet-proxy = { workspace = true } precompile-utils = { workspace = true } sp-core = { workspace = true } +sp-io = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } subtensor-runtime-common = { workspace = true } @@ -44,6 +46,7 @@ std = [ "log/std", "pallet-admin-utils/std", "pallet-balances/std", + "pallet-evm-precompile-dispatch/std", "pallet-evm-precompile-modexp/std", "pallet-evm-precompile-sha3fips/std", "pallet-evm-precompile-simple/std", @@ -52,6 +55,7 @@ std = [ "pallet-subtensor/std", "precompile-utils/std", "sp-core/std", + "sp-io/std", "sp-runtime/std", "sp-std/std", "subtensor-runtime-common/std", diff --git a/precompiles/src/lib.rs b/precompiles/src/lib.rs index 4c6824e07b..89902ec9ee 100644 --- a/precompiles/src/lib.rs +++ b/precompiles/src/lib.rs @@ -4,11 +4,15 @@ extern crate alloc; use core::marker::PhantomData; -use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; +use frame_support::{ + dispatch::{GetDispatchInfo, PostDispatchInfo}, + pallet_prelude::Decode, +}; use pallet_evm::{ AddressMapping, IsPrecompileResult, Precompile, PrecompileHandle, PrecompileResult, PrecompileSet, }; +use pallet_evm_precompile_dispatch::Dispatch; use pallet_evm_precompile_modexp::Modexp; use pallet_evm_precompile_sha3fips::Sha3FIPS256; use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; @@ -25,6 +29,7 @@ use crate::extensions::*; use crate::metagraph::*; use crate::neuron::*; use crate::staking::*; +use crate::storage_query::*; use crate::subnet::*; use crate::uid_lookup::*; @@ -34,9 +39,9 @@ mod extensions; mod metagraph; mod neuron; mod staking; +mod storage_query; mod subnet; mod uid_lookup; - pub struct Precompiles(PhantomData); impl Default for Precompiles @@ -86,13 +91,14 @@ where Self(Default::default()) } - pub fn used_addresses() -> [H160; 15] { + pub fn used_addresses() -> [H160; 17] { [ hash(1), hash(2), hash(3), hash(4), hash(5), + hash(6), hash(1024), hash(1025), hash(Ed25519Verify::::INDEX), @@ -102,6 +108,7 @@ where hash(MetagraphPrecompile::::INDEX), hash(NeuronPrecompile::::INDEX), hash(StakingPrecompileV2::::INDEX), + hash(StorageQueryPrecompile::::INDEX), hash(UidLookupPrecompile::::INDEX), ] } @@ -120,7 +127,10 @@ where + From> + From> + GetDispatchInfo - + Dispatchable, + + Dispatchable + + Decode, + <::RuntimeCall as Dispatchable>::RuntimeOrigin: + From>, ::AddressMapping: AddressMapping, ::Balance: TryFrom, <::Lookup as StaticLookup>::Source: From, @@ -133,6 +143,7 @@ where a if a == hash(3) => Some(Ripemd160::execute(handle)), a if a == hash(4) => Some(Identity::execute(handle)), a if a == hash(5) => Some(Modexp::execute(handle)), + a if a == hash(6) => Some(Dispatch::::execute(handle)), // Non-Frontier specific nor Ethereum precompiles : a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)), a if a == hash(1025) => Some(ECRecoverPublicKey::execute(handle)), @@ -164,6 +175,9 @@ where a if a == hash(UidLookupPrecompile::::INDEX) => { UidLookupPrecompile::::try_execute::(handle, PrecompileEnum::UidLookup) } + a if a == hash(StorageQueryPrecompile::::INDEX) => { + Some(StorageQueryPrecompile::::execute(handle)) + } _ => None, } } diff --git a/precompiles/src/storage_query.rs b/precompiles/src/storage_query.rs new file mode 100644 index 0000000000..b9455ae708 --- /dev/null +++ b/precompiles/src/storage_query.rs @@ -0,0 +1,56 @@ +use core::marker::PhantomData; + +use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; +use pallet_evm::{ExitSucceed, Precompile, PrecompileHandle, PrecompileOutput}; +use sp_runtime::traits::{Dispatchable, StaticLookup}; +use sp_std::vec::Vec; + +use crate::PrecompileExt; + +pub(crate) struct StorageQueryPrecompile(PhantomData); + +impl PrecompileExt for StorageQueryPrecompile +where + R: frame_system::Config + pallet_subtensor::Config + pallet_evm::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: + GetDispatchInfo + Dispatchable, + ::RuntimeCall: From> + + GetDispatchInfo + + Dispatchable, + <::Lookup as StaticLookup>::Source: From, +{ + const INDEX: u64 = 2055; +} + +impl Precompile for StorageQueryPrecompile +where + R: frame_system::Config + pallet_subtensor::Config + pallet_evm::Config, + R::AccountId: From<[u8; 32]>, + ::RuntimeCall: + GetDispatchInfo + Dispatchable, + ::RuntimeCall: From> + + GetDispatchInfo + + Dispatchable, + <::Lookup as StaticLookup>::Source: From, +{ + fn execute(handle: &mut impl PrecompileHandle) -> fp_evm::PrecompileResult { + let input = handle.input(); + let data = sp_io::storage::get(input); + + match data { + Some(value) => { + let result = value.to_vec(); + + Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: result, + }) + } + None => Ok(PrecompileOutput { + exit_status: ExitSucceed::Returned, + output: Vec::new(), + }), + } + } +}