git.delta.rocks / unique-network / refs/commits / 7d5b680cd73d

difftreelog

refactor(evm-coder) toposort generated code

Yaroslav Bolyukin2022-07-12parent: #50d005e.patch.diff
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

modifiedcrates/evm-coder-macros/src/lib.rsdiffbeforeafterboth
before · crates/evm-coder-macros/src/lib.rs
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, 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}
after · crates/evm-coder-macros/src/lib.rs
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}
modifiedcrates/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 {
modifiedcrates/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()
 	}
 }