git.delta.rocks / unique-network / refs/commits / 4aadc003b1ed

difftreelog

feat support complex types in solidity defs

Yaroslav Bolyukin2021-09-01parent: #07de194.patch.diff
in: master

5 files changed

modifiedcrates/evm-coder-macros/src/solidity_interface.rsdiffbeforeafterboth
before · crates/evm-coder-macros/src/solidity_interface.rs
1#![allow(dead_code)]23use quote::quote;4use darling::FromMeta;5use inflector::cases;6use std::fmt::Write;7use syn::{8	FnArg, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Meta, NestedMeta, PatType, Path,9	ReturnType, Type, spanned::Spanned,10};1112use crate::{13	fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_ident_from_type,14	parse_result_ok, pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal,15	snake_ident_to_screaming,16};1718struct Is {19	name: Ident,20	pascal_call_name: Ident,21	snake_call_name: Ident,22}23impl Is {24	fn try_from(path: &Path) -> syn::Result<Self> {25		let name = parse_ident_from_path(path, false)?.clone();26		Ok(Self {27			pascal_call_name: pascal_ident_to_call(&name),28			snake_call_name: pascal_ident_to_snake_call(&name),29			name,30		})31	}3233	fn expand_call_def(&self) -> proc_macro2::TokenStream {34		let name = &self.name;35		let pascal_call_name = &self.pascal_call_name;36		quote! {37			#name(#pascal_call_name)38		}39	}4041	fn expand_interface_id(&self) -> proc_macro2::TokenStream {42		let pascal_call_name = &self.pascal_call_name;43		quote! {44			interface_id ^= #pascal_call_name::interface_id();45		}46	}4748	fn expand_supports_interface(&self) -> proc_macro2::TokenStream {49		let pascal_call_name = &self.pascal_call_name;50		quote! {51			#pascal_call_name::supports_interface(interface_id)52		}53	}5455	fn expand_variant_call(&self) -> proc_macro2::TokenStream {56		let name = &self.name;57		let pascal_call_name = &self.pascal_call_name;58		quote! {59			InternalCall::#name(call) => return <Self as ::evm_coder::Callable<#pascal_call_name>>::call(self, Msg {60				call,61				caller: c.caller,62				value: c.value,63			})64		}65	}6667	fn expand_parse(&self) -> proc_macro2::TokenStream {68		let name = &self.name;69		let pascal_call_name = &self.pascal_call_name;70		quote! {71			if let Some(parsed_call) = #pascal_call_name::parse(method_id, reader)? {72				return Ok(Some(Self::#name(parsed_call)))73			}74		}75	}7677	fn expand_generator(&self) -> proc_macro2::TokenStream {78		let pascal_call_name = &self.pascal_call_name;79		quote! {80			#pascal_call_name::generate_solidity_interface(out_set, is_impl);81		}82	}8384	fn expand_event_generator(&self) -> proc_macro2::TokenStream {85		let name = &self.name;86		quote! {87			#name::generate_solidity_interface(out_set, is_impl);88		}89	}90}9192#[derive(Default)]93struct IsList(Vec<Is>);94impl FromMeta for IsList {95	fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {96		let mut out = Vec::new();97		for item in items {98			match item {99				NestedMeta::Meta(Meta::Path(path)) => out.push(Is::try_from(path)?),100				_ => return Err(syn::Error::new(item.span(), "expected path").into()),101			}102		}103		Ok(Self(out))104	}105}106107#[derive(FromMeta)]108pub struct InterfaceInfo {109	name: Ident,110	#[darling(default)]111	is: IsList,112	#[darling(default)]113	inline_is: IsList,114	#[darling(default)]115	events: IsList,116}117118#[derive(FromMeta)]119struct MethodInfo {120	#[darling(default)]121	rename_selector: Option<String>,122}123124struct MethodArg {125	name: Ident,126	camel_name: String,127	ty: Ident,128}129impl MethodArg {130	fn try_from(value: &PatType) -> syn::Result<Self> {131		let name = parse_ident_from_pat(&value.pat)?.clone();132		Ok(Self {133			camel_name: cases::camelcase::to_camel_case(&name.to_string()),134			name,135			ty: parse_ident_from_type(&value.ty, false)?.clone(),136		})137	}138	fn is_value(&self) -> bool {139		self.ty == "value"140	}141	fn is_caller(&self) -> bool {142		self.ty == "caller"143	}144	fn is_special(&self) -> bool {145		self.is_value() || self.is_caller()146	}147	fn selector_ty(&self) -> &Ident {148		assert!(!self.is_special());149		&self.ty150	}151152	fn expand_call_def(&self) -> proc_macro2::TokenStream {153		assert!(!self.is_special());154		let name = &self.name;155		let ty = &self.ty;156157		quote! {158			#name: #ty159		}160	}161162	fn expand_parse(&self) -> proc_macro2::TokenStream {163		assert!(!self.is_special());164		let name = &self.name;165		quote! {166			#name: reader.abi_read()?167		}168	}169170	fn expand_call_arg(&self) -> proc_macro2::TokenStream {171		if self.is_value() {172			quote! {173				c.value.clone()174			}175		} else if self.is_caller() {176			quote! {177				c.caller.clone()178			}179		} else {180			let name = &self.name;181			quote! {182				#name183			}184		}185	}186187	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {188		let camel_name = &self.camel_name.to_string();189		let ty = &self.ty;190		quote! {191			<NamedArgument<#ty>>::new(#camel_name)192		}193	}194}195196#[derive(PartialEq)]197enum Mutability {198	Mutable,199	View,200	Pure,201}202203struct Method {204	name: Ident,205	camel_name: String,206	pascal_name: Ident,207	screaming_name: Ident,208	selector_str: String,209	selector: u32,210	args: Vec<MethodArg>,211	has_normal_args: bool,212	mutability: Mutability,213	result: Type,214}215impl Method {216	fn try_from(value: &ImplItemMethod) -> syn::Result<Self> {217		let mut info = MethodInfo {218			rename_selector: None,219		};220		for attr in &value.attrs {221			let ident = parse_ident_from_path(&attr.path, false)?;222			if ident == "solidity" {223				let args = attr.parse_meta().unwrap();224				info = MethodInfo::from_meta(&args).unwrap();225			} else if ident == "doc" {226				// TODO: Add docs to evm interfaces227			}228		}229		let ident = &value.sig.ident;230		let ident_str = ident.to_string();231		if !cases::snakecase::is_snake_case(&ident_str) {232			return Err(syn::Error::new(ident.span(), "method name should be snake_cased\nif alternative solidity name needs to be set - use #[solidity] attribute"));233		}234235		let mut mutability = Mutability::Pure;236237		if let Some(FnArg::Receiver(receiver)) = value238			.sig239			.inputs240			.iter()241			.find(|arg| matches!(arg, FnArg::Receiver(_)))242		{243			if receiver.reference.is_none() {244				return Err(syn::Error::new(245					receiver.span(),246					"receiver should be by ref",247				));248			}249			if receiver.mutability.is_some() {250				mutability = Mutability::Mutable;251			} else {252				mutability = Mutability::View;253			}254		}255		let mut args = Vec::new();256		for typ in value257			.sig258			.inputs259			.iter()260			.filter(|arg| matches!(arg, FnArg::Typed(_)))261		{262			let typ = match typ {263				FnArg::Typed(typ) => typ,264				_ => unreachable!(),265			};266			args.push(MethodArg::try_from(typ)?);267		}268269		if mutability != Mutability::Mutable && args.iter().any(|arg| arg.is_value()) {270			return Err(syn::Error::new(271				args.iter().find(|arg| arg.is_value()).unwrap().ty.span(),272				"payable function should be mutable",273			));274		}275276		let result = match &value.sig.output {277			ReturnType::Type(_, ty) => ty,278			_ => 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)")),279		};280		let result = parse_result_ok(result)?;281282		let camel_name = info283			.rename_selector284			.unwrap_or_else(|| cases::camelcase::to_camel_case(&ident.to_string()));285		let mut selector_str = camel_name.clone();286		selector_str.push('(');287		let mut has_normal_args = false;288		for (i, arg) in args.iter().filter(|arg| !arg.is_special()).enumerate() {289			if i != 0 {290				selector_str.push(',');291			}292			write!(selector_str, "{}", arg.selector_ty()).unwrap();293			has_normal_args = true;294		}295		selector_str.push(')');296		let selector = fn_selector_str(&selector_str);297298		Ok(Self {299			name: ident.clone(),300			camel_name,301			pascal_name: snake_ident_to_pascal(ident),302			screaming_name: snake_ident_to_screaming(ident),303			selector_str,304			selector,305			args,306			has_normal_args,307			mutability,308			result: result.clone(),309		})310	}311	fn expand_call_def(&self) -> proc_macro2::TokenStream {312		let defs = self313			.args314			.iter()315			.filter(|a| !a.is_special())316			.map(|a| a.expand_call_def());317		let pascal_name = &self.pascal_name;318319		if self.has_normal_args {320			quote! {321				#pascal_name {322					#(323						#defs,324					)*325				}326			}327		} else {328			quote! {#pascal_name}329		}330	}331332	fn expand_const(&self) -> proc_macro2::TokenStream {333		let screaming_name = &self.screaming_name;334		let selector = self.selector;335		let selector_str = &self.selector_str;336		quote! {337			#[doc = #selector_str]338			const #screaming_name: u32 = #selector;339		}340	}341342	fn expand_interface_id(&self) -> proc_macro2::TokenStream {343		let screaming_name = &self.screaming_name;344		quote! {345			interface_id ^= Self::#screaming_name;346		}347	}348349	fn expand_parse(&self) -> proc_macro2::TokenStream {350		let pascal_name = &self.pascal_name;351		let screaming_name = &self.screaming_name;352		if self.has_normal_args {353			let parsers = self354				.args355				.iter()356				.filter(|a| !a.is_special())357				.map(|a| a.expand_parse());358			quote! {359				Self::#screaming_name => return Ok(Some(Self::#pascal_name {360					#(361						#parsers,362					)*363				}))364			}365		} else {366			quote! { Self::#screaming_name => return Ok(Some(Self::#pascal_name)) }367		}368	}369370	fn expand_variant_call(&self) -> proc_macro2::TokenStream {371		let pascal_name = &self.pascal_name;372		let name = &self.name;373374		let matcher = if self.has_normal_args {375			let names = self376				.args377				.iter()378				.filter(|a| !a.is_special())379				.map(|a| &a.name);380381			quote! {{382				#(383					#names,384				)*385			}}386		} else {387			quote! {}388		};389390		let receiver = match self.mutability {391			Mutability::Mutable | Mutability::View => quote! {self.},392			Mutability::Pure => quote! {Self::},393		};394		let args = self.args.iter().map(|a| a.expand_call_arg());395396		quote! {397			InternalCall::#pascal_name #matcher => {398				let result = #receiver #name(399					#(400						#args,401					)*402				)?;403				(&result).abi_write(&mut writer);404			}405		}406	}407408	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {409		let camel_name = &self.camel_name;410		let mutability = match self.mutability {411			Mutability::Mutable => quote! {SolidityMutability::Mutable},412			Mutability::View => quote! { SolidityMutability::View },413			Mutability::Pure => quote! {SolidityMutability::Pure},414		};415		let result = &self.result;416417		let args = self418			.args419			.iter()420			.filter(|a| !a.is_special())421			.map(MethodArg::expand_solidity_argument);422423		quote! {424			SolidityFunction {425				name: #camel_name,426				mutability: #mutability,427				args: (428					#(429						#args,430					)*431				),432				result: <UnnamedArgument<#result>>::default(),433			}434		}435	}436}437438pub struct SolidityInterface {439	generics: Generics,440	name: Box<syn::Type>,441	info: InterfaceInfo,442	methods: Vec<Method>,443}444impl SolidityInterface {445	pub fn try_from(info: InterfaceInfo, value: &ItemImpl) -> syn::Result<Self> {446		let mut methods = Vec::new();447448		for item in &value.items {449			if let ImplItem::Method(method) = item {450				methods.push(Method::try_from(method)?)451			}452		}453		Ok(Self {454			generics: value.generics.clone(),455			name: value.self_ty.clone(),456			info,457			methods,458		})459	}460	pub fn expand(self) -> proc_macro2::TokenStream {461		let name = self.name;462463		let solidity_name = self.info.name.to_string();464		let call_name = pascal_ident_to_call(&self.info.name);465		let generics = self.generics;466467		let call_sub = self468			.info469			.inline_is470			.0471			.iter()472			.chain(self.info.is.0.iter())473			.map(Is::expand_call_def);474		let call_parse = self475			.info476			.inline_is477			.0478			.iter()479			.chain(self.info.is.0.iter())480			.map(Is::expand_parse);481		let call_variants = self482			.info483			.inline_is484			.0485			.iter()486			.chain(self.info.is.0.iter())487			.map(Is::expand_variant_call);488489		let inline_interface_id = self.info.inline_is.0.iter().map(Is::expand_interface_id);490		let supports_interface = self.info.is.0.iter().map(Is::expand_supports_interface);491492		let calls = self.methods.iter().map(Method::expand_call_def);493		let consts = self.methods.iter().map(Method::expand_const);494		let interface_id = self.methods.iter().map(Method::expand_interface_id);495		let parsers = self.methods.iter().map(Method::expand_parse);496		let call_variants_this = self.methods.iter().map(Method::expand_variant_call);497		let solidity_functions = self.methods.iter().map(Method::expand_solidity_function);498499		// TODO: Inline inline_is500		let solidity_is = self501			.info502			.is503			.0504			.iter()505			.chain(self.info.inline_is.0.iter())506			.map(|is| is.name.to_string());507		let solidity_events_is = self.info.events.0.iter().map(|is| is.name.to_string());508		let solidity_generators = self509			.info510			.is511			.0512			.iter()513			.chain(self.info.inline_is.0.iter())514			.map(Is::expand_generator);515		let solidity_event_generators = self.info.events.0.iter().map(Is::expand_event_generator);516517		// let methods = self.methods.iter().map(Method::solidity_def);518519		quote! {520			#[derive(Debug)]521			pub enum #call_name {522				#(523					#calls,524				)*525				#(526					#call_sub,527				)*528			}529			impl #call_name {530				#(531					#consts532				)*533				pub const fn interface_id() -> u32 {534					let mut interface_id = 0;535					#(#interface_id)*536					#(#inline_interface_id)*537					interface_id538				}539				pub fn supports_interface(interface_id: u32) -> bool {540					interface_id != 0xffffff && (541						interface_id == Self::interface_id()542						#(543							|| #supports_interface544						)*545					)546				}547				pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {548					use evm_coder::solidity::*;549					use core::fmt::Write;550					let interface = SolidityInterface {551						name: #solidity_name,552						is: &["Dummy", #(553							#solidity_is,554						)* #(555							#solidity_events_is,556						)* ],557						functions: (#(558							#solidity_functions,559						)*),560					};561					if is_impl {562						out_set.insert("// Common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\n".into());563					} else {564						out_set.insert("// Common stubs holder\ninterface Dummy {\n}\n".into());565					}566					#(567						#solidity_generators568					)*569					#(570						#solidity_event_generators571					)*572573					let mut out = string::new();574					// In solidity interface usage (is) should be preceeded by interface definition575					// This comment helps to sort it in a set576					if #solidity_name.starts_with("Inline") {577						out.push_str("// Inline\n");578					}579					let _ = interface.format(is_impl, &mut out);580					out_set.insert(out);581				}582			}583			impl ::evm_coder::Call for #call_name {584				fn parse(method_id: u32, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result<Option<Self>> {585					use ::evm_coder::abi::AbiRead;586					match method_id {587						#(588							#parsers,589						)*590						_ => {},591					}592					#(593						#call_parse594					)else*595					return Ok(None);596				}597			}598			impl #generics ::evm_coder::Callable<#call_name> for #name {599				#[allow(unreachable_code)] // In case of no inner calls600				fn call(&mut self, c: Msg<#call_name>) -> Result<::evm_coder::abi::AbiWriter> {601					use ::evm_coder::abi::AbiWrite;602					type InternalCall = #call_name;603					match c.call {604						#(605							#call_variants,606						)*607						_ => {},608					}609					let mut writer = ::evm_coder::abi::AbiWriter::default();610					match c.call {611						#(612							#call_variants_this,613						)*614						_ => unreachable!()615					}616					Ok(writer)617				}618			}619		}620	}621}
after · crates/evm-coder-macros/src/solidity_interface.rs
1#![allow(dead_code)]23use quote::quote;4use darling::{FromMeta, ToTokens};5use inflector::cases;6use std::fmt::Write;7use syn::{8	Expr, FnArg, GenericArgument, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta,9	NestedMeta, PatType, Path, PathArguments, ReturnType, Type, spanned::Spanned,10};1112use crate::{13	fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_path, parse_path_segment,14	parse_result_ok, pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal,15	snake_ident_to_screaming,16};1718struct Is {19	name: Ident,20	pascal_call_name: Ident,21	snake_call_name: Ident,22}23impl Is {24	fn try_from(path: &Path) -> syn::Result<Self> {25		let name = parse_ident_from_path(path, false)?.clone();26		Ok(Self {27			pascal_call_name: pascal_ident_to_call(&name),28			snake_call_name: pascal_ident_to_snake_call(&name),29			name,30		})31	}3233	fn expand_call_def(&self) -> proc_macro2::TokenStream {34		let name = &self.name;35		let pascal_call_name = &self.pascal_call_name;36		quote! {37			#name(#pascal_call_name)38		}39	}4041	fn expand_interface_id(&self) -> proc_macro2::TokenStream {42		let pascal_call_name = &self.pascal_call_name;43		quote! {44			interface_id ^= #pascal_call_name::interface_id();45		}46	}4748	fn expand_supports_interface(&self) -> proc_macro2::TokenStream {49		let pascal_call_name = &self.pascal_call_name;50		quote! {51			#pascal_call_name::supports_interface(interface_id)52		}53	}5455	fn expand_variant_call(&self) -> proc_macro2::TokenStream {56		let name = &self.name;57		let pascal_call_name = &self.pascal_call_name;58		quote! {59			InternalCall::#name(call) => return <Self as ::evm_coder::Callable<#pascal_call_name>>::call(self, Msg {60				call,61				caller: c.caller,62				value: c.value,63			})64		}65	}6667	fn expand_parse(&self) -> proc_macro2::TokenStream {68		let name = &self.name;69		let pascal_call_name = &self.pascal_call_name;70		quote! {71			if let Some(parsed_call) = #pascal_call_name::parse(method_id, reader)? {72				return Ok(Some(Self::#name(parsed_call)))73			}74		}75	}7677	fn expand_generator(&self) -> proc_macro2::TokenStream {78		let pascal_call_name = &self.pascal_call_name;79		quote! {80			#pascal_call_name::generate_solidity_interface(tc, is_impl);81		}82	}8384	fn expand_event_generator(&self) -> proc_macro2::TokenStream {85		let name = &self.name;86		quote! {87			#name::generate_solidity_interface(tc, is_impl);88		}89	}90}9192#[derive(Default)]93struct IsList(Vec<Is>);94impl FromMeta for IsList {95	fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {96		let mut out = Vec::new();97		for item in items {98			match item {99				NestedMeta::Meta(Meta::Path(path)) => out.push(Is::try_from(path)?),100				_ => return Err(syn::Error::new(item.span(), "expected path").into()),101			}102		}103		Ok(Self(out))104	}105}106107#[derive(FromMeta)]108pub struct InterfaceInfo {109	name: Ident,110	#[darling(default)]111	is: IsList,112	#[darling(default)]113	inline_is: IsList,114	#[darling(default)]115	events: IsList,116}117118#[derive(FromMeta)]119struct MethodInfo {120	#[darling(default)]121	rename_selector: Option<String>,122}123124enum AbiType {125	// type126	Plain(Ident),127	// (type1,type2)128	Tuple(Vec<AbiType>),129	// type[]130	Vec(Box<AbiType>),131	// type[20]132	Array(Box<AbiType>, usize),133}134impl AbiType {135	fn try_from(value: &Type) -> syn::Result<Self> {136		let value = Self::try_maybe_special_from(value)?;137		if value.is_special() {138			return Err(syn::Error::new(value.span(), "unexpected special type"));139		}140		Ok(value)141	}142	fn try_maybe_special_from(value: &Type) -> syn::Result<Self> {143		match value {144			Type::Array(arr) => {145				let wrapped = AbiType::try_from(&arr.elem)?;146				match &arr.len {147					Expr::Lit(l) => match &l.lit {148						Lit::Int(i) => {149							let num = i.base10_parse::<usize>()?;150							Ok(AbiType::Array(Box::new(wrapped), num as usize))151						}152						_ => Err(syn::Error::new(arr.len.span(), "should be int literal")),153					},154					_ => Err(syn::Error::new(arr.len.span(), "should be literal")),155				}156			}157			Type::Path(_) => {158				let path = parse_path(value)?;159				let segment = parse_path_segment(path)?;160				if segment.ident == "Vec" {161					let args = match &segment.arguments {162						PathArguments::AngleBracketed(e) => e,163						_ => {164							return Err(syn::Error::new(165								segment.arguments.span(),166								"missing Vec generic",167							))168						}169					};170					let args = &args.args;171					if args.len() != 1 {172						return Err(syn::Error::new(173							args.span(),174							"expected only one generic for vec",175						));176					}177					let arg = args.first().unwrap();178179					let ty = match arg {180						GenericArgument::Type(ty) => ty,181						_ => {182							return Err(syn::Error::new(183								arg.span(),184								"expected first generic to be type",185							))186						}187					};188189					let wrapped = AbiType::try_from(ty)?;190					Ok(Self::Vec(Box::new(wrapped)))191				} else {192					if !segment.arguments.is_empty() {193						return Err(syn::Error::new(194							segment.arguments.span(),195							"unexpected generic arguments for non-vec type",196						));197					}198					Ok(Self::Plain(segment.ident.clone()))199				}200			}201			Type::Tuple(t) => {202				let mut out = Vec::with_capacity(t.elems.len());203				for el in t.elems.iter() {204					out.push(AbiType::try_from(el)?)205				}206				Ok(Self::Tuple(out))207			}208			_ => Err(syn::Error::new(209				value.span(),210				"unexpected type, only arrays, plain types and tuples are supported",211			)),212		}213	}214	fn is_value(&self) -> bool {215		match self {216			Self::Plain(v) if v == "value" => true,217			_ => false,218		}219	}220	fn is_caller(&self) -> bool {221		match self {222			Self::Plain(v) if v == "caller" => true,223			_ => false,224		}225	}226	fn is_special(&self) -> bool {227		self.is_caller() || self.is_value()228	}229	fn selector_ty_buf(&self, buf: &mut String) -> std::fmt::Result {230		match self {231			AbiType::Plain(t) => {232				write!(buf, "{}", t)233			}234			AbiType::Tuple(t) => {235				write!(buf, "(")?;236				for (i, t) in t.iter().enumerate() {237					if i != 0 {238						write!(buf, ",")?;239					}240					t.selector_ty_buf(buf)?;241				}242				write!(buf, ")")243			}244			AbiType::Vec(v) => {245				v.selector_ty_buf(buf)?;246				write!(buf, "[]")247			}248			AbiType::Array(v, len) => {249				v.selector_ty_buf(buf)?;250				write!(buf, "[{}]", len)251			}252		}253	}254	fn selector_ty(&self) -> String {255		let mut out = String::new();256		self.selector_ty_buf(&mut out).expect("no fmt error");257		out258	}259}260impl ToTokens for AbiType {261	fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {262		match self {263			AbiType::Plain(t) => tokens.extend(quote! {#t}),264			AbiType::Tuple(t) => {265				tokens.extend(quote! {(266					#(#t),*267				)});268			}269			AbiType::Vec(v) => tokens.extend(quote! {Vec<#v>}),270			AbiType::Array(v, l) => tokens.extend(quote! {[#v; #l]}),271		}272	}273}274275struct MethodArg {276	name: Ident,277	camel_name: String,278	ty: AbiType,279}280impl MethodArg {281	fn try_from(value: &PatType) -> syn::Result<Self> {282		let name = parse_ident_from_pat(&value.pat)?.clone();283		Ok(Self {284			camel_name: cases::camelcase::to_camel_case(&name.to_string()),285			name,286			ty: AbiType::try_maybe_special_from(&value.ty)?,287		})288	}289	fn is_value(&self) -> bool {290		self.ty.is_value()291	}292	fn is_caller(&self) -> bool {293		self.ty.is_caller()294	}295	fn is_special(&self) -> bool {296		self.ty.is_special()297	}298	fn selector_ty(&self) -> String {299		assert!(!self.is_special());300		self.ty.selector_ty()301	}302303	fn expand_call_def(&self) -> proc_macro2::TokenStream {304		assert!(!self.is_special());305		let name = &self.name;306		let ty = &self.ty;307308		quote! {309			#name: #ty310		}311	}312313	fn expand_parse(&self) -> proc_macro2::TokenStream {314		assert!(!self.is_special());315		let name = &self.name;316		quote! {317			#name: reader.abi_read()?318		}319	}320321	fn expand_call_arg(&self) -> proc_macro2::TokenStream {322		if self.is_value() {323			quote! {324				c.value.clone()325			}326		} else if self.is_caller() {327			quote! {328				c.caller.clone()329			}330		} else {331			let name = &self.name;332			quote! {333				#name334			}335		}336	}337338	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {339		let camel_name = &self.camel_name.to_string();340		let ty = &self.ty;341		quote! {342			<NamedArgument<#ty>>::new(#camel_name)343		}344	}345}346347#[derive(PartialEq)]348enum Mutability {349	Mutable,350	View,351	Pure,352}353354struct Method {355	name: Ident,356	camel_name: String,357	pascal_name: Ident,358	screaming_name: Ident,359	selector_str: String,360	selector: u32,361	args: Vec<MethodArg>,362	has_normal_args: bool,363	mutability: Mutability,364	result: Type,365}366impl Method {367	fn try_from(value: &ImplItemMethod) -> syn::Result<Self> {368		let mut info = MethodInfo {369			rename_selector: None,370		};371		for attr in &value.attrs {372			let ident = parse_ident_from_path(&attr.path, false)?;373			if ident == "solidity" {374				let args = attr.parse_meta().unwrap();375				info = MethodInfo::from_meta(&args).unwrap();376			} else if ident == "doc" {377				// TODO: Add docs to evm interfaces378			}379		}380		let ident = &value.sig.ident;381		let ident_str = ident.to_string();382		if !cases::snakecase::is_snake_case(&ident_str) {383			return Err(syn::Error::new(ident.span(), "method name should be snake_cased\nif alternative solidity name needs to be set - use #[solidity] attribute"));384		}385386		let mut mutability = Mutability::Pure;387388		if let Some(FnArg::Receiver(receiver)) = value389			.sig390			.inputs391			.iter()392			.find(|arg| matches!(arg, FnArg::Receiver(_)))393		{394			if receiver.reference.is_none() {395				return Err(syn::Error::new(396					receiver.span(),397					"receiver should be by ref",398				));399			}400			if receiver.mutability.is_some() {401				mutability = Mutability::Mutable;402			} else {403				mutability = Mutability::View;404			}405		}406		let mut args = Vec::new();407		for typ in value408			.sig409			.inputs410			.iter()411			.filter(|arg| matches!(arg, FnArg::Typed(_)))412		{413			let typ = match typ {414				FnArg::Typed(typ) => typ,415				_ => unreachable!(),416			};417			args.push(MethodArg::try_from(typ)?);418		}419420		if mutability != Mutability::Mutable && args.iter().any(|arg| arg.is_value()) {421			return Err(syn::Error::new(422				args.iter().find(|arg| arg.is_value()).unwrap().ty.span(),423				"payable function should be mutable",424			));425		}426427		let result = match &value.sig.output {428			ReturnType::Type(_, ty) => ty,429			_ => 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)")),430		};431		let result = parse_result_ok(result)?;432433		let camel_name = info434			.rename_selector435			.unwrap_or_else(|| cases::camelcase::to_camel_case(&ident.to_string()));436		let mut selector_str = camel_name.clone();437		selector_str.push('(');438		let mut has_normal_args = false;439		for (i, arg) in args.iter().filter(|arg| !arg.is_special()).enumerate() {440			if i != 0 {441				selector_str.push(',');442			}443			write!(selector_str, "{}", arg.selector_ty()).unwrap();444			has_normal_args = true;445		}446		selector_str.push(')');447		let selector = fn_selector_str(&selector_str);448449		Ok(Self {450			name: ident.clone(),451			camel_name,452			pascal_name: snake_ident_to_pascal(ident),453			screaming_name: snake_ident_to_screaming(ident),454			selector_str,455			selector,456			args,457			has_normal_args,458			mutability,459			result: result.clone(),460		})461	}462	fn expand_call_def(&self) -> proc_macro2::TokenStream {463		let defs = self464			.args465			.iter()466			.filter(|a| !a.is_special())467			.map(|a| a.expand_call_def());468		let pascal_name = &self.pascal_name;469470		if self.has_normal_args {471			quote! {472				#pascal_name {473					#(474						#defs,475					)*476				}477			}478		} else {479			quote! {#pascal_name}480		}481	}482483	fn expand_const(&self) -> proc_macro2::TokenStream {484		let screaming_name = &self.screaming_name;485		let selector = self.selector;486		let selector_str = &self.selector_str;487		quote! {488			#[doc = #selector_str]489			const #screaming_name: u32 = #selector;490		}491	}492493	fn expand_interface_id(&self) -> proc_macro2::TokenStream {494		let screaming_name = &self.screaming_name;495		quote! {496			interface_id ^= Self::#screaming_name;497		}498	}499500	fn expand_parse(&self) -> proc_macro2::TokenStream {501		let pascal_name = &self.pascal_name;502		let screaming_name = &self.screaming_name;503		if self.has_normal_args {504			let parsers = self505				.args506				.iter()507				.filter(|a| !a.is_special())508				.map(|a| a.expand_parse());509			quote! {510				Self::#screaming_name => return Ok(Some(Self::#pascal_name {511					#(512						#parsers,513					)*514				}))515			}516		} else {517			quote! { Self::#screaming_name => return Ok(Some(Self::#pascal_name)) }518		}519	}520521	fn expand_variant_call(&self) -> proc_macro2::TokenStream {522		let pascal_name = &self.pascal_name;523		let name = &self.name;524525		let matcher = if self.has_normal_args {526			let names = self527				.args528				.iter()529				.filter(|a| !a.is_special())530				.map(|a| &a.name);531532			quote! {{533				#(534					#names,535				)*536			}}537		} else {538			quote! {}539		};540541		let receiver = match self.mutability {542			Mutability::Mutable | Mutability::View => quote! {self.},543			Mutability::Pure => quote! {Self::},544		};545		let args = self.args.iter().map(|a| a.expand_call_arg());546547		quote! {548			InternalCall::#pascal_name #matcher => {549				let result = #receiver #name(550					#(551						#args,552					)*553				)?;554				(&result).abi_write(&mut writer);555			}556		}557	}558559	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {560		let camel_name = &self.camel_name;561		let mutability = match self.mutability {562			Mutability::Mutable => quote! {SolidityMutability::Mutable},563			Mutability::View => quote! { SolidityMutability::View },564			Mutability::Pure => quote! {SolidityMutability::Pure},565		};566		let result = &self.result;567568		let args = self569			.args570			.iter()571			.filter(|a| !a.is_special())572			.map(MethodArg::expand_solidity_argument);573574		quote! {575			SolidityFunction {576				name: #camel_name,577				mutability: #mutability,578				args: (579					#(580						#args,581					)*582				),583				result: <UnnamedArgument<#result>>::default(),584			}585		}586	}587}588589pub struct SolidityInterface {590	generics: Generics,591	name: Box<syn::Type>,592	info: InterfaceInfo,593	methods: Vec<Method>,594}595impl SolidityInterface {596	pub fn try_from(info: InterfaceInfo, value: &ItemImpl) -> syn::Result<Self> {597		let mut methods = Vec::new();598599		for item in &value.items {600			if let ImplItem::Method(method) = item {601				methods.push(Method::try_from(method)?)602			}603		}604		Ok(Self {605			generics: value.generics.clone(),606			name: value.self_ty.clone(),607			info,608			methods,609		})610	}611	pub fn expand(self) -> proc_macro2::TokenStream {612		let name = self.name;613614		let solidity_name = self.info.name.to_string();615		let call_name = pascal_ident_to_call(&self.info.name);616		let generics = self.generics;617618		let call_sub = self619			.info620			.inline_is621			.0622			.iter()623			.chain(self.info.is.0.iter())624			.map(Is::expand_call_def);625		let call_parse = self626			.info627			.inline_is628			.0629			.iter()630			.chain(self.info.is.0.iter())631			.map(Is::expand_parse);632		let call_variants = self633			.info634			.inline_is635			.0636			.iter()637			.chain(self.info.is.0.iter())638			.map(Is::expand_variant_call);639640		let inline_interface_id = self.info.inline_is.0.iter().map(Is::expand_interface_id);641		let supports_interface = self.info.is.0.iter().map(Is::expand_supports_interface);642643		let calls = self.methods.iter().map(Method::expand_call_def);644		let consts = self.methods.iter().map(Method::expand_const);645		let interface_id = self.methods.iter().map(Method::expand_interface_id);646		let parsers = self.methods.iter().map(Method::expand_parse);647		let call_variants_this = self.methods.iter().map(Method::expand_variant_call);648		let solidity_functions = self.methods.iter().map(Method::expand_solidity_function);649650		// TODO: Inline inline_is651		let solidity_is = self652			.info653			.is654			.0655			.iter()656			.chain(self.info.inline_is.0.iter())657			.map(|is| is.name.to_string());658		let solidity_events_is = self.info.events.0.iter().map(|is| is.name.to_string());659		let solidity_generators = self660			.info661			.is662			.0663			.iter()664			.chain(self.info.inline_is.0.iter())665			.map(Is::expand_generator);666		let solidity_event_generators = self.info.events.0.iter().map(Is::expand_event_generator);667668		// let methods = self.methods.iter().map(Method::solidity_def);669670		quote! {671			#[derive(Debug)]672			pub enum #call_name {673				#(674					#calls,675				)*676				#(677					#call_sub,678				)*679			}680			impl #call_name {681				#(682					#consts683				)*684				pub const fn interface_id() -> u32 {685					let mut interface_id = 0;686					#(#interface_id)*687					#(#inline_interface_id)*688					interface_id689				}690				pub fn supports_interface(interface_id: u32) -> bool {691					interface_id != 0xffffff && (692						interface_id == Self::interface_id()693						#(694							|| #supports_interface695						)*696					)697				}698				pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {699					use evm_coder::solidity::*;700					use core::fmt::Write;701					let interface = SolidityInterface {702						name: #solidity_name,703						is: &["Dummy", #(704							#solidity_is,705						)* #(706							#solidity_events_is,707						)* ],708						functions: (#(709							#solidity_functions,710						)*),711					};712					if is_impl {713						tc.collect("// Common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\n".into());714					} else {715						tc.collect("// Common stubs holder\ninterface Dummy {\n}\n".into());716					}717					#(718						#solidity_generators719					)*720					#(721						#solidity_event_generators722					)*723724					let mut out = string::new();725					// In solidity interface usage (is) should be preceeded by interface definition726					// This comment helps to sort it in a set727					if #solidity_name.starts_with("Inline") {728						out.push_str("// Inline\n");729					}730					let _ = interface.format(is_impl, &mut out, tc);731					tc.collect(out);732				}733			}734			impl ::evm_coder::Call for #call_name {735				fn parse(method_id: u32, reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result<Option<Self>> {736					use ::evm_coder::abi::AbiRead;737					match method_id {738						#(739							#parsers,740						)*741						_ => {},742					}743					#(744						#call_parse745					)else*746					return Ok(None);747				}748			}749			impl #generics ::evm_coder::Callable<#call_name> for #name {750				#[allow(unreachable_code)] // In case of no inner calls751				fn call(&mut self, c: Msg<#call_name>) -> Result<::evm_coder::abi::AbiWriter> {752					use ::evm_coder::abi::AbiWrite;753					type InternalCall = #call_name;754					match c.call {755						#(756							#call_variants,757						)*758						_ => {},759					}760					let mut writer = ::evm_coder::abi::AbiWriter::default();761					match c.call {762						#(763							#call_variants_this,764						)*765						_ => unreachable!()766					}767					Ok(writer)768				}769			}770		}771	}772}
modifiedcrates/evm-coder-macros/src/to_log.rsdiffbeforeafterboth
--- a/crates/evm-coder-macros/src/to_log.rs
+++ b/crates/evm-coder-macros/src/to_log.rs
@@ -179,7 +179,7 @@
 					#consts
 				)*
 
-				pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {
+				pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {
 					use evm_coder::solidity::*;
 					use core::fmt::Write;
 					let interface = SolidityInterface {
@@ -191,8 +191,8 @@
 					};
 					let mut out = string::new();
 					out.push_str("// Inline\n");
-					let _ = interface.format(is_impl, &mut out);
-					out_set.insert(out);
+					let _ = interface.format(is_impl, &mut out, tc);
+					tc.collect(out);
 				}
 			}
 
modifiedcrates/evm-coder/src/abi.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/abi.rs
+++ b/crates/evm-coder/src/abi.rs
@@ -247,6 +247,51 @@
 impl_abi_readable!(bool, bool);
 impl_abi_readable!(string, string);
 
+mod sealed {
+	/// Not all types can be placed in vec, i.e `Vec<u8>` is restricted, `bytes` should be used instead
+	pub trait CanBePlacedInVec {}
+}
+
+impl sealed::CanBePlacedInVec for U256 {}
+impl sealed::CanBePlacedInVec for string {}
+impl sealed::CanBePlacedInVec for H160 {}
+
+impl<R: sealed::CanBePlacedInVec> AbiRead<Vec<R>> for AbiReader<'_>
+where
+	Self: AbiRead<R>,
+{
+	fn abi_read(&mut self) -> Result<Vec<R>> {
+		todo!()
+	}
+}
+
+macro_rules! impl_tuples {
+	($($ident:ident)+) => {
+		impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}
+		impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_>
+		where
+			$(Self: AbiRead<$ident>),+
+		{
+			fn abi_read(&mut self) -> Result<($($ident,)+)> {
+				Ok((
+					$(<Self as AbiRead<$ident>>::abi_read(self)?,)+
+				))
+			}
+		}
+	};
+}
+
+impl_tuples! {A}
+impl_tuples! {A B}
+impl_tuples! {A B C}
+impl_tuples! {A B C D}
+impl_tuples! {A B C D E}
+impl_tuples! {A B C D E F}
+impl_tuples! {A B C D E F G}
+impl_tuples! {A B C D E F G H}
+impl_tuples! {A B C D E F G H I}
+impl_tuples! {A B C D E F G H I J}
+
 pub trait AbiWrite {
 	fn abi_write(&self, writer: &mut AbiWriter);
 }
modifiedcrates/evm-coder/src/lib.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -67,8 +67,8 @@
 		#[test]
 		#[ignore]
 		fn $name() {
-			use sp_std::collections::btree_set::BTreeSet;
-			let mut out = BTreeSet::new();
+			use evm_coder::solidity::TypeCollector;
+			let mut out = TypeCollector::new();
 			$decl::generate_solidity_interface(&mut out, $is_impl);
 			println!("=== SNIP START ===");
 			println!("// SPDX-License-Identifier: OTHER");
@@ -76,7 +76,7 @@
 			println!();
 			println!("pragma solidity >=0.8.0 <0.9.0;");
 			println!();
-			for b in out {
+			for b in out.finish() {
 				println!("{}", b);
 			}
 			println!("=== SNIP END ===");
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -1,25 +1,79 @@
 #[cfg(not(feature = "std"))]
-use alloc::{string::String};
-use core::{fmt, marker::PhantomData};
+use alloc::{
+	string::String,
+	vec::Vec,
+	collections::{BTreeSet, BTreeMap},
+	format,
+};
+#[cfg(feature = "std")]
+use std::collections::{BTreeSet, BTreeMap};
+use core::{
+	fmt::{self, Write},
+	marker::PhantomData,
+	cell::{Cell, RefCell},
+};
 use impl_trait_for_tuples::impl_for_tuples;
 use crate::types::*;
 
+#[derive(Default)]
+pub struct TypeCollector {
+	structs: RefCell<BTreeSet<string>>,
+	anonymous: RefCell<BTreeMap<Vec<string>, usize>>,
+	id: Cell<usize>,
+}
+impl TypeCollector {
+	pub fn new() -> Self {
+		Self::default()
+	}
+	pub fn collect(&self, item: string) {
+		self.structs.borrow_mut().insert(item);
+	}
+	pub fn next_id(&self) -> usize {
+		let v = self.id.get();
+		self.id.set(v + 1);
+		v
+	}
+	pub fn collect_tuple<T: SolidityTupleType>(&self) -> String {
+		let names = T::names(self);
+		if let Some(id) = self.anonymous.borrow().get(&names).cloned() {
+			return format!("Tuple{}", id);
+		}
+		let id = self.next_id();
+		let mut str = String::new();
+		writeln!(str, "// Anonymous struct").unwrap();
+		writeln!(str, "struct Tuple{} {{", id).unwrap();
+		for (i, name) in names.iter().enumerate() {
+			writeln!(str, "\t{} field_{};", name, i).unwrap();
+		}
+		writeln!(str, "}}").unwrap();
+		self.collect(str);
+		self.anonymous.borrow_mut().insert(names, id);
+		format!("Tuple{}", id)
+	}
+	pub fn finish(self) -> BTreeSet<string> {
+		self.structs.into_inner()
+	}
+}
+
 pub trait SolidityTypeName: 'static {
-	fn solidity_name(writer: &mut impl fmt::Write) -> fmt::Result;
-	fn solidity_default(writer: &mut impl fmt::Write) -> fmt::Result;
+	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
+	fn is_simple() -> bool;
+	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
 	fn is_void() -> bool {
 		false
 	}
 }
-
 macro_rules! solidity_type_name {
-    ($($ty:ident => $name:literal = $default:literal),* $(,)?) => {
+    ($($ty:ty => $name:literal $simple:literal = $default:literal),* $(,)?) => {
         $(
             impl SolidityTypeName for $ty {
-                fn solidity_name(writer: &mut impl core::fmt::Write) -> core::fmt::Result {
+                fn solidity_name(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {
                     write!(writer, $name)
                 }
-				fn solidity_default(writer: &mut impl core::fmt::Write) -> core::fmt::Result {
+				fn is_simple() -> bool {
+					$simple
+				}
+				fn solidity_default(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {
 					write!(writer, $default)
 				}
             }
@@ -28,20 +82,23 @@
 }
 
 solidity_type_name! {
-	uint8 => "uint8" = "0",
-	uint32 => "uint32" = "0",
-	uint128 => "uint128" = "0",
-	uint256 => "uint256" = "0",
-	address => "address" = "0x0000000000000000000000000000000000000000",
-	string => "string memory" = "\"\"",
-	bytes => "bytes memory" = "hex\"\"",
-	bool => "bool" = "false",
+	uint8 => "uint8" true = "0",
+	uint32 => "uint32" true = "0",
+	uint128 => "uint128" true = "0",
+	uint256 => "uint256" true = "0",
+	address => "address" true = "0x0000000000000000000000000000000000000000",
+	string => "string" false = "\"\"",
+	bytes => "bytes" false = "hex\"\"",
+	bool => "bool" true = "false",
 }
 impl SolidityTypeName for void {
-	fn solidity_name(_writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
 		Ok(())
 	}
-	fn solidity_default(_writer: &mut impl fmt::Write) -> fmt::Result {
+	fn is_simple() -> bool {
+		true
+	}
+	fn solidity_default(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
 		Ok(())
 	}
 	fn is_void() -> bool {
@@ -49,10 +106,88 @@
 	}
 }
 
+mod sealed {
+	pub trait CanBePlacedInVec {}
+}
+
+impl sealed::CanBePlacedInVec for uint256 {}
+impl sealed::CanBePlacedInVec for string {}
+impl sealed::CanBePlacedInVec for address {}
+
+impl<T: SolidityTypeName + sealed::CanBePlacedInVec> SolidityTypeName for Vec<T> {
+	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		T::solidity_name(writer, tc)?;
+		write!(writer, "[]")
+	}
+	fn is_simple() -> bool {
+		false
+	}
+	fn solidity_default(writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
+		write!(writer, "[]")
+	}
+}
+
+pub trait SolidityTupleType {
+	fn names(tc: &TypeCollector) -> Vec<String>;
+	fn len() -> usize;
+}
+
+macro_rules! count {
+    () => (0usize);
+    ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*));
+}
+
+macro_rules! impl_tuples {
+	($($ident:ident)+) => {
+		impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}
+		impl<$($ident: SolidityTypeName + 'static),+> SolidityTupleType for ($($ident,)+) {
+			fn names(tc: &TypeCollector) -> Vec<string> {
+				let mut collected = Vec::with_capacity(Self::len());
+				$({
+					let mut out = string::new();
+					$ident::solidity_name(&mut out, tc).expect("no fmt error");
+					collected.push(out);
+				})*;
+				collected
+			}
+
+			fn len() -> usize {
+				count!($($ident)*)
+			}
+		}
+		impl<$($ident: SolidityTypeName + 'static),+> SolidityTypeName for ($($ident,)+) {
+			fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+				write!(writer, "{}", tc.collect_tuple::<Self>())
+			}
+			fn is_simple() -> bool {
+				false
+			}
+			fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+				write!(writer, "{}(", tc.collect_tuple::<Self>())?;
+				$(
+					<$ident>::solidity_default(writer, tc)?;
+				)*
+				write!(writer, ")")
+			}
+		}
+	};
+}
+
+impl_tuples! {A}
+impl_tuples! {A B}
+impl_tuples! {A B C}
+impl_tuples! {A B C D}
+impl_tuples! {A B C D E}
+impl_tuples! {A B C D E F}
+impl_tuples! {A B C D E F G}
+impl_tuples! {A B C D E F G H}
+impl_tuples! {A B C D E F G H I}
+impl_tuples! {A B C D E F G H I J}
+
 pub trait SolidityArguments {
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result;
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
 	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result;
-	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result;
+	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
 	fn is_empty(&self) -> bool {
 		self.len() == 0
 	}
@@ -63,9 +198,13 @@
 pub struct UnnamedArgument<T>(PhantomData<*const T>);
 
 impl<T: SolidityTypeName> SolidityArguments for UnnamedArgument<T> {
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
 		if !T::is_void() {
-			T::solidity_name(writer)
+			T::solidity_name(writer, tc)?;
+			if !T::is_simple() {
+				write!(writer, " memory")?;
+			}
+			Ok(())
 		} else {
 			Ok(())
 		}
@@ -73,8 +212,8 @@
 	fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
 		Ok(())
 	}
-	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
-		T::solidity_default(writer)
+	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		T::solidity_default(writer, tc)
 	}
 	fn len(&self) -> usize {
 		if T::is_void() {
@@ -94,9 +233,12 @@
 }
 
 impl<T: SolidityTypeName> SolidityArguments for NamedArgument<T> {
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
 		if !T::is_void() {
-			T::solidity_name(writer)?;
+			T::solidity_name(writer, tc)?;
+			if !T::is_simple() {
+				write!(writer, " memory")?;
+			}
 			write!(writer, " {}", self.0)
 		} else {
 			Ok(())
@@ -105,8 +247,8 @@
 	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {
 		writeln!(writer, "\t\t{};", self.0)
 	}
-	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
-		T::solidity_default(writer)
+	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		T::solidity_default(writer, tc)
 	}
 	fn len(&self) -> usize {
 		if T::is_void() {
@@ -126,9 +268,9 @@
 }
 
 impl<T: SolidityTypeName> SolidityArguments for SolidityEventArgument<T> {
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
 		if !T::is_void() {
-			T::solidity_name(writer)?;
+			T::solidity_name(writer, tc)?;
 			if self.0 {
 				write!(writer, " indexed")?;
 			}
@@ -140,8 +282,8 @@
 	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {
 		writeln!(writer, "\t\t{};", self.1)
 	}
-	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
-		T::solidity_default(writer)
+	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		T::solidity_default(writer, tc)
 	}
 	fn len(&self) -> usize {
 		if T::is_void() {
@@ -153,13 +295,13 @@
 }
 
 impl SolidityArguments for () {
-	fn solidity_name(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
 		Ok(())
 	}
 	fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
 		Ok(())
 	}
-	fn solidity_default(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_default(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
 		Ok(())
 	}
 	fn len(&self) -> usize {
@@ -171,7 +313,7 @@
 impl SolidityArguments for Tuple {
 	for_tuples!( where #( Tuple: SolidityArguments ),* );
 
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
 		let mut first = true;
 		for_tuples!( #(
             if !Tuple.is_empty() {
@@ -179,7 +321,7 @@
                     write!(writer, ", ")?;
                 }
                 first = false;
-                Tuple.solidity_name(writer)?;
+                Tuple.solidity_name(writer, tc)?;
             }
         )* );
 		Ok(())
@@ -190,12 +332,12 @@
         )* );
 		Ok(())
 	}
-	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
 		if self.is_empty() {
 			Ok(())
 		} else if self.len() == 1 {
 			for_tuples!( #(
-				Tuple.solidity_default(writer)?;
+				Tuple.solidity_default(writer, tc)?;
 			)* );
 			Ok(())
 		} else {
@@ -207,7 +349,7 @@
 						write!(writer, ", ")?;
 					}
 					first = false;
-					Tuple.solidity_name(writer)?;
+					Tuple.solidity_default(writer, tc)?;
 				}
 			)* );
 			write!(writer, ")")?;
@@ -220,7 +362,12 @@
 }
 
 pub trait SolidityFunctions {
-	fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result;
+	fn solidity_name(
+		&self,
+		is_impl: bool,
+		writer: &mut impl fmt::Write,
+		tc: &TypeCollector,
+	) -> fmt::Result;
 }
 
 pub enum SolidityMutability {
@@ -235,9 +382,14 @@
 	pub mutability: SolidityMutability,
 }
 impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {
-	fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(
+		&self,
+		is_impl: bool,
+		writer: &mut impl fmt::Write,
+		tc: &TypeCollector,
+	) -> fmt::Result {
 		write!(writer, "\tfunction {}(", self.name)?;
-		self.args.solidity_name(writer)?;
+		self.args.solidity_name(writer, tc)?;
 		write!(writer, ")")?;
 		if is_impl {
 			write!(writer, " public")?;
@@ -251,7 +403,7 @@
 		}
 		if !self.result.is_empty() {
 			write!(writer, " returns (")?;
-			self.result.solidity_name(writer)?;
+			self.result.solidity_name(writer, tc)?;
 			write!(writer, ")")?;
 		}
 		if is_impl {
@@ -265,7 +417,7 @@
 			}
 			if !self.result.is_empty() {
 				write!(writer, "\t\treturn ")?;
-				self.result.solidity_default(writer)?;
+				self.result.solidity_default(writer, tc)?;
 				writeln!(writer, ";")?;
 			}
 			writeln!(writer, "\t}}")?;
@@ -280,10 +432,15 @@
 impl SolidityFunctions for Tuple {
 	for_tuples!( where #( Tuple: SolidityFunctions ),* );
 
-	fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(
+		&self,
+		is_impl: bool,
+		writer: &mut impl fmt::Write,
+		tc: &TypeCollector,
+	) -> fmt::Result {
 		let mut first = false;
 		for_tuples!( #(
-            Tuple.solidity_name(is_impl, writer)?;
+            Tuple.solidity_name(is_impl, writer, tc)?;
         )* );
 		Ok(())
 	}
@@ -296,7 +453,12 @@
 }
 
 impl<F: SolidityFunctions> SolidityInterface<F> {
-	pub fn format(&self, is_impl: bool, out: &mut impl fmt::Write) -> fmt::Result {
+	pub fn format(
+		&self,
+		is_impl: bool,
+		out: &mut impl fmt::Write,
+		tc: &TypeCollector,
+	) -> fmt::Result {
 		if is_impl {
 			write!(out, "contract ")?;
 		} else {
@@ -313,7 +475,7 @@
 			}
 		}
 		writeln!(out, " {{")?;
-		self.functions.solidity_name(is_impl, out)?;
+		self.functions.solidity_name(is_impl, out, tc)?;
 		writeln!(out, "}}")?;
 		Ok(())
 	}
@@ -325,9 +487,14 @@
 }
 
 impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {
-	fn solidity_name(&self, _is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(
+		&self,
+		_is_impl: bool,
+		writer: &mut impl fmt::Write,
+		tc: &TypeCollector,
+	) -> fmt::Result {
 		write!(writer, "\tevent {}(", self.name)?;
-		self.args.solidity_name(writer)?;
+		self.args.solidity_name(writer, tc)?;
 		writeln!(writer, ");")
 	}
 }