Skip to content

Commit 4c8ba8c

Browse files
committed
feat: erc20 evm precompiles
Signed-off-by: Gregory Hill <gregorydhill@outlook.com>
1 parent f77d5b1 commit 4c8ba8c

16 files changed

Lines changed: 1316 additions & 23 deletions

File tree

Cargo.lock

Lines changed: 37 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ members = [
44
"primitives",
55
"parachain",
66
"parachain/runtime/*",
7+
"parachain/runtime/common/evm/*",
78
"rpc",
89
]
910

parachain/runtime/common/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ version = "1.2.0"
88
targets = ['x86_64-unknown-linux-gnu']
99

1010
[dependencies]
11+
evm-macro = { path = "evm/macro", default-features = false }
12+
evm-utils = { path = "evm/utils", default-features = false }
1113

1214
# Substrate dependencies
1315
sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false }
@@ -30,6 +32,7 @@ clients-info = { path = "../../../crates/clients-info", default-features = false
3032
collator-selection = { path = "../../../crates/collator-selection", default-features = false }
3133
currency = { path = "../../../crates/currency", default-features = false }
3234
democracy = { path = "../../../crates/democracy", default-features = false }
35+
dex-stable = { path = "../../../crates/dex-stable", default-features = false }
3336
escrow = { path = "../../../crates/escrow", default-features = false }
3437
fee = { path = "../../../crates/fee", default-features = false }
3538
issue = { path = "../../../crates/issue", default-features = false }
@@ -58,6 +61,7 @@ orml-xcm-support = { git = "https://github.com/open-web3-stack/open-runtime-modu
5861
orml-unknown-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "3fcd3cf9e63fe80fd9671912833a900ba09d1cc0", default-features = false }
5962

6063
# Frontier dependencies
64+
fp-evm = { git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.42", default-features = false }
6165
pallet-base-fee = { git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.42", default-features = false }
6266
pallet-ethereum = { git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.42", default-features = false }
6367
pallet-evm = { git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.42", default-features = false }
@@ -73,6 +77,9 @@ pallet-evm-precompile-simple = { git = "https://github.com/paritytech/frontier",
7377
[features]
7478
default = ["std"]
7579
std = [
80+
"evm-macro/std",
81+
"evm-utils/std",
82+
7683
"sp-std/std",
7784
"sp-runtime/std",
7885
"sp-core/std",
@@ -91,6 +98,7 @@ std = [
9198
"currency/std",
9299
"collator-selection/std",
93100
"democracy/std",
101+
"dex-stable/std",
94102
"escrow/std",
95103
"fee/std",
96104
"issue/std",
@@ -117,6 +125,7 @@ std = [
117125
"orml-xcm-support/std",
118126
"orml-unknown-tokens/std",
119127

128+
"fp-evm/std",
120129
"pallet-base-fee/std",
121130
"pallet-ethereum/std",
122131
"pallet-evm/std",
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[package]
2+
authors = ["Interlay Ltd"]
3+
edition = "2021"
4+
name = 'evm-macro'
5+
version = "1.2.0"
6+
7+
[lib]
8+
proc-macro = true
9+
10+
[dependencies]
11+
quote = "1.0.20"
12+
syn = { version = "1.0.98", features = ["full", "fold", "extra-traits", "visit"] }
13+
proc-macro2 = "1.0.40"
14+
sha3 = { version = "0.10", default-features = false }
15+
16+
[dev-dependencies]
17+
hex = "0.4.2"
18+
hex-literal = "0.3.1"
19+
evm-utils = { path = "../utils" }
20+
21+
# Substrate dependencies
22+
frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31" }
23+
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31" }
24+
25+
# Frontier dependencies
26+
fp-evm = { git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.42" }
27+
28+
[features]
29+
default = ["std"]
30+
std = [
31+
"sha3/std",
32+
]
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
use proc_macro::TokenStream;
2+
use proc_macro2::{Group, Span, TokenTree};
3+
use quote::quote;
4+
use sha3::{Digest, Keccak256};
5+
use std::collections::BTreeMap;
6+
use syn::{parse_macro_input, spanned::Spanned, DeriveInput};
7+
8+
fn parse_selector(signature_lit: syn::LitStr) -> u32 {
9+
let signature = signature_lit.value();
10+
let digest = Keccak256::digest(signature.as_bytes());
11+
let selector = u32::from_be_bytes([digest[0], digest[1], digest[2], digest[3]]);
12+
selector
13+
}
14+
15+
fn parse_call_enum(input: DeriveInput) -> syn::Result<TokenStream> {
16+
let enum_ident = input.ident.clone();
17+
let variants = if let syn::Data::Enum(syn::DataEnum { variants, .. }) = input.data {
18+
variants
19+
} else {
20+
return Err(syn::Error::new(input.ident.span(), "Structure not supported"));
21+
};
22+
23+
struct Call {
24+
variant: syn::Variant,
25+
}
26+
27+
let mut selector_to_call = BTreeMap::new();
28+
29+
for v in variants {
30+
for a in &v.attrs {
31+
match a.parse_meta() {
32+
Ok(syn::Meta::NameValue(syn::MetaNameValue {
33+
path: syn::Path { segments, .. },
34+
lit: syn::Lit::Str(signature_lit),
35+
..
36+
})) if segments.first().filter(|path| path.ident == "selector").is_some() => {
37+
selector_to_call.insert(parse_selector(signature_lit), Call { variant: v.clone() });
38+
for f in &v.fields {
39+
if f.ident.is_none() {
40+
return Err(syn::Error::new(f.span(), "Unnamed fields not supported"));
41+
}
42+
}
43+
}
44+
_ => return Err(syn::Error::new(a.span(), "Attribute not supported")),
45+
}
46+
}
47+
}
48+
49+
let selectors: Vec<_> = selector_to_call.keys().collect();
50+
let variants_ident: Vec<_> = selector_to_call
51+
.values()
52+
.map(|Call { variant, .. }| variant.ident.clone())
53+
.collect();
54+
let variants_args: Vec<Vec<_>> = selector_to_call
55+
.values()
56+
.map(|Call { variant, .. }| {
57+
variant
58+
.fields
59+
.iter()
60+
.map(|field| field.ident.clone().expect("Only named fields supported"))
61+
.collect()
62+
})
63+
.collect();
64+
65+
Ok(quote! {
66+
impl #enum_ident {
67+
pub fn new(input: &[u8]) -> ::evm_utils::EvmResult<Self> {
68+
use ::evm_utils::RevertReason;
69+
70+
let mut reader = ::evm_utils::Reader::new(input);
71+
let selector = reader.read_selector()?;
72+
match selector {
73+
#(
74+
#selectors => Ok(Self::#variants_ident {
75+
#(
76+
#variants_args: reader.read()?
77+
),*
78+
}),
79+
)*
80+
_ => Err(RevertReason::UnknownSelector.into())
81+
}
82+
}
83+
}
84+
}
85+
.into())
86+
}
87+
88+
#[proc_macro_derive(EvmCall, attributes(selector))]
89+
pub fn precompile_calls(input: TokenStream) -> TokenStream {
90+
let input = parse_macro_input!(input as DeriveInput);
91+
match parse_call_enum(input) {
92+
Ok(ts) => ts,
93+
Err(err) => err.to_compile_error().into(),
94+
}
95+
}
96+
97+
fn parse_event_enum(input: DeriveInput) -> syn::Result<TokenStream> {
98+
let enum_ident = input.ident.clone();
99+
let variants = if let syn::Data::Enum(syn::DataEnum { variants, .. }) = input.data {
100+
variants
101+
} else {
102+
return Err(syn::Error::new(input.ident.span(), "Structure not supported"));
103+
};
104+
105+
struct Event {
106+
variant: syn::Variant,
107+
topics: Vec<TokenTree>,
108+
// NOTE: we do not yet support tuple encoding
109+
data: Option<syn::Ident>,
110+
}
111+
112+
let mut selector_to_event = BTreeMap::new();
113+
114+
for v in variants {
115+
for a in &v.attrs {
116+
match a.parse_meta() {
117+
Ok(syn::Meta::NameValue(syn::MetaNameValue {
118+
path: syn::Path { segments, .. },
119+
lit: syn::Lit::Str(signature_lit),
120+
..
121+
})) if segments.first().filter(|path| path.ident == "selector").is_some() => {
122+
let selector = Keccak256::digest(signature_lit.value().as_bytes()).to_vec();
123+
selector_to_event.insert(
124+
selector.clone(),
125+
Event {
126+
variant: v.clone(),
127+
topics: vec![TokenTree::Group(Group::new(
128+
proc_macro2::Delimiter::Bracket,
129+
quote!(#(#selector),*),
130+
))],
131+
data: None,
132+
},
133+
);
134+
if let syn::Fields::Named(syn::FieldsNamed { ref named, .. }) = v.fields {
135+
for n in named {
136+
let param = n
137+
.ident
138+
.clone()
139+
.ok_or(syn::Error::new(n.span(), "Unnamed fields not supported"))?;
140+
141+
match n.attrs.first().map(|attr| attr.parse_meta()) {
142+
Some(Ok(syn::Meta::Path(syn::Path { segments, .. })))
143+
if segments.first().filter(|path| path.ident == "indexed").is_some() =>
144+
{
145+
if let Some(event) = selector_to_event.get_mut(&selector) {
146+
event.topics.push(TokenTree::Ident(param))
147+
}
148+
}
149+
_ => {
150+
if let Some(event) = selector_to_event.get_mut(&selector) {
151+
if event.data.is_some() {
152+
return Err(syn::Error::new(n.span(), "Only one data field is allowed"));
153+
} else {
154+
event.data = Some(param)
155+
}
156+
}
157+
}
158+
}
159+
}
160+
}
161+
}
162+
_ => return Err(syn::Error::new(a.span(), "Attribute not supported")),
163+
}
164+
}
165+
}
166+
167+
let variants_ident: Vec<_> = selector_to_event
168+
.values()
169+
.map(|Event { variant, .. }| variant.ident.clone())
170+
.collect();
171+
let variants_args: Vec<Vec<_>> = selector_to_event
172+
.values()
173+
.map(|Event { variant, .. }| {
174+
variant
175+
.fields
176+
.iter()
177+
.map(|arg| arg.ident.as_ref().expect("Named field"))
178+
.collect()
179+
})
180+
.collect();
181+
let topics: Vec<Vec<_>> = selector_to_event
182+
.values()
183+
.map(|Event { topics, .. }| topics.clone())
184+
.collect();
185+
let data: Vec<_> = selector_to_event
186+
.values()
187+
.map(|Event { data, .. }| {
188+
data.clone()
189+
.ok_or(syn::Error::new(Span::call_site(), "Requires data field"))
190+
})
191+
.collect::<Result<_, _>>()?;
192+
193+
Ok(quote! {
194+
impl #enum_ident {
195+
pub fn log(self, handle: &mut impl ::fp_evm::PrecompileHandle) -> ::evm_utils::EvmResult {
196+
let (topics, data): (Vec<::sp_core::H256>, _) = match self {
197+
#(
198+
Self::#variants_ident { #(#variants_args),* } => {
199+
(vec![#(#topics.into()),*], #data)
200+
},
201+
)*
202+
};
203+
204+
let mut writer = ::evm_utils::Writer::new();
205+
let data = writer.write(data).build();
206+
207+
handle.log(
208+
handle.context().address,
209+
topics,
210+
data,
211+
)?;
212+
Ok(())
213+
}
214+
}
215+
}
216+
.into())
217+
}
218+
219+
#[proc_macro_derive(EvmEvent, attributes(selector, indexed, data))]
220+
pub fn precompile_events(input: TokenStream) -> TokenStream {
221+
let input = parse_macro_input!(input as DeriveInput);
222+
match parse_event_enum(input) {
223+
Ok(ts) => ts,
224+
Err(err) => err.to_compile_error().into(),
225+
}
226+
}

0 commit comments

Comments
 (0)