diff --git a/Cargo.lock b/Cargo.lock index 9b627281f9..87c68e07c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5262,6 +5262,7 @@ dependencies = [ "pallet-evm-precompile-proxy", "pallet-evm-precompile-randomness", "pallet-evm-precompile-referenda", + "pallet-evm-precompile-registry", "pallet-evm-precompile-relay-encoder", "pallet-evm-precompile-sha3fips", "pallet-evm-precompile-simple", @@ -7650,6 +7651,30 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-evm-precompile-registry" +version = "0.1.0" +dependencies = [ + "derive_more", + "fp-evm", + "frame-support", + "frame-system", + "hex-literal", + "log", + "pallet-balances", + "pallet-evm", + "pallet-scheduler", + "pallet-timestamp", + "parity-scale-codec", + "precompile-utils", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-evm-precompile-relay-encoder" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index c019e1217a..732e37759a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ "precompiles/crowdloan-rewards", "precompiles/pallet-democracy", "precompiles/parachain-staking", + "precompiles/precompile-registry", "precompiles/preimage", "precompiles/proxy", "precompiles/randomness", @@ -81,6 +82,7 @@ pallet-evm-precompile-preimage = { path = "precompiles/preimage", default-featur pallet-evm-precompile-proxy = { path = "precompiles/proxy", default-features = false } pallet-evm-precompile-randomness = { path = "precompiles/randomness", default-features = false } pallet-evm-precompile-referenda = { path = "precompiles/referenda", default-features = false } +pallet-evm-precompile-registry = { path = "precompiles/precompile-registry", default-features = false } pallet-evm-precompile-relay-encoder = { path = "precompiles/relay-encoder", default-features = false } pallet-evm-precompile-xcm-transactor = { path = "precompiles/xcm-transactor", default-features = false } pallet-evm-precompile-xcm-utils = { path = "precompiles/xcm-utils", default-features = false } diff --git a/precompiles/precompile-registry/Cargo.toml b/precompiles/precompile-registry/Cargo.toml new file mode 100644 index 0000000000..1a6df27791 --- /dev/null +++ b/precompiles/precompile-registry/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "pallet-evm-precompile-registry" +authors = { workspace = true } +description = "Registry of active precompiles" +edition = "2021" +version = "0.1.0" + +[dependencies] +log = { workspace = true } + +# Moonbeam +precompile-utils = { workspace = true } + +# Substrate +frame-support = { workspace = true } +frame-system = { workspace = true } +parity-scale-codec = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } + +# Frontier +fp-evm = { workspace = true } +pallet-evm = { workspace = true, features = [ "forbid-evm-reentrancy" ] } + +[dev-dependencies] +derive_more = { workspace = true } +hex-literal = { workspace = true } +serde = { workspace = true } + +# Moonbeam +precompile-utils = { workspace = true, features = [ "std", "testing" ] } + +# Substrate +pallet-balances = { workspace = true, features = [ "std" ] } +pallet-scheduler = { workspace = true } +pallet-timestamp = { workspace = true } +scale-info = { workspace = true, features = [ "derive" ] } +sp-runtime = { workspace = true } + +[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", + "sp-std/std", +] diff --git a/precompiles/precompile-registry/PrecompileRegistry.sol b/precompiles/precompile-registry/PrecompileRegistry.sol new file mode 100644 index 0000000000..a21b85b56a --- /dev/null +++ b/precompiles/precompile-registry/PrecompileRegistry.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity >=0.8.3; + +/// @author The Moonbeam Team +/// @title Precompile Registry +/// @dev Interface to the set of available precompiles. +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 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 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/precompiles/precompile-registry/src/lib.rs b/precompiles/precompile-registry/src/lib.rs new file mode 100644 index 0000000000..7c3d965c2e --- /dev/null +++ b/precompiles/precompile-registry/src/lib.rs @@ -0,0 +1,79 @@ +// Copyright 2019-2022 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::PrecompileSet; +use precompile_utils::{precompile_set::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. + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + let active = ::get().is_precompile(address.0); + Ok(active) + } + + #[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. + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + let active = ::get().is_active_precompile(address.0); + Ok(active) + } + + #[precompile::public("updateAccountCode(address)")] + fn update_account_code(handle: &mut impl PrecompileHandle, address: Address) -> EvmResult<()> { + // We consider the precompile set is optimized to do at most one storage read. + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + // We will write into the account code. + handle.record_cost(RuntimeHelper::::db_write_gas_cost())?; + + // Prevent touching addresses that are not precompiles. + if !::get().is_precompile(address.0) { + return Err(revert("provided address is not a precompile")); + } + + pallet_evm::Pallet::::create_account(address.0, DUMMY_CODE.to_vec()); + + Ok(()) + } +} diff --git a/precompiles/precompile-registry/src/mock.rs b/precompiles/precompile-registry/src/mock.rs new file mode 100644 index 0000000000..f050b97033 --- /dev/null +++ b/precompiles/precompile-registry/src/mock.rs @@ -0,0 +1,186 @@ +// Copyright 2019-2022 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 . + +//! Test utilities +use super::*; + +use frame_support::traits::Everything; +use frame_support::{construct_runtime, pallet_prelude::*, parameter_types}; +use pallet_evm::{EnsureAddressNever, EnsureAddressRoot}; +use precompile_utils::{mock_account, precompile_set::*, testing::MockAccount}; +use sp_core::H160; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; + +pub type AccountId = MockAccount; +pub type Balance = u128; +pub type BlockNumber = u32; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Evm: pallet_evm::{Pallet, Call, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + } +); + +parameter_types! { + pub const BlockHashCount: u32 = 250; + pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024); + 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 Index = u64; + type BlockNumber = BlockNumber; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = sp_runtime::generic::Header; + 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>; +} +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} +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 = (); +} + +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_ref_time(1); +} + +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 = (); +} + +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/precompiles/precompile-registry/src/tests.rs b/precompiles/precompile-registry/src/tests.rs new file mode 100644 index 0000000000..4d14427d7d --- /dev/null +++ b/precompiles/precompile-registry/src/tests.rs @@ -0,0 +1,219 @@ +// Copyright 2019-2022 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_encoded(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_encoded(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_encoded(()); + 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() { + for file in ["PrecompileRegistry.sol"] { + for solidity_fn in solidity::get_selectors(file) { + assert_eq!( + solidity_fn.compute_selector_hex(), + solidity_fn.docs_selector, + "documented selector for '{}' did not match for file '{}'", + solidity_fn.signature(), + file, + ); + + let selector = solidity_fn.compute_selector(); + if !PCall::supports_selector(selector) { + panic!( + "unsupported selector 0x{:x} => '{}' for file '{}'", + selector, + solidity_fn.signature(), + file, + ) + } + } + } +} diff --git a/precompiles/utils/src/precompile_set.rs b/precompiles/utils/src/precompile_set.rs index 94743c99d1..c8d0015ce4 100644 --- a/precompiles/utils/src/precompile_set.rs +++ b/precompiles/utils/src/precompile_set.rs @@ -29,10 +29,6 @@ use sp_std::{ vec::Vec, }; -mod sealed { - pub trait Sealed {} -} - /// Trait representing checks that can be made on a precompile call. /// Types implementing this trait are made to be chained in a tuple. /// @@ -405,6 +401,16 @@ impl<'a, H: PrecompileHandle> PrecompileHandle for RestrictiveHandle<'a, H> { } } +/// Allows to know if a precompile is active or not. +/// This allows to detect deactivated precompile, that are still considered precompiles by +/// the EVM but that will always revert when called. +pub trait IsActivePrecompile { + /// Is the provided address an active precompile, a precompile that has + /// not be deactivated. Note that a deactivated precompile is still considered a precompile + /// for the EVM, but it will always revert when called. + fn is_active_precompile(&self, address: H160) -> bool; +} + // INDIVIDUAL PRECOMPILE(SET) /// A fragment of a PrecompileSet. Should be implemented as is it @@ -538,6 +544,16 @@ where } } +impl IsActivePrecompile for PrecompileAt +where + A: Get, +{ + #[inline(always)] + fn is_active_precompile(&self, address: H160) -> bool { + address == A::get() + } +} + /// Wraps an inner PrecompileSet with all its addresses starting with /// a common prefix. /// Type parameters allow to define: @@ -655,6 +671,16 @@ where } } +impl IsActivePrecompile for PrecompileSetStartingWith +where + Self: PrecompileSetFragment, +{ + #[inline(always)] + fn is_active_precompile(&self, address: H160) -> bool { + self.is_precompile(address) + } +} + /// Make a precompile that always revert. /// Can be useful when writing tests. pub struct RevertPrecompile(PhantomData); @@ -702,6 +728,66 @@ where } } +impl IsActivePrecompile for RevertPrecompile { + #[inline(always)] + fn is_active_precompile(&self, _address: H160) -> bool { + true + } +} + +/// A precompile that was removed from a precompile set. +/// Still considered a precompile but is inactive and always revert. +pub struct RemovedPrecompileAt(PhantomData); +impl PrecompileSetFragment for RemovedPrecompileAt +where + A: Get, +{ + #[inline(always)] + fn new() -> Self { + Self(PhantomData) + } + + #[inline(always)] + fn execute( + &self, + handle: &mut impl PrecompileHandle, + ) -> Option { + if A::get() == handle.code_address() { + Some(Err(revert("Removed precompile"))) + } else { + None + } + } + + #[inline(always)] + fn is_precompile(&self, address: H160) -> bool { + address == A::get() + } + + #[inline(always)] + fn used_addresses(&self) -> Vec { + vec![A::get()] + } + + fn summarize_checks(&self) -> Vec { + vec![PrecompileCheckSummary { + name: None, + precompile_kind: PrecompileKind::Single(A::get()), + recursion_limit: Some(0), + accept_delegate_call: true, + callable_by_smart_contract: "Reverts in all cases".into(), + callable_by_precompile: "Reverts in all cases".into(), + }] + } +} + +impl IsActivePrecompile for RemovedPrecompileAt { + #[inline(always)] + fn is_active_precompile(&self, _address: H160) -> bool { + false + } +} + // COMPOSITION OF PARTS #[impl_for_tuples(1, 100)] impl PrecompileSetFragment for Tuple { @@ -761,6 +847,20 @@ impl PrecompileSetFragment for Tuple { } } +#[impl_for_tuples(1, 100)] +impl IsActivePrecompile for Tuple { + #[inline(always)] + fn is_active_precompile(&self, address: H160) -> bool { + for_tuples!(#( + if self.Tuple.is_active_precompile(address) { + return true; + } + )*); + + false + } +} + /// Wraps a precompileset fragment into a range, and will skip processing it if the address /// is out of the range. pub struct PrecompilesInRangeInclusive { @@ -811,6 +911,19 @@ where } } +impl IsActivePrecompile for PrecompilesInRangeInclusive<(S, E), P> +where + P: IsActivePrecompile, +{ + fn is_active_precompile(&self, address: H160) -> bool { + if self.range.contains(&address) { + self.inner.is_active_precompile(address) + } else { + false + } + } +} + /// Wraps a tuple of `PrecompileSetFragment` to make a real `PrecompileSet`. pub struct PrecompileSetBuilder { inner: P, @@ -827,6 +940,12 @@ impl PrecompileSet for Precompi } } +impl IsActivePrecompile for PrecompileSetBuilder { + fn is_active_precompile(&self, address: H160) -> bool { + self.inner.is_active_precompile(address) + } +} + impl PrecompileSetBuilder { /// Create a new instance of the PrecompileSet. pub fn new() -> Self { diff --git a/runtime/moonbase/Cargo.toml b/runtime/moonbase/Cargo.toml index b811395132..2f3641357d 100644 --- a/runtime/moonbase/Cargo.toml +++ b/runtime/moonbase/Cargo.toml @@ -58,6 +58,7 @@ pallet-evm-precompile-preimage = { workspace = true } pallet-evm-precompile-proxy = { workspace = true } pallet-evm-precompile-randomness = { workspace = true } pallet-evm-precompile-referenda = { workspace = true } +pallet-evm-precompile-registry = { workspace = true } pallet-evm-precompile-relay-encoder = { workspace = true } pallet-evm-precompile-xcm-transactor = { workspace = true } pallet-evm-precompile-xcm-utils = { workspace = true } @@ -222,6 +223,7 @@ std = [ "pallet-evm-precompile-preimage/std", "pallet-evm-precompile-randomness/std", "pallet-evm-precompile-referenda/std", + "pallet-evm-precompile-registry/std", "pallet-evm-precompile-xcm-transactor/std", "pallet-evm-precompile-xcm-utils/std", "pallet-evm-precompile-xtokens/std", diff --git a/runtime/moonbase/src/precompiles.rs b/runtime/moonbase/src/precompiles.rs index e2255016a2..2f29dc380c 100644 --- a/runtime/moonbase/src/precompiles.rs +++ b/runtime/moonbase/src/precompiles.rs @@ -37,6 +37,7 @@ use pallet_evm_precompile_preimage::PreimagePrecompile; use pallet_evm_precompile_proxy::{OnlyIsProxy, ProxyPrecompile}; use pallet_evm_precompile_randomness::RandomnessPrecompile; use pallet_evm_precompile_referenda::ReferendaPrecompile; +use pallet_evm_precompile_registry::PrecompileRegistry; use pallet_evm_precompile_relay_encoder::RelayEncoderPrecompile; use pallet_evm_precompile_sha3fips::Sha3FIPS256; use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256}; @@ -103,7 +104,7 @@ type MoonbasePrecompilesAt = ( PrecompileAt, Blake2F, EthereumPrecompilesChecks>, // Non-Moonbeam specific nor Ethereum precompiles : PrecompileAt, Sha3FIPS256, (CallableByContract, CallableByPrecompile)>, - // PrecompileAt, Dispatch>, + RemovedPrecompileAt>, // Dispatch PrecompileAt, ECRecoverPublicKey, (CallableByContract, CallableByPrecompile)>, // Moonbeam specific precompiles: PrecompileAt< @@ -224,6 +225,11 @@ type MoonbasePrecompilesAt = ( CollectivePrecompile, (CallableByContract, CallableByPrecompile), >, + PrecompileAt< + AddressU64<2069>, + PrecompileRegistry, + (CallableByContract, CallableByPrecompile), + >, ); /// The PrecompileSet installed in the Moonbase runtime. diff --git a/runtime/moonbase/tests/integration_test.rs b/runtime/moonbase/tests/integration_test.rs index e191cb2c0b..24fe259269 100644 --- a/runtime/moonbase/tests/integration_test.rs +++ b/runtime/moonbase/tests/integration_test.rs @@ -19,7 +19,7 @@ mod common; use common::*; -use precompile_utils::{prelude::*, testing::*}; +use precompile_utils::{precompile_set::IsActivePrecompile, prelude::*, testing::*}; use fp_evm::Context; use frame_support::{ @@ -2948,8 +2948,9 @@ fn precompile_existence() { ExtBuilder::default().build().execute_with(|| { let precompiles = Precompiles::new(); let precompile_addresses: std::collections::BTreeSet<_> = vec![ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1026, 2048, 2049, 2050, 2051, 2052, 2053, 2054, 2055, - 2056, 2057, 2058, 2059, 2060, 2061, 2062, 2063, 2064, 2065, 2066, 2067, 2068, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 1024, 1025, 1026, 2048, 2049, 2050, 2051, 2052, 2053, 2054, + 2055, 2056, 2057, 2058, 2059, 2060, 2061, 2062, 2063, 2064, 2065, 2066, 2067, 2068, + 2069, ] .into_iter() .map(H160::from_low_u64_be) @@ -3005,6 +3006,39 @@ fn precompile_existence() { }); } +#[test] +fn removed_precompiles() { + ExtBuilder::default().build().execute_with(|| { + let precompiles = Precompiles::new(); + let removed_precompiles = [1025]; + + for i in 1..3000 { + let address = H160::from_low_u64_be(i); + + if !precompiles.is_precompile(address) { + continue; + } + + if !removed_precompiles.contains(&i) { + assert!( + precompiles.is_active_precompile(address), + "{i} should be an active precompile" + ); + continue; + } + + assert!( + !precompiles.is_active_precompile(address), + "{i} shouldn't be an active precompile" + ); + + precompiles + .prepare_test(Alice, address, []) + .execute_reverts(|out| out == b"Removed precompile"); + } + }) +} + #[test] fn substrate_based_fees_zero_txn_costs_only_base_extrinsic() { use frame_support::dispatch::{DispatchInfo, Pays};