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
--- a/crates/evm-coder-macros/src/solidity_interface.rs
+++ b/crates/evm-coder-macros/src/solidity_interface.rs
@@ -1,16 +1,16 @@
 #![allow(dead_code)]
 
 use quote::quote;
-use darling::FromMeta;
+use darling::{FromMeta, ToTokens};
 use inflector::cases;
 use std::fmt::Write;
 use syn::{
-	FnArg, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Meta, NestedMeta, PatType, Path,
-	ReturnType, Type, spanned::Spanned,
+	Expr, FnArg, GenericArgument, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta,
+	NestedMeta, PatType, Path, PathArguments, ReturnType, Type, spanned::Spanned,
 };
 
 use crate::{
-	fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_ident_from_type,
+	fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_path, parse_path_segment,
 	parse_result_ok, pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal,
 	snake_ident_to_screaming,
 };
@@ -77,14 +77,14 @@
 	fn expand_generator(&self) -> proc_macro2::TokenStream {
 		let pascal_call_name = &self.pascal_call_name;
 		quote! {
-			#pascal_call_name::generate_solidity_interface(out_set, is_impl);
+			#pascal_call_name::generate_solidity_interface(tc, is_impl);
 		}
 	}
 
 	fn expand_event_generator(&self) -> proc_macro2::TokenStream {
 		let name = &self.name;
 		quote! {
-			#name::generate_solidity_interface(out_set, is_impl);
+			#name::generate_solidity_interface(tc, is_impl);
 		}
 	}
 }
@@ -121,10 +121,161 @@
 	rename_selector: Option<String>,
 }
 
+enum AbiType {
+	// type
+	Plain(Ident),
+	// (type1,type2)
+	Tuple(Vec<AbiType>),
+	// type[]
+	Vec(Box<AbiType>),
+	// type[20]
+	Array(Box<AbiType>, usize),
+}
+impl AbiType {
+	fn try_from(value: &Type) -> syn::Result<Self> {
+		let value = Self::try_maybe_special_from(value)?;
+		if value.is_special() {
+			return Err(syn::Error::new(value.span(), "unexpected special type"));
+		}
+		Ok(value)
+	}
+	fn try_maybe_special_from(value: &Type) -> syn::Result<Self> {
+		match value {
+			Type::Array(arr) => {
+				let wrapped = AbiType::try_from(&arr.elem)?;
+				match &arr.len {
+					Expr::Lit(l) => match &l.lit {
+						Lit::Int(i) => {
+							let num = i.base10_parse::<usize>()?;
+							Ok(AbiType::Array(Box::new(wrapped), num as usize))
+						}
+						_ => Err(syn::Error::new(arr.len.span(), "should be int literal")),
+					},
+					_ => Err(syn::Error::new(arr.len.span(), "should be literal")),
+				}
+			}
+			Type::Path(_) => {
+				let path = parse_path(value)?;
+				let segment = parse_path_segment(path)?;
+				if segment.ident == "Vec" {
+					let args = match &segment.arguments {
+						PathArguments::AngleBracketed(e) => e,
+						_ => {
+							return Err(syn::Error::new(
+								segment.arguments.span(),
+								"missing Vec generic",
+							))
+						}
+					};
+					let args = &args.args;
+					if args.len() != 1 {
+						return Err(syn::Error::new(
+							args.span(),
+							"expected only one generic for vec",
+						));
+					}
+					let arg = args.first().unwrap();
+
+					let ty = match arg {
+						GenericArgument::Type(ty) => ty,
+						_ => {
+							return Err(syn::Error::new(
+								arg.span(),
+								"expected first generic to be type",
+							))
+						}
+					};
+
+					let wrapped = AbiType::try_from(ty)?;
+					Ok(Self::Vec(Box::new(wrapped)))
+				} else {
+					if !segment.arguments.is_empty() {
+						return Err(syn::Error::new(
+							segment.arguments.span(),
+							"unexpected generic arguments for non-vec type",
+						));
+					}
+					Ok(Self::Plain(segment.ident.clone()))
+				}
+			}
+			Type::Tuple(t) => {
+				let mut out = Vec::with_capacity(t.elems.len());
+				for el in t.elems.iter() {
+					out.push(AbiType::try_from(el)?)
+				}
+				Ok(Self::Tuple(out))
+			}
+			_ => Err(syn::Error::new(
+				value.span(),
+				"unexpected type, only arrays, plain types and tuples are supported",
+			)),
+		}
+	}
+	fn is_value(&self) -> bool {
+		match self {
+			Self::Plain(v) if v == "value" => true,
+			_ => false,
+		}
+	}
+	fn is_caller(&self) -> bool {
+		match self {
+			Self::Plain(v) if v == "caller" => true,
+			_ => false,
+		}
+	}
+	fn is_special(&self) -> bool {
+		self.is_caller() || self.is_value()
+	}
+	fn selector_ty_buf(&self, buf: &mut String) -> std::fmt::Result {
+		match self {
+			AbiType::Plain(t) => {
+				write!(buf, "{}", t)
+			}
+			AbiType::Tuple(t) => {
+				write!(buf, "(")?;
+				for (i, t) in t.iter().enumerate() {
+					if i != 0 {
+						write!(buf, ",")?;
+					}
+					t.selector_ty_buf(buf)?;
+				}
+				write!(buf, ")")
+			}
+			AbiType::Vec(v) => {
+				v.selector_ty_buf(buf)?;
+				write!(buf, "[]")
+			}
+			AbiType::Array(v, len) => {
+				v.selector_ty_buf(buf)?;
+				write!(buf, "[{}]", len)
+			}
+		}
+	}
+	fn selector_ty(&self) -> String {
+		let mut out = String::new();
+		self.selector_ty_buf(&mut out).expect("no fmt error");
+		out
+	}
+}
+impl ToTokens for AbiType {
+	fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+		match self {
+			AbiType::Plain(t) => tokens.extend(quote! {#t}),
+			AbiType::Tuple(t) => {
+				tokens.extend(quote! {(
+					#(#t),*
+				)});
+			}
+			AbiType::Vec(v) => tokens.extend(quote! {Vec<#v>}),
+			AbiType::Array(v, l) => tokens.extend(quote! {[#v; #l]}),
+		}
+	}
+}
+
 struct MethodArg {
 	name: Ident,
 	camel_name: String,
-	ty: Ident,
+	ty: AbiType,
 }
 impl MethodArg {
 	fn try_from(value: &PatType) -> syn::Result<Self> {
@@ -132,21 +283,21 @@
 		Ok(Self {
 			camel_name: cases::camelcase::to_camel_case(&name.to_string()),
 			name,
-			ty: parse_ident_from_type(&value.ty, false)?.clone(),
+			ty: AbiType::try_maybe_special_from(&value.ty)?,
 		})
 	}
 	fn is_value(&self) -> bool {
-		self.ty == "value"
+		self.ty.is_value()
 	}
 	fn is_caller(&self) -> bool {
-		self.ty == "caller"
+		self.ty.is_caller()
 	}
 	fn is_special(&self) -> bool {
-		self.is_value() || self.is_caller()
+		self.ty.is_special()
 	}
-	fn selector_ty(&self) -> &Ident {
+	fn selector_ty(&self) -> String {
 		assert!(!self.is_special());
-		&self.ty
+		self.ty.selector_ty()
 	}
 
 	fn expand_call_def(&self) -> proc_macro2::TokenStream {
@@ -544,7 +695,7 @@
 						)*
 					)
 				}
-				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 {
@@ -559,9 +710,9 @@
 						)*),
 					};
 					if is_impl {
-						out_set.insert("// Common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\n".into());
+						tc.collect("// Common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\n".into());
 					} else {
-						out_set.insert("// Common stubs holder\ninterface Dummy {\n}\n".into());
+						tc.collect("// Common stubs holder\ninterface Dummy {\n}\n".into());
 					}
 					#(
 						#solidity_generators
@@ -576,8 +727,8 @@
 					if #solidity_name.starts_with("Inline") {
 						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);
 				}
 			}
 			impl ::evm_coder::Call for #call_name {
modifiedcrates/evm-coder-macros/src/to_log.rsdiffbeforeafterboth
before · crates/evm-coder-macros/src/to_log.rs
1use inflector::cases;2use syn::{Data, DeriveInput, Field, Fields, Ident, Variant, spanned::Spanned};3use std::fmt::Write;4use quote::quote;56use crate::{parse_ident_from_path, parse_ident_from_type, snake_ident_to_screaming};78struct EventField {9	name: Ident,10	camel_name: String,11	ty: Ident,12	indexed: bool,13}1415impl EventField {16	fn try_from(field: &Field) -> syn::Result<Self> {17		let name = field.ident.as_ref().unwrap();18		let ty = parse_ident_from_type(&field.ty, false)?;19		let mut indexed = false;20		for attr in &field.attrs {21			if let Ok(ident) = parse_ident_from_path(&attr.path, false) {22				if ident == "indexed" {23					indexed = true;24				}25			}26		}27		Ok(Self {28			name: name.to_owned(),29			camel_name: cases::camelcase::to_camel_case(&name.to_string()),30			ty: ty.to_owned(),31			indexed,32		})33	}34	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {35		let camel_name = &self.camel_name;36		let ty = &self.ty;37		let indexed = self.indexed;38		quote! {39			<SolidityEventArgument<#ty>>::new(#indexed, #camel_name)40		}41	}42}4344struct Event {45	name: Ident,46	name_screaming: Ident,47	fields: Vec<EventField>,48	selector: [u8; 32],49	selector_str: String,50}5152impl Event {53	fn try_from(variant: &Variant) -> syn::Result<Self> {54		let name = &variant.ident;55		let name_screaming = snake_ident_to_screaming(name);5657		let named = match &variant.fields {58			Fields::Named(named) => named,59			_ => {60				return Err(syn::Error::new(61					variant.fields.span(),62					"expected named fields",63				))64			}65		};66		let mut fields = Vec::new();67		for field in &named.named {68			fields.push(EventField::try_from(field)?);69		}70		if fields.iter().filter(|f| f.indexed).count() > 3 {71			return Err(syn::Error::new(72				variant.fields.span(),73				"events can have at most 4 indexed fields (1 indexed field is reserved for event signature)"74			));75		}76		let mut selector_str = format!("{}(", name);77		for (i, arg) in fields.iter().enumerate() {78			if i != 0 {79				write!(selector_str, ",").unwrap();80			}81			write!(selector_str, "{}", arg.ty).unwrap();82		}83		selector_str.push(')');84		let selector = crate::event_selector_str(&selector_str);8586		Ok(Self {87			name: name.to_owned(),88			name_screaming,89			fields,90			selector,91			selector_str,92		})93	}9495	fn expand_serializers(&self) -> proc_macro2::TokenStream {96		let name = &self.name;97		let name_screaming = &self.name_screaming;98		let fields = self.fields.iter().map(|f| &f.name);99100		let indexed = self.fields.iter().filter(|f| f.indexed).map(|f| &f.name);101		let plain = self.fields.iter().filter(|f| !f.indexed).map(|f| &f.name);102103		quote! {104			Self::#name {#(105				#fields,106			)*} => {107				topics.push(topic::from(Self::#name_screaming));108				#(109					topics.push(#indexed.to_topic());110				)*111				#(112					#plain.abi_write(&mut writer);113				)*114			}115		}116	}117118	fn expand_consts(&self) -> proc_macro2::TokenStream {119		let name_screaming = &self.name_screaming;120		let selector_str = &self.selector_str;121		let selector = &self.selector;122123		quote! {124			#[doc = #selector_str]125			const #name_screaming: [u8; 32] = [#(126				#selector,127			)*];128		}129	}130131	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {132		let name = self.name.to_string();133		let args = self.fields.iter().map(EventField::expand_solidity_argument);134		quote! {135			SolidityEvent {136				name: #name,137				args: (138					#(139						#args,140					)*141				),142			}143		}144	}145}146147pub struct Events {148	name: Ident,149	events: Vec<Event>,150}151152impl Events {153	pub fn try_from(data: &DeriveInput) -> syn::Result<Self> {154		let name = &data.ident;155		let en = match &data.data {156			Data::Enum(en) => en,157			_ => return Err(syn::Error::new(data.span(), "expected enum")),158		};159		let mut events = Vec::new();160		for variant in &en.variants {161			events.push(Event::try_from(variant)?);162		}163		Ok(Self {164			name: name.to_owned(),165			events,166		})167	}168	pub fn expand(&self) -> proc_macro2::TokenStream {169		let name = &self.name;170171		let consts = self.events.iter().map(Event::expand_consts);172		let serializers = self.events.iter().map(Event::expand_serializers);173		let solidity_name = self.name.to_string();174		let solidity_functions = self.events.iter().map(Event::expand_solidity_function);175176		quote! {177			impl #name {178				#(179					#consts180				)*181182				pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {183					use evm_coder::solidity::*;184					use core::fmt::Write;185					let interface = SolidityInterface {186						name: #solidity_name,187						is: &[],188						functions: (#(189							#solidity_functions,190						)*),191					};192					let mut out = string::new();193					out.push_str("// Inline\n");194					let _ = interface.format(is_impl, &mut out);195					out_set.insert(out);196				}197			}198199			#[automatically_derived]200			impl ::evm_coder::events::ToLog for #name {201				fn to_log(&self, contract: address) -> ::ethereum::Log {202					use ::evm_coder::events::ToTopic;203					use ::evm_coder::abi::AbiWrite;204					let mut writer = ::evm_coder::abi::AbiWriter::new();205					let mut topics = Vec::new();206					match self {207						#(208							#serializers,209						)*210					}211					::ethereum::Log {212						address: contract,213						topics,214						data: writer.finish(),215					}216				}217			}218		}219	}220}
after · crates/evm-coder-macros/src/to_log.rs
1use inflector::cases;2use syn::{Data, DeriveInput, Field, Fields, Ident, Variant, spanned::Spanned};3use std::fmt::Write;4use quote::quote;56use crate::{parse_ident_from_path, parse_ident_from_type, snake_ident_to_screaming};78struct EventField {9	name: Ident,10	camel_name: String,11	ty: Ident,12	indexed: bool,13}1415impl EventField {16	fn try_from(field: &Field) -> syn::Result<Self> {17		let name = field.ident.as_ref().unwrap();18		let ty = parse_ident_from_type(&field.ty, false)?;19		let mut indexed = false;20		for attr in &field.attrs {21			if let Ok(ident) = parse_ident_from_path(&attr.path, false) {22				if ident == "indexed" {23					indexed = true;24				}25			}26		}27		Ok(Self {28			name: name.to_owned(),29			camel_name: cases::camelcase::to_camel_case(&name.to_string()),30			ty: ty.to_owned(),31			indexed,32		})33	}34	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {35		let camel_name = &self.camel_name;36		let ty = &self.ty;37		let indexed = self.indexed;38		quote! {39			<SolidityEventArgument<#ty>>::new(#indexed, #camel_name)40		}41	}42}4344struct Event {45	name: Ident,46	name_screaming: Ident,47	fields: Vec<EventField>,48	selector: [u8; 32],49	selector_str: String,50}5152impl Event {53	fn try_from(variant: &Variant) -> syn::Result<Self> {54		let name = &variant.ident;55		let name_screaming = snake_ident_to_screaming(name);5657		let named = match &variant.fields {58			Fields::Named(named) => named,59			_ => {60				return Err(syn::Error::new(61					variant.fields.span(),62					"expected named fields",63				))64			}65		};66		let mut fields = Vec::new();67		for field in &named.named {68			fields.push(EventField::try_from(field)?);69		}70		if fields.iter().filter(|f| f.indexed).count() > 3 {71			return Err(syn::Error::new(72				variant.fields.span(),73				"events can have at most 4 indexed fields (1 indexed field is reserved for event signature)"74			));75		}76		let mut selector_str = format!("{}(", name);77		for (i, arg) in fields.iter().enumerate() {78			if i != 0 {79				write!(selector_str, ",").unwrap();80			}81			write!(selector_str, "{}", arg.ty).unwrap();82		}83		selector_str.push(')');84		let selector = crate::event_selector_str(&selector_str);8586		Ok(Self {87			name: name.to_owned(),88			name_screaming,89			fields,90			selector,91			selector_str,92		})93	}9495	fn expand_serializers(&self) -> proc_macro2::TokenStream {96		let name = &self.name;97		let name_screaming = &self.name_screaming;98		let fields = self.fields.iter().map(|f| &f.name);99100		let indexed = self.fields.iter().filter(|f| f.indexed).map(|f| &f.name);101		let plain = self.fields.iter().filter(|f| !f.indexed).map(|f| &f.name);102103		quote! {104			Self::#name {#(105				#fields,106			)*} => {107				topics.push(topic::from(Self::#name_screaming));108				#(109					topics.push(#indexed.to_topic());110				)*111				#(112					#plain.abi_write(&mut writer);113				)*114			}115		}116	}117118	fn expand_consts(&self) -> proc_macro2::TokenStream {119		let name_screaming = &self.name_screaming;120		let selector_str = &self.selector_str;121		let selector = &self.selector;122123		quote! {124			#[doc = #selector_str]125			const #name_screaming: [u8; 32] = [#(126				#selector,127			)*];128		}129	}130131	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {132		let name = self.name.to_string();133		let args = self.fields.iter().map(EventField::expand_solidity_argument);134		quote! {135			SolidityEvent {136				name: #name,137				args: (138					#(139						#args,140					)*141				),142			}143		}144	}145}146147pub struct Events {148	name: Ident,149	events: Vec<Event>,150}151152impl Events {153	pub fn try_from(data: &DeriveInput) -> syn::Result<Self> {154		let name = &data.ident;155		let en = match &data.data {156			Data::Enum(en) => en,157			_ => return Err(syn::Error::new(data.span(), "expected enum")),158		};159		let mut events = Vec::new();160		for variant in &en.variants {161			events.push(Event::try_from(variant)?);162		}163		Ok(Self {164			name: name.to_owned(),165			events,166		})167	}168	pub fn expand(&self) -> proc_macro2::TokenStream {169		let name = &self.name;170171		let consts = self.events.iter().map(Event::expand_consts);172		let serializers = self.events.iter().map(Event::expand_serializers);173		let solidity_name = self.name.to_string();174		let solidity_functions = self.events.iter().map(Event::expand_solidity_function);175176		quote! {177			impl #name {178				#(179					#consts180				)*181182				pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {183					use evm_coder::solidity::*;184					use core::fmt::Write;185					let interface = SolidityInterface {186						name: #solidity_name,187						is: &[],188						functions: (#(189							#solidity_functions,190						)*),191					};192					let mut out = string::new();193					out.push_str("// Inline\n");194					let _ = interface.format(is_impl, &mut out, tc);195					tc.collect(out);196				}197			}198199			#[automatically_derived]200			impl ::evm_coder::events::ToLog for #name {201				fn to_log(&self, contract: address) -> ::ethereum::Log {202					use ::evm_coder::events::ToTopic;203					use ::evm_coder::abi::AbiWrite;204					let mut writer = ::evm_coder::abi::AbiWriter::new();205					let mut topics = Vec::new();206					match self {207						#(208							#serializers,209						)*210					}211					::ethereum::Log {212						address: contract,213						topics,214						data: writer.finish(),215					}216				}217			}218		}219	}220}
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, ");")
 	}
 }