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
--- a/crates/evm-coder-macros/src/lib.rs
+++ b/crates/evm-coder-macros/src/lib.rs
@@ -22,7 +22,7 @@
 use sha3::{Digest, Keccak256};
 use syn::{
 	DeriveInput, GenericArgument, Ident, ItemImpl, Pat, Path, PathArguments,
-	PathSegment, Type, parse_macro_input, spanned::Spanned, Attribute, parse::Parse,
+	PathSegment, Type, parse_macro_input, spanned::Spanned,
 };
 
 mod solidity_interface;
@@ -241,7 +241,7 @@
 ///     Event(#[indexed] uint32),
 /// }
 ///
-/// #[solidity_interface(name = "MyContract", is(SuperContract), inline_is(InlineContract))]
+/// #[solidity_interface(name = MyContract, is(SuperContract), inline_is(InlineContract))]
 /// impl Contract {
 ///     /// Multiply two numbers
 ///     #[weight(200 + a + b)]
modifiedcrates/evm-coder-macros/src/solidity_interface.rsdiffbeforeafterboth
before · crates/evm-coder-macros/src/solidity_interface.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 quote::{quote, ToTokens};20use inflector::cases;21use std::fmt::Write;22use syn::{23	Expr, FnArg, GenericArgument, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta,24	MetaNameValue, PatType, PathArguments, ReturnType, Type,25	spanned::Spanned,26	parse::{Parse, ParseStream},27	parenthesized, Token, LitInt, LitStr,28};2930use crate::{31	fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_path, parse_path_segment,32	parse_result_ok, pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal,33	snake_ident_to_screaming,34};3536struct Is {37	name: Ident,38	pascal_call_name: Ident,39	snake_call_name: Ident,40	via: Option<(Type, Ident)>,41}42impl Is {43	fn expand_call_def(&self, gen_ref: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {44		let name = &self.name;45		let pascal_call_name = &self.pascal_call_name;46		quote! {47			#name(#pascal_call_name #gen_ref)48		}49	}5051	fn expand_interface_id(&self) -> proc_macro2::TokenStream {52		let pascal_call_name = &self.pascal_call_name;53		quote! {54			interface_id ^= u32::from_be_bytes(#pascal_call_name::interface_id());55		}56	}5758	fn expand_supports_interface(59		&self,60		generics: &proc_macro2::TokenStream,61	) -> proc_macro2::TokenStream {62		let pascal_call_name = &self.pascal_call_name;63		quote! {64			<#pascal_call_name #generics>::supports_interface(interface_id)65		}66	}6768	fn expand_variant_weight(&self) -> proc_macro2::TokenStream {69		let name = &self.name;70		quote! {71			Self::#name(call) => call.weight()72		}73	}7475	fn expand_variant_call(76		&self,77		call_name: &proc_macro2::Ident,78		generics: &proc_macro2::TokenStream,79	) -> proc_macro2::TokenStream {80		let name = &self.name;81		let pascal_call_name = &self.pascal_call_name;82		let via_typ = self83			.via84			.as_ref()85			.map(|(t, _)| quote! {#t})86			.unwrap_or_else(|| quote! {Self});87		let via_map = self88			.via89			.as_ref()90			.map(|(_, i)| quote! {.#i()})91			.unwrap_or_default();92		quote! {93			#call_name::#name(call) => return <#via_typ as ::evm_coder::Callable<#pascal_call_name #generics>>::call(self #via_map, Msg {94				call,95				caller: c.caller,96				value: c.value,97			})98		}99	}100101	fn expand_parse(&self, generics: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {102		let name = &self.name;103		let pascal_call_name = &self.pascal_call_name;104		quote! {105			if let Some(parsed_call) = <#pascal_call_name #generics>::parse(method_id, reader)? {106				return Ok(Some(Self::#name(parsed_call)))107			}108		}109	}110111	fn expand_generator(&self, generics: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {112		let pascal_call_name = &self.pascal_call_name;113		quote! {114			<#pascal_call_name #generics>::generate_solidity_interface(tc, is_impl);115		}116	}117118	fn expand_event_generator(&self) -> proc_macro2::TokenStream {119		let name = &self.name;120		quote! {121			#name::generate_solidity_interface(tc, is_impl);122		}123	}124}125126#[derive(Default)]127struct IsList(Vec<Is>);128impl Parse for IsList {129	fn parse(input: ParseStream) -> syn::Result<Self> {130		let mut out = vec![];131		loop {132			if input.is_empty() {133				break;134			}135			let name = input.parse::<Ident>()?;136			let lookahead = input.lookahead1();137			let via = if lookahead.peek(syn::token::Paren) {138				let contents;139				parenthesized!(contents in input);140				let method = contents.parse::<Ident>()?;141				contents.parse::<Token![,]>()?;142				let ty = contents.parse::<Type>()?;143				Some((ty, method))144			} else if lookahead.peek(Token![,]) {145				None146			} else if input.is_empty() {147				None148			} else {149				return Err(lookahead.error());150			};151			out.push(Is {152				pascal_call_name: pascal_ident_to_call(&name),153				snake_call_name: pascal_ident_to_snake_call(&name),154				name,155				via,156			});157			if input.peek(Token![,]) {158				input.parse::<Token![,]>()?;159				continue;160			} else {161				break;162			}163		}164		Ok(Self(out))165	}166}167168pub struct InterfaceInfo {169	name: Ident,170	is: IsList,171	inline_is: IsList,172	events: IsList,173	expect_selector: Option<u32>,174}175impl Parse for InterfaceInfo {176	fn parse(input: ParseStream) -> syn::Result<Self> {177		let mut name = None;178		let mut is = None;179		let mut inline_is = None;180		let mut events = None;181		let mut expect_selector = None;182		// TODO: create proc-macro to optimize proc-macro boilerplate? :D183		loop {184			let lookahead = input.lookahead1();185			if lookahead.peek(kw::name) {186				let k = input.parse::<kw::name>()?;187				input.parse::<Token![=]>()?;188				if name.replace(input.parse::<Ident>()?).is_some() {189					return Err(syn::Error::new(k.span(), "name is already set"));190				}191			} else if lookahead.peek(kw::is) {192				let k = input.parse::<kw::is>()?;193				let contents;194				parenthesized!(contents in input);195				if is.replace(contents.parse::<IsList>()?).is_some() {196					return Err(syn::Error::new(k.span(), "is is already set"));197				}198			} else if lookahead.peek(kw::inline_is) {199				let k = input.parse::<kw::inline_is>()?;200				let contents;201				parenthesized!(contents in input);202				if inline_is.replace(contents.parse::<IsList>()?).is_some() {203					return Err(syn::Error::new(k.span(), "inline_is is already set"));204				}205			} else if lookahead.peek(kw::events) {206				let k = input.parse::<kw::events>()?;207				let contents;208				parenthesized!(contents in input);209				if events.replace(contents.parse::<IsList>()?).is_some() {210					return Err(syn::Error::new(k.span(), "events is already set"));211				}212			} else if lookahead.peek(kw::expect_selector) {213				let k = input.parse::<kw::expect_selector>()?;214				input.parse::<Token![=]>()?;215				let value = input.parse::<LitInt>()?;216				if expect_selector217					.replace(value.base10_parse::<u32>()?)218					.is_some()219				{220					return Err(syn::Error::new(k.span(), "expect_selector is already set"));221				}222			} else if input.is_empty() {223				break;224			} else {225				return Err(lookahead.error());226			}227			if input.peek(Token![,]) {228				input.parse::<Token![,]>()?;229			} else {230				break;231			}232		}233		Ok(Self {234			name: name.ok_or_else(|| syn::Error::new(input.span(), "missing name"))?,235			is: is.unwrap_or_default(),236			inline_is: inline_is.unwrap_or_default(),237			events: events.unwrap_or_default(),238			expect_selector,239		})240	}241}242243struct MethodInfo {244	rename_selector: Option<String>,245}246impl Parse for MethodInfo {247	fn parse(input: ParseStream) -> syn::Result<Self> {248		let mut rename_selector = None;249		let lookahead = input.lookahead1();250		if lookahead.peek(kw::rename_selector) {251			let k = input.parse::<kw::rename_selector>()?;252			input.parse::<Token![=]>()?;253			if rename_selector254				.replace(input.parse::<LitStr>()?.value())255				.is_some()256			{257				return Err(syn::Error::new(k.span(), "rename_selector is already set"));258			}259		}260		Ok(Self { rename_selector })261	}262}263264enum AbiType {265	// type266	Plain(Ident),267	// (type1,type2)268	Tuple(Vec<AbiType>),269	// type[]270	Vec(Box<AbiType>),271	// type[20]272	Array(Box<AbiType>, usize),273}274impl AbiType {275	fn try_from(value: &Type) -> syn::Result<Self> {276		let value = Self::try_maybe_special_from(value)?;277		if value.is_special() {278			return Err(syn::Error::new(value.span(), "unexpected special type"));279		}280		Ok(value)281	}282	fn try_maybe_special_from(value: &Type) -> syn::Result<Self> {283		match value {284			Type::Array(arr) => {285				let wrapped = AbiType::try_from(&arr.elem)?;286				match &arr.len {287					Expr::Lit(l) => match &l.lit {288						Lit::Int(i) => {289							let num = i.base10_parse::<usize>()?;290							Ok(AbiType::Array(Box::new(wrapped), num as usize))291						}292						_ => Err(syn::Error::new(arr.len.span(), "should be int literal")),293					},294					_ => Err(syn::Error::new(arr.len.span(), "should be literal")),295				}296			}297			Type::Path(_) => {298				let path = parse_path(value)?;299				let segment = parse_path_segment(path)?;300				if segment.ident == "Vec" {301					let args = match &segment.arguments {302						PathArguments::AngleBracketed(e) => e,303						_ => {304							return Err(syn::Error::new(305								segment.arguments.span(),306								"missing Vec generic",307							))308						}309					};310					let args = &args.args;311					if args.len() != 1 {312						return Err(syn::Error::new(313							args.span(),314							"expected only one generic for vec",315						));316					}317					let arg = args.first().expect("first arg");318319					let ty = match arg {320						GenericArgument::Type(ty) => ty,321						_ => {322							return Err(syn::Error::new(323								arg.span(),324								"expected first generic to be type",325							))326						}327					};328329					let wrapped = AbiType::try_from(ty)?;330					Ok(Self::Vec(Box::new(wrapped)))331				} else {332					if !segment.arguments.is_empty() {333						return Err(syn::Error::new(334							segment.arguments.span(),335							"unexpected generic arguments for non-vec type",336						));337					}338					Ok(Self::Plain(segment.ident.clone()))339				}340			}341			Type::Tuple(t) => {342				let mut out = Vec::with_capacity(t.elems.len());343				for el in t.elems.iter() {344					out.push(AbiType::try_from(el)?)345				}346				Ok(Self::Tuple(out))347			}348			_ => Err(syn::Error::new(349				value.span(),350				"unexpected type, only arrays, plain types and tuples are supported",351			)),352		}353	}354	fn is_value(&self) -> bool {355		matches!(self, Self::Plain(v) if v == "value")356	}357	fn is_caller(&self) -> bool {358		matches!(self, Self::Plain(v) if v == "caller")359	}360	fn is_special(&self) -> bool {361		self.is_caller() || self.is_value()362	}363	fn selector_ty_buf(&self, buf: &mut String) -> std::fmt::Result {364		match self {365			AbiType::Plain(t) => {366				write!(buf, "{}", t)367			}368			AbiType::Tuple(t) => {369				write!(buf, "(")?;370				for (i, t) in t.iter().enumerate() {371					if i != 0 {372						write!(buf, ",")?;373					}374					t.selector_ty_buf(buf)?;375				}376				write!(buf, ")")377			}378			AbiType::Vec(v) => {379				v.selector_ty_buf(buf)?;380				write!(buf, "[]")381			}382			AbiType::Array(v, len) => {383				v.selector_ty_buf(buf)?;384				write!(buf, "[{}]", len)385			}386		}387	}388	fn selector_ty(&self) -> String {389		let mut out = String::new();390		self.selector_ty_buf(&mut out).expect("no fmt error");391		out392	}393}394impl ToTokens for AbiType {395	fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {396		match self {397			AbiType::Plain(t) => tokens.extend(quote! {#t}),398			AbiType::Tuple(t) => {399				tokens.extend(quote! {(400					#(#t),*401				)});402			}403			AbiType::Vec(v) => tokens.extend(quote! {Vec<#v>}),404			AbiType::Array(v, l) => tokens.extend(quote! {[#v; #l]}),405		}406	}407}408409struct MethodArg {410	name: Ident,411	camel_name: String,412	ty: AbiType,413}414impl MethodArg {415	fn try_from(value: &PatType) -> syn::Result<Self> {416		let name = parse_ident_from_pat(&value.pat)?.clone();417		Ok(Self {418			camel_name: cases::camelcase::to_camel_case(&name.to_string()),419			name,420			ty: AbiType::try_maybe_special_from(&value.ty)?,421		})422	}423	fn is_value(&self) -> bool {424		self.ty.is_value()425	}426	fn is_caller(&self) -> bool {427		self.ty.is_caller()428	}429	fn is_special(&self) -> bool {430		self.ty.is_special()431	}432	fn selector_ty(&self) -> String {433		assert!(!self.is_special());434		self.ty.selector_ty()435	}436437	fn expand_call_def(&self) -> proc_macro2::TokenStream {438		assert!(!self.is_special());439		let name = &self.name;440		let ty = &self.ty;441442		quote! {443			#name: #ty444		}445	}446447	fn expand_parse(&self) -> proc_macro2::TokenStream {448		assert!(!self.is_special());449		let name = &self.name;450		quote! {451			#name: reader.abi_read()?452		}453	}454455	fn expand_call_arg(&self) -> proc_macro2::TokenStream {456		if self.is_value() {457			quote! {458				c.value.clone()459			}460		} else if self.is_caller() {461			quote! {462				c.caller.clone()463			}464		} else {465			let name = &self.name;466			quote! {467				#name468			}469		}470	}471472	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {473		let camel_name = &self.camel_name.to_string();474		let ty = &self.ty;475		quote! {476			<NamedArgument<#ty>>::new(#camel_name)477		}478	}479}480481#[derive(PartialEq)]482enum Mutability {483	Mutable,484	View,485	Pure,486}487488mod kw {489	syn::custom_keyword!(weight);490491	syn::custom_keyword!(via);492	syn::custom_keyword!(name);493	syn::custom_keyword!(is);494	syn::custom_keyword!(inline_is);495	syn::custom_keyword!(events);496	syn::custom_keyword!(expect_selector);497498	syn::custom_keyword!(rename_selector);499}500501struct Method {502	name: Ident,503	camel_name: String,504	pascal_name: Ident,505	screaming_name: Ident,506	selector_str: String,507	selector: u32,508	args: Vec<MethodArg>,509	has_normal_args: bool,510	mutability: Mutability,511	result: Type,512	weight: Option<Expr>,513	docs: Vec<String>,514}515impl Method {516	fn try_from(value: &ImplItemMethod) -> syn::Result<Self> {517		let mut info = MethodInfo {518			rename_selector: None,519		};520		let mut docs = Vec::new();521		let mut weight = None;522		for attr in &value.attrs {523			let ident = parse_ident_from_path(&attr.path, false)?;524			if ident == "solidity" {525				info = attr.parse_args::<MethodInfo>()?;526			} else if ident == "doc" {527				let args = attr.parse_meta().unwrap();528				let value = match args {529					Meta::NameValue(MetaNameValue {530						lit: Lit::Str(str), ..531					}) => str.value(),532					_ => unreachable!(),533				};534				docs.push(value);535			} else if ident == "weight" {536				weight = Some(attr.parse_args::<Expr>()?);537			}538		}539		let ident = &value.sig.ident;540		let ident_str = ident.to_string();541		if !cases::snakecase::is_snake_case(&ident_str) {542			return Err(syn::Error::new(ident.span(), "method name should be snake_cased\nif alternative solidity name needs to be set - use #[solidity] attribute"));543		}544545		let mut mutability = Mutability::Pure;546547		if let Some(FnArg::Receiver(receiver)) = value548			.sig549			.inputs550			.iter()551			.find(|arg| matches!(arg, FnArg::Receiver(_)))552		{553			if receiver.reference.is_none() {554				return Err(syn::Error::new(555					receiver.span(),556					"receiver should be by ref",557				));558			}559			if receiver.mutability.is_some() {560				mutability = Mutability::Mutable;561			} else {562				mutability = Mutability::View;563			}564		}565		let mut args = Vec::new();566		for typ in value567			.sig568			.inputs569			.iter()570			.filter(|arg| matches!(arg, FnArg::Typed(_)))571		{572			let typ = match typ {573				FnArg::Typed(typ) => typ,574				_ => unreachable!(),575			};576			args.push(MethodArg::try_from(typ)?);577		}578579		if mutability != Mutability::Mutable && args.iter().any(|arg| arg.is_value()) {580			return Err(syn::Error::new(581				args.iter().find(|arg| arg.is_value()).unwrap().ty.span(),582				"payable function should be mutable",583			));584		}585586		let result = match &value.sig.output {587			ReturnType::Type(_, ty) => ty,588			_ => return Err(syn::Error::new(value.sig.output.span(), "interface method should return Result<value>\nif there is no value to return - specify void (which is alias to unit)")),589		};590		let result = parse_result_ok(result)?;591592		let camel_name = info593			.rename_selector594			.unwrap_or_else(|| cases::camelcase::to_camel_case(&ident.to_string()));595		let mut selector_str = camel_name.clone();596		selector_str.push('(');597		let mut has_normal_args = false;598		for (i, arg) in args.iter().filter(|arg| !arg.is_special()).enumerate() {599			if i != 0 {600				selector_str.push(',');601			}602			write!(selector_str, "{}", arg.selector_ty()).unwrap();603			has_normal_args = true;604		}605		selector_str.push(')');606		let selector = fn_selector_str(&selector_str);607608		Ok(Self {609			name: ident.clone(),610			camel_name,611			pascal_name: snake_ident_to_pascal(ident),612			screaming_name: snake_ident_to_screaming(ident),613			selector_str,614			selector,615			args,616			has_normal_args,617			mutability,618			result: result.clone(),619			weight,620			docs,621		})622	}623	fn expand_call_def(&self) -> proc_macro2::TokenStream {624		let defs = self625			.args626			.iter()627			.filter(|a| !a.is_special())628			.map(|a| a.expand_call_def());629		let pascal_name = &self.pascal_name;630631		if self.has_normal_args {632			quote! {633				#pascal_name {634					#(635						#defs,636					)*637				}638			}639		} else {640			quote! {#pascal_name}641		}642	}643644	fn expand_const(&self) -> proc_macro2::TokenStream {645		let screaming_name = &self.screaming_name;646		let selector = u32::to_be_bytes(self.selector);647		let selector_str = &self.selector_str;648		quote! {649			#[doc = #selector_str]650			const #screaming_name: ::evm_coder::types::bytes4 = [#(#selector,)*];651		}652	}653654	fn expand_interface_id(&self) -> proc_macro2::TokenStream {655		let screaming_name = &self.screaming_name;656		quote! {657			interface_id ^= u32::from_be_bytes(Self::#screaming_name);658		}659	}660661	fn expand_parse(&self) -> proc_macro2::TokenStream {662		let pascal_name = &self.pascal_name;663		let screaming_name = &self.screaming_name;664		if self.has_normal_args {665			let parsers = self666				.args667				.iter()668				.filter(|a| !a.is_special())669				.map(|a| a.expand_parse());670			quote! {671				Self::#screaming_name => return Ok(Some(Self::#pascal_name {672					#(673						#parsers,674					)*675				}))676			}677		} else {678			quote! { Self::#screaming_name => return Ok(Some(Self::#pascal_name)) }679		}680	}681682	fn expand_variant_call(&self, call_name: &proc_macro2::Ident) -> proc_macro2::TokenStream {683		let pascal_name = &self.pascal_name;684		let name = &self.name;685686		let matcher = if self.has_normal_args {687			let names = self688				.args689				.iter()690				.filter(|a| !a.is_special())691				.map(|a| &a.name);692693			quote! {{694				#(695					#names,696				)*697			}}698		} else {699			quote! {}700		};701702		let receiver = match self.mutability {703			Mutability::Mutable | Mutability::View => quote! {self.},704			Mutability::Pure => quote! {Self::},705		};706		let args = self.args.iter().map(|a| a.expand_call_arg());707708		quote! {709			#call_name::#pascal_name #matcher => {710				let result = #receiver #name(711					#(712						#args,713					)*714				)?;715				(&result).to_result()716			}717		}718	}719720	fn expand_variant_weight(&self) -> proc_macro2::TokenStream {721		let pascal_name = &self.pascal_name;722		if let Some(weight) = &self.weight {723			let matcher = if self.has_normal_args {724				let names = self725					.args726					.iter()727					.filter(|a| !a.is_special())728					.map(|a| &a.name);729730				quote! {{731					#(732						#names,733					)*734				}}735			} else {736				quote! {}737			};738			quote! {739				Self::#pascal_name #matcher => (#weight).into()740			}741		} else {742			let matcher = if self.has_normal_args {743				quote! {{..}}744			} else {745				quote! {}746			};747			quote! {748				Self::#pascal_name #matcher => ().into()749			}750		}751	}752753	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {754		let camel_name = &self.camel_name;755		let mutability = match self.mutability {756			Mutability::Mutable => quote! {SolidityMutability::Mutable},757			Mutability::View => quote! { SolidityMutability::View },758			Mutability::Pure => quote! {SolidityMutability::Pure},759		};760		let result = &self.result;761762		let args = self763			.args764			.iter()765			.filter(|a| !a.is_special())766			.map(MethodArg::expand_solidity_argument);767		let docs = self.docs.iter();768		let selector_str = &self.selector_str;769		let selector = self.selector;770771		quote! {772			SolidityFunction {773				docs: &[#(#docs),*],774				selector_str: #selector_str,775				selector: #selector,776				name: #camel_name,777				mutability: #mutability,778				args: (779					#(780						#args,781					)*782				),783				result: <UnnamedArgument<#result>>::default(),784			}785		}786	}787}788789fn generics_list(gen: &Generics) -> proc_macro2::TokenStream {790	if gen.params.is_empty() {791		return quote! {};792	}793	let params = gen.params.iter().map(|p| match p {794		syn::GenericParam::Type(id) => {795			let v = &id.ident;796			quote! {#v}797		}798		syn::GenericParam::Lifetime(lt) => {799			let v = &lt.lifetime;800			quote! {#v}801		}802		syn::GenericParam::Const(c) => {803			let i = &c.ident;804			quote! {#i}805		}806	});807	quote! { #(#params),* }808}809fn generics_reference(gen: &Generics) -> proc_macro2::TokenStream {810	if gen.params.is_empty() {811		return quote! {};812	}813	let list = generics_list(gen);814	quote! { <#list> }815}816fn generics_data(gen: &Generics) -> proc_macro2::TokenStream {817	let list = generics_list(gen);818	if gen.params.len() == 1 {819		quote! {#list}820	} else {821		quote! { (#list) }822	}823}824825pub struct SolidityInterface {826	generics: Generics,827	name: Box<syn::Type>,828	info: InterfaceInfo,829	methods: Vec<Method>,830}831impl SolidityInterface {832	pub fn try_from(info: InterfaceInfo, value: &ItemImpl) -> syn::Result<Self> {833		let mut methods = Vec::new();834835		for item in &value.items {836			if let ImplItem::Method(method) = item {837				methods.push(Method::try_from(method)?)838			}839		}840		Ok(Self {841			generics: value.generics.clone(),842			name: value.self_ty.clone(),843			info,844			methods,845		})846	}847	pub fn expand(self) -> proc_macro2::TokenStream {848		let name = self.name;849850		let solidity_name = self.info.name.to_string();851		let call_name = pascal_ident_to_call(&self.info.name);852		let generics = self.generics;853		let gen_ref = generics_reference(&generics);854		let gen_data = generics_data(&generics);855		let gen_where = &generics.where_clause;856857		let call_sub = self858			.info859			.inline_is860			.0861			.iter()862			.chain(self.info.is.0.iter())863			.map(|c| Is::expand_call_def(c, &gen_ref));864		let call_parse = self865			.info866			.inline_is867			.0868			.iter()869			.chain(self.info.is.0.iter())870			.map(|is| Is::expand_parse(is, &gen_ref));871		let call_variants = self872			.info873			.inline_is874			.0875			.iter()876			.chain(self.info.is.0.iter())877			.map(|c| Is::expand_variant_call(c, &call_name, &gen_ref));878		let weight_variants = self879			.info880			.inline_is881			.0882			.iter()883			.chain(self.info.is.0.iter())884			.map(Is::expand_variant_weight);885886		let inline_interface_id = self.info.inline_is.0.iter().map(Is::expand_interface_id);887		let supports_interface = self888			.info889			.is890			.0891			.iter()892			.map(|is| Is::expand_supports_interface(is, &gen_ref));893894		let calls = self.methods.iter().map(Method::expand_call_def);895		let consts = self.methods.iter().map(Method::expand_const);896		let interface_id = self.methods.iter().map(Method::expand_interface_id);897		let parsers = self.methods.iter().map(Method::expand_parse);898		let call_variants_this = self899			.methods900			.iter()901			.map(|m| Method::expand_variant_call(m, &call_name));902		let weight_variants_this = self.methods.iter().map(Method::expand_variant_weight);903		let solidity_functions = self.methods.iter().map(Method::expand_solidity_function);904905		// TODO: Inline inline_is906		let solidity_is = self907			.info908			.is909			.0910			.iter()911			.chain(self.info.inline_is.0.iter())912			.map(|is| is.name.to_string());913		let solidity_events_is = self.info.events.0.iter().map(|is| is.name.to_string());914		let solidity_generators = self915			.info916			.is917			.0918			.iter()919			.chain(self.info.inline_is.0.iter())920			.map(|is| Is::expand_generator(is, &gen_ref));921		let solidity_event_generators = self.info.events.0.iter().map(Is::expand_event_generator);922923		if let Some(expect_selector) = &self.info.expect_selector {924			if !self.info.inline_is.0.is_empty() {925				return syn::Error::new(926					name.span(),927					"expect_selector is not compatible with inline_is",928				)929				.to_compile_error();930			}931			let selector = self932				.methods933				.iter()934				.map(|m| m.selector)935				.fold(0, |a, b| a ^ b);936937			if *expect_selector != selector {938				let mut methods = String::new();939				for meth in self.methods.iter() {940					write!(methods, "\n- {}", meth.selector_str).expect("write to string");941				}942				return syn::Error::new(name.span(), format!("expected selector mismatch, expected {expect_selector:0>8x}, but implementation has {selector:0>8x}{methods}")).to_compile_error();943			}944		}945		// let methods = self.methods.iter().map(Method::solidity_def);946947		quote! {948			#[derive(Debug)]949			pub enum #call_name #gen_ref {950				ERC165Call(::evm_coder::ERC165Call, ::core::marker::PhantomData<#gen_data>),951				#(952					#calls,953				)*954				#(955					#call_sub,956				)*957			}958			impl #gen_ref #call_name #gen_ref {959				#(960					#consts961				)*962				pub fn interface_id() -> ::evm_coder::types::bytes4 {963					let mut interface_id = 0;964					#(#interface_id)*965					#(#inline_interface_id)*966					u32::to_be_bytes(interface_id)967				}968				pub fn supports_interface(interface_id: ::evm_coder::types::bytes4) -> bool {969					interface_id != u32::to_be_bytes(0xffffff) && (970						interface_id == ::evm_coder::ERC165Call::INTERFACE_ID ||971						interface_id == Self::interface_id()972						#(973							|| #supports_interface974						)*975					)976				}977				pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {978					use evm_coder::solidity::*;979					use core::fmt::Write;980					let interface = SolidityInterface {981						name: #solidity_name,982						selector: Self::interface_id(),983						is: &["Dummy", "ERC165", #(984							#solidity_is,985						)* #(986							#solidity_events_is,987						)* ],988						functions: (#(989							#solidity_functions,990						)*),991					};992					if is_impl {993						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());994					} else {995						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());996					}997					#(998						#solidity_generators999					)*1000					#(1001						#solidity_event_generators1002					)*10031004					let mut out = string::new();1005					// In solidity interface usage (is) should be preceeded by interface definition1006					// HACK: this comment helps to sort it in a set1007					if #solidity_name.starts_with("Inline") {1008						out.push_str("/// @dev inlined interface\n");1009					}1010					let _ = interface.format(is_impl, &mut out, tc);1011					tc.collect(out);1012				}1013			}1014			impl #gen_ref ::evm_coder::Call for #call_name #gen_ref {1015				fn parse(method_id: ::evm_coder::types::bytes4, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result<Option<Self>> {1016					use ::evm_coder::abi::AbiRead;1017					match method_id {1018						::evm_coder::ERC165Call::INTERFACE_ID => return Ok(1019							::evm_coder::ERC165Call::parse(method_id, reader)?1020							.map(|c| Self::ERC165Call(c, ::core::marker::PhantomData))1021						),1022						#(1023							#parsers,1024						)*1025						_ => {},1026					}1027					#(1028						#call_parse1029					)else*1030					return Ok(None);1031				}1032			}1033			impl #generics ::evm_coder::Weighted for #call_name #gen_ref1034			#gen_where1035			{1036				#[allow(unused_variables)]1037				fn weight(&self) -> ::evm_coder::execution::DispatchInfo {1038					match self {1039						#(1040							#weight_variants,1041						)*1042						// TODO: It should be very cheap, but not free1043						Self::ERC165Call(::evm_coder::ERC165Call::SupportsInterface {..}, _) => 100u64.into(),1044						#(1045							#weight_variants_this,1046						)*1047					}1048				}1049			}1050			impl #generics ::evm_coder::Callable<#call_name #gen_ref> for #name1051			#gen_where1052			{1053				#[allow(unreachable_code)] // In case of no inner calls1054				fn call(&mut self, c: Msg<#call_name #gen_ref>) -> ::evm_coder::execution::ResultWithPostInfo<::evm_coder::abi::AbiWriter> {1055					use ::evm_coder::abi::AbiWrite;1056					match c.call {1057						#(1058							#call_variants,1059						)*1060						#call_name::ERC165Call(::evm_coder::ERC165Call::SupportsInterface {interface_id}, _) => {1061							let mut writer = ::evm_coder::abi::AbiWriter::default();1062							writer.bool(&<#call_name #gen_ref>::supports_interface(interface_id));1063							return Ok(writer.into());1064						}1065						_ => {},1066					}1067					let mut writer = ::evm_coder::abi::AbiWriter::default();1068					match c.call {1069						#(1070							#call_variants_this,1071						)*1072						_ => unreachable!()1073					}1074				}1075			}1076		}1077	}1078}
after · crates/evm-coder-macros/src/solidity_interface.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 quote::{quote, ToTokens};20use inflector::cases;21use std::fmt::Write;22use syn::{23	Expr, FnArg, GenericArgument, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta,24	MetaNameValue, PatType, PathArguments, ReturnType, Type,25	spanned::Spanned,26	parse::{Parse, ParseStream},27	parenthesized, Token, LitInt, LitStr,28};2930use crate::{31	fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_path, parse_path_segment,32	parse_result_ok, pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal,33	snake_ident_to_screaming,34};3536struct Is {37	name: Ident,38	pascal_call_name: Ident,39	snake_call_name: Ident,40	via: Option<(Type, Ident)>,41}42impl Is {43	fn expand_call_def(&self, gen_ref: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {44		let name = &self.name;45		let pascal_call_name = &self.pascal_call_name;46		quote! {47			#name(#pascal_call_name #gen_ref)48		}49	}5051	fn expand_interface_id(&self) -> proc_macro2::TokenStream {52		let pascal_call_name = &self.pascal_call_name;53		quote! {54			interface_id ^= u32::from_be_bytes(#pascal_call_name::interface_id());55		}56	}5758	fn expand_supports_interface(59		&self,60		generics: &proc_macro2::TokenStream,61	) -> proc_macro2::TokenStream {62		let pascal_call_name = &self.pascal_call_name;63		quote! {64			<#pascal_call_name #generics>::supports_interface(interface_id)65		}66	}6768	fn expand_variant_weight(&self) -> proc_macro2::TokenStream {69		let name = &self.name;70		quote! {71			Self::#name(call) => call.weight()72		}73	}7475	fn expand_variant_call(76		&self,77		call_name: &proc_macro2::Ident,78		generics: &proc_macro2::TokenStream,79	) -> proc_macro2::TokenStream {80		let name = &self.name;81		let pascal_call_name = &self.pascal_call_name;82		let via_typ = self83			.via84			.as_ref()85			.map(|(t, _)| quote! {#t})86			.unwrap_or_else(|| quote! {Self});87		let via_map = self88			.via89			.as_ref()90			.map(|(_, i)| quote! {.#i()})91			.unwrap_or_default();92		quote! {93			#call_name::#name(call) => return <#via_typ as ::evm_coder::Callable<#pascal_call_name #generics>>::call(self #via_map, Msg {94				call,95				caller: c.caller,96				value: c.value,97			})98		}99	}100101	fn expand_parse(&self, generics: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {102		let name = &self.name;103		let pascal_call_name = &self.pascal_call_name;104		quote! {105			if let Some(parsed_call) = <#pascal_call_name #generics>::parse(method_id, reader)? {106				return Ok(Some(Self::#name(parsed_call)))107			}108		}109	}110111	fn expand_generator(&self, generics: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {112		let pascal_call_name = &self.pascal_call_name;113		quote! {114			<#pascal_call_name #generics>::generate_solidity_interface(tc, is_impl);115		}116	}117118	fn expand_event_generator(&self) -> proc_macro2::TokenStream {119		let name = &self.name;120		quote! {121			#name::generate_solidity_interface(tc, is_impl);122		}123	}124}125126#[derive(Default)]127struct IsList(Vec<Is>);128impl Parse for IsList {129	fn parse(input: ParseStream) -> syn::Result<Self> {130		let mut out = vec![];131		loop {132			if input.is_empty() {133				break;134			}135			let name = input.parse::<Ident>()?;136			let lookahead = input.lookahead1();137			let via = if lookahead.peek(syn::token::Paren) {138				let contents;139				parenthesized!(contents in input);140				let method = contents.parse::<Ident>()?;141				contents.parse::<Token![,]>()?;142				let ty = contents.parse::<Type>()?;143				Some((ty, method))144			} else if lookahead.peek(Token![,]) {145				None146			} else if input.is_empty() {147				None148			} else {149				return Err(lookahead.error());150			};151			out.push(Is {152				pascal_call_name: pascal_ident_to_call(&name),153				snake_call_name: pascal_ident_to_snake_call(&name),154				name,155				via,156			});157			if input.peek(Token![,]) {158				input.parse::<Token![,]>()?;159				continue;160			} else {161				break;162			}163		}164		Ok(Self(out))165	}166}167168pub struct InterfaceInfo {169	name: Ident,170	is: IsList,171	inline_is: IsList,172	events: IsList,173	expect_selector: Option<u32>,174}175impl Parse for InterfaceInfo {176	fn parse(input: ParseStream) -> syn::Result<Self> {177		let mut name = None;178		let mut is = None;179		let mut inline_is = None;180		let mut events = None;181		let mut expect_selector = None;182		// TODO: create proc-macro to optimize proc-macro boilerplate? :D183		loop {184			let lookahead = input.lookahead1();185			if lookahead.peek(kw::name) {186				let k = input.parse::<kw::name>()?;187				input.parse::<Token![=]>()?;188				if name.replace(input.parse::<Ident>()?).is_some() {189					return Err(syn::Error::new(k.span(), "name is already set"));190				}191			} else if lookahead.peek(kw::is) {192				let k = input.parse::<kw::is>()?;193				let contents;194				parenthesized!(contents in input);195				if is.replace(contents.parse::<IsList>()?).is_some() {196					return Err(syn::Error::new(k.span(), "is is already set"));197				}198			} else if lookahead.peek(kw::inline_is) {199				let k = input.parse::<kw::inline_is>()?;200				let contents;201				parenthesized!(contents in input);202				if inline_is.replace(contents.parse::<IsList>()?).is_some() {203					return Err(syn::Error::new(k.span(), "inline_is is already set"));204				}205			} else if lookahead.peek(kw::events) {206				let k = input.parse::<kw::events>()?;207				let contents;208				parenthesized!(contents in input);209				if events.replace(contents.parse::<IsList>()?).is_some() {210					return Err(syn::Error::new(k.span(), "events is already set"));211				}212			} else if lookahead.peek(kw::expect_selector) {213				let k = input.parse::<kw::expect_selector>()?;214				input.parse::<Token![=]>()?;215				let value = input.parse::<LitInt>()?;216				if expect_selector217					.replace(value.base10_parse::<u32>()?)218					.is_some()219				{220					return Err(syn::Error::new(k.span(), "expect_selector is already set"));221				}222			} else if input.is_empty() {223				break;224			} else {225				return Err(lookahead.error());226			}227			if input.peek(Token![,]) {228				input.parse::<Token![,]>()?;229			} else {230				break;231			}232		}233		Ok(Self {234			name: name.ok_or_else(|| syn::Error::new(input.span(), "missing name"))?,235			is: is.unwrap_or_default(),236			inline_is: inline_is.unwrap_or_default(),237			events: events.unwrap_or_default(),238			expect_selector,239		})240	}241}242243struct MethodInfo {244	rename_selector: Option<String>,245}246impl Parse for MethodInfo {247	fn parse(input: ParseStream) -> syn::Result<Self> {248		let mut rename_selector = None;249		let lookahead = input.lookahead1();250		if lookahead.peek(kw::rename_selector) {251			let k = input.parse::<kw::rename_selector>()?;252			input.parse::<Token![=]>()?;253			if rename_selector254				.replace(input.parse::<LitStr>()?.value())255				.is_some()256			{257				return Err(syn::Error::new(k.span(), "rename_selector is already set"));258			}259		}260		Ok(Self { rename_selector })261	}262}263264enum AbiType {265	// type266	Plain(Ident),267	// (type1,type2)268	Tuple(Vec<AbiType>),269	// type[]270	Vec(Box<AbiType>),271	// type[20]272	Array(Box<AbiType>, usize),273}274impl AbiType {275	fn try_from(value: &Type) -> syn::Result<Self> {276		let value = Self::try_maybe_special_from(value)?;277		if value.is_special() {278			return Err(syn::Error::new(value.span(), "unexpected special type"));279		}280		Ok(value)281	}282	fn try_maybe_special_from(value: &Type) -> syn::Result<Self> {283		match value {284			Type::Array(arr) => {285				let wrapped = AbiType::try_from(&arr.elem)?;286				match &arr.len {287					Expr::Lit(l) => match &l.lit {288						Lit::Int(i) => {289							let num = i.base10_parse::<usize>()?;290							Ok(AbiType::Array(Box::new(wrapped), num as usize))291						}292						_ => Err(syn::Error::new(arr.len.span(), "should be int literal")),293					},294					_ => Err(syn::Error::new(arr.len.span(), "should be literal")),295				}296			}297			Type::Path(_) => {298				let path = parse_path(value)?;299				let segment = parse_path_segment(path)?;300				if segment.ident == "Vec" {301					let args = match &segment.arguments {302						PathArguments::AngleBracketed(e) => e,303						_ => {304							return Err(syn::Error::new(305								segment.arguments.span(),306								"missing Vec generic",307							))308						}309					};310					let args = &args.args;311					if args.len() != 1 {312						return Err(syn::Error::new(313							args.span(),314							"expected only one generic for vec",315						));316					}317					let arg = args.first().expect("first arg");318319					let ty = match arg {320						GenericArgument::Type(ty) => ty,321						_ => {322							return Err(syn::Error::new(323								arg.span(),324								"expected first generic to be type",325							))326						}327					};328329					let wrapped = AbiType::try_from(ty)?;330					Ok(Self::Vec(Box::new(wrapped)))331				} else {332					if !segment.arguments.is_empty() {333						return Err(syn::Error::new(334							segment.arguments.span(),335							"unexpected generic arguments for non-vec type",336						));337					}338					Ok(Self::Plain(segment.ident.clone()))339				}340			}341			Type::Tuple(t) => {342				let mut out = Vec::with_capacity(t.elems.len());343				for el in t.elems.iter() {344					out.push(AbiType::try_from(el)?)345				}346				Ok(Self::Tuple(out))347			}348			_ => Err(syn::Error::new(349				value.span(),350				"unexpected type, only arrays, plain types and tuples are supported",351			)),352		}353	}354	fn is_value(&self) -> bool {355		matches!(self, Self::Plain(v) if v == "value")356	}357	fn is_caller(&self) -> bool {358		matches!(self, Self::Plain(v) if v == "caller")359	}360	fn is_special(&self) -> bool {361		self.is_caller() || self.is_value()362	}363	fn selector_ty_buf(&self, buf: &mut String) -> std::fmt::Result {364		match self {365			AbiType::Plain(t) => {366				write!(buf, "{}", t)367			}368			AbiType::Tuple(t) => {369				write!(buf, "(")?;370				for (i, t) in t.iter().enumerate() {371					if i != 0 {372						write!(buf, ",")?;373					}374					t.selector_ty_buf(buf)?;375				}376				write!(buf, ")")377			}378			AbiType::Vec(v) => {379				v.selector_ty_buf(buf)?;380				write!(buf, "[]")381			}382			AbiType::Array(v, len) => {383				v.selector_ty_buf(buf)?;384				write!(buf, "[{}]", len)385			}386		}387	}388	fn selector_ty(&self) -> String {389		let mut out = String::new();390		self.selector_ty_buf(&mut out).expect("no fmt error");391		out392	}393}394impl ToTokens for AbiType {395	fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {396		match self {397			AbiType::Plain(t) => tokens.extend(quote! {#t}),398			AbiType::Tuple(t) => {399				tokens.extend(quote! {(400					#(#t),*401				)});402			}403			AbiType::Vec(v) => tokens.extend(quote! {Vec<#v>}),404			AbiType::Array(v, l) => tokens.extend(quote! {[#v; #l]}),405		}406	}407}408409struct MethodArg {410	name: Ident,411	camel_name: String,412	ty: AbiType,413}414impl MethodArg {415	fn try_from(value: &PatType) -> syn::Result<Self> {416		let name = parse_ident_from_pat(&value.pat)?.clone();417		Ok(Self {418			camel_name: cases::camelcase::to_camel_case(&name.to_string()),419			name,420			ty: AbiType::try_maybe_special_from(&value.ty)?,421		})422	}423	fn is_value(&self) -> bool {424		self.ty.is_value()425	}426	fn is_caller(&self) -> bool {427		self.ty.is_caller()428	}429	fn is_special(&self) -> bool {430		self.ty.is_special()431	}432	fn selector_ty(&self) -> String {433		assert!(!self.is_special());434		self.ty.selector_ty()435	}436437	fn expand_call_def(&self) -> proc_macro2::TokenStream {438		assert!(!self.is_special());439		let name = &self.name;440		let ty = &self.ty;441442		quote! {443			#name: #ty444		}445	}446447	fn expand_parse(&self) -> proc_macro2::TokenStream {448		assert!(!self.is_special());449		let name = &self.name;450		quote! {451			#name: reader.abi_read()?452		}453	}454455	fn expand_call_arg(&self) -> proc_macro2::TokenStream {456		if self.is_value() {457			quote! {458				c.value.clone()459			}460		} else if self.is_caller() {461			quote! {462				c.caller.clone()463			}464		} else {465			let name = &self.name;466			quote! {467				#name468			}469		}470	}471472	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {473		let camel_name = &self.camel_name.to_string();474		let ty = &self.ty;475		quote! {476			<NamedArgument<#ty>>::new(#camel_name)477		}478	}479}480481#[derive(PartialEq)]482enum Mutability {483	Mutable,484	View,485	Pure,486}487488mod kw {489	syn::custom_keyword!(weight);490491	syn::custom_keyword!(via);492	syn::custom_keyword!(name);493	syn::custom_keyword!(is);494	syn::custom_keyword!(inline_is);495	syn::custom_keyword!(events);496	syn::custom_keyword!(expect_selector);497498	syn::custom_keyword!(rename_selector);499}500501struct Method {502	name: Ident,503	camel_name: String,504	pascal_name: Ident,505	screaming_name: Ident,506	selector_str: String,507	selector: u32,508	args: Vec<MethodArg>,509	has_normal_args: bool,510	mutability: Mutability,511	result: Type,512	weight: Option<Expr>,513	docs: Vec<String>,514}515impl Method {516	fn try_from(value: &ImplItemMethod) -> syn::Result<Self> {517		let mut info = MethodInfo {518			rename_selector: None,519		};520		let mut docs = Vec::new();521		let mut weight = None;522		for attr in &value.attrs {523			let ident = parse_ident_from_path(&attr.path, false)?;524			if ident == "solidity" {525				info = attr.parse_args::<MethodInfo>()?;526			} else if ident == "doc" {527				let args = attr.parse_meta().unwrap();528				let value = match args {529					Meta::NameValue(MetaNameValue {530						lit: Lit::Str(str), ..531					}) => str.value(),532					_ => unreachable!(),533				};534				docs.push(value);535			} else if ident == "weight" {536				weight = Some(attr.parse_args::<Expr>()?);537			}538		}539		let ident = &value.sig.ident;540		let ident_str = ident.to_string();541		if !cases::snakecase::is_snake_case(&ident_str) {542			return Err(syn::Error::new(ident.span(), "method name should be snake_cased\nif alternative solidity name needs to be set - use #[solidity] attribute"));543		}544545		let mut mutability = Mutability::Pure;546547		if let Some(FnArg::Receiver(receiver)) = value548			.sig549			.inputs550			.iter()551			.find(|arg| matches!(arg, FnArg::Receiver(_)))552		{553			if receiver.reference.is_none() {554				return Err(syn::Error::new(555					receiver.span(),556					"receiver should be by ref",557				));558			}559			if receiver.mutability.is_some() {560				mutability = Mutability::Mutable;561			} else {562				mutability = Mutability::View;563			}564		}565		let mut args = Vec::new();566		for typ in value567			.sig568			.inputs569			.iter()570			.filter(|arg| matches!(arg, FnArg::Typed(_)))571		{572			let typ = match typ {573				FnArg::Typed(typ) => typ,574				_ => unreachable!(),575			};576			args.push(MethodArg::try_from(typ)?);577		}578579		if mutability != Mutability::Mutable && args.iter().any(|arg| arg.is_value()) {580			return Err(syn::Error::new(581				args.iter().find(|arg| arg.is_value()).unwrap().ty.span(),582				"payable function should be mutable",583			));584		}585586		let result = match &value.sig.output {587			ReturnType::Type(_, ty) => ty,588			_ => return Err(syn::Error::new(value.sig.output.span(), "interface method should return Result<value>\nif there is no value to return - specify void (which is alias to unit)")),589		};590		let result = parse_result_ok(result)?;591592		let camel_name = info593			.rename_selector594			.unwrap_or_else(|| cases::camelcase::to_camel_case(&ident.to_string()));595		let mut selector_str = camel_name.clone();596		selector_str.push('(');597		let mut has_normal_args = false;598		for (i, arg) in args.iter().filter(|arg| !arg.is_special()).enumerate() {599			if i != 0 {600				selector_str.push(',');601			}602			write!(selector_str, "{}", arg.selector_ty()).unwrap();603			has_normal_args = true;604		}605		selector_str.push(')');606		let selector = fn_selector_str(&selector_str);607608		Ok(Self {609			name: ident.clone(),610			camel_name,611			pascal_name: snake_ident_to_pascal(ident),612			screaming_name: snake_ident_to_screaming(ident),613			selector_str,614			selector,615			args,616			has_normal_args,617			mutability,618			result: result.clone(),619			weight,620			docs,621		})622	}623	fn expand_call_def(&self) -> proc_macro2::TokenStream {624		let defs = self625			.args626			.iter()627			.filter(|a| !a.is_special())628			.map(|a| a.expand_call_def());629		let pascal_name = &self.pascal_name;630631		if self.has_normal_args {632			quote! {633				#pascal_name {634					#(635						#defs,636					)*637				}638			}639		} else {640			quote! {#pascal_name}641		}642	}643644	fn expand_const(&self) -> proc_macro2::TokenStream {645		let screaming_name = &self.screaming_name;646		let selector = u32::to_be_bytes(self.selector);647		let selector_str = &self.selector_str;648		quote! {649			#[doc = #selector_str]650			const #screaming_name: ::evm_coder::types::bytes4 = [#(#selector,)*];651		}652	}653654	fn expand_interface_id(&self) -> proc_macro2::TokenStream {655		let screaming_name = &self.screaming_name;656		quote! {657			interface_id ^= u32::from_be_bytes(Self::#screaming_name);658		}659	}660661	fn expand_parse(&self) -> proc_macro2::TokenStream {662		let pascal_name = &self.pascal_name;663		let screaming_name = &self.screaming_name;664		if self.has_normal_args {665			let parsers = self666				.args667				.iter()668				.filter(|a| !a.is_special())669				.map(|a| a.expand_parse());670			quote! {671				Self::#screaming_name => return Ok(Some(Self::#pascal_name {672					#(673						#parsers,674					)*675				}))676			}677		} else {678			quote! { Self::#screaming_name => return Ok(Some(Self::#pascal_name)) }679		}680	}681682	fn expand_variant_call(&self, call_name: &proc_macro2::Ident) -> proc_macro2::TokenStream {683		let pascal_name = &self.pascal_name;684		let name = &self.name;685686		let matcher = if self.has_normal_args {687			let names = self688				.args689				.iter()690				.filter(|a| !a.is_special())691				.map(|a| &a.name);692693			quote! {{694				#(695					#names,696				)*697			}}698		} else {699			quote! {}700		};701702		let receiver = match self.mutability {703			Mutability::Mutable | Mutability::View => quote! {self.},704			Mutability::Pure => quote! {Self::},705		};706		let args = self.args.iter().map(|a| a.expand_call_arg());707708		quote! {709			#call_name::#pascal_name #matcher => {710				let result = #receiver #name(711					#(712						#args,713					)*714				)?;715				(&result).to_result()716			}717		}718	}719720	fn expand_variant_weight(&self) -> proc_macro2::TokenStream {721		let pascal_name = &self.pascal_name;722		if let Some(weight) = &self.weight {723			let matcher = if self.has_normal_args {724				let names = self725					.args726					.iter()727					.filter(|a| !a.is_special())728					.map(|a| &a.name);729730				quote! {{731					#(732						#names,733					)*734				}}735			} else {736				quote! {}737			};738			quote! {739				Self::#pascal_name #matcher => (#weight).into()740			}741		} else {742			let matcher = if self.has_normal_args {743				quote! {{..}}744			} else {745				quote! {}746			};747			quote! {748				Self::#pascal_name #matcher => ().into()749			}750		}751	}752753	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {754		let camel_name = &self.camel_name;755		let mutability = match self.mutability {756			Mutability::Mutable => quote! {SolidityMutability::Mutable},757			Mutability::View => quote! { SolidityMutability::View },758			Mutability::Pure => quote! {SolidityMutability::Pure},759		};760		let result = &self.result;761762		let args = self763			.args764			.iter()765			.filter(|a| !a.is_special())766			.map(MethodArg::expand_solidity_argument);767		let docs = self.docs.iter();768		let selector_str = &self.selector_str;769		let selector = self.selector;770771		quote! {772			SolidityFunction {773				docs: &[#(#docs),*],774				selector_str: #selector_str,775				selector: #selector,776				name: #camel_name,777				mutability: #mutability,778				args: (779					#(780						#args,781					)*782				),783				result: <UnnamedArgument<#result>>::default(),784			}785		}786	}787}788789fn generics_list(gen: &Generics) -> proc_macro2::TokenStream {790	if gen.params.is_empty() {791		return quote! {};792	}793	let params = gen.params.iter().map(|p| match p {794		syn::GenericParam::Type(id) => {795			let v = &id.ident;796			quote! {#v}797		}798		syn::GenericParam::Lifetime(lt) => {799			let v = &lt.lifetime;800			quote! {#v}801		}802		syn::GenericParam::Const(c) => {803			let i = &c.ident;804			quote! {#i}805		}806	});807	quote! { #(#params),* }808}809fn generics_reference(gen: &Generics) -> proc_macro2::TokenStream {810	if gen.params.is_empty() {811		return quote! {};812	}813	let list = generics_list(gen);814	quote! { <#list> }815}816fn generics_data(gen: &Generics) -> proc_macro2::TokenStream {817	let list = generics_list(gen);818	if gen.params.len() == 1 {819		quote! {#list}820	} else {821		quote! { (#list) }822	}823}824825pub struct SolidityInterface {826	generics: Generics,827	name: Box<syn::Type>,828	info: InterfaceInfo,829	methods: Vec<Method>,830}831impl SolidityInterface {832	pub fn try_from(info: InterfaceInfo, value: &ItemImpl) -> syn::Result<Self> {833		let mut methods = Vec::new();834835		for item in &value.items {836			if let ImplItem::Method(method) = item {837				methods.push(Method::try_from(method)?)838			}839		}840		Ok(Self {841			generics: value.generics.clone(),842			name: value.self_ty.clone(),843			info,844			methods,845		})846	}847	pub fn expand(self) -> proc_macro2::TokenStream {848		let name = self.name;849850		let solidity_name = self.info.name.to_string();851		let call_name = pascal_ident_to_call(&self.info.name);852		let generics = self.generics;853		let gen_ref = generics_reference(&generics);854		let gen_data = generics_data(&generics);855		let gen_where = &generics.where_clause;856857		let call_sub = self858			.info859			.inline_is860			.0861			.iter()862			.chain(self.info.is.0.iter())863			.map(|c| Is::expand_call_def(c, &gen_ref));864		let call_parse = self865			.info866			.inline_is867			.0868			.iter()869			.chain(self.info.is.0.iter())870			.map(|is| Is::expand_parse(is, &gen_ref));871		let call_variants = self872			.info873			.inline_is874			.0875			.iter()876			.chain(self.info.is.0.iter())877			.map(|c| Is::expand_variant_call(c, &call_name, &gen_ref));878		let weight_variants = self879			.info880			.inline_is881			.0882			.iter()883			.chain(self.info.is.0.iter())884			.map(Is::expand_variant_weight);885886		let inline_interface_id = self.info.inline_is.0.iter().map(Is::expand_interface_id);887		let supports_interface = self888			.info889			.is890			.0891			.iter()892			.map(|is| Is::expand_supports_interface(is, &gen_ref));893894		let calls = self.methods.iter().map(Method::expand_call_def);895		let consts = self.methods.iter().map(Method::expand_const);896		let interface_id = self.methods.iter().map(Method::expand_interface_id);897		let parsers = self.methods.iter().map(Method::expand_parse);898		let call_variants_this = self899			.methods900			.iter()901			.map(|m| Method::expand_variant_call(m, &call_name));902		let weight_variants_this = self.methods.iter().map(Method::expand_variant_weight);903		let solidity_functions = self.methods.iter().map(Method::expand_solidity_function);904905		// TODO: Inline inline_is906		let solidity_is = self907			.info908			.is909			.0910			.iter()911			.chain(self.info.inline_is.0.iter())912			.map(|is| is.name.to_string());913		let solidity_events_is = self.info.events.0.iter().map(|is| is.name.to_string());914		let solidity_generators = self915			.info916			.is917			.0918			.iter()919			.chain(self.info.inline_is.0.iter())920			.map(|is| Is::expand_generator(is, &gen_ref));921		let solidity_event_generators = self.info.events.0.iter().map(Is::expand_event_generator);922923		if let Some(expect_selector) = &self.info.expect_selector {924			if !self.info.inline_is.0.is_empty() {925				return syn::Error::new(926					name.span(),927					"expect_selector is not compatible with inline_is",928				)929				.to_compile_error();930			}931			let selector = self932				.methods933				.iter()934				.map(|m| m.selector)935				.fold(0, |a, b| a ^ b);936937			if *expect_selector != selector {938				let mut methods = String::new();939				for meth in self.methods.iter() {940					write!(methods, "\n- {}", meth.selector_str).expect("write to string");941				}942				return syn::Error::new(name.span(), format!("expected selector mismatch, expected {expect_selector:0>8x}, but implementation has {selector:0>8x}{methods}")).to_compile_error();943			}944		}945		// let methods = self.methods.iter().map(Method::solidity_def);946947		quote! {948			#[derive(Debug)]949			pub enum #call_name #gen_ref {950				ERC165Call(::evm_coder::ERC165Call, ::core::marker::PhantomData<#gen_data>),951				#(952					#calls,953				)*954				#(955					#call_sub,956				)*957			}958			impl #gen_ref #call_name #gen_ref {959				#(960					#consts961				)*962				pub fn interface_id() -> ::evm_coder::types::bytes4 {963					let mut interface_id = 0;964					#(#interface_id)*965					#(#inline_interface_id)*966					u32::to_be_bytes(interface_id)967				}968				pub fn supports_interface(interface_id: ::evm_coder::types::bytes4) -> bool {969					interface_id != u32::to_be_bytes(0xffffff) && (970						interface_id == ::evm_coder::ERC165Call::INTERFACE_ID ||971						interface_id == Self::interface_id()972						#(973							|| #supports_interface974						)*975					)976				}977				pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {978					use evm_coder::solidity::*;979					use core::fmt::Write;980					let interface = SolidityInterface {981						name: #solidity_name,982						selector: Self::interface_id(),983						is: &["Dummy", "ERC165", #(984							#solidity_is,985						)* #(986							#solidity_events_is,987						)* ],988						functions: (#(989							#solidity_functions,990						)*),991					};992993					let mut out = string::new();994					if #solidity_name.starts_with("Inline") {995						out.push_str("/// @dev inlined interface\n");996					}997					let _ = interface.format(is_impl, &mut out, tc);998					tc.collect(out);999					#(1000						#solidity_event_generators1001					)*1002					#(1003						#solidity_generators1004					)*1005					if is_impl {1006						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());1007					} else {1008						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());1009					}1010				}1011			}1012			impl #gen_ref ::evm_coder::Call for #call_name #gen_ref {1013				fn parse(method_id: ::evm_coder::types::bytes4, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result<Option<Self>> {1014					use ::evm_coder::abi::AbiRead;1015					match method_id {1016						::evm_coder::ERC165Call::INTERFACE_ID => return Ok(1017							::evm_coder::ERC165Call::parse(method_id, reader)?1018							.map(|c| Self::ERC165Call(c, ::core::marker::PhantomData))1019						),1020						#(1021							#parsers,1022						)*1023						_ => {},1024					}1025					#(1026						#call_parse1027					)else*1028					return Ok(None);1029				}1030			}1031			impl #generics ::evm_coder::Weighted for #call_name #gen_ref1032			#gen_where1033			{1034				#[allow(unused_variables)]1035				fn weight(&self) -> ::evm_coder::execution::DispatchInfo {1036					match self {1037						#(1038							#weight_variants,1039						)*1040						// TODO: It should be very cheap, but not free1041						Self::ERC165Call(::evm_coder::ERC165Call::SupportsInterface {..}, _) => 100u64.into(),1042						#(1043							#weight_variants_this,1044						)*1045					}1046				}1047			}1048			impl #generics ::evm_coder::Callable<#call_name #gen_ref> for #name1049			#gen_where1050			{1051				#[allow(unreachable_code)] // In case of no inner calls1052				fn call(&mut self, c: Msg<#call_name #gen_ref>) -> ::evm_coder::execution::ResultWithPostInfo<::evm_coder::abi::AbiWriter> {1053					use ::evm_coder::abi::AbiWrite;1054					match c.call {1055						#(1056							#call_variants,1057						)*1058						#call_name::ERC165Call(::evm_coder::ERC165Call::SupportsInterface {interface_id}, _) => {1059							let mut writer = ::evm_coder::abi::AbiWriter::default();1060							writer.bool(&<#call_name #gen_ref>::supports_interface(interface_id));1061							return Ok(writer.into());1062						}1063						_ => {},1064					}1065					let mut writer = ::evm_coder::abi::AbiWriter::default();1066					match c.call {1067						#(1068							#call_variants_this,1069						)*1070						_ => unreachable!()1071					}1072				}1073			}1074		}1075	}1076}
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()
 	}
 }