Skip to content

Expose all extrinsic and storage to EVM #1639

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
May 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
6 changes: 5 additions & 1 deletion evm-tests/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ export const IBalanceTransferABI = [
stateMutability: "payable",
type: "function",
},
];
];

export const IDISPATCH_ADDRESS = "0x0000000000000000000000000000000000000006";

export const ISTORAGE_QUERY_ADDRESS = "0x0000000000000000000000000000000000000807";
8 changes: 8 additions & 0 deletions evm-tests/src/subtensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,12 @@ export async function startCall(api: TypedApi<typeof devnet>, netuid: number, ke
const callStarted = await api.query.SubtensorModule.FirstEmissionBlockNumber
.getValue(netuid);
assert.notEqual(callStarted, undefined);
}

export async function setMaxChildkeyTake(api: TypedApi<typeof devnet>, 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)
}
75 changes: 75 additions & 0 deletions evm-tests/test/runtime.call.precompile.test.ts
Original file line number Diff line number Diff line change
@@ -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<typeof devnet>
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();
Copy link
Collaborator

@l0r1s l0r1s May 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this line needed given value is not stored in a variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just check the value can be got after setMaxChildkeyTake, without exception. maybe it is not necessary, will consider to remove it in the future.

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")
})
});
4 changes: 4 additions & 0 deletions precompiles/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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",
Expand All @@ -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",
Expand Down
22 changes: 18 additions & 4 deletions precompiles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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::*;

Expand All @@ -34,9 +39,9 @@ mod extensions;
mod metagraph;
mod neuron;
mod staking;
mod storage_query;
mod subnet;
mod uid_lookup;

pub struct Precompiles<R>(PhantomData<R>);

impl<R> Default for Precompiles<R>
Expand Down Expand Up @@ -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::<R::AccountId>::INDEX),
Expand All @@ -102,6 +108,7 @@ where
hash(MetagraphPrecompile::<R>::INDEX),
hash(NeuronPrecompile::<R>::INDEX),
hash(StakingPrecompileV2::<R>::INDEX),
hash(StorageQueryPrecompile::<R>::INDEX),
hash(UidLookupPrecompile::<R>::INDEX),
]
}
Expand All @@ -120,7 +127,10 @@ where
+ From<pallet_balances::Call<R>>
+ From<pallet_admin_utils::Call<R>>
+ GetDispatchInfo
+ Dispatchable<PostInfo = PostDispatchInfo>,
+ Dispatchable<PostInfo = PostDispatchInfo>
+ Decode,
<<R as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
From<Option<R::AccountId>>,
<R as pallet_evm::Config>::AddressMapping: AddressMapping<R::AccountId>,
<R as pallet_balances::Config>::Balance: TryFrom<U256>,
<<R as frame_system::Config>::Lookup as StaticLookup>::Source: From<R::AccountId>,
Expand All @@ -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::<R>::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)),
Expand Down Expand Up @@ -164,6 +175,9 @@ where
a if a == hash(UidLookupPrecompile::<R>::INDEX) => {
UidLookupPrecompile::<R>::try_execute::<R>(handle, PrecompileEnum::UidLookup)
}
a if a == hash(StorageQueryPrecompile::<R>::INDEX) => {
Some(StorageQueryPrecompile::<R>::execute(handle))
}
_ => None,
}
}
Expand Down
56 changes: 56 additions & 0 deletions precompiles/src/storage_query.rs
Original file line number Diff line number Diff line change
@@ -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<R>(PhantomData<R>);

impl<R> PrecompileExt<R::AccountId> for StorageQueryPrecompile<R>
where
R: frame_system::Config + pallet_subtensor::Config + pallet_evm::Config,
R::AccountId: From<[u8; 32]>,
<R as frame_system::Config>::RuntimeCall:
GetDispatchInfo + Dispatchable<PostInfo = PostDispatchInfo>,
<R as frame_system::Config>::RuntimeCall: From<pallet_subtensor::Call<R>>
+ GetDispatchInfo
+ Dispatchable<PostInfo = PostDispatchInfo>,
<<R as frame_system::Config>::Lookup as StaticLookup>::Source: From<R::AccountId>,
{
const INDEX: u64 = 2055;
}

impl<R> Precompile for StorageQueryPrecompile<R>
where
R: frame_system::Config + pallet_subtensor::Config + pallet_evm::Config,
R::AccountId: From<[u8; 32]>,
<R as frame_system::Config>::RuntimeCall:
GetDispatchInfo + Dispatchable<PostInfo = PostDispatchInfo>,
<R as frame_system::Config>::RuntimeCall: From<pallet_subtensor::Call<R>>
+ GetDispatchInfo
+ Dispatchable<PostInfo = PostDispatchInfo>,
<<R as frame_system::Config>::Lookup as StaticLookup>::Source: From<R::AccountId>,
{
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(),
}),
}
}
}
Loading