difftreelog
refactor(evm-coder) toposort generated code
in: master
Previously, all items from evm-coder codegen was sorted lexically, which caused some problems, where lexically sorted names of contract were not in the same order as contract inherit chain Now they are sorted by insertion order, and macro-generated code now inserts them in order of definition, thus providing topological sort of output contract items
3 files changed
crates/evm-coder-macros/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![allow(dead_code)]1819use inflector::cases;20use proc_macro::TokenStream;21use quote::quote;22use sha3::{Digest, Keccak256};23use syn::{24 DeriveInput, GenericArgument, Ident, ItemImpl, Pat, Path, PathArguments,25 PathSegment, Type, parse_macro_input, spanned::Spanned, Attribute, parse::Parse,26};2728mod solidity_interface;29mod to_log;3031fn fn_selector_str(input: &str) -> u32 {32 let mut hasher = Keccak256::new();33 hasher.update(input.as_bytes());34 let result = hasher.finalize();3536 let mut selector_bytes = [0; 4];37 selector_bytes.copy_from_slice(&result[0..4]);3839 u32::from_be_bytes(selector_bytes)40}4142/// Returns solidity function selector (first 4 bytes of hash) by its43/// textual representation44///45/// ```ignore46/// use evm_coder_macros::fn_selector;47///48/// assert_eq!(fn_selector!(transfer(address, uint256)), 0xa9059cbb);49/// ```50#[proc_macro]51pub fn fn_selector(input: TokenStream) -> TokenStream {52 let input = input.to_string().replace(' ', "");53 let selector = fn_selector_str(&input);5455 (quote! {56 #selector57 })58 .into()59}6061fn event_selector_str(input: &str) -> [u8; 32] {62 let mut hasher = Keccak256::new();63 hasher.update(input.as_bytes());64 let result = hasher.finalize();6566 let mut selector_bytes = [0; 32];67 selector_bytes.copy_from_slice(&result[0..32]);68 selector_bytes69}7071/// Returns solidity topic (hash) by its textual representation72///73/// ```ignore74/// use evm_coder_macros::event_topic;75///76/// assert_eq!(77/// format!("{:x}", event_topic!(Transfer(address, address, uint256))),78/// "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",79/// );80/// ```81#[proc_macro]82pub fn event_topic(stream: TokenStream) -> TokenStream {83 let input = stream.to_string().replace(' ', "");84 let selector_bytes = event_selector_str(&input);8586 (quote! {87 ::primitive_types::H256([#(88 #selector_bytes,89 )*])90 })91 .into()92}9394fn parse_path(ty: &Type) -> syn::Result<&Path> {95 match &ty {96 syn::Type::Path(pat) => {97 if let Some(qself) = &pat.qself {98 return Err(syn::Error::new(qself.ty.span(), "no receiver expected"));99 }100 Ok(&pat.path)101 }102 _ => Err(syn::Error::new(ty.span(), "expected ty to be path")),103 }104}105106fn parse_path_segment(path: &Path) -> syn::Result<&PathSegment> {107 if path.segments.len() != 1 {108 return Err(syn::Error::new(109 path.span(),110 "expected path to have only segment",111 ));112 }113 let last_segment = &path.segments.last().unwrap();114 Ok(last_segment)115}116117fn parse_ident_from_pat(pat: &Pat) -> syn::Result<&Ident> {118 match pat {119 Pat::Ident(i) => Ok(&i.ident),120 _ => Err(syn::Error::new(pat.span(), "expected pat ident")),121 }122}123124fn parse_ident_from_segment(segment: &PathSegment, allow_generics: bool) -> syn::Result<&Ident> {125 if segment.arguments != PathArguments::None && !allow_generics {126 return Err(syn::Error::new(127 segment.arguments.span(),128 "unexpected generic type",129 ));130 }131 Ok(&segment.ident)132}133134fn parse_ident_from_path(path: &Path, allow_generics: bool) -> syn::Result<&Ident> {135 let segment = parse_path_segment(path)?;136 parse_ident_from_segment(segment, allow_generics)137}138139fn parse_ident_from_type(ty: &Type, allow_generics: bool) -> syn::Result<&Ident> {140 let path = parse_path(ty)?;141 parse_ident_from_path(path, allow_generics)142}143144// Gets T out of Result<T>145fn parse_result_ok(ty: &Type) -> syn::Result<&Type> {146 let path = parse_path(ty)?;147 let segment = parse_path_segment(path)?;148149 if segment.ident != "Result" {150 return Err(syn::Error::new(151 ty.span(),152 "expected Result as return type (no renamed aliases allowed)",153 ));154 }155 let args = match &segment.arguments {156 PathArguments::AngleBracketed(e) => e,157 _ => {158 return Err(syn::Error::new(159 segment.arguments.span(),160 "missing Result generics",161 ))162 }163 };164165 let args = &args.args;166 let arg = args.first().unwrap();167168 let ty = match arg {169 GenericArgument::Type(ty) => ty,170 _ => {171 return Err(syn::Error::new(172 arg.span(),173 "expected first generic to be type",174 ))175 }176 };177178 Ok(ty)179}180181fn pascal_ident_to_call(ident: &Ident) -> Ident {182 let name = format!("{}Call", ident);183 Ident::new(&name, ident.span())184}185fn snake_ident_to_pascal(ident: &Ident) -> Ident {186 let name = ident.to_string();187 let name = cases::pascalcase::to_pascal_case(&name);188 Ident::new(&name, ident.span())189}190fn snake_ident_to_screaming(ident: &Ident) -> Ident {191 let name = ident.to_string();192 let name = cases::screamingsnakecase::to_screaming_snake_case(&name);193 Ident::new(&name, ident.span())194}195fn pascal_ident_to_snake_call(ident: &Ident) -> Ident {196 let name = ident.to_string();197 let name = cases::snakecase::to_snake_case(&name);198 let name = format!("call_{}", name);199 Ident::new(&name, ident.span())200}201202/// Derives call enum implementing [`evm_coder::Callable`], [`evm_coder::Weighted`]203/// and [`evm_coder::Call`] from impl block204///205/// ## Macro syntax206///207/// `#[solidity_interface(name, is, inline_is, events)]`208/// - *name*: used in generated code, and for Call enum name209/// - *is*: used to provide call inheritance, not found methods will be delegated to all contracts210/// specified in is/inline_is211/// - *inline_is*: same as is, but selectors for passed contracts will be used by derived ERC165212/// implementation213///214/// `#[weight(value)]`215/// Can be added to every method of impl block, used for deriving [`evm_coder::Weighted`], which216/// is used by substrate bridge217/// - *value*: expression, which evaluates to weight required to call this method.218/// This expression can use call arguments to calculate non-constant execution time.219/// This expression should evaluate faster than actual execution does, and may provide worser case220/// than one is called221///222/// `#[solidity_interface(rename_selector)]`223/// - *rename_selector*: by default, selector name will be generated by transforming method name224/// from snake_case to camelCase. Use this option, if other naming convention is required.225/// I.e: method `token_uri` will be automatically renamed to `tokenUri` in selector, but name226/// required by ERC721 standard is `tokenURI`, thus we need to specify `rename_selector = "tokenURI"`227/// explicitly228///229/// Also, any contract method may have doc comments, which will be automatically added to generated230/// solidity interface definitions231///232/// ## Example233///234/// ```ignore235/// struct SuperContract;236/// struct InlineContract;237/// struct Contract;238///239/// #[derive(ToLog)]240/// enum ContractEvents {241/// Event(#[indexed] uint32),242/// }243///244/// #[solidity_interface(name = "MyContract", is(SuperContract), inline_is(InlineContract))]245/// impl Contract {246/// /// Multiply two numbers247/// #[weight(200 + a + b)]248/// #[solidity_interface(rename_selector = "mul")]249/// fn mul(&mut self, a: uint32, b: uint32) -> Result<uint32> {250/// Ok(a.checked_mul(b).ok_or("overflow")?)251/// }252/// }253/// ```254#[proc_macro_attribute]255pub fn solidity_interface(args: TokenStream, stream: TokenStream) -> TokenStream {256 let args = parse_macro_input!(args as solidity_interface::InterfaceInfo);257258 let input: ItemImpl = match syn::parse(stream) {259 Ok(t) => t,260 Err(e) => return e.to_compile_error().into(),261 };262263 let expanded = match solidity_interface::SolidityInterface::try_from(args, &input) {264 Ok(v) => v.expand(),265 Err(e) => e.to_compile_error(),266 };267268 (quote! {269 #input270271 #expanded272 })273 .into()274}275276#[proc_macro_attribute]277pub fn solidity(_args: TokenStream, stream: TokenStream) -> TokenStream {278 stream279}280#[proc_macro_attribute]281pub fn weight(_args: TokenStream, stream: TokenStream) -> TokenStream {282 stream283}284285/// ## Syntax286///287/// `#[indexed]`288/// Marks this field as indexed, so it will appear in [`ethereum::Log`] topics instead of data289#[proc_macro_derive(ToLog, attributes(indexed))]290pub fn to_log(value: TokenStream) -> TokenStream {291 let input = parse_macro_input!(value as DeriveInput);292293 match to_log::Events::try_from(&input) {294 Ok(e) => e.expand(),295 Err(e) => e.to_compile_error(),296 }297 .into()298}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![allow(dead_code)]1819use inflector::cases;20use proc_macro::TokenStream;21use quote::quote;22use sha3::{Digest, Keccak256};23use syn::{24 DeriveInput, GenericArgument, Ident, ItemImpl, Pat, Path, PathArguments,25 PathSegment, Type, parse_macro_input, spanned::Spanned,26};2728mod solidity_interface;29mod to_log;3031fn fn_selector_str(input: &str) -> u32 {32 let mut hasher = Keccak256::new();33 hasher.update(input.as_bytes());34 let result = hasher.finalize();3536 let mut selector_bytes = [0; 4];37 selector_bytes.copy_from_slice(&result[0..4]);3839 u32::from_be_bytes(selector_bytes)40}4142/// Returns solidity function selector (first 4 bytes of hash) by its43/// textual representation44///45/// ```ignore46/// use evm_coder_macros::fn_selector;47///48/// assert_eq!(fn_selector!(transfer(address, uint256)), 0xa9059cbb);49/// ```50#[proc_macro]51pub fn fn_selector(input: TokenStream) -> TokenStream {52 let input = input.to_string().replace(' ', "");53 let selector = fn_selector_str(&input);5455 (quote! {56 #selector57 })58 .into()59}6061fn event_selector_str(input: &str) -> [u8; 32] {62 let mut hasher = Keccak256::new();63 hasher.update(input.as_bytes());64 let result = hasher.finalize();6566 let mut selector_bytes = [0; 32];67 selector_bytes.copy_from_slice(&result[0..32]);68 selector_bytes69}7071/// Returns solidity topic (hash) by its textual representation72///73/// ```ignore74/// use evm_coder_macros::event_topic;75///76/// assert_eq!(77/// format!("{:x}", event_topic!(Transfer(address, address, uint256))),78/// "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",79/// );80/// ```81#[proc_macro]82pub fn event_topic(stream: TokenStream) -> TokenStream {83 let input = stream.to_string().replace(' ', "");84 let selector_bytes = event_selector_str(&input);8586 (quote! {87 ::primitive_types::H256([#(88 #selector_bytes,89 )*])90 })91 .into()92}9394fn parse_path(ty: &Type) -> syn::Result<&Path> {95 match &ty {96 syn::Type::Path(pat) => {97 if let Some(qself) = &pat.qself {98 return Err(syn::Error::new(qself.ty.span(), "no receiver expected"));99 }100 Ok(&pat.path)101 }102 _ => Err(syn::Error::new(ty.span(), "expected ty to be path")),103 }104}105106fn parse_path_segment(path: &Path) -> syn::Result<&PathSegment> {107 if path.segments.len() != 1 {108 return Err(syn::Error::new(109 path.span(),110 "expected path to have only segment",111 ));112 }113 let last_segment = &path.segments.last().unwrap();114 Ok(last_segment)115}116117fn parse_ident_from_pat(pat: &Pat) -> syn::Result<&Ident> {118 match pat {119 Pat::Ident(i) => Ok(&i.ident),120 _ => Err(syn::Error::new(pat.span(), "expected pat ident")),121 }122}123124fn parse_ident_from_segment(segment: &PathSegment, allow_generics: bool) -> syn::Result<&Ident> {125 if segment.arguments != PathArguments::None && !allow_generics {126 return Err(syn::Error::new(127 segment.arguments.span(),128 "unexpected generic type",129 ));130 }131 Ok(&segment.ident)132}133134fn parse_ident_from_path(path: &Path, allow_generics: bool) -> syn::Result<&Ident> {135 let segment = parse_path_segment(path)?;136 parse_ident_from_segment(segment, allow_generics)137}138139fn parse_ident_from_type(ty: &Type, allow_generics: bool) -> syn::Result<&Ident> {140 let path = parse_path(ty)?;141 parse_ident_from_path(path, allow_generics)142}143144// Gets T out of Result<T>145fn parse_result_ok(ty: &Type) -> syn::Result<&Type> {146 let path = parse_path(ty)?;147 let segment = parse_path_segment(path)?;148149 if segment.ident != "Result" {150 return Err(syn::Error::new(151 ty.span(),152 "expected Result as return type (no renamed aliases allowed)",153 ));154 }155 let args = match &segment.arguments {156 PathArguments::AngleBracketed(e) => e,157 _ => {158 return Err(syn::Error::new(159 segment.arguments.span(),160 "missing Result generics",161 ))162 }163 };164165 let args = &args.args;166 let arg = args.first().unwrap();167168 let ty = match arg {169 GenericArgument::Type(ty) => ty,170 _ => {171 return Err(syn::Error::new(172 arg.span(),173 "expected first generic to be type",174 ))175 }176 };177178 Ok(ty)179}180181fn pascal_ident_to_call(ident: &Ident) -> Ident {182 let name = format!("{}Call", ident);183 Ident::new(&name, ident.span())184}185fn snake_ident_to_pascal(ident: &Ident) -> Ident {186 let name = ident.to_string();187 let name = cases::pascalcase::to_pascal_case(&name);188 Ident::new(&name, ident.span())189}190fn snake_ident_to_screaming(ident: &Ident) -> Ident {191 let name = ident.to_string();192 let name = cases::screamingsnakecase::to_screaming_snake_case(&name);193 Ident::new(&name, ident.span())194}195fn pascal_ident_to_snake_call(ident: &Ident) -> Ident {196 let name = ident.to_string();197 let name = cases::snakecase::to_snake_case(&name);198 let name = format!("call_{}", name);199 Ident::new(&name, ident.span())200}201202/// Derives call enum implementing [`evm_coder::Callable`], [`evm_coder::Weighted`]203/// and [`evm_coder::Call`] from impl block204///205/// ## Macro syntax206///207/// `#[solidity_interface(name, is, inline_is, events)]`208/// - *name*: used in generated code, and for Call enum name209/// - *is*: used to provide call inheritance, not found methods will be delegated to all contracts210/// specified in is/inline_is211/// - *inline_is*: same as is, but selectors for passed contracts will be used by derived ERC165212/// implementation213///214/// `#[weight(value)]`215/// Can be added to every method of impl block, used for deriving [`evm_coder::Weighted`], which216/// is used by substrate bridge217/// - *value*: expression, which evaluates to weight required to call this method.218/// This expression can use call arguments to calculate non-constant execution time.219/// This expression should evaluate faster than actual execution does, and may provide worser case220/// than one is called221///222/// `#[solidity_interface(rename_selector)]`223/// - *rename_selector*: by default, selector name will be generated by transforming method name224/// from snake_case to camelCase. Use this option, if other naming convention is required.225/// I.e: method `token_uri` will be automatically renamed to `tokenUri` in selector, but name226/// required by ERC721 standard is `tokenURI`, thus we need to specify `rename_selector = "tokenURI"`227/// explicitly228///229/// Also, any contract method may have doc comments, which will be automatically added to generated230/// solidity interface definitions231///232/// ## Example233///234/// ```ignore235/// struct SuperContract;236/// struct InlineContract;237/// struct Contract;238///239/// #[derive(ToLog)]240/// enum ContractEvents {241/// Event(#[indexed] uint32),242/// }243///244/// #[solidity_interface(name = MyContract, is(SuperContract), inline_is(InlineContract))]245/// impl Contract {246/// /// Multiply two numbers247/// #[weight(200 + a + b)]248/// #[solidity_interface(rename_selector = "mul")]249/// fn mul(&mut self, a: uint32, b: uint32) -> Result<uint32> {250/// Ok(a.checked_mul(b).ok_or("overflow")?)251/// }252/// }253/// ```254#[proc_macro_attribute]255pub fn solidity_interface(args: TokenStream, stream: TokenStream) -> TokenStream {256 let args = parse_macro_input!(args as solidity_interface::InterfaceInfo);257258 let input: ItemImpl = match syn::parse(stream) {259 Ok(t) => t,260 Err(e) => return e.to_compile_error().into(),261 };262263 let expanded = match solidity_interface::SolidityInterface::try_from(args, &input) {264 Ok(v) => v.expand(),265 Err(e) => e.to_compile_error(),266 };267268 (quote! {269 #input270271 #expanded272 })273 .into()274}275276#[proc_macro_attribute]277pub fn solidity(_args: TokenStream, stream: TokenStream) -> TokenStream {278 stream279}280#[proc_macro_attribute]281pub fn weight(_args: TokenStream, stream: TokenStream) -> TokenStream {282 stream283}284285/// ## Syntax286///287/// `#[indexed]`288/// Marks this field as indexed, so it will appear in [`ethereum::Log`] topics instead of data289#[proc_macro_derive(ToLog, attributes(indexed))]290pub fn to_log(value: TokenStream) -> TokenStream {291 let input = parse_macro_input!(value as DeriveInput);292293 match to_log::Events::try_from(&input) {294 Ok(e) => e.expand(),295 Err(e) => e.to_compile_error(),296 }297 .into()298}crates/evm-coder-macros/src/solidity_interface.rsdiffbeforeafterboth--- a/crates/evm-coder-macros/src/solidity_interface.rs
+++ b/crates/evm-coder-macros/src/solidity_interface.rs
@@ -989,26 +989,24 @@
#solidity_functions,
)*),
};
- if is_impl {
- tc.collect("/// @dev common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\ncontract ERC165 is Dummy {\n\tfunction supportsInterface(bytes4 interfaceID) external view returns (bool) {\n\t\trequire(false, stub_error);\n\t\tinterfaceID;\n\t\treturn true;\n\t}\n}\n".into());
- } else {
- tc.collect("/// @dev common stubs holder\ninterface Dummy {\n}\ninterface ERC165 is Dummy {\n\tfunction supportsInterface(bytes4 interfaceID) external view returns (bool);\n}\n".into());
- }
- #(
- #solidity_generators
- )*
- #(
- #solidity_event_generators
- )*
let mut out = string::new();
- // In solidity interface usage (is) should be preceeded by interface definition
- // HACK: this comment helps to sort it in a set
if #solidity_name.starts_with("Inline") {
out.push_str("/// @dev inlined interface\n");
}
let _ = interface.format(is_impl, &mut out, tc);
tc.collect(out);
+ #(
+ #solidity_event_generators
+ )*
+ #(
+ #solidity_generators
+ )*
+ if is_impl {
+ tc.collect("/// @dev common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\ncontract ERC165 is Dummy {\n\tfunction supportsInterface(bytes4 interfaceID) external view returns (bool) {\n\t\trequire(false, stub_error);\n\t\tinterfaceID;\n\t\treturn true;\n\t}\n}\n".into());
+ } else {
+ tc.collect("/// @dev common stubs holder\ninterface Dummy {\n}\ninterface ERC165 is Dummy {\n\tfunction supportsInterface(bytes4 interfaceID) external view returns (bool);\n}\n".into());
+ }
}
}
impl #gen_ref ::evm_coder::Call for #call_name #gen_ref {
crates/evm-coder/src/solidity.rsdiffbeforeafterboth--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -14,26 +14,31 @@
// You should have received a copy of the GNU General Public License
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+//! Implementation detail of [`evm_coder::solidity_interface`] macro code-generation
+
#[cfg(not(feature = "std"))]
use alloc::{
string::String,
vec::Vec,
- collections::{BTreeSet, BTreeMap},
+ collections::BTreeMap,
format,
};
#[cfg(feature = "std")]
-use std::collections::{BTreeSet, BTreeMap};
+use std::collections::BTreeMap;
use core::{
fmt::{self, Write},
marker::PhantomData,
cell::{Cell, RefCell},
+ cmp::Reverse,
};
use impl_trait_for_tuples::impl_for_tuples;
use crate::types::*;
#[derive(Default)]
pub struct TypeCollector {
- structs: RefCell<BTreeSet<string>>,
+ /// Code => id
+ /// id ordering is required to perform topo-sort on the resulting data
+ structs: RefCell<BTreeMap<string, usize>>,
anonymous: RefCell<BTreeMap<Vec<string>, usize>>,
id: Cell<usize>,
}
@@ -42,7 +47,8 @@
Self::default()
}
pub fn collect(&self, item: string) {
- self.structs.borrow_mut().insert(item);
+ let id = self.next_id();
+ self.structs.borrow_mut().insert(item, id);
}
pub fn next_id(&self) -> usize {
let v = self.id.get();
@@ -66,8 +72,10 @@
self.anonymous.borrow_mut().insert(names, id);
format!("Tuple{}", id)
}
- pub fn finish(self) -> BTreeSet<string> {
- self.structs.into_inner()
+ pub fn finish(self) -> Vec<string> {
+ let mut data = self.structs.into_inner().into_iter().collect::<Vec<_>>();
+ data.sort_by_key(|(_, id)| Reverse(*id));
+ data.into_iter().map(|(code, _)| code).collect()
}
}