-
Notifications
You must be signed in to change notification settings - Fork 487
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
Moves precompile-utils from moonbeam into frontier. #1150
Merged
sorpaas
merged 15 commits into
polkadot-evm:master
from
moonbeam-foundation:cem-precompile-utils
Aug 29, 2023
Merged
Changes from 6 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
ce712e1
Moves precompile-utils into frontier
478a312
Remove redundant affix dependency
a4d81b2
Fix formatting
2d64750
Fix toml formatting
2dd3489
Fix clippy issues
0978a5e
Fix formatting
9da34c7
Update Cargo.toml
rimbi 10ccdca
Update Cargo.toml
rimbi dd50944
Remove paste from dependencies
59189de
Move the precompile-utils crates from utils folder to the root folder
9dd5823
Replace sha3 crate with sp-core-hashing
077a6df
Replace sha3 crate with sp-core-hashing
ba58247
Replaces Moonbeam license statement with Parity license statement
83c36c7
Merge branch 'upstream-master' into cem-precompile-utils
e81b7dc
Remove the pre-commit file
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -31,6 +31,9 @@ members = [ | |||
"primitives/self-contained", | ||||
"template/node", | ||||
"template/runtime", | ||||
"utils/precompiles", | ||||
"utils/precompiles/macro", | ||||
"utils/precompiles/tests-external", | ||||
] | ||||
resolver = "2" | ||||
|
||||
|
@@ -43,6 +46,7 @@ repository = "https://github.com/paritytech/frontier/" | |||
async-trait = "0.1" | ||||
bn = { package = "substrate-bn", version = "0.6", default-features = false } | ||||
clap = { version = "4.3", features = ["derive", "deprecated"] } | ||||
derive_more = "0.99" | ||||
environmental = { version = "1.1.4", default-features = false } | ||||
ethereum = { version = "0.14.0", default-features = false } | ||||
ethereum-types = { version = "0.14.1", default-features = false } | ||||
|
@@ -51,17 +55,22 @@ futures = "0.3.28" | |||
hex = { version = "0.4.3", default-features = false, features = ["alloc"] } | ||||
hex-literal = "0.4.1" | ||||
impl-serde = { version = "0.4.0", default-features = false } | ||||
impl-trait-for-tuples = "0.2.1" | ||||
jsonrpsee = "0.16.2" | ||||
kvdb-rocksdb = "0.19.0" | ||||
libsecp256k1 = { version = "0.7.1", default-features = false } | ||||
log = { version = "0.4.19", default-features = false } | ||||
num_enum = { version = "0.5.3", default-features = false } | ||||
rimbi marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
parity-db = "0.4.10" | ||||
parking_lot = "0.12.1" | ||||
paste = "1.0.6" | ||||
rimbi marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
rlp = { version = "0.5.2", default-features = false } | ||||
scale-codec = { package = "parity-scale-codec", version = "3.6.4", default-features = false, features = ["derive"] } | ||||
scale-info = { version = "2.9.0", default-features = false, features = ["derive"] } | ||||
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } | ||||
serde_json = "1.0" | ||||
sha3 = { version = "0.10", default-features = false } | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
we could use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||||
similar-asserts = "1.1.0" | ||||
sqlx = { version = "0.7.1", default-features = false, features = ["macros"] } | ||||
thiserror = "1.0" | ||||
tokio = "1.29.1" | ||||
|
@@ -165,6 +174,10 @@ pallet-evm-test-vector-support = { version = "1.0.0-dev", path = "frame/evm/test | |||
pallet-hotfix-sufficients = { version = "1.0.0", path = "frame/hotfix-sufficients", default-features = false } | ||||
# Frontier Template | ||||
frontier-template-runtime = { path = "template/runtime", default-features = false } | ||||
|
||||
# Frontier utils | ||||
precompile-utils = { path = "utils/precompiles", default-features = false } | ||||
|
||||
# Arkworks | ||||
ark-bls12-377 = { version = "0.4.0", default-features = false, features = ["curve"] } | ||||
ark-bw6-761 = { version = "0.4.0", default-features = false } | ||||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
[package] | ||
name = "precompile-utils" | ||
authors = { workspace = true } | ||
description = "Utils to write EVM precompiles." | ||
edition = "2021" | ||
version = "0.1.0" | ||
|
||
[dependencies] | ||
derive_more = { workspace = true, optional = true } | ||
environmental = { workspace = true } | ||
hex = { workspace = true } | ||
hex-literal = { workspace = true, optional = true } | ||
impl-trait-for-tuples = { workspace = true } | ||
log = { workspace = true } | ||
num_enum = { workspace = true } | ||
paste = { workspace = true } | ||
scale-info = { workspace = true, optional = true, features = ["derive"] } | ||
serde = { workspace = true, optional = true } | ||
sha3 = { workspace = true } | ||
similar-asserts = { workspace = true, optional = true } | ||
|
||
# Moonbeam | ||
precompile-utils-macro = { path = "macro" } | ||
|
||
# Substrate | ||
frame-support = { workspace = true } | ||
frame-system = { workspace = true } | ||
scale-codec = { package = "parity-scale-codec", workspace = true } | ||
sp-core = { workspace = true } | ||
sp-io = { workspace = true } | ||
sp-runtime = { workspace = true } | ||
sp-std = { workspace = true } | ||
|
||
# Frontier | ||
evm = { workspace = true, features = ["with-codec"] } | ||
fp-evm = { workspace = true } | ||
pallet-evm = { workspace = true, features = ["forbid-evm-reentrancy"] } | ||
|
||
[dev-dependencies] | ||
hex-literal = { workspace = true } | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"environmental/std", | ||
"fp-evm/std", | ||
"frame-support/std", | ||
"frame-system/std", | ||
"pallet-evm/std", | ||
"scale-codec/std", | ||
"sp-core/std", | ||
"sp-io/std", | ||
"sp-std/std", | ||
] | ||
testing = ["derive_more", "hex-literal", "scale-info", "serde", "similar-asserts", "std"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
[package] | ||
name = "precompile-utils-macro" | ||
authors = { workspace = true } | ||
description = "" | ||
edition = "2021" | ||
version = "0.1.0" | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[[test]] | ||
name = "tests" | ||
path = "tests/tests.rs" | ||
|
||
[dependencies] | ||
case = "1.0" | ||
num_enum = { version = "0.5.3", default-features = false } | ||
prettyplease = "0.1.18" | ||
proc-macro2 = "1.0" | ||
quote = "1.0" | ||
sha3 = "0.10" | ||
syn = { version = "1.0", features = ["extra-traits", "fold", "full", "visit"] } | ||
|
||
[dev-dependencies] | ||
macrotest = "1.0.9" | ||
trybuild = "1.0" | ||
|
||
precompile-utils = { path = "../", features = ["testing"] } | ||
|
||
fp-evm = { workspace = true } | ||
frame-support = { workspace = true } | ||
sp-core = { workspace = true } | ||
sp-std = { workspace = true } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
# `#[precompile]` procedural macro. | ||
|
||
This procedural macro allows to simplify the implementation of an EVM precompile or precompile set | ||
using an `impl` block with annotations to automatically generate: | ||
|
||
- the implementation of the trait `Precompile` or `PrecompileSet` (exposed by the `fp_evm` crate) | ||
- parsing of the method parameters from Solidity encoding into Rust type, based on the `solidity::Codec` | ||
trait (exposed by the `precompile-utils` crate) | ||
- a test to ensure the types expressed in the Solidity signature match the Rust types in the | ||
implementation. | ||
|
||
## How to use | ||
|
||
Define your precompile type and write an `impl` block that will contain the precompile methods | ||
implementation. This `impl` block can have type parameters and a `where` clause, which will be | ||
reused to generate the `Precompile`/`PrecompileSet` trait implementation and the enum representing | ||
each public function of precompile with its parsed arguments. | ||
|
||
```rust,ignore | ||
pub struct ExemplePrecompile<R, I>(PhantomData<(R,I)>); | ||
|
||
#[precomile_utils::precompile] | ||
impl<R, I> ExemplePrecompile<R, I> | ||
where | ||
R: pallet_evm::Config | ||
{ | ||
#[precompile::public("example(uint32)")] | ||
fn example(handle: &mut impl PrecompileHandle, arg: u32) -> EvmResult<u32> { | ||
Ok(arg * 2) | ||
} | ||
} | ||
``` | ||
|
||
The example code above will automatically generate an enum like | ||
|
||
```rust,ignore | ||
#[allow(non_camel_case_types)] | ||
pub enum ExemplePrecompileCall<R, I> | ||
where | ||
R: pallet_evm::Config | ||
{ | ||
example { | ||
arg: u32 | ||
}, | ||
// + an non constrible variant with a PhantomData<(R,I)> | ||
} | ||
``` | ||
|
||
This enum have the function `parse_call_data` that can parse the calldata, recognize the Solidity | ||
4-bytes selector and parse the appropriate enum variant. | ||
|
||
It will also generate automatically an implementation of `Precompile`/`PrecompileSet` that calls | ||
this function and the content of the variant to its associated function of the `impl` block. | ||
|
||
## Function attributes | ||
|
||
`#[precompile::public("signature")]` allows to declare a function as a public method of the | ||
precompile with the provided Solidity signature. A function can have multiple `public` attributes to | ||
support renamed functions with backward compatibility, however the arguments must have the same | ||
type. It is not allowed to use the exact same signature multiple times. | ||
|
||
The function must take a `&mut impl PrecompileHandle` as parameter, followed by all the parameters | ||
of the Solidity function in the same order. Those parameters types must implement `solidity::Codec`, and | ||
their name should match the one used in the Solidity interface (.sol) while being in `snake_case`, | ||
which will automatically be converted to `camelCase` in revert messages. The function must return an | ||
`EvmResult<T>`, which is an alias of `Result<T, PrecompileFailure>`. This `T` must implement the | ||
`solidity::Codec` trait and must match the return type in the Solidity interface. The macro will | ||
automatically encode it to Solidity format. | ||
|
||
By default those functions are considered non-payable and non-view (can cause state changes). This | ||
can be changed using either `#[precompile::payable]` or `#[precompile::view]`. Only one can be used. | ||
|
||
It is also possible to declare a fallback function using `#[precompile::fallback]`. This function | ||
will be called if the selector is unknown or if the input is less than 4-bytes long (no selector). | ||
This function cannot have any parameter outside of the `PrecompileHandle`. A function can be both | ||
`public` and `fallback`. | ||
|
||
In case some check must be performed before parsing the input, such as forbidding being called from | ||
some address, a function can be annotated with `#[precompile::pre_check]`: | ||
|
||
```rust,ignore | ||
#[precompile::pre_check] | ||
fn pre_check(handle: &mut impl PrecompileHandle) -> EvmResult { | ||
todo!("Perform your check here") | ||
} | ||
``` | ||
|
||
This function cannot have other attributes. | ||
|
||
## PrecompileSet | ||
|
||
By default the macro considers the `impl` block to represent a precompile and this will implement | ||
the `Precompile` trait. If you want to instead implement a precompile set, you must add the | ||
`#[precompile::precompile_set]` to the `impl` block. | ||
|
||
Then, it is necessary to have a function annotated with the `#[precompile::discriminant]` attribute. | ||
This function is called with the **code address**, the address of the precompile. It must return | ||
`None` if this address is not part of the precompile set, or `Some` if it is. The `Some` variants | ||
contains a value of a type of your choice that represents which member of the set this address | ||
corresponds to. For example for our XC20 precompile sets this function returns the asset id | ||
corresponding to this address if it exists. | ||
|
||
Finally, every other function annotated with a `precompile::_` attribute must now take this | ||
discriminant as first parameter, before the `PrecompileHandle`. | ||
|
||
```rust,ignore | ||
pub struct ExemplePrecompileSet<R>(PhantomData<R>); | ||
|
||
#[precompile_utils::precompile] | ||
#[precompile::precompile_set] | ||
impl<R> ExamplePrecompileSet<R> | ||
where | ||
R: pallet_evm::Config | ||
{ | ||
#[precompile::discriminant] | ||
fn discriminant(address: H160) -> Option<u8> { | ||
// Replace with your discriminant logic. | ||
Some(match address { | ||
a if a == H160::from(42) => 1 | ||
a if a == H160::from(43) => 2, | ||
_ => return None, | ||
}) | ||
} | ||
|
||
#[precompile::public("example(uint32)")] | ||
fn example(discriminant: u8, handle: &mut impl PrecompileHandle, arg: u32) -> EvmResult { | ||
// Discriminant can be used here. | ||
Ok(arg * discriminant) | ||
} | ||
} | ||
``` | ||
|
||
## Solidity signatures test | ||
|
||
The macro will automatically generate a unit test to ensure that the types expressed in a `public` | ||
attribute matches the Rust parameters of the function, thanks to the `solidity::Codec` trait having the | ||
`solidity_type() -> String` function. | ||
|
||
If any **parsed** argument (discriminant is not concerned) depends on the type parameters of the | ||
`impl` block, the macro will not be able to produce valid code and output an error like: | ||
|
||
```text | ||
error[E0412]: cannot find type `R` in this scope | ||
--> tests/precompile/compile-fail/test/generic-arg.rs:25:63 | ||
| | ||
23 | impl<R: Get<u32>> Precompile<R> { | ||
| - help: you might be missing a type parameter: `<R>` | ||
24 | #[precompile::public("foo(bytes)")] | ||
25 | fn foo(handle: &mut impl PrecompileHandle, arg: BoundedBytes<R>) -> EvmResult { | ||
| ^ not found in this scope | ||
``` | ||
|
||
In this case you need to annotate the `impl` block with the `#[precompile::test_concrete_types(...)]` | ||
attributes. The `...` should be replaced with concrete types for each type parameter, like a mock | ||
runtime. Those types are only used to generate the test and only one set of types can be used. | ||
|
||
```rust,ignore | ||
pub struct ExamplePrecompile<R, I>(PhantomData<(R, I)>); | ||
|
||
pub struct GetMaxSize<R, I>(PhantomData<(R, I)>); | ||
|
||
impl<R: SomeConfig, I> Get<u32> for GetMaxSize<R, I> { | ||
fn get() -> u32 { | ||
<R as SomeConfig<I>>::SomeConstant::get() | ||
} | ||
} | ||
|
||
#[precompile_utils::precompile] | ||
#[precompile::test_concrete_types(mock::Runtime, Instance1)] | ||
impl<R, I> ExamplePrecompile<R, I> | ||
where | ||
R: pallet_evm::Config + SomeConfig<I> | ||
{ | ||
#[precompile::public("example(bytes)")] | ||
fn example( | ||
handle: &mut impl PrecompileHandle, | ||
data: BoundedBytes<GetMaxSize<R>>, | ||
) -> EvmResult { | ||
todo!("Method implementation") | ||
} | ||
} | ||
``` | ||
|
||
## Enum functions | ||
|
||
The generated enums exposes the following public functions: | ||
|
||
- `parse_call_data`: take a `PrecompileHandle` and tries to parse the call data. Returns an | ||
`EvmResult<Self>`. It **DOES NOT** execute the code of the annotated `impl` block. | ||
- `supports_selector`: take a selector as a `u32` is returns if this selector is supported by the | ||
precompile(set) as a `bool`. Note that the presence of a fallback function is not taken into | ||
account. | ||
- `selectors`: returns a static array (`&'static [u32]`) of all the supported selectors. | ||
- For each variant/public function `foo`, there is a function `foo_selectors` which returns a static | ||
array of all the supported selectors **for that function**. That can be used to ensure in tests | ||
that some function have a selector that was computed by hand. | ||
- `encode`: take `self` and encodes it in Solidity format. Additionally, `Vec<u8>` implements | ||
`From<CallEnum>` which simply call encodes. This is useful to write tests as you can construct the | ||
variant you want and it will be encoded to Solidity format for you. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This
utils
path seems unnecessaryThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done