Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow for precompiles to have arbitrary addresses, potentially more than one. #1016

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
23 changes: 23 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"frame/ethereum",
"frame/evm",
"frame/evm-chain-id",
"frame/evm-precompile",
"frame/hotfix-sufficients",
"frame/evm/precompile/sha3fips",
"frame/evm/precompile/simple",
Expand Down Expand Up @@ -137,6 +138,7 @@ pallet-dynamic-fee = { version = "4.0.0-dev", path = "frame/dynamic-fee", defaul
pallet-ethereum = { version = "4.0.0-dev", path = "frame/ethereum", default-features = false }
pallet-evm = { version = "6.0.0-dev", path = "frame/evm", default-features = false }
pallet-evm-chain-id = { version = "1.0.0-dev", path = "frame/evm-chain-id", default-features = false }
pallet-evm-precompile = { version = "1.0.0-dev", path = "frame/evm-precompile", default-features = false }
pallet-evm-precompile-modexp = { version = "2.0.0-dev", path = "frame/evm/precompile/modexp", default-features = false }
pallet-evm-precompile-sha3fips = { version = "2.0.0-dev", path = "frame/evm/precompile/sha3fips", default-features = false }
pallet-evm-precompile-simple = { version = "2.0.0-dev", path = "frame/evm/precompile/simple", default-features = false }
Expand Down
68 changes: 68 additions & 0 deletions frame/evm-precompile/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
[package]
name = "pallet-evm-precompile"
version = "1.0.0-dev"
license = "Apache-2.0"
readme = "README.md"
description = "FRAME EVM Precompile pallet."
authors = { workspace = true }
edition = { workspace = true }
repository = { workspace = true }

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
scale-codec = { package = "parity-scale-codec", workspace = true }
scale-info = { workspace = true }
serde = { workspace = true, default-features = false }
# Substrate
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }

# Frontier
pallet-evm = { workspace = true }
pallet-evm-precompile-modexp = { workspace = true }
pallet-evm-precompile-sha3fips = { workspace = true }
pallet-evm-precompile-simple = { workspace = true }

[dev-dependencies]
# Substrate
pallet-balances = { workspace = true, features = ["default"] }
pallet-timestamp = { workspace = true }
sp-io = { workspace = true }

# Frontier
fp-evm = { workspace = true }

[features]
default = ["std"]
std = [
"serde/std",
# Substrate
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"sp-std/std",
"sp-core/std",
"sp-runtime/std",
# Frontier
"pallet-evm/std",
"pallet-evm-precompile-modexp/std",
"pallet-evm-precompile-sha3fips/std",
"pallet-evm-precompile-simple/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-evm/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-evm/try-runtime",
]
12 changes: 12 additions & 0 deletions frame/evm-precompile/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# EVM Precompile Module

The EVM Precompile Module allows using Precompiles with arbitrary addresses, potentially more than one.

A `StorageMap` keeps track of the Precompiles on-chain, where:
- key: `H160`
- value: `PrecompileLabel`

A `PrecompileLabel` determines which functionality the Precompile has. It is declared as a `BoundedVec<u8, ConstU32<32>>`, which means the user is free to choose a label (e.g.: `b"Sha3FIPS512"`) that's up-to 32 bytes long.

`OnChainPrecompiles` implements the `PrecompileSet` trait, where the Precompile addresses are routed to the appropriate `Precompile::execute` implementation according to the on-chan mapping.

44 changes: 44 additions & 0 deletions frame/evm-precompile/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use super::*;

#[allow(unused)]
use crate::Pallet as EVMPrecompile;
use frame_benchmarking::{benchmarks, impl_benchmark_test_suite};
use frame_system::RawOrigin;

benchmarks! {
add_precompile {
let address = H160::from_low_u64_be(1);
let label = PrecompileLabel::new(
b"SomePrecompileLabel"
.to_vec()
.try_into()
.expect("less than 32 chars; qed"),
);

}: _(RawOrigin::Root, address, label.clone())
verify {
let read_precompile = EVMPrecompile::<T>::precompiles(address);
assert_eq!(read_precompile, label);
}

remove_precompile {
let address = H160::from_low_u64_be(1);
let label = PrecompileLabel::new(
b"SomePrecompileLabel"
.to_vec()
.try_into()
.expect("less than 32 chars; qed"),
);
EVMPrecompile::<T>::add_precompile(RawOrigin::Root.into(), address, label).unwrap();
}: _(RawOrigin::Root, address)
verify {
let read_precompile = EVMPrecompile::<T>::precompiles(address);
assert_eq!(read_precompile, PrecompileLabel::default());
}
}

impl_benchmark_test_suite!(
EVMPrecompile,
crate::tests::new_test_ext(),
crate::mock::Test
);
218 changes: 218 additions & 0 deletions frame/evm-precompile/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
// SPDX-License-Identifier: Apache-2.0
// This file is part of Frontier.
//
// Copyright (c) 2020-2023 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![cfg_attr(not(feature = "std"), no_std)]

pub use pallet::*;

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

pub mod weights;
pub use weights::*;

use frame_support::BoundedVec;
use pallet_evm::{Precompile, PrecompileHandle, PrecompileResult, PrecompileSet};
use pallet_evm_precompile_modexp::Modexp;
use pallet_evm_precompile_sha3fips::Sha3FIPS256;
use pallet_evm_precompile_simple::{ECRecover, ECRecoverPublicKey, Identity, Ripemd160, Sha256};
use scale_codec::{Decode, Encode};
use scale_info::{prelude::marker::PhantomData, TypeInfo};
use sp_core::{ConstU32, MaxEncodedLen, H160};
use sp_std::ops::Deref;

#[derive(Decode, Encode, Default, TypeInfo, Clone, PartialEq, Debug, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct PrecompileLabel(BoundedVec<u8, ConstU32<32>>);

impl PrecompileLabel {
pub fn new(l: BoundedVec<u8, ConstU32<32>>) -> PrecompileLabel {
PrecompileLabel(l)
}
}

impl Deref for PrecompileLabel {
type Target = BoundedVec<u8, ConstU32<32>>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

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

impl<R> OnChainPrecompiles<R>
where
R: pallet::Config + pallet_evm::Config,
{
pub fn new() -> Self {
Self(Default::default())
}
}
impl<R> PrecompileSet for OnChainPrecompiles<R>
Copy link
Member

Choose a reason for hiding this comment

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

You can directly impl<T: Config> PrecompileSet for Pallet<T>. That will allow the interface to be much nicer.

To do that you need to create another trait whose purpose is to map labels to executions, something like this:

trait PrecompileLabelSet {
  fn execute(&self, label: &PrecompileLabel, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult>;
}

Then add that to Config.

Copy link
Contributor Author

@bernardoaraujor bernardoaraujor Mar 12, 2023

Choose a reason for hiding this comment

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

pallet-evm has the following types in its Config trait:

type PrecompilesType: PrecompileSet;
type PrecompilesValue: Get<Self::PrecompilesType>;

as pointed out by @kianenigma , it seems that impl<T: Config> PrecompileSet for Pallet<T> is not doable, because pallet structs are not meant to be instantiated: https://substrate.stackexchange.com/questions/7579/how-to-implement-the-get-trait-for-a-pallett?noredirect=1#comment7602_7579

Copy link
Member

Choose a reason for hiding this comment

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

I'd still prefer that we use the pallet name directly instead of creating another custom type, to keep things consistent everywhere.

To do this we need to remove the self attribute from PrecompileSet. This is trivial on type level:

trait Precompiles {
   fn execute(arg1, arg2) -> res;
}

trait PrecompileSet {
  fn execute(&self, arg1, arg2) -> res;
}

impl<T: PrecompileSet> Precompiles for Get<T> {
  fn execute(arg1, arg2) -> res {
    T::get().execute(arg1, arg2)
  }
}

Then on config:

type Precompiles: Precompiles;

However, on code level we uses in a lot of places actual references of precompiles, which makes things slightly complicated.

evm and pallet-evm had different design goals. In evm we'd actually want the reference thing because precompile set can be dynamic. And people should be able to change the set without recompiling their code. In pallet-evm, this is not needed at all because all the storage accesses (which is the only thing that makes things dynamic) is entirely static.

We probably have other type workarounds, so unless you have better ideas, please temporarily do not work on this until we figure something out (or decide to use the old wrapper type method).

where
R: pallet::Config + pallet_evm::Config,
{
fn execute(&self, handle: &mut impl PrecompileHandle) -> Option<PrecompileResult> {
match handle.code_address() {
// Ethereum precompiles :
a if &Precompiles::<R>::get(a)[..] == b"ECRecover" => Some(ECRecover::execute(handle)),
a if &Precompiles::<R>::get(a)[..] == b"Sha256" => Some(Sha256::execute(handle)),
a if &Precompiles::<R>::get(a)[..] == b"Ripemd160" => Some(Ripemd160::execute(handle)),
a if &Precompiles::<R>::get(a)[..] == b"Identity" => Some(Identity::execute(handle)),
a if &Precompiles::<R>::get(a)[..] == b"Modexp" => Some(Modexp::execute(handle)),
// Non-Frontier specific nor Ethereum precompiles :
a if &Precompiles::<R>::get(a)[..] == b"Sha3FIPS256" => {
Some(Sha3FIPS256::execute(handle))
}
a if &Precompiles::<R>::get(a)[..] == b"ECRecoverPublicKey" => {
Some(ECRecoverPublicKey::execute(handle))
}
_ => None,
}
}

fn is_precompile(&self, address: H160) -> bool {
match address {
a if &Precompiles::<R>::get(a)[..] == b"ECRecover" => true,
a if &Precompiles::<R>::get(a)[..] == b"Sha256" => true,
a if &Precompiles::<R>::get(a)[..] == b"Ripemd160" => true,
a if &Precompiles::<R>::get(a)[..] == b"Identity" => true,
a if &Precompiles::<R>::get(a)[..] == b"Modexp" => true,
a if &Precompiles::<R>::get(a)[..] == b"Sha3FIPS256" => true,
a if &Precompiles::<R>::get(a)[..] == b"ECRecoverPublicKey" => true,
_ => false,
}
}
}

#[frame_support::pallet]
pub mod pallet {
use crate::{PrecompileLabel, WeightInfo};
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_core::H160;

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);

#[pallet::config]
pub trait Config: frame_system::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

/// Origin allowed to modify Precompiles
type PrecompileModifierOrigin: EnsureOrigin<Self::RuntimeOrigin>;

// WeightInfo type
type WeightInfo: WeightInfo;
}

#[pallet::genesis_config]
#[cfg_attr(feature = "std", derive(Default))]
pub struct GenesisConfig {
pub precompiles: Vec<(H160, PrecompileLabel)>,
}

#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig {
fn build(&self) {
for (address, label) in &self.precompiles {
Pallet::<T>::do_add_precompile(address, label.clone());
}
}
}

#[pallet::storage]
#[pallet::getter(fn precompiles)]
pub type Precompiles<T: Config> =
StorageMap<_, Blake2_128Concat, H160, PrecompileLabel, ValueQuery>;

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
PrecompileAdded {
address: H160,
label: PrecompileLabel,
},
PrecompileRemoved {
address: H160,
},
}

#[pallet::error]
pub enum Error<T> {
PrecompileDoesNotExist,
}

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Add a precompile to storage
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::add_precompile())]
pub fn add_precompile(
origin: OriginFor<T>,
address: H160,
label: PrecompileLabel,
) -> DispatchResult {
T::PrecompileModifierOrigin::ensure_origin(origin)?;

Self::do_add_precompile(&address, label.clone());

Self::deposit_event(Event::PrecompileAdded { address, label });

Ok(())
}

/// Remove a precompile from storage
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::remove_precompile())]
pub fn remove_precompile(origin: OriginFor<T>, address: H160) -> DispatchResult {
T::PrecompileModifierOrigin::ensure_origin(origin)?;

ensure!(
Precompiles::<T>::contains_key(address),
Error::<T>::PrecompileDoesNotExist
);

Self::do_remove_precompile(&address);

Self::deposit_event(Event::PrecompileRemoved { address });

Ok(())
}
}
}

impl<T: Config> Pallet<T> {
/// Add a precompile to storage
pub fn do_add_precompile(address: &H160, label: PrecompileLabel) {
Precompiles::<T>::set(address, label);
}

/// Remove a precompile from storage
pub fn do_remove_precompile(address: &H160) {
Precompiles::<T>::remove(address);
}
}
Loading