From 6886bcbdde302e8b56dde4e1b42d578163f93c5a Mon Sep 17 00:00:00 2001 From: Steve Degosserie <723552+stiiifff@users.noreply.github.com> Date: Thu, 4 Sep 2025 10:25:59 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20Add=20Moonbeam=20EVM=20Prec?= =?UTF-8?q?ompile=20Registry=20(#137)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR implements a comprehensive EVM precompile registry system for DataHaven, following Moonbeam's exact architecture and patterns. The implementation includes: - **Registry Precompile**: A new precompile at address `0x0815` (2069) that manages and queries available precompiles - **Core Ethereum Precompiles**: Standard Ethereum precompiles (ECRecover, SHA256, RIPEMD160, Identity, ModExp, BN128Add, BN128Mul, BN128Pairing, Blake2F, SHA3FIPS) - **Modular Architecture**: Clean separation following Moonbeam's structure with dedicated precompile modules per runtime ## Key Features ### Registry Precompile Functions - `isPrecompile(address)`: Check if an address corresponds to any precompile (active or inactive) - `isActivePrecompile(address)`: Check if a precompile is currently active in the runtime - `updateAccountCode(address)`: Insert dummy EVM bytecode for Solidity compatibility ### Runtime Integration - Integrated across all three runtimes (testnet, stagenet, mainnet) - Uses Moonbeam's `PrecompileSetBuilder` pattern for composable precompile management - Proper gas accounting with database read/write operations - Access control through `CallableByContract` and `CallableByPrecompile` traits --------- Co-authored-by: undercover-cactus --- operator/Cargo.lock | 232 ++++++++++++++++-- operator/Cargo.toml | 7 + .../precompile-registry/Cargo.toml | 42 ++++ .../PrecompileRegistry.sol | 41 ++++ .../precompile-registry/src/lib.rs | 99 ++++++++ .../precompile-registry/src/mock.rs | 198 +++++++++++++++ .../precompile-registry/src/tests.rs | 202 +++++++++++++++ operator/runtime/mainnet/Cargo.toml | 23 +- operator/runtime/mainnet/src/configs/mod.rs | 22 +- operator/runtime/mainnet/src/lib.rs | 1 + operator/runtime/mainnet/src/precompiles.rs | 67 +++++ operator/runtime/stagenet/Cargo.toml | 21 +- operator/runtime/stagenet/src/configs/mod.rs | 22 +- operator/runtime/stagenet/src/lib.rs | 1 + operator/runtime/stagenet/src/precompiles.rs | 67 +++++ operator/runtime/testnet/Cargo.toml | 21 +- operator/runtime/testnet/src/configs/mod.rs | 22 +- operator/runtime/testnet/src/lib.rs | 1 + operator/runtime/testnet/src/precompiles.rs | 67 +++++ 19 files changed, 1093 insertions(+), 63 deletions(-) create mode 100644 operator/precompiles/precompile-registry/Cargo.toml create mode 100644 operator/precompiles/precompile-registry/PrecompileRegistry.sol create mode 100644 operator/precompiles/precompile-registry/src/lib.rs create mode 100644 operator/precompiles/precompile-registry/src/mock.rs create mode 100644 operator/precompiles/precompile-registry/src/tests.rs create mode 100644 operator/runtime/mainnet/src/precompiles.rs create mode 100644 operator/runtime/stagenet/src/precompiles.rs create mode 100644 operator/runtime/testnet/src/precompiles.rs diff --git a/operator/Cargo.lock b/operator/Cargo.lock index a58b2ee8..9f7d0738 100644 --- a/operator/Cargo.lock +++ b/operator/Cargo.lock @@ -1309,6 +1309,17 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "regex-automata 0.4.9", + "serde", +] + [[package]] name = "build-helper" version = "0.1.1" @@ -1409,6 +1420,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "case" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6c0e7b807d60291f42f33f58480c0bfafe28ed08286446f45e463728cf9c1c" + [[package]] name = "cc" version = "1.2.22" @@ -2355,6 +2372,7 @@ dependencies = [ "hex-literal 0.3.4", "log", "num-bigint", + "num_enum", "pallet-authorship", "pallet-babe", "pallet-balances", @@ -2368,6 +2386,12 @@ dependencies = [ "pallet-ethereum", "pallet-evm", "pallet-evm-chain-id", + "pallet-evm-precompile-blake2", + "pallet-evm-precompile-bn128", + "pallet-evm-precompile-modexp", + "pallet-evm-precompile-registry", + "pallet-evm-precompile-sha3fips", + "pallet-evm-precompile-simple", "pallet-external-validators", "pallet-external-validators-rewards", "pallet-external-validators-rewards-runtime-api", @@ -2405,6 +2429,7 @@ dependencies = [ "parity-scale-codec", "polkadot-primitives", "polkadot-runtime-common", + "precompile-utils", "scale-info", "serde_json", "shp-constants", @@ -2579,6 +2604,7 @@ dependencies = [ "hex-literal 0.3.4", "log", "num-bigint", + "num_enum", "pallet-authorship", "pallet-babe", "pallet-balances", @@ -2592,6 +2618,12 @@ dependencies = [ "pallet-ethereum", "pallet-evm", "pallet-evm-chain-id", + "pallet-evm-precompile-blake2", + "pallet-evm-precompile-bn128", + "pallet-evm-precompile-modexp", + "pallet-evm-precompile-registry", + "pallet-evm-precompile-sha3fips", + "pallet-evm-precompile-simple", "pallet-external-validators", "pallet-external-validators-rewards", "pallet-external-validators-rewards-runtime-api", @@ -2629,6 +2661,7 @@ dependencies = [ "parity-scale-codec", "polkadot-primitives", "polkadot-runtime-common", + "precompile-utils", "scale-info", "serde_json", "shp-constants", @@ -2704,6 +2737,7 @@ dependencies = [ "hex-literal 0.3.4", "log", "num-bigint", + "num_enum", "pallet-authorship", "pallet-babe", "pallet-balances", @@ -2717,6 +2751,12 @@ dependencies = [ "pallet-ethereum", "pallet-evm", "pallet-evm-chain-id", + "pallet-evm-precompile-blake2", + "pallet-evm-precompile-bn128", + "pallet-evm-precompile-modexp", + "pallet-evm-precompile-registry", + "pallet-evm-precompile-sha3fips", + "pallet-evm-precompile-simple", "pallet-external-validators", "pallet-external-validators-rewards", "pallet-external-validators-rewards-runtime-api", @@ -2754,6 +2794,7 @@ dependencies = [ "parity-scale-codec", "polkadot-primitives", "polkadot-runtime-common", + "precompile-utils", "scale-info", "serde_json", "shp-constants", @@ -2925,6 +2966,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.101", + "unicode-xid", ] [[package]] @@ -3841,12 +3883,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - [[package]] name = "float-cmp" version = "0.9.0" @@ -5989,6 +6025,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] [[package]] name = "lazycell" @@ -7347,6 +7386,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -7391,6 +7444,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.2" @@ -7599,7 +7663,7 @@ dependencies = [ "expander", "indexmap 2.9.0", "itertools 0.11.0", - "petgraph 0.6.5", + "petgraph", "proc-macro-crate 3.3.0", "proc-macro2", "quote", @@ -7961,6 +8025,70 @@ dependencies = [ "scale-info", ] +[[package]] +name = "pallet-evm-precompile-blake2" +version = "2.0.0-dev" +source = "git+https://github.com/polkadot-evm/frontier?rev=75329a2df49e2cc7981485392c31160929d1bd48#75329a2df49e2cc7981485392c31160929d1bd48" +dependencies = [ + "fp-evm", +] + +[[package]] +name = "pallet-evm-precompile-bn128" +version = "2.0.0-dev" +source = "git+https://github.com/polkadot-evm/frontier?rev=75329a2df49e2cc7981485392c31160929d1bd48#75329a2df49e2cc7981485392c31160929d1bd48" +dependencies = [ + "fp-evm", + "sp-core", + "substrate-bn", +] + +[[package]] +name = "pallet-evm-precompile-modexp" +version = "2.0.0-dev" +source = "git+https://github.com/polkadot-evm/frontier?rev=75329a2df49e2cc7981485392c31160929d1bd48#75329a2df49e2cc7981485392c31160929d1bd48" +dependencies = [ + "fp-evm", + "num", +] + +[[package]] +name = "pallet-evm-precompile-registry" +version = "0.1.0" +dependencies = [ + "fp-evm", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-evm", + "pallet-timestamp", + "parity-scale-codec", + "precompile-utils", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", +] + +[[package]] +name = "pallet-evm-precompile-sha3fips" +version = "2.0.0-dev" +source = "git+https://github.com/polkadot-evm/frontier?rev=75329a2df49e2cc7981485392c31160929d1bd48#75329a2df49e2cc7981485392c31160929d1bd48" +dependencies = [ + "fp-evm", + "tiny-keccak", +] + +[[package]] +name = "pallet-evm-precompile-simple" +version = "2.0.0-dev" +source = "git+https://github.com/polkadot-evm/frontier?rev=75329a2df49e2cc7981485392c31160929d1bd48#75329a2df49e2cc7981485392c31160929d1bd48" +dependencies = [ + "fp-evm", + "ripemd", + "sp-io", +] + [[package]] name = "pallet-external-validators" version = "0.1.0" @@ -8871,17 +8999,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "fixedbitset 0.4.2", - "indexmap 2.9.0", -] - -[[package]] -name = "petgraph" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset 0.5.7", + "fixedbitset", "indexmap 2.9.0", ] @@ -9438,6 +9556,49 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "precompile-utils" +version = "0.1.0" +source = "git+https://github.com/polkadot-evm/frontier?rev=75329a2df49e2cc7981485392c31160929d1bd48#75329a2df49e2cc7981485392c31160929d1bd48" +dependencies = [ + "derive_more 1.0.0", + "environmental", + "evm", + "fp-evm", + "frame-support", + "frame-system", + "hex", + "hex-literal 0.4.1", + "impl-trait-for-tuples", + "log", + "num_enum", + "pallet-evm", + "parity-scale-codec", + "precompile-utils-macro", + "scale-info", + "serde", + "similar-asserts", + "sp-core", + "sp-io", + "sp-runtime", + "sp-weights", + "staging-xcm", +] + +[[package]] +name = "precompile-utils-macro" +version = "0.1.0" +source = "git+https://github.com/polkadot-evm/frontier?rev=75329a2df49e2cc7981485392c31160929d1bd48#75329a2df49e2cc7981485392c31160929d1bd48" +dependencies = [ + "case", + "num_enum", + "prettyplease", + "proc-macro2", + "quote", + "sp-crypto-hashing 0.1.0 (git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2412-6)", + "syn 2.0.101", +] + [[package]] name = "predicates" version = "2.1.5" @@ -9726,7 +9887,7 @@ dependencies = [ "log", "multimap", "once_cell", - "petgraph 0.7.1", + "petgraph", "prettyplease", "prost 0.13.5", "prost-types", @@ -12448,6 +12609,26 @@ dependencies = [ "wide", ] +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +dependencies = [ + "bstr", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b441962c817e33508847a22bd82f03a30cff43642dc2fae8b050566121eb9a" +dependencies = [ + "console", + "similar", +] + [[package]] name = "simple-dns" version = "0.9.3" @@ -14280,6 +14461,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "substrate-bn" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b5bbfa79abbae15dd642ea8176a21a635ff3c00059961d1ea27ad04e5b441c" +dependencies = [ + "byteorder", + "crunchy", + "lazy_static", + "rand 0.8.5", + "rustc-hex", +] + [[package]] name = "substrate-build-script-utils" version = "11.0.0" diff --git a/operator/Cargo.toml b/operator/Cargo.toml index b463c3b8..a46c4610 100644 --- a/operator/Cargo.toml +++ b/operator/Cargo.toml @@ -10,6 +10,7 @@ members = [ "node", "pallets/outbound-commitment-store", "pallets/*", + "precompiles/*", "primitives/bridge", "runtime/*", ] @@ -29,6 +30,7 @@ pallet-external-validators-rewards = { path = "./pallets/external-validators-rew pallet-external-validators-rewards-runtime-api = { path = "./pallets/external-validators-rewards/runtime-api", default-features = false } pallet-outbound-commitment-store = { path = "./pallets/outbound-commitment-store", default-features = false } pallet-datahaven-native-transfer = { path = "./pallets/datahaven-native-transfer", default-features = false } +pallet-evm-precompile-registry = { path = "./precompiles/precompile-registry", default-features = false } # Crates.io (wasm) alloy-core = { version = "0.8.15", default-features = false } @@ -54,6 +56,7 @@ libsecp256k1 = { version = "0.7", default-features = false } log = { version = "0.4.25" } milagro-bls = { version = "1.5.4", default-features = false, package = "snowbridge-milagro-bls" } num-bigint = { version = "0.4.3", default-features = false } +num_enum = { version = "0.7.3", default-features = false } openssl-sys = { version = "0.9", features = [ "vendored", ] } # This is just to set the "vendored" feature required for the crossbuild, so that OpenSSL builds from source @@ -211,10 +214,14 @@ pallet-dynamic-fee = { git = "https://github.com/polkadot-evm/frontier", rev="75 pallet-ethereum = { git = "https://github.com/polkadot-evm/frontier/", rev="75329a2df49e2cc7981485392c31160929d1bd48", default-features = false } pallet-evm = { git = "https://github.com/polkadot-evm/frontier/", rev="75329a2df49e2cc7981485392c31160929d1bd48", default-features = false } pallet-evm-chain-id = { git = "https://github.com/polkadot-evm/frontier/", rev="75329a2df49e2cc7981485392c31160929d1bd48", default-features = false } +pallet-evm-precompile-blake2 = { git = "https://github.com/polkadot-evm/frontier", rev="75329a2df49e2cc7981485392c31160929d1bd48", default-features = false } +pallet-evm-precompile-bn128 = { git = "https://github.com/polkadot-evm/frontier", rev="75329a2df49e2cc7981485392c31160929d1bd48", default-features = false } pallet-evm-precompile-modexp = { git = "https://github.com/polkadot-evm/frontier", rev="75329a2df49e2cc7981485392c31160929d1bd48", default-features = false } pallet-evm-precompile-sha3fips = { git = "https://github.com/polkadot-evm/frontier", rev="75329a2df49e2cc7981485392c31160929d1bd48", default-features = false } pallet-evm-precompile-simple = { git = "https://github.com/polkadot-evm/frontier", rev="75329a2df49e2cc7981485392c31160929d1bd48", default-features = false } pallet-hotfix-sufficients = { git = "https://github.com/polkadot-evm/frontier", rev="75329a2df49e2cc7981485392c31160929d1bd48", default-features = false } +precompile-utils = { git = "https://github.com/polkadot-evm/frontier/", rev="75329a2df49e2cc7981485392c31160929d1bd48", default-features = false } +precompile-utils-macro = { git = "https://github.com/polkadot-evm/frontier", rev="75329a2df49e2cc7981485392c31160929d1bd48", default-features = false } # Frontier (client) fc-api = { git = "https://github.com/polkadot-evm/frontier", rev="75329a2df49e2cc7981485392c31160929d1bd48", default-features = false } diff --git a/operator/precompiles/precompile-registry/Cargo.toml b/operator/precompiles/precompile-registry/Cargo.toml new file mode 100644 index 00000000..8dee9cf8 --- /dev/null +++ b/operator/precompiles/precompile-registry/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "pallet-evm-precompile-registry" +authors = ["The DataHaven Team"] +description = "Registry of active precompiles" +edition = "2021" +version = "0.1.0" + +[dependencies] +# Substrate +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } + +# Frontier +fp-evm = { workspace = true } +pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] } +precompile-utils = { workspace = true } + +[dev-dependencies] +# Precompile utils for testing +precompile-utils = { workspace = true, features = ["std", "testing"] } + +# Substrate +pallet-balances = { workspace = true, features = ["insecure_zero_ed", "std"] } +pallet-timestamp = { workspace = true, features = ["std"] } +parity-scale-codec = { workspace = true, features = ["max-encoded-len", "std"] } +scale-info = { workspace = true, features = ["derive", "std"] } +sp-runtime = { workspace = true, features = ["std"] } + +[features] +default = ["std"] +std = [ + "fp-evm/std", + "frame-support/std", + "frame-system/std", + "pallet-evm/std", + "parity-scale-codec/std", + "precompile-utils/std", + "sp-core/std", + "sp-io/std", +] \ No newline at end of file diff --git a/operator/precompiles/precompile-registry/PrecompileRegistry.sol b/operator/precompiles/precompile-registry/PrecompileRegistry.sol new file mode 100644 index 00000000..4c27fe06 --- /dev/null +++ b/operator/precompiles/precompile-registry/PrecompileRegistry.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity >=0.8.3; + +/// @dev The PrecompileRegistry contract's address. +address constant PRECOMPILE_REGISTRY_ADDRESS = 0x0000000000000000000000000000000000000815; + +/// @dev The PrecompileRegistry contract's instance. +PrecompileRegistry constant PRECOMPILE_REGISTRY_CONTRACT = PrecompileRegistry( + PRECOMPILE_REGISTRY_ADDRESS +); + +/// @author The Moonbeam Team +/// @title Precompile Registry +/// @dev Interface to the set of available precompiles. +/// @custom:address 0x0000000000000000000000000000000000000815 +interface PrecompileRegistry { + /// @dev Query if the given address is a precompile. Note that deactivated precompiles + /// are still considered precompiles and will return `true`. + /// @param a: Address to query + /// @return output Is this address a precompile? + /// @custom:selector 446b450e + function isPrecompile(address a) external view returns (bool); + + /// @dev Query if the given address is an active precompile. Will return false if the + /// address is not a precompile or if this precompile is deactivated. + /// @param a: Address to query + /// @return output Is this address an active precompile? + /// @custom:selector 6f5e23cf + function isActivePrecompile(address a) external view returns (bool); + + /// @dev Update the account code of a precompile address. + /// As precompiles are implemented inside the Runtime, they don't have a bytecode, and + /// their account code is empty by default. However in Solidity calling a function of a + /// contract often automatically adds a check that the contract bytecode is non-empty. + /// For that reason a dummy code (0x60006000fd) can be inserted at the precompile address + /// to pass that check. This function allows any user to insert that code to precompile address + /// if they need it. + /// @param a: Address of the precompile. + /// @custom:selector 48ceb1b4 + function updateAccountCode(address a) external; +} diff --git a/operator/precompiles/precompile-registry/src/lib.rs b/operator/precompiles/precompile-registry/src/lib.rs new file mode 100644 index 00000000..e81bb477 --- /dev/null +++ b/operator/precompiles/precompile-registry/src/lib.rs @@ -0,0 +1,99 @@ +// Copyright 2019-2025 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +use core::marker::PhantomData; +use fp_evm::{ExitError, IsPrecompileResult, PrecompileFailure}; +use precompile_utils::{ + precompile_set::{is_precompile_or_fail, IsActivePrecompile}, + prelude::*, +}; +use sp_core::Get; + +const DUMMY_CODE: [u8; 5] = [0x60, 0x00, 0x60, 0x00, 0xfd]; + +pub struct PrecompileRegistry(PhantomData); + +#[precompile_utils::precompile] +impl PrecompileRegistry +where + Runtime: pallet_evm::Config, + Runtime::PrecompilesType: IsActivePrecompile, +{ + #[precompile::public("isPrecompile(address)")] + #[precompile::view] + fn is_precompile(handle: &mut impl PrecompileHandle, address: Address) -> EvmResult { + // We consider the precompile set is optimized to do at most one storage read. + // In the case of moonbeam, the storage item that can be read is pallet_asset::Asset + // (TODO make it more generic, maybe add a const generic on PrecompileRegistry type) + // Storage item: Asset: + // Blake2_128(16) + AssetId(16) + AssetDetails((4 * AccountId(20)) + (3 * Balance(16)) + 15) + handle.record_db_read::(175)?; + is_precompile_or_fail::(address.0, handle.remaining_gas()) + } + + #[precompile::public("isActivePrecompile(address)")] + #[precompile::view] + fn is_active_precompile( + handle: &mut impl PrecompileHandle, + address: Address, + ) -> EvmResult { + // We consider the precompile set is optimized to do at most one storage read. + // In the case of moonbeam, the storage item that can be read is pallet_asset::Asset + // (TODO make it more generic, maybe add a const generic on PrecompileRegistry type) + // Storage item: Asset: + // Blake2_128(16) + AssetId(16) + AssetDetails((4 * AccountId(20)) + (3 * Balance(16)) + 15) + handle.record_db_read::(175)?; + match ::get() + .is_active_precompile(address.0, handle.remaining_gas()) + { + IsPrecompileResult::Answer { is_precompile, .. } => Ok(is_precompile), + IsPrecompileResult::OutOfGas => Err(PrecompileFailure::Error { + exit_status: ExitError::OutOfGas, + }), + } + } + + #[precompile::public("updateAccountCode(address)")] + fn update_account_code(handle: &mut impl PrecompileHandle, address: Address) -> EvmResult<()> { + // Prevent touching addresses that are not precompiles. + // + // We consider the precompile set is optimized to do at most one storage read. + // In the case of moonbeam, the storage item that can be read is pallet_asset::Asset + // (TODO make it more generic, maybe add a const generic on PrecompileRegistry type) + // Storage item: Asset: + // Blake2_128(16) + AssetId(16) + AssetDetails((4 * AccountId(20)) + (3 * Balance(16)) + 15) + handle.record_db_read::(175)?; + if !is_precompile_or_fail::(address.0, handle.remaining_gas())? { + return Err(revert("provided address is not a precompile")); + } + + // pallet_evm::create_account read storage item pallet_evm::AccountCodes + // + // AccountCodes: Blake2128(16) + H160(20) + Vec(5) + // We asume an existing precompile can hold at most 5 bytes worth of dummy code. + handle.record_db_read::(41)?; + pallet_evm::Pallet::::create_account(address.0, DUMMY_CODE.to_vec()); + + Ok(()) + } +} diff --git a/operator/precompiles/precompile-registry/src/mock.rs b/operator/precompiles/precompile-registry/src/mock.rs new file mode 100644 index 00000000..c7bf887f --- /dev/null +++ b/operator/precompiles/precompile-registry/src/mock.rs @@ -0,0 +1,198 @@ +// Copyright 2019-2025 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +use super::*; + +use frame_support::traits::Everything; +use frame_support::{construct_runtime, pallet_prelude::*, parameter_types}; +use pallet_evm::{EnsureAddressNever, EnsureAddressRoot, FrameSystemAccountProvider}; +use precompile_utils::{mock_account, precompile_set::*, testing::MockAccount}; +use sp_core::H256; +use sp_runtime::BuildStorage; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; + +pub type AccountId = MockAccount; +pub type Balance = u128; + +type Block = frame_system::mocking::MockBlockU32; + +construct_runtime!( + pub enum Runtime { + System: frame_system, + Balances: pallet_balances, + Evm: pallet_evm, + Timestamp: pallet_timestamp, + } +); + +parameter_types! { + pub const BlockHashCount: u32 = 250; + pub const MaximumBlockWeight: Weight = Weight::from_parts(1024, 1); + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); + pub const SS58Prefix: u8 = 42; +} + +impl frame_system::Config for Runtime { + type BaseCallFilter = Everything; + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeTask = RuntimeTask; + type Nonce = u64; + type Block = Block; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type BlockWeights = (); + type BlockLength = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + type SingleBlockMigrations = (); + type MultiBlockMigrator = (); + type PreInherents = (); + type PostInherents = (); + type PostTransactions = (); + type ExtensionsWeightInfo = (); +} + +parameter_types! { + pub const ExistentialDeposit: u128 = 0; +} + +impl pallet_balances::Config for Runtime { + type MaxReserves = (); + type ReserveIdentifier = [u8; 4]; + type MaxLocks = (); + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type RuntimeHoldReason = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeFreezeReason = (); + type DoneSlashHandler = (); +} + +mock_account!(Registry, |_| MockAccount::from_u64(1)); +mock_account!(Removed, |_| MockAccount::from_u64(2)); +mock_account!(SmartContract, |_| MockAccount::from_u64(3)); + +pub type Precompiles = PrecompileSetBuilder< + R, + ( + PrecompileAt, PrecompileRegistry>, + RemovedPrecompileAt>, + ), +>; + +pub type PCall = PrecompileRegistryCall; + +parameter_types! { + pub PrecompilesValue: Precompiles = Precompiles::new(); + pub const WeightPerGas: Weight = Weight::from_parts(1, 0); +} + +impl pallet_evm::Config for Runtime { + type FeeCalculator = (); + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type CallOrigin = EnsureAddressRoot; + type WithdrawOrigin = EnsureAddressNever; + type AddressMapping = AccountId; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type Runner = pallet_evm::runner::stack::Runner; + type PrecompilesType = Precompiles; + type PrecompilesValue = PrecompilesValue; + type ChainId = (); + type OnChargeTransaction = (); + type BlockGasLimit = (); + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type FindAuthor = (); + type OnCreate = (); + type GasLimitPovSizeRatio = (); + type GasLimitStorageGrowthRatio = (); + type Timestamp = Timestamp; + type WeightInfo = pallet_evm::weights::SubstrateWeight; + type AccountProvider = FrameSystemAccountProvider; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +pub(crate) struct ExtBuilder { + // endowed accounts with balances + balances: Vec<(AccountId, Balance)>, +} + +impl Default for ExtBuilder { + fn default() -> ExtBuilder { + ExtBuilder { balances: vec![] } + } +} + +impl ExtBuilder { + pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { + self.balances = balances; + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + pallet_balances::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut t) + .expect("Pallet balances storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + pallet_evm::Pallet::::create_account( + SmartContract.into(), + b"SmartContract".to_vec(), + ); + }); + ext + } +} diff --git a/operator/precompiles/precompile-registry/src/tests.rs b/operator/precompiles/precompile-registry/src/tests.rs new file mode 100644 index 00000000..347beeee --- /dev/null +++ b/operator/precompiles/precompile-registry/src/tests.rs @@ -0,0 +1,202 @@ +// Copyright 2019-2025 PureStake Inc. +// This file is part of Moonbeam. + +// Moonbeam is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Moonbeam is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Moonbeam. If not, see . + +use crate::mock::{ + ExtBuilder, PCall, Precompiles, PrecompilesValue, Registry, Removed, Runtime, SmartContract, +}; +use precompile_utils::{prelude::*, testing::*}; +use sp_core::H160; + +fn precompiles() -> Precompiles { + PrecompilesValue::get() +} + +mod selectors { + use super::*; + + #[test] + fn selectors() { + assert!(PCall::is_precompile_selectors().contains(&0x446b450e)); + assert!(PCall::is_active_precompile_selectors().contains(&0x6f5e23cf)); + assert!(PCall::update_account_code_selectors().contains(&0x48ceb1b4)); + } + + #[test] + fn modifiers() { + ExtBuilder::default() + .with_balances(vec![(CryptoAlith.into(), 1000)]) + .build() + .execute_with(|| { + let mut tester = + PrecompilesModifierTester::new(precompiles(), CryptoAlith, Registry); + + tester.test_view_modifier(PCall::is_precompile_selectors()); + tester.test_view_modifier(PCall::is_active_precompile_selectors()); + tester.test_default_modifier(PCall::update_account_code_selectors()); + }); + } +} + +mod is_precompile { + + use super::*; + + fn call(target_address: impl Into, output: bool) { + ExtBuilder::default() + .with_balances(vec![(CryptoAlith.into(), 1000)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Alice, // can be anyone + Registry, + PCall::is_precompile { + address: Address(target_address.into()), + }, + ) + .expect_no_logs() + .execute_returns(output); + }); + } + + #[test] + fn works_on_precompile() { + call(Registry, true); + } + + #[test] + fn works_on_removed_precompile() { + call(Removed, true); + } + + #[test] + fn works_on_eoa() { + call(CryptoAlith, false); + } + + #[test] + fn works_on_smart_contract() { + call(SmartContract, false); + } +} + +mod is_active_precompile { + + use super::*; + + fn call(target_address: impl Into, output: bool) { + ExtBuilder::default() + .with_balances(vec![(CryptoAlith.into(), 1000)]) + .build() + .execute_with(|| { + precompiles() + .prepare_test( + Alice, // can be anyone + Registry, + PCall::is_active_precompile { + address: Address(target_address.into()), + }, + ) + .expect_no_logs() + .execute_returns(output); + }); + } + + #[test] + fn works_on_precompile() { + call(Registry, true); + } + + #[test] + fn works_on_removed_precompile() { + call(Removed, false); + } + + #[test] + fn works_on_eoa() { + call(CryptoAlith, false); + } + + #[test] + fn works_on_smart_contract() { + call(SmartContract, false); + } +} + +mod update_account_code { + use super::*; + + fn call(target_address: impl Into, expect_changes: bool) { + ExtBuilder::default() + .with_balances(vec![(CryptoAlith.into(), 1000)]) + .build() + .execute_with(|| { + let target_address = target_address.into(); + + let precompiles = precompiles(); + let tester = precompiles.prepare_test( + Alice, // can be anyone + Registry, + PCall::update_account_code { + address: Address(target_address), + }, + ); + + if expect_changes { + tester.execute_returns(()); + let new_code = pallet_evm::AccountCodes::::get(target_address); + assert_eq!(&new_code, &[0x60, 0x00, 0x60, 0x00, 0xfd]); + } else { + let current_code = pallet_evm::AccountCodes::::get(target_address); + + tester.execute_reverts(|revert| { + revert == b"provided address is not a precompile" + }); + + let new_code = pallet_evm::AccountCodes::::get(target_address); + assert_eq!(current_code, new_code); + } + }); + } + + #[test] + fn works_on_precompile() { + call(Registry, true); + } + + #[test] + fn works_on_removed_precompile() { + call(Removed, true); + } + + #[test] + fn works_on_eoa() { + call(CryptoAlith, false); + } + + #[test] + fn works_on_smart_contract() { + call(SmartContract, false); + } +} + +#[test] +fn test_solidity_interface() { + check_precompile_implements_solidity_interfaces( + &["PrecompileRegistry.sol"], + PCall::supports_selector, + ) +} diff --git a/operator/runtime/mainnet/Cargo.toml b/operator/runtime/mainnet/Cargo.toml index 53c2155a..8252ec0a 100644 --- a/operator/runtime/mainnet/Cargo.toml +++ b/operator/runtime/mainnet/Cargo.toml @@ -33,6 +33,7 @@ hex = { workspace = true } hex-literal = { workspace = true } log = { workspace = true } num-bigint = { workspace = true } +num_enum = { workspace = true } pallet-authorship = { workspace = true } pallet-babe = { workspace = true } pallet-balances = { workspace = true, features = ["insecure_zero_ed"] } @@ -40,10 +41,14 @@ pallet-beefy = { workspace = true } pallet-beefy-mmr = { workspace = true } pallet-collective = { workspace = true } pallet-conviction-voting = { workspace = true } -pallet-ethereum = { workspace = true } -pallet-referenda = { workspace = true } -pallet-evm = { workspace = true } +pallet-ethereum = { workspace = true, features = ["forbid-evm-reentrancy"] } +pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] } pallet-evm-chain-id = { workspace = true } +pallet-evm-precompile-blake2 = { workspace = true } +pallet-evm-precompile-bn128 = { workspace = true } +pallet-evm-precompile-modexp = { workspace = true } +pallet-evm-precompile-sha3fips = { workspace = true } +pallet-evm-precompile-simple = { workspace = true } pallet-external-validators = { workspace = true } pallet-external-validators-rewards = { workspace = true } pallet-external-validators-rewards-runtime-api = { workspace = true } @@ -59,17 +64,19 @@ pallet-datahaven-native-transfer = { workspace = true } pallet-parameters = { workspace = true } pallet-preimage = { workspace = true } pallet-proxy = { workspace = true } +pallet-referenda = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } -pallet-transaction-payment = { workspace = true } +pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } pallet-whitelist = { workspace = true } polkadot-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +precompile-utils = { workspace = true } scale-info = { workspace = true, features = ["derive", "serde"] } serde_json = { workspace = true, default-features = false, features = [ "alloc", @@ -112,6 +119,8 @@ xcm-executor = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } +# DataHaven precompiles +pallet-evm-precompile-registry = { workspace = true } # StorageHub pallet-bucket-nfts = { workspace = true } @@ -135,7 +144,6 @@ shp-file-key-verifier = { workspace = true } shp-data-price-updater = { workspace = true } sp-trie = { workspace = true } - [build-dependencies] substrate-wasm-builder = { workspace = true, optional = true, default-features = true } @@ -145,6 +153,8 @@ frame-support-test = { workspace = true } sp-io = { workspace = true } sp-tracing = { workspace = true } +precompile-utils = { workspace = true, features = ["std", "testing"] } + # Snowbridge testing snowbridge-core = { workspace = true } snowbridge-pallet-system = { workspace = true } @@ -157,6 +167,7 @@ std = [ "codec/std", "datahaven-runtime-common/std", "fp-account/std", + "fp-evm/std", "frame-benchmarking?/std", "frame-executive/std", "frame-metadata-hash-extension/std", @@ -175,6 +186,7 @@ std = [ "pallet-ethereum/std", "pallet-evm-chain-id/std", "pallet-evm/std", + "pallet-evm-precompile-registry/std", "pallet-external-validators/std", "pallet-external-validators-rewards/std", "pallet-external-validators-rewards-runtime-api/std", @@ -200,6 +212,7 @@ std = [ "pallet-whitelist/std", "polkadot-primitives/std", "polkadot-runtime-common/std", + "precompile-utils/std", "scale-info/std", "serde_json/std", "snowbridge-beacon-primitives/std", diff --git a/operator/runtime/mainnet/src/configs/mod.rs b/operator/runtime/mainnet/src/configs/mod.rs index f8e29f42..405162fa 100644 --- a/operator/runtime/mainnet/src/configs/mod.rs +++ b/operator/runtime/mainnet/src/configs/mod.rs @@ -28,14 +28,14 @@ pub mod runtime_params; mod storagehub; use super::{ - currency::*, AccountId, Babe, Balance, Balances, BeefyMmrLeaf, Block, BlockNumber, - EthereumBeaconClient, EthereumOutboundQueueV2, EvmChainId, ExistentialDeposit, - ExternalValidators, ExternalValidatorsRewards, Hash, Historical, ImOnline, MessageQueue, Nonce, - Offences, OriginCaller, OutboundCommitmentStore, PalletInfo, Preimage, Referenda, Runtime, - RuntimeCall, RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin, RuntimeTask, - Scheduler, Session, SessionKeys, Signature, System, Timestamp, Treasury, BLOCK_HASH_COUNT, - EXTRINSIC_BASE_WEIGHT, MAXIMUM_BLOCK_WEIGHT, NORMAL_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, - SLOT_DURATION, VERSION, + currency::*, precompiles::DataHavenPrecompiles, AccountId, Babe, Balance, Balances, + BeefyMmrLeaf, Block, BlockNumber, EthereumBeaconClient, EthereumOutboundQueueV2, EvmChainId, + ExistentialDeposit, ExternalValidators, ExternalValidatorsRewards, Hash, Historical, ImOnline, + MessageQueue, Nonce, Offences, OriginCaller, OutboundCommitmentStore, PalletInfo, Preimage, + Referenda, Runtime, RuntimeCall, RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, + RuntimeOrigin, RuntimeTask, Scheduler, Session, SessionKeys, Signature, System, Timestamp, + Treasury, BLOCK_HASH_COUNT, EXTRINSIC_BASE_WEIGHT, MAXIMUM_BLOCK_WEIGHT, NORMAL_BLOCK_WEIGHT, + NORMAL_DISPATCH_RATIO, SLOT_DURATION, VERSION, }; use codec::{Decode, Encode}; use datahaven_runtime_common::{ @@ -774,7 +774,7 @@ datahaven_runtime_common::impl_on_charge_evm_transaction!(); parameter_types! { pub BlockGasLimit: U256 = U256::from(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT.ref_time() / WEIGHT_PER_GAS); - // pub PrecompilesValue: TemplatePrecompiles = TemplatePrecompiles::<_>::new(); + pub PrecompilesValue: DataHavenPrecompiles = DataHavenPrecompiles::<_>::new(); pub WeightPerGas: Weight = Weight::from_parts(WEIGHT_PER_GAS, 0); pub SuicideQuickClearLimit: u32 = 0; /// The amount of gas per pov. A ratio of 16 if we convert ref_time to gas and we compare @@ -800,8 +800,8 @@ impl pallet_evm::Config for Runtime { type AddressMapping = IdentityAddressMapping; type Currency = Balances; type RuntimeEvent = RuntimeEvent; - type PrecompilesType = (); - type PrecompilesValue = (); + type PrecompilesType = DataHavenPrecompiles; + type PrecompilesValue = PrecompilesValue; type ChainId = EvmChainId; type BlockGasLimit = BlockGasLimit; type Runner = pallet_evm::runner::stack::Runner; diff --git a/operator/runtime/mainnet/src/lib.rs b/operator/runtime/mainnet/src/lib.rs index 4fb5487d..267f297a 100644 --- a/operator/runtime/mainnet/src/lib.rs +++ b/operator/runtime/mainnet/src/lib.rs @@ -9,6 +9,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); #[cfg(feature = "runtime-benchmarks")] mod benchmarks; pub mod configs; +pub mod precompiles; pub mod weights; // Re-export governance for tests diff --git a/operator/runtime/mainnet/src/precompiles.rs b/operator/runtime/mainnet/src/precompiles.rs new file mode 100644 index 00000000..199b82fa --- /dev/null +++ b/operator/runtime/mainnet/src/precompiles.rs @@ -0,0 +1,67 @@ +// Copyright 2019-2025 The DataHaven Team +// This file is part of DataHaven. + +// DataHaven is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// DataHaven is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with DataHaven. If not, see . + +use pallet_evm_precompile_blake2::Blake2F; +use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; +use pallet_evm_precompile_modexp::Modexp; +use pallet_evm_precompile_registry::PrecompileRegistry; +use pallet_evm_precompile_sha3fips::Sha3FIPS256; +use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; +use precompile_utils::precompile_set::*; + +type EthereumPrecompilesChecks = (AcceptDelegateCall, CallableByContract, CallableByPrecompile); + +/// EVM precompiles available in the DataHaven Mainnet runtime. +#[precompile_utils::precompile_name_from_address] +type DataHavenPrecompilesAt = ( + // Ethereum precompiles: + // We allow DELEGATECALL to stay compliant with Ethereum behavior. + PrecompileAt, ECRecover, EthereumPrecompilesChecks>, + PrecompileAt, Sha256, EthereumPrecompilesChecks>, + PrecompileAt, Ripemd160, EthereumPrecompilesChecks>, + PrecompileAt, Identity, EthereumPrecompilesChecks>, + PrecompileAt, Modexp, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Add, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Mul, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Pairing, EthereumPrecompilesChecks>, + PrecompileAt, Blake2F, EthereumPrecompilesChecks>, + // Non-DataHaven specific nor Ethereum precompiles : + PrecompileAt, Sha3FIPS256, (CallableByContract, CallableByPrecompile)>, + RemovedPrecompileAt>, + PrecompileAt, ECRecoverPublicKey, (CallableByContract, CallableByPrecompile)>, + RemovedPrecompileAt>, + // DataHaven specific precompiles: + PrecompileAt< + AddressU64<2069>, + PrecompileRegistry, + (CallableByContract, CallableByPrecompile), + >, +); + +/// The PrecompileSet installed in the DataHaven runtime. +/// We include the nine Istanbul precompiles +/// (https://github.com/ethereum/go-ethereum/blob/3c46f557/core/vm/contracts.go#L69) +/// The following distribution has been decided for the precompiles +/// 0-1023: Ethereum Mainnet Precompiles +/// 1024-2047 Precompiles that are not in Ethereum Mainnet but are neither DataHaven specific +/// 2048-4095 DataHaven specific precompiles +pub type DataHavenPrecompiles = PrecompileSetBuilder< + R, + ( + // Skip precompiles if out of range. + PrecompilesInRangeInclusive<(AddressU64<1>, AddressU64<4095>), DataHavenPrecompilesAt>, + ), +>; diff --git a/operator/runtime/stagenet/Cargo.toml b/operator/runtime/stagenet/Cargo.toml index 6e67bf85..b482979e 100644 --- a/operator/runtime/stagenet/Cargo.toml +++ b/operator/runtime/stagenet/Cargo.toml @@ -33,6 +33,7 @@ hex = { workspace = true } hex-literal = { workspace = true } log = { workspace = true } num-bigint = { workspace = true } +num_enum = { workspace = true } pallet-authorship = { workspace = true } pallet-babe = { workspace = true } pallet-balances = { workspace = true, features = ["insecure_zero_ed"]} @@ -40,10 +41,14 @@ pallet-beefy = { workspace = true } pallet-beefy-mmr = { workspace = true } pallet-collective = { workspace = true } pallet-conviction-voting = { workspace = true } -pallet-ethereum = { workspace = true } -pallet-referenda = { workspace = true } -pallet-evm = { workspace = true } +pallet-ethereum = { workspace = true, features = ["forbid-evm-reentrancy"] } +pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] } pallet-evm-chain-id = { workspace = true } +pallet-evm-precompile-blake2 = { workspace = true } +pallet-evm-precompile-bn128 = { workspace = true } +pallet-evm-precompile-modexp = { workspace = true } +pallet-evm-precompile-sha3fips = { workspace = true } +pallet-evm-precompile-simple = { workspace = true } pallet-external-validators = { workspace = true } pallet-external-validators-rewards = { workspace = true } pallet-external-validators-rewards-runtime-api = { workspace = true } @@ -59,6 +64,7 @@ pallet-datahaven-native-transfer = { workspace = true } pallet-parameters = { workspace = true } pallet-preimage = { workspace = true } pallet-proxy = { workspace = true } +pallet-referenda = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-sudo = { workspace = true } @@ -70,6 +76,7 @@ pallet-utility = { workspace = true } pallet-whitelist = { workspace = true } polkadot-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +precompile-utils = { workspace = true } scale-info = { workspace = true, features = ["derive", "serde"] } serde_json = { workspace = true, default-features = false, features = [ "alloc", @@ -112,6 +119,9 @@ xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } +# DataHaven precompiles +pallet-evm-precompile-registry = { workspace = true } + # StorageHub pallet-bucket-nfts = { workspace = true } pallet-nfts = { workspace = true } @@ -143,6 +153,8 @@ frame-support-test = { workspace = true } sp-io = { workspace = true } sp-tracing = { workspace = true } +precompile-utils = { workspace = true, features = ["std", "testing"] } + # Snowbridge testing snowbridge-core = { workspace = true } snowbridge-pallet-system = { workspace = true } @@ -155,6 +167,7 @@ std = [ "codec/std", "datahaven-runtime-common/std", "fp-account/std", + "fp-evm/std", "frame-benchmarking?/std", "frame-executive/std", "frame-metadata-hash-extension/std", @@ -173,6 +186,7 @@ std = [ "pallet-ethereum/std", "pallet-evm-chain-id/std", "pallet-evm/std", + "pallet-evm-precompile-registry/std", "pallet-external-validators/std", "pallet-external-validators-rewards/std", "pallet-external-validators-rewards-runtime-api/std", @@ -198,6 +212,7 @@ std = [ "pallet-whitelist/std", "polkadot-primitives/std", "polkadot-runtime-common/std", + "precompile-utils/std", "scale-info/std", "serde_json/std", "snowbridge-beacon-primitives/std", diff --git a/operator/runtime/stagenet/src/configs/mod.rs b/operator/runtime/stagenet/src/configs/mod.rs index 1119ecb7..efa71fb8 100644 --- a/operator/runtime/stagenet/src/configs/mod.rs +++ b/operator/runtime/stagenet/src/configs/mod.rs @@ -28,14 +28,14 @@ pub mod runtime_params; mod storagehub; use super::{ - currency::*, AccountId, Babe, Balance, Balances, BeefyMmrLeaf, Block, BlockNumber, - EthereumBeaconClient, EthereumOutboundQueueV2, EvmChainId, ExistentialDeposit, - ExternalValidators, ExternalValidatorsRewards, Hash, Historical, ImOnline, MessageQueue, Nonce, - Offences, OriginCaller, OutboundCommitmentStore, PalletInfo, Preimage, Referenda, Runtime, - RuntimeCall, RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin, RuntimeTask, - Scheduler, Session, SessionKeys, Signature, System, Timestamp, Treasury, BLOCK_HASH_COUNT, - EXTRINSIC_BASE_WEIGHT, MAXIMUM_BLOCK_WEIGHT, NORMAL_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, - SLOT_DURATION, VERSION, + currency::*, precompiles::DataHavenPrecompiles, AccountId, Babe, Balance, Balances, + BeefyMmrLeaf, Block, BlockNumber, EthereumBeaconClient, EthereumOutboundQueueV2, EvmChainId, + ExistentialDeposit, ExternalValidators, ExternalValidatorsRewards, Hash, Historical, ImOnline, + MessageQueue, Nonce, Offences, OriginCaller, OutboundCommitmentStore, PalletInfo, Preimage, + Referenda, Runtime, RuntimeCall, RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, + RuntimeOrigin, RuntimeTask, Scheduler, Session, SessionKeys, Signature, System, Timestamp, + Treasury, BLOCK_HASH_COUNT, EXTRINSIC_BASE_WEIGHT, MAXIMUM_BLOCK_WEIGHT, NORMAL_BLOCK_WEIGHT, + NORMAL_DISPATCH_RATIO, SLOT_DURATION, VERSION, }; use codec::{Decode, Encode}; use datahaven_runtime_common::{ @@ -773,7 +773,7 @@ datahaven_runtime_common::impl_on_charge_evm_transaction!(); parameter_types! { pub BlockGasLimit: U256 = U256::from(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT.ref_time() / WEIGHT_PER_GAS); - // pub PrecompilesValue: TemplatePrecompiles = TemplatePrecompiles::<_>::new(); + pub PrecompilesValue: DataHavenPrecompiles = DataHavenPrecompiles::<_>::new(); pub WeightPerGas: Weight = Weight::from_parts(WEIGHT_PER_GAS, 0); pub SuicideQuickClearLimit: u32 = 0; /// The amount of gas per pov. A ratio of 16 if we convert ref_time to gas and we compare @@ -799,8 +799,8 @@ impl pallet_evm::Config for Runtime { type AddressMapping = IdentityAddressMapping; type Currency = Balances; type RuntimeEvent = RuntimeEvent; - type PrecompilesType = (); - type PrecompilesValue = (); + type PrecompilesType = DataHavenPrecompiles; + type PrecompilesValue = PrecompilesValue; type ChainId = EvmChainId; type BlockGasLimit = BlockGasLimit; type Runner = pallet_evm::runner::stack::Runner; diff --git a/operator/runtime/stagenet/src/lib.rs b/operator/runtime/stagenet/src/lib.rs index 6860c626..ab072071 100644 --- a/operator/runtime/stagenet/src/lib.rs +++ b/operator/runtime/stagenet/src/lib.rs @@ -9,6 +9,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); #[cfg(feature = "runtime-benchmarks")] mod benchmarks; pub mod configs; +pub mod precompiles; pub mod weights; // Re-export governance for tests diff --git a/operator/runtime/stagenet/src/precompiles.rs b/operator/runtime/stagenet/src/precompiles.rs new file mode 100644 index 00000000..15081a31 --- /dev/null +++ b/operator/runtime/stagenet/src/precompiles.rs @@ -0,0 +1,67 @@ +// Copyright 2019-2025 The DataHaven Team +// This file is part of DataHaven. + +// DataHaven is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// DataHaven is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with DataHaven. If not, see . + +use pallet_evm_precompile_blake2::Blake2F; +use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; +use pallet_evm_precompile_modexp::Modexp; +use pallet_evm_precompile_registry::PrecompileRegistry; +use pallet_evm_precompile_sha3fips::Sha3FIPS256; +use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; +use precompile_utils::precompile_set::*; + +type EthereumPrecompilesChecks = (AcceptDelegateCall, CallableByContract, CallableByPrecompile); + +/// EVM precompiles available in the DataHaven Stagenet runtime. +#[precompile_utils::precompile_name_from_address] +type DataHavenPrecompilesAt = ( + // Ethereum precompiles: + // We allow DELEGATECALL to stay compliant with Ethereum behavior. + PrecompileAt, ECRecover, EthereumPrecompilesChecks>, + PrecompileAt, Sha256, EthereumPrecompilesChecks>, + PrecompileAt, Ripemd160, EthereumPrecompilesChecks>, + PrecompileAt, Identity, EthereumPrecompilesChecks>, + PrecompileAt, Modexp, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Add, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Mul, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Pairing, EthereumPrecompilesChecks>, + PrecompileAt, Blake2F, EthereumPrecompilesChecks>, + // Non-DataHaven specific nor Ethereum precompiles : + PrecompileAt, Sha3FIPS256, (CallableByContract, CallableByPrecompile)>, + RemovedPrecompileAt>, + PrecompileAt, ECRecoverPublicKey, (CallableByContract, CallableByPrecompile)>, + RemovedPrecompileAt>, + // DataHaven specific precompiles: + PrecompileAt< + AddressU64<2069>, + PrecompileRegistry, + (CallableByContract, CallableByPrecompile), + >, +); + +/// The PrecompileSet installed in the DataHaven runtime. +/// We include the nine Istanbul precompiles +/// (https://github.com/ethereum/go-ethereum/blob/3c46f557/core/vm/contracts.go#L69) +/// The following distribution has been decided for the precompiles +/// 0-1023: Ethereum Mainnet Precompiles +/// 1024-2047 Precompiles that are not in Ethereum Mainnet but are neither DataHaven specific +/// 2048-4095 DataHaven specific precompiles +pub type DataHavenPrecompiles = PrecompileSetBuilder< + R, + ( + // Skip precompiles if out of range. + PrecompilesInRangeInclusive<(AddressU64<1>, AddressU64<4095>), DataHavenPrecompilesAt>, + ), +>; diff --git a/operator/runtime/testnet/Cargo.toml b/operator/runtime/testnet/Cargo.toml index 7040c7fe..3ced4e27 100644 --- a/operator/runtime/testnet/Cargo.toml +++ b/operator/runtime/testnet/Cargo.toml @@ -33,6 +33,7 @@ hex = { workspace = true } hex-literal = { workspace = true } log = { workspace = true } num-bigint = { workspace = true } +num_enum = { workspace = true } pallet-authorship = { workspace = true } pallet-babe = { workspace = true } pallet-balances = { workspace = true, features = ["insecure_zero_ed"] } @@ -41,10 +42,14 @@ pallet-beefy-mmr = { workspace = true } pallet-collective = { workspace = true } pallet-conviction-voting = { workspace = true } pallet-datahaven-native-transfer = { workspace = true } -pallet-referenda = { workspace = true } -pallet-ethereum = { workspace = true } -pallet-evm = { workspace = true } +pallet-ethereum = { workspace = true, features = ["forbid-evm-reentrancy"] } +pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] } pallet-evm-chain-id = { workspace = true } +pallet-evm-precompile-blake2 = { workspace = true } +pallet-evm-precompile-bn128 = { workspace = true } +pallet-evm-precompile-modexp = { workspace = true } +pallet-evm-precompile-sha3fips = { workspace = true } +pallet-evm-precompile-simple = { workspace = true } pallet-external-validators = { workspace = true } pallet-external-validators-rewards = { workspace = true } pallet-external-validators-rewards-runtime-api = { workspace = true } @@ -59,6 +64,7 @@ pallet-outbound-commitment-store = { workspace = true } pallet-parameters = { workspace = true } pallet-preimage = { workspace = true } pallet-proxy = { workspace = true } +pallet-referenda = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-sudo = { workspace = true } @@ -70,6 +76,7 @@ pallet-utility = { workspace = true } pallet-whitelist = { workspace = true } polkadot-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +precompile-utils = { workspace = true } scale-info = { workspace = true, features = ["derive", "serde"] } serde_json = { workspace = true, default-features = false, features = [ "alloc", @@ -112,6 +119,9 @@ xcm-executor = { workspace = true } strum = { workspace = true } strum_macros = { workspace = true } +# DataHaven precompiles +pallet-evm-precompile-registry = { workspace = true } + # StorageHub pallet-bucket-nfts = { workspace = true } pallet-nfts = { workspace = true } @@ -143,6 +153,8 @@ frame-support-test = { workspace = true } sp-io = { workspace = true } sp-tracing = { workspace = true } +precompile-utils = { workspace = true, features = ["std", "testing"] } + # Snowbridge testing snowbridge-core = { workspace = true } snowbridge-outbound-queue-primitives = { workspace = true } @@ -155,6 +167,7 @@ std = [ "codec/std", "datahaven-runtime-common/std", "fp-account/std", + "fp-evm/std", "frame-benchmarking?/std", "frame-executive/std", "frame-metadata-hash-extension/std", @@ -173,6 +186,7 @@ std = [ "pallet-ethereum/std", "pallet-evm-chain-id/std", "pallet-evm/std", + "pallet-evm-precompile-registry/std", "pallet-grandpa/std", "pallet-identity/std", "pallet-im-online/std", @@ -195,6 +209,7 @@ std = [ "pallet-whitelist/std", "polkadot-primitives/std", "polkadot-runtime-common/std", + "precompile-utils/std", "scale-info/std", "serde_json/std", "snowbridge-beacon-primitives/std", diff --git a/operator/runtime/testnet/src/configs/mod.rs b/operator/runtime/testnet/src/configs/mod.rs index e9bb1e4e..d43a9f01 100644 --- a/operator/runtime/testnet/src/configs/mod.rs +++ b/operator/runtime/testnet/src/configs/mod.rs @@ -28,14 +28,14 @@ pub mod runtime_params; mod storagehub; use super::{ - currency::*, AccountId, Babe, Balance, Balances, BeefyMmrLeaf, Block, BlockNumber, - EthereumBeaconClient, EthereumOutboundQueueV2, EvmChainId, ExistentialDeposit, - ExternalValidators, ExternalValidatorsRewards, Hash, Historical, ImOnline, MessageQueue, Nonce, - Offences, OriginCaller, OutboundCommitmentStore, PalletInfo, Preimage, Referenda, Runtime, - RuntimeCall, RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin, RuntimeTask, - Scheduler, Session, SessionKeys, Signature, System, Timestamp, Treasury, BLOCK_HASH_COUNT, - EXTRINSIC_BASE_WEIGHT, MAXIMUM_BLOCK_WEIGHT, NORMAL_BLOCK_WEIGHT, NORMAL_DISPATCH_RATIO, - SLOT_DURATION, VERSION, + currency::*, precompiles::DataHavenPrecompiles, AccountId, Babe, Balance, Balances, + BeefyMmrLeaf, Block, BlockNumber, EthereumBeaconClient, EthereumOutboundQueueV2, EvmChainId, + ExistentialDeposit, ExternalValidators, ExternalValidatorsRewards, Hash, Historical, ImOnline, + MessageQueue, Nonce, Offences, OriginCaller, OutboundCommitmentStore, PalletInfo, Preimage, + Referenda, Runtime, RuntimeCall, RuntimeEvent, RuntimeFreezeReason, RuntimeHoldReason, + RuntimeOrigin, RuntimeTask, Scheduler, Session, SessionKeys, Signature, System, Timestamp, + Treasury, BLOCK_HASH_COUNT, EXTRINSIC_BASE_WEIGHT, MAXIMUM_BLOCK_WEIGHT, NORMAL_BLOCK_WEIGHT, + NORMAL_DISPATCH_RATIO, SLOT_DURATION, VERSION, }; use codec::{Decode, Encode}; use datahaven_runtime_common::{ @@ -773,7 +773,7 @@ datahaven_runtime_common::impl_on_charge_evm_transaction!(); parameter_types! { pub BlockGasLimit: U256 = U256::from(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT.ref_time() / WEIGHT_PER_GAS); - // pub PrecompilesValue: TemplatePrecompiles = TemplatePrecompiles::<_>::new(); + pub PrecompilesValue: DataHavenPrecompiles = DataHavenPrecompiles::<_>::new(); pub WeightPerGas: Weight = Weight::from_parts(WEIGHT_PER_GAS, 0); pub SuicideQuickClearLimit: u32 = 0; /// The amount of gas per pov. A ratio of 16 if we convert ref_time to gas and we compare @@ -799,8 +799,8 @@ impl pallet_evm::Config for Runtime { type AddressMapping = IdentityAddressMapping; type Currency = Balances; type RuntimeEvent = RuntimeEvent; - type PrecompilesType = (); - type PrecompilesValue = (); + type PrecompilesType = DataHavenPrecompiles; + type PrecompilesValue = PrecompilesValue; type ChainId = EvmChainId; type BlockGasLimit = BlockGasLimit; type Runner = pallet_evm::runner::stack::Runner; diff --git a/operator/runtime/testnet/src/lib.rs b/operator/runtime/testnet/src/lib.rs index b8d25d5b..c79b01c0 100644 --- a/operator/runtime/testnet/src/lib.rs +++ b/operator/runtime/testnet/src/lib.rs @@ -9,6 +9,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); #[cfg(feature = "runtime-benchmarks")] mod benchmarks; pub mod configs; +pub mod precompiles; pub mod weights; // Re-export governance for tests pub use configs::governance; diff --git a/operator/runtime/testnet/src/precompiles.rs b/operator/runtime/testnet/src/precompiles.rs new file mode 100644 index 00000000..6f453657 --- /dev/null +++ b/operator/runtime/testnet/src/precompiles.rs @@ -0,0 +1,67 @@ +// Copyright 2019-2025 The DataHaven Team +// This file is part of DataHaven. + +// DataHaven is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// DataHaven is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with DataHaven. If not, see . + +use pallet_evm_precompile_blake2::Blake2F; +use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; +use pallet_evm_precompile_modexp::Modexp; +use pallet_evm_precompile_registry::PrecompileRegistry; +use pallet_evm_precompile_sha3fips::Sha3FIPS256; +use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; +use precompile_utils::precompile_set::*; + +type EthereumPrecompilesChecks = (AcceptDelegateCall, CallableByContract, CallableByPrecompile); + +/// EVM precompiles available in the DataHaven Testnet runtime. +#[precompile_utils::precompile_name_from_address] +type DataHavenPrecompilesAt = ( + // Ethereum precompiles: + // We allow DELEGATECALL to stay compliant with Ethereum behavior. + PrecompileAt, ECRecover, EthereumPrecompilesChecks>, + PrecompileAt, Sha256, EthereumPrecompilesChecks>, + PrecompileAt, Ripemd160, EthereumPrecompilesChecks>, + PrecompileAt, Identity, EthereumPrecompilesChecks>, + PrecompileAt, Modexp, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Add, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Mul, EthereumPrecompilesChecks>, + PrecompileAt, Bn128Pairing, EthereumPrecompilesChecks>, + PrecompileAt, Blake2F, EthereumPrecompilesChecks>, + // Non-DataHaven specific nor Ethereum precompiles : + PrecompileAt, Sha3FIPS256, (CallableByContract, CallableByPrecompile)>, + RemovedPrecompileAt>, + PrecompileAt, ECRecoverPublicKey, (CallableByContract, CallableByPrecompile)>, + RemovedPrecompileAt>, + // DataHaven specific precompiles: + PrecompileAt< + AddressU64<2069>, + PrecompileRegistry, + (CallableByContract, CallableByPrecompile), + >, +); + +/// The PrecompileSet installed in the DataHaven runtime. +/// We include the nine Istanbul precompiles +/// (https://github.com/ethereum/go-ethereum/blob/3c46f557/core/vm/contracts.go#L69) +/// The following distribution has been decided for the precompiles +/// 0-1023: Ethereum Mainnet Precompiles +/// 1024-2047 Precompiles that are not in Ethereum Mainnet but are neither DataHaven specific +/// 2048-4095 DataHaven specific precompiles +pub type DataHavenPrecompiles = PrecompileSetBuilder< + R, + ( + // Skip precompiles if out of range. + PrecompilesInRangeInclusive<(AddressU64<1>, AddressU64<4095>), DataHavenPrecompilesAt>, + ), +>;