Skip to content

Commit

Permalink
feat: erc20 evm precompiles
Browse files Browse the repository at this point in the history
Signed-off-by: Gregory Hill <[email protected]>
  • Loading branch information
gregdhill committed Aug 24, 2023
1 parent f77d5b1 commit 4c8ba8c
Show file tree
Hide file tree
Showing 16 changed files with 1,316 additions and 23 deletions.
41 changes: 37 additions & 4 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"primitives",
"parachain",
"parachain/runtime/*",
"parachain/runtime/common/evm/*",
"rpc",
]

Expand Down
9 changes: 9 additions & 0 deletions parachain/runtime/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ version = "1.2.0"
targets = ['x86_64-unknown-linux-gnu']

[dependencies]
evm-macro = { path = "evm/macro", default-features = false }
evm-utils = { path = "evm/utils", default-features = false }

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

# Frontier dependencies
fp-evm = { git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.42", default-features = false }
pallet-base-fee = { git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.42", default-features = false }
pallet-ethereum = { git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.42", default-features = false }
pallet-evm = { git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.42", default-features = false }
Expand All @@ -73,6 +77,9 @@ pallet-evm-precompile-simple = { git = "https://github.com/paritytech/frontier",
[features]
default = ["std"]
std = [
"evm-macro/std",
"evm-utils/std",

"sp-std/std",
"sp-runtime/std",
"sp-core/std",
Expand All @@ -91,6 +98,7 @@ std = [
"currency/std",
"collator-selection/std",
"democracy/std",
"dex-stable/std",
"escrow/std",
"fee/std",
"issue/std",
Expand All @@ -117,6 +125,7 @@ std = [
"orml-xcm-support/std",
"orml-unknown-tokens/std",

"fp-evm/std",
"pallet-base-fee/std",
"pallet-ethereum/std",
"pallet-evm/std",
Expand Down
32 changes: 32 additions & 0 deletions parachain/runtime/common/evm/macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
authors = ["Interlay Ltd"]
edition = "2021"
name = 'evm-macro'
version = "1.2.0"

[lib]
proc-macro = true

[dependencies]
quote = "1.0.20"
syn = { version = "1.0.98", features = ["full", "fold", "extra-traits", "visit"] }
proc-macro2 = "1.0.40"
sha3 = { version = "0.10", default-features = false }

[dev-dependencies]
hex = "0.4.2"
hex-literal = "0.3.1"
evm-utils = { path = "../utils" }

# Substrate dependencies
frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31" }

# Frontier dependencies
fp-evm = { git = "https://github.com/paritytech/frontier", branch = "polkadot-v0.9.42" }

[features]
default = ["std"]
std = [
"sha3/std",
]
226 changes: 226 additions & 0 deletions parachain/runtime/common/evm/macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
use proc_macro::TokenStream;
use proc_macro2::{Group, Span, TokenTree};
use quote::quote;
use sha3::{Digest, Keccak256};
use std::collections::BTreeMap;
use syn::{parse_macro_input, spanned::Spanned, DeriveInput};

fn parse_selector(signature_lit: syn::LitStr) -> u32 {
let signature = signature_lit.value();
let digest = Keccak256::digest(signature.as_bytes());
let selector = u32::from_be_bytes([digest[0], digest[1], digest[2], digest[3]]);
selector
}

fn parse_call_enum(input: DeriveInput) -> syn::Result<TokenStream> {
let enum_ident = input.ident.clone();
let variants = if let syn::Data::Enum(syn::DataEnum { variants, .. }) = input.data {
variants
} else {
return Err(syn::Error::new(input.ident.span(), "Structure not supported"));
};

struct Call {
variant: syn::Variant,
}

let mut selector_to_call = BTreeMap::new();

for v in variants {
for a in &v.attrs {
match a.parse_meta() {
Ok(syn::Meta::NameValue(syn::MetaNameValue {
path: syn::Path { segments, .. },
lit: syn::Lit::Str(signature_lit),
..
})) if segments.first().filter(|path| path.ident == "selector").is_some() => {
selector_to_call.insert(parse_selector(signature_lit), Call { variant: v.clone() });
for f in &v.fields {
if f.ident.is_none() {
return Err(syn::Error::new(f.span(), "Unnamed fields not supported"));
}
}
}
_ => return Err(syn::Error::new(a.span(), "Attribute not supported")),
}
}
}

let selectors: Vec<_> = selector_to_call.keys().collect();
let variants_ident: Vec<_> = selector_to_call
.values()
.map(|Call { variant, .. }| variant.ident.clone())
.collect();
let variants_args: Vec<Vec<_>> = selector_to_call
.values()
.map(|Call { variant, .. }| {
variant
.fields
.iter()
.map(|field| field.ident.clone().expect("Only named fields supported"))
.collect()
})
.collect();

Ok(quote! {
impl #enum_ident {
pub fn new(input: &[u8]) -> ::evm_utils::EvmResult<Self> {
use ::evm_utils::RevertReason;

let mut reader = ::evm_utils::Reader::new(input);
let selector = reader.read_selector()?;
match selector {
#(
#selectors => Ok(Self::#variants_ident {
#(
#variants_args: reader.read()?
),*
}),
)*
_ => Err(RevertReason::UnknownSelector.into())
}
}
}
}
.into())
}

#[proc_macro_derive(EvmCall, attributes(selector))]
pub fn precompile_calls(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match parse_call_enum(input) {
Ok(ts) => ts,
Err(err) => err.to_compile_error().into(),
}
}

fn parse_event_enum(input: DeriveInput) -> syn::Result<TokenStream> {
let enum_ident = input.ident.clone();
let variants = if let syn::Data::Enum(syn::DataEnum { variants, .. }) = input.data {
variants
} else {
return Err(syn::Error::new(input.ident.span(), "Structure not supported"));
};

struct Event {
variant: syn::Variant,
topics: Vec<TokenTree>,
// NOTE: we do not yet support tuple encoding
data: Option<syn::Ident>,
}

let mut selector_to_event = BTreeMap::new();

for v in variants {
for a in &v.attrs {
match a.parse_meta() {
Ok(syn::Meta::NameValue(syn::MetaNameValue {
path: syn::Path { segments, .. },
lit: syn::Lit::Str(signature_lit),
..
})) if segments.first().filter(|path| path.ident == "selector").is_some() => {
let selector = Keccak256::digest(signature_lit.value().as_bytes()).to_vec();
selector_to_event.insert(
selector.clone(),
Event {
variant: v.clone(),
topics: vec![TokenTree::Group(Group::new(
proc_macro2::Delimiter::Bracket,
quote!(#(#selector),*),
))],
data: None,
},
);
if let syn::Fields::Named(syn::FieldsNamed { ref named, .. }) = v.fields {
for n in named {
let param = n
.ident
.clone()
.ok_or(syn::Error::new(n.span(), "Unnamed fields not supported"))?;

match n.attrs.first().map(|attr| attr.parse_meta()) {
Some(Ok(syn::Meta::Path(syn::Path { segments, .. })))
if segments.first().filter(|path| path.ident == "indexed").is_some() =>
{
if let Some(event) = selector_to_event.get_mut(&selector) {
event.topics.push(TokenTree::Ident(param))
}
}
_ => {
if let Some(event) = selector_to_event.get_mut(&selector) {
if event.data.is_some() {
return Err(syn::Error::new(n.span(), "Only one data field is allowed"));
} else {
event.data = Some(param)
}
}
}
}
}
}
}
_ => return Err(syn::Error::new(a.span(), "Attribute not supported")),
}
}
}

let variants_ident: Vec<_> = selector_to_event
.values()
.map(|Event { variant, .. }| variant.ident.clone())
.collect();
let variants_args: Vec<Vec<_>> = selector_to_event
.values()
.map(|Event { variant, .. }| {
variant
.fields
.iter()
.map(|arg| arg.ident.as_ref().expect("Named field"))
.collect()
})
.collect();
let topics: Vec<Vec<_>> = selector_to_event
.values()
.map(|Event { topics, .. }| topics.clone())
.collect();
let data: Vec<_> = selector_to_event
.values()
.map(|Event { data, .. }| {
data.clone()
.ok_or(syn::Error::new(Span::call_site(), "Requires data field"))
})
.collect::<Result<_, _>>()?;

Ok(quote! {
impl #enum_ident {
pub fn log(self, handle: &mut impl ::fp_evm::PrecompileHandle) -> ::evm_utils::EvmResult {
let (topics, data): (Vec<::sp_core::H256>, _) = match self {
#(
Self::#variants_ident { #(#variants_args),* } => {
(vec![#(#topics.into()),*], #data)
},
)*
};

let mut writer = ::evm_utils::Writer::new();
let data = writer.write(data).build();

handle.log(
handle.context().address,
topics,
data,
)?;
Ok(())
}
}
}
.into())
}

#[proc_macro_derive(EvmEvent, attributes(selector, indexed, data))]
pub fn precompile_events(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match parse_event_enum(input) {
Ok(ts) => ts,
Err(err) => err.to_compile_error().into(),
}
}
Loading

0 comments on commit 4c8ba8c

Please sign in to comment.