git.delta.rocks / unique-network / refs/commits / 2293f038f8e5

difftreelog

Merge branch 'develop' into tests/eth-helpers

Max Andreev2022-12-03parents: #a851f1b #bf1349a.patch.diff
in: master

59 files changed

modified.maintain/scripts/generate_abi.shdiffbeforeafterboth
--- a/.maintain/scripts/generate_abi.sh
+++ b/.maintain/scripts/generate_abi.sh
@@ -4,6 +4,7 @@
 dir=$PWD
 
 tmp=$(mktemp -d)
+echo "Tmp file: $tmp/input.sol"
 cd $tmp
 cp $dir/$INPUT input.sol
 solcjs --abi -p input.sol
modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1058,16 +1058,6 @@
 ]
 
 [[package]]
-name = "concat-idents"
-version = "1.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fe0e1d9f7de897d18e590a7496b5facbe87813f746cf4b8db596ba77e07e832"
-dependencies = [
- "quote",
- "syn",
-]
-
-[[package]]
 name = "concurrent-queue"
 version = "1.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2347,9 +2337,8 @@
 
 [[package]]
 name = "evm-coder"
-version = "0.1.4"
+version = "0.1.5"
 dependencies = [
- "concat-idents",
  "ethereum",
  "evm-coder-procedural",
  "evm-core",
@@ -2367,7 +2356,7 @@
 
 [[package]]
 name = "evm-coder-procedural"
-version = "0.2.1"
+version = "0.2.2"
 dependencies = [
  "Inflector",
  "hex",
modifiedcrates/evm-coder/CHANGELOG.mddiffbeforeafterboth
--- a/crates/evm-coder/CHANGELOG.md
+++ b/crates/evm-coder/CHANGELOG.md
@@ -3,6 +3,10 @@
 All notable changes to this project will be documented in this file.
 
 <!-- bureaucrate goes here -->
+## [v0.1.5] - 2022-11-30
+
+### Added
+- Derive macro to support structures and enums.
 
 ## [v0.1.4] - 2022-11-02
 
modifiedcrates/evm-coder/Cargo.tomldiffbeforeafterboth
--- a/crates/evm-coder/Cargo.toml
+++ b/crates/evm-coder/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "evm-coder"
-version = "0.1.4"
+version = "0.1.5"
 license = "GPLv3"
 edition = "2021"
 
@@ -26,7 +26,6 @@
 hex = "0.4.3"
 hex-literal = "0.3.4"
 similar-asserts = "1.4.2"
-concat-idents = "1.1.3"
 trybuild = "1.0"
 
 [features]
modifiedcrates/evm-coder/procedural/Cargo.tomldiffbeforeafterboth
--- a/crates/evm-coder/procedural/Cargo.toml
+++ b/crates/evm-coder/procedural/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "evm-coder-procedural"
-version = "0.2.1"
+version = "0.2.2"
 license = "GPLv3"
 edition = "2021"
 
addedcrates/evm-coder/procedural/src/abi_derive/derive_enum.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/procedural/src/abi_derive/derive_enum.rs
@@ -0,0 +1,211 @@
+use quote::quote;
+
+pub fn impl_solidity_option<'a>(
+	name: &proc_macro2::Ident,
+	enum_options: impl Iterator<Item = &'a syn::Ident>,
+) -> proc_macro2::TokenStream {
+	let enum_options = enum_options.map(|opt| {
+		let s = name.to_string() + "." + opt.to_string().as_str();
+		let as_string = proc_macro2::Literal::string(s.as_str());
+		quote!(#name::#opt => #as_string,)
+	});
+	quote!(
+		#[cfg(feature = "stubgen")]
+		impl ::evm_coder::solidity::SolidityEnum for #name {
+			fn solidity_option(&self) -> &str {
+				match self {
+					#(#enum_options)*
+				}
+			}
+		}
+	)
+}
+
+pub fn impl_enum_from_u8<'a>(
+	name: &proc_macro2::Ident,
+	enum_options: impl Iterator<Item = &'a syn::Ident>,
+) -> proc_macro2::TokenStream {
+	let error_str = format!("Value not convertible into enum \"{name}\"");
+	let error_str = proc_macro2::Literal::string(&error_str);
+	let enum_options = enum_options.enumerate().map(|(i, opt)| {
+		let n = proc_macro2::Literal::u8_suffixed(i as u8);
+		quote! {#n => Ok(#name::#opt),}
+	});
+
+	quote!(
+		impl TryFrom<u8> for #name {
+			type Error = &'static str;
+
+			fn try_from(value: u8) -> ::core::result::Result<Self, Self::Error> {
+				const err: &'static str = #error_str;
+				match value {
+					#(#enum_options)*
+					_ => Err(err)
+				}
+			}
+		}
+	)
+}
+
+pub fn impl_enum_abi_type(name: &syn::Ident) -> proc_macro2::TokenStream {
+	quote! {
+		impl ::evm_coder::abi::AbiType for #name {
+			const SIGNATURE: ::evm_coder::custom_signature::SignatureUnit = <u8 as ::evm_coder::abi::AbiType>::SIGNATURE;
+
+			fn is_dynamic() -> bool {
+				<u8 as ::evm_coder::abi::AbiType>::is_dynamic()
+			}
+			fn size() -> usize {
+				<u8 as ::evm_coder::abi::AbiType>::size()
+			}
+		}
+	}
+}
+
+pub fn impl_enum_abi_read(name: &syn::Ident) -> proc_macro2::TokenStream {
+	quote!(
+		impl ::evm_coder::abi::AbiRead for #name {
+			fn abi_read(reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result<Self> {
+				Ok(
+					<u8 as ::evm_coder::abi::AbiRead>::abi_read(reader)?
+						.try_into()?
+				)
+			}
+		}
+	)
+}
+
+pub fn impl_enum_abi_write(name: &syn::Ident) -> proc_macro2::TokenStream {
+	quote!(
+		impl ::evm_coder::abi::AbiWrite for #name {
+			fn abi_write(&self, writer: &mut ::evm_coder::abi::AbiWriter) {
+				::evm_coder::abi::AbiWrite::abi_write(&(*self as u8), writer);
+			}
+		}
+	)
+}
+
+pub fn impl_enum_solidity_type_name(name: &syn::Ident) -> proc_macro2::TokenStream {
+	quote!(
+		#[cfg(feature = "stubgen")]
+		impl ::evm_coder::solidity::SolidityTypeName for #name {
+			fn solidity_name(
+				writer: &mut impl ::core::fmt::Write,
+				tc: &::evm_coder::solidity::TypeCollector,
+			) -> ::core::fmt::Result {
+				write!(writer, "{}", tc.collect_struct::<Self>())
+			}
+
+			fn is_simple() -> bool {
+				true
+			}
+
+			fn solidity_default(
+				writer: &mut impl ::core::fmt::Write,
+				tc: &::evm_coder::solidity::TypeCollector,
+			) -> ::core::fmt::Result {
+				write!(writer, "{}", <#name as ::evm_coder::solidity::SolidityEnum>::solidity_option(&<#name>::default()))
+			}
+		}
+	)
+}
+
+pub fn impl_enum_solidity_struct_collect<'a>(
+	name: &syn::Ident,
+	enum_options: impl Iterator<Item = &'a syn::Ident>,
+	option_count: usize,
+	enum_options_docs: impl Iterator<Item = syn::Result<Vec<proc_macro2::TokenStream>>>,
+	docs: &[proc_macro2::TokenStream],
+) -> proc_macro2::TokenStream {
+	let string_name = name.to_string();
+	let enum_options = enum_options
+		.zip(enum_options_docs)
+		.enumerate()
+		.map(|(i, (opt, doc))| {
+			let opt = proc_macro2::Literal::string(opt.to_string().as_str());
+			let doc = doc.expect("Doc parsing error");
+			let comma = if i != option_count - 1 { "," } else { "" };
+			quote! {
+				#(#doc)*
+				writeln!(str, "\t{}{}", #opt, #comma).expect("Enum format option");
+			}
+		});
+
+	quote!(
+		#[cfg(feature = "stubgen")]
+		impl ::evm_coder::solidity::StructCollect for #name {
+			fn name() -> String {
+				#string_name.into()
+			}
+
+			fn declaration() -> String {
+				use std::fmt::Write;
+
+				let mut str = String::new();
+				#(#docs)*
+				writeln!(str, "enum {} {{", <Self as ::evm_coder::solidity::StructCollect>::name()).unwrap();
+				#(#enum_options)*
+				writeln!(str, "}}").unwrap();
+				str
+			}
+		}
+	)
+}
+
+pub fn check_and_count_options(de: &syn::DataEnum) -> syn::Result<usize> {
+	let mut count = 0;
+	for v in de.variants.iter() {
+		if !v.fields.is_empty() {
+			return Err(syn::Error::new(
+				v.ident.span(),
+				"Enumeration parameters should not have fields",
+			));
+		} else if v.discriminant.is_some() {
+			return Err(syn::Error::new(
+				v.ident.span(),
+				"Enumeration options should not have an explicit specified value",
+			));
+		} else {
+			count += 1;
+		}
+	}
+
+	Ok(count)
+}
+
+pub fn check_repr_u8(name: &syn::Ident, attrs: &[syn::Attribute]) -> syn::Result<()> {
+	let mut has_repr = false;
+	for attr in attrs.iter() {
+		if attr.path.is_ident("repr") {
+			has_repr = true;
+			let meta = attr.parse_meta()?;
+			check_meta_u8(&meta)?;
+		}
+	}
+
+	if !has_repr {
+		return Err(syn::Error::new(name.span(), "Enum is not \"repr(u8)\""));
+	}
+
+	Ok(())
+}
+
+fn check_meta_u8(meta: &syn::Meta) -> Result<(), syn::Error> {
+	if let syn::Meta::List(p) = meta {
+		for nm in p.nested.iter() {
+			if let syn::NestedMeta::Meta(syn::Meta::Path(p)) = nm {
+				if !p.is_ident("u8") {
+					return Err(syn::Error::new(
+						p.segments
+							.first()
+							.expect("repr segments are empty")
+							.ident
+							.span(),
+						"Enum is not \"repr(u8)\"",
+					));
+				}
+			}
+		}
+	}
+	Ok(())
+}
addedcrates/evm-coder/procedural/src/abi_derive/derive_struct.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/procedural/src/abi_derive/derive_struct.rs
@@ -0,0 +1,260 @@
+use super::extract_docs;
+use quote::quote;
+
+pub fn tuple_type<'a>(
+	field_types: impl Iterator<Item = &'a syn::Type> + Clone,
+) -> proc_macro2::TokenStream {
+	let field_types = field_types.map(|ty| quote!(#ty,));
+	quote! {(#(#field_types)*)}
+}
+
+pub fn tuple_ref_type<'a>(
+	field_types: impl Iterator<Item = &'a syn::Type> + Clone,
+) -> proc_macro2::TokenStream {
+	let field_types = field_types.map(|ty| quote!(&#ty,));
+	quote! {(#(#field_types)*)}
+}
+
+pub fn tuple_data_as_ref(
+	is_named_fields: bool,
+	field_names: impl Iterator<Item = syn::Ident> + Clone,
+) -> proc_macro2::TokenStream {
+	let field_names = field_names.enumerate().map(|(i, field)| {
+		if is_named_fields {
+			quote!(&self.#field,)
+		} else {
+			let field = proc_macro2::Literal::usize_unsuffixed(i);
+			quote!(&self.#field,)
+		}
+	});
+	quote! {(#(#field_names)*)}
+}
+
+pub fn tuple_names(
+	is_named_fields: bool,
+	field_names: impl Iterator<Item = syn::Ident> + Clone,
+) -> proc_macro2::TokenStream {
+	let field_names = field_names.enumerate().map(|(i, field)| {
+		if is_named_fields {
+			quote!(#field,)
+		} else {
+			let field = proc_macro2::Ident::new(
+				format!("field{}", i).as_str(),
+				proc_macro2::Span::call_site(),
+			);
+			quote!(#field,)
+		}
+	});
+	quote! {(#(#field_names)*)}
+}
+
+pub fn struct_from_tuple(
+	name: &syn::Ident,
+	is_named_fields: bool,
+	field_names: impl Iterator<Item = syn::Ident> + Clone,
+) -> proc_macro2::TokenStream {
+	let field_names = field_names.enumerate().map(|(i, field)| {
+		if is_named_fields {
+			quote!(#field,)
+		} else {
+			let field = proc_macro2::Ident::new(
+				format!("field{}", i).as_str(),
+				proc_macro2::Span::call_site(),
+			);
+			quote!(#field,)
+		}
+	});
+
+	if is_named_fields {
+		quote! {#name {#(#field_names)*}}
+	} else {
+		quote! {#name (#(#field_names)*)}
+	}
+}
+
+pub fn map_field_to_name(field: (usize, &syn::Field)) -> syn::Ident {
+	match field.1.ident.as_ref() {
+		Some(name) => name.clone(),
+		None => {
+			let mut name = "field".to_string();
+			name.push_str(field.0.to_string().as_str());
+			syn::Ident::new(name.as_str(), proc_macro2::Span::call_site())
+		}
+	}
+}
+
+pub fn map_field_to_type(field: &syn::Field) -> &syn::Type {
+	&field.ty
+}
+
+pub fn map_field_to_doc(field: &syn::Field) -> syn::Result<Vec<proc_macro2::TokenStream>> {
+	extract_docs(&field.attrs, true)
+}
+
+pub fn impl_can_be_placed_in_vec(ident: &syn::Ident) -> proc_macro2::TokenStream {
+	quote! {
+		impl ::evm_coder::sealed::CanBePlacedInVec for #ident {}
+	}
+}
+
+pub fn impl_struct_abi_type(
+	name: &syn::Ident,
+	tuple_type: proc_macro2::TokenStream,
+) -> proc_macro2::TokenStream {
+	quote! {
+		impl ::evm_coder::abi::AbiType for #name {
+			const SIGNATURE: ::evm_coder::custom_signature::SignatureUnit = <#tuple_type as ::evm_coder::abi::AbiType>::SIGNATURE;
+			fn is_dynamic() -> bool {
+				<#tuple_type as ::evm_coder::abi::AbiType>::is_dynamic()
+			}
+			fn size() -> usize {
+				<#tuple_type as ::evm_coder::abi::AbiType>::size()
+			}
+		}
+	}
+}
+
+pub fn impl_struct_abi_read(
+	name: &syn::Ident,
+	tuple_type: proc_macro2::TokenStream,
+	tuple_names: proc_macro2::TokenStream,
+	struct_from_tuple: proc_macro2::TokenStream,
+) -> proc_macro2::TokenStream {
+	quote!(
+		impl ::evm_coder::abi::AbiRead for #name {
+			fn abi_read(reader: &mut ::evm_coder::abi::AbiReader) -> ::evm_coder::execution::Result<Self> {
+				let #tuple_names = <#tuple_type as ::evm_coder::abi::AbiRead>::abi_read(reader)?;
+				Ok(#struct_from_tuple)
+			}
+		}
+	)
+}
+
+pub fn impl_struct_abi_write(
+	name: &syn::Ident,
+	_is_named_fields: bool,
+	tuple_type: proc_macro2::TokenStream,
+	tuple_data: proc_macro2::TokenStream,
+) -> proc_macro2::TokenStream {
+	quote!(
+		impl ::evm_coder::abi::AbiWrite for #name {
+			fn abi_write(&self, writer: &mut ::evm_coder::abi::AbiWriter) {
+				<#tuple_type as ::evm_coder::abi::AbiWrite>::abi_write(&#tuple_data, writer)
+			}
+		}
+	)
+}
+
+pub fn impl_struct_solidity_type<'a>(
+	name: &syn::Ident,
+	field_types: impl Iterator<Item = &'a syn::Type> + Clone,
+	params_count: usize,
+) -> proc_macro2::TokenStream {
+	let len = proc_macro2::Literal::usize_suffixed(params_count);
+	quote! {
+		#[cfg(feature = "stubgen")]
+		impl ::evm_coder::solidity::SolidityType for #name {
+			fn names(tc: &::evm_coder::solidity::TypeCollector) -> Vec<String> {
+				let mut collected =
+					Vec::with_capacity(<Self as ::evm_coder::solidity::SolidityType>::len());
+				#({
+					let mut out = String::new();
+					<#field_types as ::evm_coder::solidity::SolidityTypeName>::solidity_name(&mut out, tc)
+						.expect("no fmt error");
+					collected.push(out);
+				})*
+				collected
+			}
+
+			fn len() -> usize {
+				#len
+			}
+		}
+	}
+}
+
+pub fn impl_struct_solidity_type_name<'a>(
+	name: &syn::Ident,
+	field_types: impl Iterator<Item = &'a syn::Type> + Clone,
+	params_count: usize,
+) -> proc_macro2::TokenStream {
+	let arg_dafaults = field_types.enumerate().map(|(i, ty)| {
+		let mut defult_value = quote!(<#ty as ::evm_coder::solidity::SolidityTypeName
+			>::solidity_default(writer, tc)?;);
+		let last_item = params_count - 1;
+		if i != last_item {
+			defult_value.extend(quote! {write!(writer, ",")?;})
+		}
+		defult_value
+	});
+
+	quote! {
+		#[cfg(feature = "stubgen")]
+		impl ::evm_coder::solidity::SolidityTypeName for #name {
+			fn solidity_name(
+				writer: &mut impl ::core::fmt::Write,
+				tc: &::evm_coder::solidity::TypeCollector,
+			) -> ::core::fmt::Result {
+				write!(writer, "{}", tc.collect_struct::<Self>())
+			}
+
+			fn is_simple() -> bool {
+				false
+			}
+
+			fn solidity_default(
+				writer: &mut impl ::core::fmt::Write,
+				tc: &::evm_coder::solidity::TypeCollector,
+			) -> ::core::fmt::Result {
+				write!(writer, "{}(", tc.collect_struct::<Self>())?;
+
+				#(#arg_dafaults)*
+
+				write!(writer, ")")
+			}
+		}
+	}
+}
+
+pub fn impl_struct_solidity_struct_collect<'a>(
+	name: &syn::Ident,
+	field_names: impl Iterator<Item = proc_macro2::Ident> + Clone,
+	field_types: impl Iterator<Item = &'a syn::Type> + Clone,
+	field_docs: impl Iterator<Item = syn::Result<Vec<proc_macro2::TokenStream>>> + Clone,
+	docs: &[proc_macro2::TokenStream],
+) -> syn::Result<proc_macro2::TokenStream> {
+	let string_name = name.to_string();
+	let name_type = field_names
+		.into_iter()
+		.zip(field_types)
+		.zip(field_docs)
+		.map(|((name, ty), doc)| {
+			let field_docs = doc.expect("Doc parse error");
+			let name = format!("{}", name);
+			quote!(
+				#(#field_docs)*
+				write!(str, "\t{} ", <#ty as ::evm_coder::solidity::StructCollect>::name()).unwrap();
+				writeln!(str, "{};", #name).unwrap();
+			)
+		});
+
+	Ok(quote! {
+		#[cfg(feature = "stubgen")]
+		impl ::evm_coder::solidity::StructCollect for #name {
+			fn name() -> String {
+				#string_name.into()
+			}
+
+			fn declaration() -> String {
+				use std::fmt::Write;
+
+				let mut str = String::new();
+				#(#docs)*
+				writeln!(str, "struct {} {{", Self::name()).unwrap();
+				#(#name_type)*
+				writeln!(str, "}}").unwrap();
+				str
+			}
+		}
+	})
+}
addedcrates/evm-coder/procedural/src/abi_derive/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/procedural/src/abi_derive/mod.rs
@@ -0,0 +1,145 @@
+mod derive_enum;
+mod derive_struct;
+
+use quote::quote;
+use derive_struct::*;
+use derive_enum::*;
+
+pub(crate) fn impl_abi_macro(ast: &syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
+	let name = &ast.ident;
+	match &ast.data {
+		syn::Data::Struct(ds) => expand_struct(ds, ast),
+		syn::Data::Enum(de) => expand_enum(de, ast),
+		syn::Data::Union(_) => Err(syn::Error::new(name.span(), "Unions not supported")),
+	}
+}
+
+fn expand_struct(
+	ds: &syn::DataStruct,
+	ast: &syn::DeriveInput,
+) -> syn::Result<proc_macro2::TokenStream> {
+	let name = &ast.ident;
+	let docs = extract_docs(&ast.attrs, false)?;
+	let (is_named_fields, field_names, field_types, field_docs, params_count) = match ds.fields {
+		syn::Fields::Named(ref fields) => Ok((
+			true,
+			fields.named.iter().enumerate().map(map_field_to_name),
+			fields.named.iter().map(map_field_to_type),
+			fields.named.iter().map(map_field_to_doc),
+			fields.named.len(),
+		)),
+		syn::Fields::Unnamed(ref fields) => Ok((
+			false,
+			fields.unnamed.iter().enumerate().map(map_field_to_name),
+			fields.unnamed.iter().map(map_field_to_type),
+			fields.unnamed.iter().map(map_field_to_doc),
+			fields.unnamed.len(),
+		)),
+		syn::Fields::Unit => Err(syn::Error::new(name.span(), "Unit structs not supported")),
+	}?;
+
+	if params_count == 0 {
+		return Err(syn::Error::new(name.span(), "Empty structs not supported"));
+	};
+
+	let tuple_type = tuple_type(field_types.clone());
+	let tuple_ref_type = tuple_ref_type(field_types.clone());
+	let tuple_data = tuple_data_as_ref(is_named_fields, field_names.clone());
+	let tuple_names = tuple_names(is_named_fields, field_names.clone());
+	let struct_from_tuple = struct_from_tuple(name, is_named_fields, field_names.clone());
+
+	let can_be_plcaed_in_vec = impl_can_be_placed_in_vec(name);
+	let abi_type = impl_struct_abi_type(name, tuple_type.clone());
+	let abi_read = impl_struct_abi_read(name, tuple_type, tuple_names, struct_from_tuple);
+	let abi_write = impl_struct_abi_write(name, is_named_fields, tuple_ref_type, tuple_data);
+	let solidity_type = impl_struct_solidity_type(name, field_types.clone(), params_count);
+	let solidity_type_name =
+		impl_struct_solidity_type_name(name, field_types.clone(), params_count);
+	let solidity_struct_collect =
+		impl_struct_solidity_struct_collect(name, field_names, field_types, field_docs, &docs)?;
+
+	Ok(quote! {
+		#can_be_plcaed_in_vec
+		#abi_type
+		#abi_read
+		#abi_write
+		#solidity_type
+		#solidity_type_name
+		#solidity_struct_collect
+	})
+}
+
+fn expand_enum(
+	de: &syn::DataEnum,
+	ast: &syn::DeriveInput,
+) -> syn::Result<proc_macro2::TokenStream> {
+	let name = &ast.ident;
+	check_repr_u8(name, &ast.attrs)?;
+	let docs = extract_docs(&ast.attrs, false)?;
+	let option_count = check_and_count_options(de)?;
+	let enum_options = de.variants.iter().map(|v| &v.ident);
+	let enum_options_docs = de.variants.iter().map(|v| extract_docs(&v.attrs, true));
+
+	let from = impl_enum_from_u8(name, enum_options.clone());
+	let solidity_option = impl_solidity_option(name, enum_options.clone());
+	let can_be_plcaed_in_vec = impl_can_be_placed_in_vec(name);
+	let abi_type = impl_enum_abi_type(name);
+	let abi_read = impl_enum_abi_read(name);
+	let abi_write = impl_enum_abi_write(name);
+	let solidity_type_name = impl_enum_solidity_type_name(name);
+	let solidity_struct_collect = impl_enum_solidity_struct_collect(
+		name,
+		enum_options,
+		option_count,
+		enum_options_docs,
+		&docs,
+	);
+
+	Ok(quote! {
+		#from
+		#solidity_option
+		#can_be_plcaed_in_vec
+		#abi_type
+		#abi_read
+		#abi_write
+		#solidity_type_name
+		#solidity_struct_collect
+	})
+}
+
+fn extract_docs(
+	attrs: &[syn::Attribute],
+	is_field_doc: bool,
+) -> syn::Result<Vec<proc_macro2::TokenStream>> {
+	attrs
+		.iter()
+		.filter_map(|attr| {
+			if let Some(ps) = attr.path.segments.first() {
+				if ps.ident == "doc" {
+					let meta = match attr.parse_meta() {
+						Ok(meta) => meta,
+						Err(e) => return Some(Err(e)),
+					};
+					match meta {
+						syn::Meta::NameValue(mnv) => match &mnv.lit {
+							syn::Lit::Str(ls) => return Some(Ok(ls.value())),
+							_ => unreachable!(),
+						},
+						_ => unreachable!(),
+					}
+				}
+			}
+			None
+		})
+		.enumerate()
+		.map(|(i, doc)| {
+			let doc = doc?;
+			let doc = doc.trim();
+			let dev = if i == 0 { " @dev" } else { "" };
+			let tab = if is_field_doc { "\t" } else { "" };
+			Ok(quote! {
+				writeln!(str, "{}///{} {}", #tab, #dev, #doc).unwrap();
+			})
+		})
+		.collect()
+}
modifiedcrates/evm-coder/procedural/src/lib.rsdiffbeforeafterboth
--- a/crates/evm-coder/procedural/src/lib.rs
+++ b/crates/evm-coder/procedural/src/lib.rs
@@ -25,6 +25,7 @@
 	parse_macro_input, spanned::Spanned,
 };
 
+mod abi_derive;
 mod solidity_interface;
 mod to_log;
 
@@ -242,3 +243,13 @@
 	}
 	.into()
 }
+
+#[proc_macro_derive(AbiCoder)]
+pub fn abi_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+	let ast = syn::parse(input).unwrap();
+	let ts = match abi_derive::impl_abi_macro(&ast) {
+		Ok(e) => e,
+		Err(e) => e.to_compile_error(),
+	};
+	ts.into()
+}
modifiedcrates/evm-coder/procedural/src/solidity_interface.rsdiffbeforeafterboth
--- a/crates/evm-coder/procedural/src/solidity_interface.rs
+++ b/crates/evm-coder/procedural/src/solidity_interface.rs
@@ -404,7 +404,11 @@
 		let name = &self.name;
 		let ty = &self.ty;
 		quote! {
-			#name: <#ty>::abi_read(reader)?
+			#name: {
+				let value = <#ty as ::evm_coder::abi::AbiRead>::abi_read(reader)?;
+				if !is_dynamic {reader.bytes_read(<#ty as ::evm_coder::abi::AbiType>::size())};
+				value
+			}
 		}
 	}
 
@@ -630,17 +634,18 @@
 		let pascal_name = &self.pascal_name;
 		let screaming_name = &self.screaming_name;
 		if self.has_normal_args {
-			let parsers = self
-				.args
-				.iter()
-				.filter(|a| !a.is_special())
-				.map(|a| a.expand_parse());
+			let args_iter = self.args.iter().filter(|a| !a.is_special());
+			let arg_type = args_iter.clone().map(|a| &a.ty);
+			let parsers = args_iter.map(|a| a.expand_parse());
 			quote! {
-				Self::#screaming_name => return Ok(Some(Self::#pascal_name {
-					#(
-						#parsers,
-					)*
-				}))
+				Self::#screaming_name => {
+					let is_dynamic = false #(|| <#arg_type as ::evm_coder::abi::AbiType>::is_dynamic())*;
+					return Ok(Some(Self::#pascal_name {
+						#(
+							#parsers,
+						)*
+					}))
+				}
 			}
 		} else {
 			quote! { Self::#screaming_name => return Ok(Some(Self::#pascal_name)) }
modifiedcrates/evm-coder/src/abi/impls.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/abi/impls.rs
+++ b/crates/evm-coder/src/abi/impls.rs
@@ -1,8 +1,8 @@
 use crate::{
+	custom_signature::SignatureUnit,
 	execution::{Result, ResultWithPostInfo, WithPostDispatchInfo},
+	make_signature, sealed,
 	types::*,
-	make_signature,
-	custom_signature::SignatureUnit,
 };
 use super::{traits::*, ABI_ALIGNMENT, AbiReader, AbiWriter};
 use primitive_types::{U256, H160};
@@ -10,12 +10,12 @@
 #[cfg(not(feature = "std"))]
 use alloc::vec::Vec;
 
-macro_rules! impl_abi_readable {
-	($ty:ty, $method:ident, $dynamic:literal) => {
+macro_rules! impl_abi_type {
+	($ty:ty, $name:ident, $dynamic:literal) => {
 		impl sealed::CanBePlacedInVec for $ty {}
 
 		impl AbiType for $ty {
-			const SIGNATURE: SignatureUnit = make_signature!(new fixed(stringify!($ty)));
+			const SIGNATURE: SignatureUnit = make_signature!(new fixed(stringify!($name)));
 
 			fn is_dynamic() -> bool {
 				$dynamic
@@ -25,7 +25,11 @@
 				ABI_ALIGNMENT
 			}
 		}
+	};
+}
 
+macro_rules! impl_abi_readable {
+	($ty:ty, $method:ident) => {
 		impl AbiRead for $ty {
 			fn abi_read(reader: &mut AbiReader) -> Result<$ty> {
 				reader.$method()
@@ -34,79 +38,93 @@
 	};
 }
 
-impl_abi_readable!(uint32, uint32, false);
-impl_abi_readable!(uint64, uint64, false);
-impl_abi_readable!(uint128, uint128, false);
-impl_abi_readable!(uint256, uint256, false);
-impl_abi_readable!(bytes4, bytes4, false);
-impl_abi_readable!(address, address, false);
-impl_abi_readable!(string, string, true);
+macro_rules! impl_abi_writeable {
+	($ty:ty, $method:ident) => {
+		impl AbiWrite for $ty {
+			fn abi_write(&self, writer: &mut AbiWriter) {
+				writer.$method(&self)
+			}
+		}
+	};
+}
 
-impl sealed::CanBePlacedInVec for bool {}
+macro_rules! impl_abi {
+	($ty:ty, $method:ident, $dynamic:literal) => {
+		impl_abi_type!($ty, $method, $dynamic);
+		impl_abi_readable!($ty, $method);
+		impl_abi_writeable!($ty, $method);
+	};
+}
 
-impl AbiType for bool {
-	const SIGNATURE: SignatureUnit = make_signature!(new fixed("bool"));
+impl_abi!(bool, bool, false);
+impl_abi!(u8, uint8, false);
+impl_abi!(u32, uint32, false);
+impl_abi!(u64, uint64, false);
+impl_abi!(u128, uint128, false);
+impl_abi!(U256, uint256, false);
+impl_abi!(H160, address, false);
+impl_abi!(string, string, true);
 
-	fn is_dynamic() -> bool {
-		false
-	}
-	fn size() -> usize {
-		ABI_ALIGNMENT
+impl_abi_writeable!(&str, string);
+
+impl_abi_type!(bytes, bytes, true);
+
+impl AbiRead for bytes {
+	fn abi_read(reader: &mut AbiReader) -> Result<bytes> {
+		Ok(bytes(reader.bytes()?))
 	}
 }
-impl AbiRead for bool {
-	fn abi_read(reader: &mut AbiReader) -> Result<bool> {
-		reader.bool()
+
+impl AbiWrite for bytes {
+	fn abi_write(&self, writer: &mut AbiWriter) {
+		writer.bytes(self.0.as_slice())
 	}
 }
-
-impl AbiType for uint8 {
-	const SIGNATURE: SignatureUnit = make_signature!(new fixed("uint8"));
 
-	fn is_dynamic() -> bool {
-		false
-	}
-	fn size() -> usize {
-		ABI_ALIGNMENT
+impl_abi_type!(bytes4, bytes4, false);
+impl AbiRead for bytes4 {
+	fn abi_read(reader: &mut AbiReader) -> Result<bytes4> {
+		reader.bytes4()
 	}
 }
-impl AbiRead for uint8 {
-	fn abi_read(reader: &mut AbiReader) -> Result<uint8> {
-		reader.uint8()
+
+impl<T: AbiWrite> AbiWrite for &T {
+	fn abi_write(&self, writer: &mut AbiWriter) {
+		T::abi_write(self, writer);
 	}
 }
 
-impl AbiType for bytes {
-	const SIGNATURE: SignatureUnit = make_signature!(new fixed("bytes"));
+impl<T: AbiType> AbiType for &T {
+	const SIGNATURE: SignatureUnit = T::SIGNATURE;
 
 	fn is_dynamic() -> bool {
-		true
+		T::is_dynamic()
 	}
+
 	fn size() -> usize {
-		ABI_ALIGNMENT
-	}
-}
-impl AbiRead for bytes {
-	fn abi_read(reader: &mut AbiReader) -> Result<bytes> {
-		Ok(bytes(reader.bytes()?))
+		T::size()
 	}
 }
 
-impl<R: AbiRead + sealed::CanBePlacedInVec> AbiRead for Vec<R> {
-	fn abi_read(reader: &mut AbiReader) -> Result<Vec<R>> {
+impl<T: AbiType + AbiRead + sealed::CanBePlacedInVec> AbiRead for Vec<T> {
+	fn abi_read(reader: &mut AbiReader) -> Result<Vec<T>> {
 		let mut sub = reader.subresult(None)?;
 		let size = sub.uint32()? as usize;
 		sub.subresult_offset = sub.offset;
+		let is_dynamic = <T as AbiType>::is_dynamic();
 		let mut out = Vec::with_capacity(size);
 		for _ in 0..size {
-			out.push(<R>::abi_read(&mut sub)?);
+			out.push(<T as AbiRead>::abi_read(&mut sub)?);
+			if !is_dynamic {
+				sub.bytes_read(<T as AbiType>::size());
+			};
 		}
 		Ok(out)
 	}
 }
 
-impl<R: AbiType> AbiType for Vec<R> {
-	const SIGNATURE: SignatureUnit = make_signature!(new nameof(R::SIGNATURE) fixed("[]"));
+impl<T: AbiType> AbiType for Vec<T> {
+	const SIGNATURE: SignatureUnit = make_signature!(new nameof(T::SIGNATURE) fixed("[]"));
 
 	fn is_dynamic() -> bool {
 		true
@@ -114,42 +132,6 @@
 
 	fn size() -> usize {
 		ABI_ALIGNMENT
-	}
-}
-
-impl sealed::CanBePlacedInVec for EthCrossAccount {}
-
-impl AbiType for EthCrossAccount {
-	const SIGNATURE: SignatureUnit = make_signature!(new fixed("(address,uint256)"));
-
-	fn is_dynamic() -> bool {
-		address::is_dynamic() || uint256::is_dynamic()
-	}
-
-	fn size() -> usize {
-		<address as AbiType>::size() + <uint256 as AbiType>::size()
-	}
-}
-
-impl AbiRead for EthCrossAccount {
-	fn abi_read(reader: &mut AbiReader) -> Result<EthCrossAccount> {
-		let size = if !EthCrossAccount::is_dynamic() {
-			Some(<EthCrossAccount as AbiType>::size())
-		} else {
-			None
-		};
-		let mut subresult = reader.subresult(size)?;
-		let eth = <address>::abi_read(&mut subresult)?;
-		let sub = <uint256>::abi_read(&mut subresult)?;
-
-		Ok(EthCrossAccount { eth, sub })
-	}
-}
-
-impl AbiWrite for EthCrossAccount {
-	fn abi_write(&self, writer: &mut AbiWriter) {
-		self.eth.abi_write(writer);
-		self.sub.abi_write(writer);
 	}
 }
 
@@ -188,36 +170,6 @@
 	}
 }
 
-macro_rules! impl_abi_writeable {
-	($ty:ty, $method:ident) => {
-		impl AbiWrite for $ty {
-			fn abi_write(&self, writer: &mut AbiWriter) {
-				writer.$method(&self)
-			}
-		}
-	};
-}
-
-impl_abi_writeable!(u8, uint8);
-impl_abi_writeable!(u32, uint32);
-impl_abi_writeable!(u128, uint128);
-impl_abi_writeable!(U256, uint256);
-impl_abi_writeable!(H160, address);
-impl_abi_writeable!(bool, bool);
-impl_abi_writeable!(&str, string);
-
-impl AbiWrite for string {
-	fn abi_write(&self, writer: &mut AbiWriter) {
-		writer.string(self)
-	}
-}
-
-impl AbiWrite for bytes {
-	fn abi_write(&self, writer: &mut AbiWriter) {
-		writer.bytes(self.0.as_slice())
-	}
-}
-
 impl<T: AbiWrite + AbiType> AbiWrite for Vec<T> {
 	fn abi_write(&self, writer: &mut AbiWriter) {
 		let is_dynamic = T::is_dynamic();
@@ -295,14 +247,19 @@
 
 		impl<$($ident),+> AbiRead for ($($ident,)+)
 		where
-			$($ident: AbiRead,)+
-			($($ident,)+): AbiType,
+			Self: AbiType,
+			$($ident: AbiRead + AbiType,)+
 		{
 			fn abi_read(reader: &mut AbiReader) -> Result<($($ident,)+)> {
-				let size = if !<($($ident,)+)>::is_dynamic() { Some(<($($ident,)+)>::size()) } else { None };
+				let is_dynamic = <Self>::is_dynamic();
+				let size = if !is_dynamic { Some(<Self>::size()) } else { None };
 				let mut subresult = reader.subresult(size)?;
 				Ok((
-					$(<$ident>::abi_read(&mut subresult)?,)+
+					$({
+						let value = <$ident>::abi_read(&mut subresult)?;
+						if !is_dynamic {subresult.bytes_read(<$ident as AbiType>::size())};
+						value
+					},)+
 				))
 			}
 		}
@@ -310,11 +267,11 @@
 		#[allow(non_snake_case)]
 		impl<$($ident),+> AbiWrite for ($($ident,)+)
 		where
-			$($ident: AbiWrite,)+
+			$($ident: AbiWrite + AbiType,)+
 		{
 			fn abi_write(&self, writer: &mut AbiWriter) {
 				let ($($ident,)+) = self;
-				if writer.is_dynamic {
+				if <Self as AbiType>::is_dynamic() {
 					let mut sub = AbiWriter::new();
 					$($ident.abi_write(&mut sub);)+
 					writer.write_subresult(sub);
modifiedcrates/evm-coder/src/abi/mod.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/abi/mod.rs
+++ b/crates/evm-coder/src/abi/mod.rs
@@ -75,19 +75,19 @@
 		buf: &[u8],
 		offset: usize,
 		pad_start: usize,
-		pad_size: usize,
+		pad_end: usize,
 		block_start: usize,
-		block_size: usize,
+		block_end: usize,
 	) -> Result<[u8; S]> {
 		if buf.len() - offset < ABI_ALIGNMENT {
 			return Err(Error::Error(ExitError::OutOfOffset));
 		}
 		let mut block = [0; S];
-		let is_pad_zeroed = buf[pad_start..pad_size].iter().all(|&v| v == 0);
+		let is_pad_zeroed = buf[pad_start..pad_end].iter().all(|&v| v == 0);
 		if !is_pad_zeroed {
 			return Err(Error::Error(ExitError::InvalidRange));
 		}
-		block.copy_from_slice(&buf[block_start..block_size]);
+		block.copy_from_slice(&buf[block_start..block_end]);
 		Ok(block)
 	}
 
@@ -186,11 +186,10 @@
 
 	/// Slice recursive buffer, advance one word for buffer offset
 	/// If `size` is [`None`] then [`Self::offset`] and [`Self::subresult_offset`] evals from [`Self::buf`].
-	fn subresult(&mut self, size: Option<usize>) -> Result<AbiReader<'i>> {
+	pub fn subresult(&mut self, size: Option<usize>) -> Result<AbiReader<'i>> {
 		let subresult_offset = self.subresult_offset;
 		let offset = if let Some(size) = size {
 			self.offset += size;
-			self.subresult_offset += size;
 			0
 		} else {
 			self.uint32()? as usize
@@ -208,6 +207,11 @@
 		})
 	}
 
+	/// Notify about readed data portion.
+	pub fn bytes_read(&mut self, size: usize) {
+		self.subresult_offset += size;
+	}
+
 	/// Is this parser reached end of buffer?
 	pub fn is_finished(&self) -> bool {
 		self.buf.len() == self.offset
@@ -277,6 +281,11 @@
 		self.write_padleft(&u32::to_be_bytes(*value))
 	}
 
+	/// Write [`u64`] to end of buffer
+	pub fn uint64(&mut self, value: &u64) {
+		self.write_padleft(&u64::to_be_bytes(*value))
+	}
+
 	/// Write [`u128`] to end of buffer
 	pub fn uint128(&mut self, value: &u128) {
 		self.write_padleft(&u128::to_be_bytes(*value))
modifiedcrates/evm-coder/src/abi/test.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/abi/test.rs
+++ b/crates/evm-coder/src/abi/test.rs
@@ -6,36 +6,25 @@
 use super::{AbiReader, AbiWriter};
 use hex_literal::hex;
 use primitive_types::{H160, U256};
-use concat_idents::concat_idents;
 
-macro_rules! test_impl {
-    ($name:ident, $type:ty, $function_identifier:expr, $decoded_data:expr, $encoded_data:expr) => {
-        concat_idents!(test_name = encode_decode_, $name {
-            #[test]
-            fn test_name() {
-                let function_identifier: u32 = $function_identifier;
-                let decoded_data = $decoded_data;
-                let encoded_data = $encoded_data;
-
-                let (call, mut decoder) = AbiReader::new_call(encoded_data).unwrap();
-                assert_eq!(call, u32::to_be_bytes(function_identifier));
-                let data = <$type>::abi_read(&mut decoder).unwrap();
-                assert_eq!(data, decoded_data);
+fn test_impl<T>(function_identifier: u32, decoded_data: T, encoded_data: &[u8])
+where
+	T: AbiWrite + AbiRead + std::cmp::PartialEq + std::fmt::Debug,
+{
+	let (call, mut decoder) = AbiReader::new_call(encoded_data).unwrap();
+	assert_eq!(call, u32::to_be_bytes(function_identifier));
+	let data = <T>::abi_read(&mut decoder).unwrap();
+	assert_eq!(data, decoded_data);
 
-                let mut writer = AbiWriter::new_call(function_identifier);
-                decoded_data.abi_write(&mut writer);
-                let ed = writer.finish();
-                similar_asserts::assert_eq!(encoded_data, ed.as_slice());
-            }
-        });
-    };
+	let mut writer = AbiWriter::new_call(function_identifier);
+	decoded_data.abi_write(&mut writer);
+	let ed = writer.finish();
+	similar_asserts::assert_eq!(encoded_data, ed.as_slice());
 }
 
 macro_rules! test_impl_uint {
 	($type:ident) => {
-		test_impl!(
-			$type,
-			$type,
+		test_impl::<$type>(
 			0xdeadbeef,
 			255 as $type,
 			&hex!(
@@ -43,101 +32,148 @@
                     deadbeef
                     00000000000000000000000000000000000000000000000000000000000000ff
                 "
-			)
+			),
 		);
 	};
 }
 
-test_impl_uint!(uint8);
-test_impl_uint!(uint32);
-test_impl_uint!(uint128);
+#[test]
+fn encode_decode_uint8() {
+	test_impl_uint!(uint8);
+}
 
-test_impl!(
-	uint256,
-	uint256,
-	0xdeadbeef,
-	U256([255, 0, 0, 0]),
-	&hex!(
-		"
-            deadbeef
-            00000000000000000000000000000000000000000000000000000000000000ff
-        "
-	)
-);
+#[test]
+fn encode_decode_uint32() {
+	test_impl_uint!(uint32);
+}
 
-test_impl!(
-    vec_tuple_address_uint256,
-    Vec<(address, uint256)>,
-    0x1ACF2D55,
-    vec![
-        (
-            H160(hex!("2D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC")),
-            U256([10, 0, 0, 0]),
-        ),
-        (
-            H160(hex!("AB8E3D9134955566483B11E6825C9223B6737B10")),
-            U256([20, 0, 0, 0]),
-        ),
-        (
-            H160(hex!("8C582BDF2953046705FC56F189385255EFC1BE18")),
-            U256([30, 0, 0, 0]),
-        ),
-    ],
-    &hex!(
-        "
-            1ACF2D55
-            0000000000000000000000000000000000000000000000000000000000000020 // offset of (address, uint256)[]
-            0000000000000000000000000000000000000000000000000000000000000003 // length of (address, uint256)[]
+#[test]
+fn encode_decode_uint128() {
+	test_impl_uint!(uint128);
+}
 
-            0000000000000000000000002D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC // address
-            000000000000000000000000000000000000000000000000000000000000000A // uint256
+#[test]
+fn encode_decode_uint256() {
+	test_impl::<uint256>(
+		0xdeadbeef,
+		U256([255, 0, 0, 0]),
+		&hex!(
+			"
+                deadbeef
+                00000000000000000000000000000000000000000000000000000000000000ff
+            "
+		),
+	);
+}
 
-            000000000000000000000000AB8E3D9134955566483B11E6825C9223B6737B10 // address
-            0000000000000000000000000000000000000000000000000000000000000014 // uint256
+#[test]
+fn encode_decode_string() {
+	test_impl::<String>(
+		0xdeadbeef,
+		"some string".to_string(),
+		&hex!(
+			"
+                deadbeef
+                0000000000000000000000000000000000000000000000000000000000000020
+                000000000000000000000000000000000000000000000000000000000000000b
+                736f6d6520737472696e67000000000000000000000000000000000000000000
+            "
+		),
+	);
+}
 
-            0000000000000000000000008C582BDF2953046705FC56F189385255EFC1BE18 // address
-            000000000000000000000000000000000000000000000000000000000000001E // uint256
-        "
-    )
-);
+#[test]
+fn encode_decode_tuple_string() {
+	test_impl::<(String,)>(
+		0xdeadbeef,
+		("some string".to_string(),),
+		&hex!(
+			"
+                deadbeef
+                0000000000000000000000000000000000000000000000000000000000000020
+                0000000000000000000000000000000000000000000000000000000000000020
+                000000000000000000000000000000000000000000000000000000000000000b
+                736f6d6520737472696e67000000000000000000000000000000000000000000
+            "
+		),
+	);
+}
 
-test_impl!(
-    vec_tuple_uint256_string,
-    Vec<(uint256, string)>,
-    0xdeadbeef,
-    vec![
-        (1.into(), "Test URI 0".to_string()),
-        (11.into(), "Test URI 1".to_string()),
-        (12.into(), "Test URI 2".to_string()),
-    ],
-    &hex!(
-        "
-            deadbeef
-            0000000000000000000000000000000000000000000000000000000000000020 // offset of (uint256, string)[]
-            0000000000000000000000000000000000000000000000000000000000000003 // length of (uint256, string)[]
+#[test]
+fn encode_decode_vec_tuple_address_uint256() {
+	test_impl::<Vec<(address, uint256)>>(
+        0x1ACF2D55,
+        vec![
+            (
+                H160(hex!("2D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC")),
+                U256([10, 0, 0, 0]),
+            ),
+            (
+                H160(hex!("AB8E3D9134955566483B11E6825C9223B6737B10")),
+                U256([20, 0, 0, 0]),
+            ),
+            (
+                H160(hex!("8C582BDF2953046705FC56F189385255EFC1BE18")),
+                U256([30, 0, 0, 0]),
+            ),
+        ],
+        &hex!(
+            "
+                1ACF2D55
+                0000000000000000000000000000000000000000000000000000000000000020 // offset of (address, uint256)[]
+                0000000000000000000000000000000000000000000000000000000000000003 // length of (address, uint256)[]
 
-            0000000000000000000000000000000000000000000000000000000000000060 // offset of first elem
-            00000000000000000000000000000000000000000000000000000000000000e0 // offset of second elem
-            0000000000000000000000000000000000000000000000000000000000000160 // offset of third elem
+                0000000000000000000000002D2FF76104B7BACB2E8F6731D5BFC184EBECDDBC // address
+                000000000000000000000000000000000000000000000000000000000000000A // uint256
 
-            0000000000000000000000000000000000000000000000000000000000000001 // first token id?   					#60
-            0000000000000000000000000000000000000000000000000000000000000040 // offset of string
-            000000000000000000000000000000000000000000000000000000000000000a // size of string
-            5465737420555249203000000000000000000000000000000000000000000000 // string
+                000000000000000000000000AB8E3D9134955566483B11E6825C9223B6737B10 // address
+                0000000000000000000000000000000000000000000000000000000000000014 // uint256
 
-            000000000000000000000000000000000000000000000000000000000000000b // second token id? Why ==11?			#e0
-            0000000000000000000000000000000000000000000000000000000000000040 // offset of string
-            000000000000000000000000000000000000000000000000000000000000000a // size of string
-            5465737420555249203100000000000000000000000000000000000000000000 // string
+                0000000000000000000000008C582BDF2953046705FC56F189385255EFC1BE18 // address
+                000000000000000000000000000000000000000000000000000000000000001E // uint256
+            "
+        )
+    );
+}
 
-            000000000000000000000000000000000000000000000000000000000000000c // third token id?  Why ==12?			#160
-            0000000000000000000000000000000000000000000000000000000000000040 // offset of string
-            000000000000000000000000000000000000000000000000000000000000000a // size of string
-            5465737420555249203200000000000000000000000000000000000000000000 // string
-        "
-    )
-);
+#[test]
+fn encode_decode_vec_tuple_uint256_string() {
+	test_impl::<Vec<(uint256, string)>>(
+        0xdeadbeef,
+        vec![
+            (1.into(), "Test URI 0".to_string()),
+            (11.into(), "Test URI 1".to_string()),
+            (12.into(), "Test URI 2".to_string()),
+        ],
+        &hex!(
+            "
+                deadbeef
+                0000000000000000000000000000000000000000000000000000000000000020 // offset of (uint256, string)[]
+                0000000000000000000000000000000000000000000000000000000000000003 // length of (uint256, string)[]
 
+                0000000000000000000000000000000000000000000000000000000000000060 // offset of first elem
+                00000000000000000000000000000000000000000000000000000000000000e0 // offset of second elem
+                0000000000000000000000000000000000000000000000000000000000000160 // offset of third elem
+
+                0000000000000000000000000000000000000000000000000000000000000001 // first token id?   					#60
+                0000000000000000000000000000000000000000000000000000000000000040 // offset of string
+                000000000000000000000000000000000000000000000000000000000000000a // size of string
+                5465737420555249203000000000000000000000000000000000000000000000 // string
+
+                000000000000000000000000000000000000000000000000000000000000000b // second token id? Why ==11?			#e0
+                0000000000000000000000000000000000000000000000000000000000000040 // offset of string
+                000000000000000000000000000000000000000000000000000000000000000a // size of string
+                5465737420555249203100000000000000000000000000000000000000000000 // string
+
+                000000000000000000000000000000000000000000000000000000000000000c // third token id?  Why ==12?			#160
+                0000000000000000000000000000000000000000000000000000000000000040 // offset of string
+                000000000000000000000000000000000000000000000000000000000000000a // size of string
+                5465737420555249203200000000000000000000000000000000000000000000 // string
+            "
+        )
+    );
+}
+
 #[test]
 fn dynamic_after_static() {
 	let mut encoder = AbiWriter::new();
@@ -235,63 +271,270 @@
 	similar_asserts::assert_eq!(encoded_data, ed.as_slice());
 }
 
-test_impl!(
-	vec_tuple_string_bytes,
-	Vec<(string, bytes)>,
-	0xdeadbeef,
-	vec![
-		(
-			"Test URI 0".to_string(),
-			bytes(vec![
-				0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
-				0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
-				0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
-				0x11, 0x11, 0x11, 0x11, 0x11, 0x11
-			])
+#[test]
+fn encode_decode_vec_tuple_string_bytes() {
+	test_impl::<Vec<(string, bytes)>>(
+		0xdeadbeef,
+		vec![
+			(
+				"Test URI 0".to_string(),
+				bytes(vec![
+					0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+					0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+					0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+					0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
+				]),
+			),
+			(
+				"Test URI 1".to_string(),
+				bytes(vec![
+					0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+					0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+					0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+					0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
+				]),
+			),
+			("Test URI 2".to_string(), bytes(vec![0x33, 0x33])),
+		],
+		&hex!(
+			"
+                deadbeef
+                0000000000000000000000000000000000000000000000000000000000000020
+                0000000000000000000000000000000000000000000000000000000000000003
+
+                0000000000000000000000000000000000000000000000000000000000000060
+                0000000000000000000000000000000000000000000000000000000000000140
+                0000000000000000000000000000000000000000000000000000000000000220
+
+                0000000000000000000000000000000000000000000000000000000000000040
+                0000000000000000000000000000000000000000000000000000000000000080
+                000000000000000000000000000000000000000000000000000000000000000a
+                5465737420555249203000000000000000000000000000000000000000000000
+                0000000000000000000000000000000000000000000000000000000000000030
+                1111111111111111111111111111111111111111111111111111111111111111
+                1111111111111111111111111111111100000000000000000000000000000000
+
+                0000000000000000000000000000000000000000000000000000000000000040
+                0000000000000000000000000000000000000000000000000000000000000080
+                000000000000000000000000000000000000000000000000000000000000000a
+                5465737420555249203100000000000000000000000000000000000000000000
+                000000000000000000000000000000000000000000000000000000000000002f
+                2222222222222222222222222222222222222222222222222222222222222222
+                2222222222222222222222222222220000000000000000000000000000000000
+
+                0000000000000000000000000000000000000000000000000000000000000040
+                0000000000000000000000000000000000000000000000000000000000000080
+                000000000000000000000000000000000000000000000000000000000000000a
+                5465737420555249203200000000000000000000000000000000000000000000
+                0000000000000000000000000000000000000000000000000000000000000002
+                3333000000000000000000000000000000000000000000000000000000000000
+            "
+		),
+	);
+}
+
+#[test]
+// #[ignore = "reason"]
+fn encode_decode_tuple0_tuple1_uint8_tuple1_string_bytes_tuple1_uint8_bytes() {
+	let int = 0xff;
+	let by = bytes(vec![0x11, 0x22, 0x33]);
+	let string = "some string".to_string();
+
+	test_impl::<((u8,), (String, bytes), (u8, bytes))>(
+		0xdeadbeef,
+		((int,), (string.clone(), by.clone()), (int, by)),
+		&hex!(
+			"
+                deadbeef
+                0000000000000000000000000000000000000000000000000000000000000020
+                00000000000000000000000000000000000000000000000000000000000000ff
+                0000000000000000000000000000000000000000000000000000000000000060
+                0000000000000000000000000000000000000000000000000000000000000120
+                0000000000000000000000000000000000000000000000000000000000000040
+                0000000000000000000000000000000000000000000000000000000000000080
+                000000000000000000000000000000000000000000000000000000000000000b
+                736f6d6520737472696e67000000000000000000000000000000000000000000
+                0000000000000000000000000000000000000000000000000000000000000003
+                1122330000000000000000000000000000000000000000000000000000000000
+                00000000000000000000000000000000000000000000000000000000000000ff
+                0000000000000000000000000000000000000000000000000000000000000040
+                0000000000000000000000000000000000000000000000000000000000000003
+                1122330000000000000000000000000000000000000000000000000000000000
+            "
+		),
+	);
+}
+
+#[test]
+fn encode_decode_tuple0_tuple1_uint8_tuple1_uint8_uint8_tuple1_uint8_uint8() {
+	test_impl::<((u8,), (u8, u8), (u8, u8))>(
+		0xdeadbeef,
+		((43,), (44, 45), (46, 47)),
+		&hex!(
+			"
+                deadbeef
+                000000000000000000000000000000000000000000000000000000000000002b
+                000000000000000000000000000000000000000000000000000000000000002c
+                000000000000000000000000000000000000000000000000000000000000002d
+                000000000000000000000000000000000000000000000000000000000000002e
+                000000000000000000000000000000000000000000000000000000000000002f
+            "
+		),
+	);
+}
+
+#[test]
+fn encode_decode_tuple0_tuple1_uint8_tuple1_uint8() {
+	test_impl::<((u8,), (u8,))>(
+		0xdeadbeef,
+		((43,), (44,)),
+		&hex!(
+			"
+                deadbeef
+                000000000000000000000000000000000000000000000000000000000000002b
+                000000000000000000000000000000000000000000000000000000000000002c
+            "
+		),
+	);
+}
+
+#[test]
+fn encode_decode_tuple0_tuple1_uint8_uint8() {
+	test_impl::<((u8, u8),)>(
+		0xdeadbeef,
+		((43, 44),),
+		&hex!(
+			"
+                deadbeef
+                000000000000000000000000000000000000000000000000000000000000002b
+                000000000000000000000000000000000000000000000000000000000000002c
+            "
+		),
+	);
+}
+
+#[test]
+fn encode_decode_tuple_uint8_uint8() {
+	test_impl::<(u8, u8)>(
+		0xdeadbeef,
+		(43, 44),
+		&hex!(
+			"
+                deadbeef
+                000000000000000000000000000000000000000000000000000000000000002b
+                000000000000000000000000000000000000000000000000000000000000002c
+            "
+		),
+	);
+}
+
+#[test]
+fn encode_decode_tuple0_tuple1_uint8_uint8_tuple1_uint8_uint8_and_uint8() {
+	test_impl::<((u8, u8), (u8, u8), u8)>(
+		0xdeadbeef,
+		((10, 11), (12, 13), 14),
+		&hex!(
+			"
+                deadbeef
+                000000000000000000000000000000000000000000000000000000000000000a
+                000000000000000000000000000000000000000000000000000000000000000b
+                000000000000000000000000000000000000000000000000000000000000000c
+                000000000000000000000000000000000000000000000000000000000000000d
+                000000000000000000000000000000000000000000000000000000000000000e
+            "
 		),
-		(
-			"Test URI 1".to_string(),
-			bytes(vec![
-				0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
-				0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
-				0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22,
-				0x22, 0x22, 0x22, 0x22, 0x22
-			])
+	);
+}
+
+#[test]
+fn encode_decode_tuple0_tuple1_string() {
+	test_impl::<((String,),)>(
+		0xdeadbeef,
+		(("some string".to_string(),),),
+		&hex!(
+			"
+                deadbeef
+                0000000000000000000000000000000000000000000000000000000000000020
+                0000000000000000000000000000000000000000000000000000000000000020
+                0000000000000000000000000000000000000000000000000000000000000020
+                000000000000000000000000000000000000000000000000000000000000000b
+                736f6d6520737472696e67000000000000000000000000000000000000000000
+            "
 		),
-		("Test URI 2".to_string(), bytes(vec![0x33, 0x33])),
-	],
-	&hex!(
-		"
-            deadbeef
-            0000000000000000000000000000000000000000000000000000000000000020
-            0000000000000000000000000000000000000000000000000000000000000003
-            
-            0000000000000000000000000000000000000000000000000000000000000060
-            0000000000000000000000000000000000000000000000000000000000000140
-            0000000000000000000000000000000000000000000000000000000000000220
+	);
+}
 
-            0000000000000000000000000000000000000000000000000000000000000040
-            0000000000000000000000000000000000000000000000000000000000000080
-            000000000000000000000000000000000000000000000000000000000000000a
-            5465737420555249203000000000000000000000000000000000000000000000
-            0000000000000000000000000000000000000000000000000000000000000030
-            1111111111111111111111111111111111111111111111111111111111111111
-            1111111111111111111111111111111100000000000000000000000000000000
+#[test]
+fn encode_decode_tuple0_tuple1_uint8_string() {
+	test_impl::<((u8, String),)>(
+		0xdeadbeef,
+		((0xff, "some string".to_string()),),
+		&hex!(
+			"
+                deadbeef
+                0000000000000000000000000000000000000000000000000000000000000020
+                0000000000000000000000000000000000000000000000000000000000000020
+                00000000000000000000000000000000000000000000000000000000000000ff
+                0000000000000000000000000000000000000000000000000000000000000040
+                000000000000000000000000000000000000000000000000000000000000000b
+                736f6d6520737472696e67000000000000000000000000000000000000000000
+            "
+		),
+	);
+}
 
-            0000000000000000000000000000000000000000000000000000000000000040
-            0000000000000000000000000000000000000000000000000000000000000080
-            000000000000000000000000000000000000000000000000000000000000000a
-            5465737420555249203100000000000000000000000000000000000000000000
-            000000000000000000000000000000000000000000000000000000000000002f
-            2222222222222222222222222222222222222222222222222222222222222222
-            2222222222222222222222222222220000000000000000000000000000000000
+#[test]
+fn encode_decode_tuple0_tuple1_string_bytes() {
+	test_impl::<((String, bytes),)>(
+		0xdeadbeef,
+		(("some string".to_string(), bytes(vec![1, 2, 3])),),
+		&hex!(
+			"
+                deadbeef
+                0000000000000000000000000000000000000000000000000000000000000020
+                0000000000000000000000000000000000000000000000000000000000000020
+                0000000000000000000000000000000000000000000000000000000000000040
+                0000000000000000000000000000000000000000000000000000000000000080
+                000000000000000000000000000000000000000000000000000000000000000b
+                736f6d6520737472696e67000000000000000000000000000000000000000000
+                0000000000000000000000000000000000000000000000000000000000000003
+                0102030000000000000000000000000000000000000000000000000000000000
+            "
+		),
+	);
+}
 
-            0000000000000000000000000000000000000000000000000000000000000040
-            0000000000000000000000000000000000000000000000000000000000000080
+#[test]
+fn encode_decode_tuple0_tuple1_uint8_tuple1_string() {
+	test_impl::<((u8,), (String,))>(
+		0xdeadbeef,
+		((0xff,), ("some string".to_string(),)),
+		&hex!(
+			"
+                deadbeef
+                0000000000000000000000000000000000000000000000000000000000000020
+                00000000000000000000000000000000000000000000000000000000000000ff
+                0000000000000000000000000000000000000000000000000000000000000040
+                0000000000000000000000000000000000000000000000000000000000000020
+                000000000000000000000000000000000000000000000000000000000000000b
+                736f6d6520737472696e67000000000000000000000000000000000000000000
+            "
+		),
+	);
+}
+
+#[test]
+fn parse_multiple_params() {
+	let encoded_data = hex!(
+		"
+            deadbeef
             000000000000000000000000000000000000000000000000000000000000000a
-            5465737420555249203200000000000000000000000000000000000000000000
-            0000000000000000000000000000000000000000000000000000000000000002
-            3333000000000000000000000000000000000000000000000000000000000000
+            000000000000000000000000000000000000000000000000000000000000000b
         "
-	)
-);
+	);
+	let (_, mut decoder) = AbiReader::new_call(&encoded_data).unwrap();
+	let p1 = <u8>::abi_read(&mut decoder).unwrap();
+	let p2 = <u8>::abi_read(&mut decoder).unwrap();
+	assert_eq!(p1, 0x0a);
+	assert_eq!(p2, 0x0b);
+}
modifiedcrates/evm-coder/src/abi/traits.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/abi/traits.rs
+++ b/crates/evm-coder/src/abi/traits.rs
@@ -22,12 +22,6 @@
 	fn size() -> usize;
 }
 
-/// Sealed traits.
-pub mod sealed {
-	/// Not all types can be placed in vec, i.e `Vec<u8>` is restricted, `bytes` should be used instead
-	pub trait CanBePlacedInVec {}
-}
-
 /// [`AbiReader`] implements reading of many types.
 pub trait AbiRead {
 	/// Read item from current position, advanding decoder
@@ -47,11 +41,5 @@
 		let mut writer = AbiWriter::new();
 		self.abi_write(&mut writer);
 		Ok(writer.into())
-	}
-}
-
-impl<T: AbiWrite> AbiWrite for &T {
-	fn abi_write(&self, writer: &mut AbiWriter) {
-		T::abi_write(self, writer);
 	}
 }
modifiedcrates/evm-coder/src/lib.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -93,6 +93,7 @@
 pub use evm_coder_procedural::solidity;
 /// See [`solidity_interface`]
 pub use evm_coder_procedural::weight;
+pub use evm_coder_procedural::AbiCoder;
 pub use sha3_const;
 
 /// Derives [`ToLog`] for enum
@@ -111,6 +112,15 @@
 #[cfg(feature = "stubgen")]
 pub mod solidity;
 
+/// Sealed traits.
+pub mod sealed {
+	/// Not every type should be directly placed in vec.
+	/// Vec encoding is not memory efficient, as every item will be padded
+	/// to 32 bytes.
+	/// Instead you should use specialized types (`bytes` in case of `Vec<u8>`)
+	pub trait CanBePlacedInVec {}
+}
+
 /// Solidity type definitions (aliases from solidity name to rust type)
 /// To be used in [`solidity_interface`] definitions, to make sure there is no
 /// type conflict between Rust code and generated definitions
@@ -119,7 +129,6 @@
 
 	#[cfg(not(feature = "std"))]
 	use alloc::{vec::Vec};
-	use pallet_evm::account::CrossAccountId;
 	use primitive_types::{U256, H160, H256};
 
 	pub type address = H160;
@@ -137,7 +146,7 @@
 	#[cfg(feature = "std")]
 	pub type string = ::std::string::String;
 
-	#[derive(Default, Debug, PartialEq)]
+	#[derive(Default, Debug, PartialEq, Eq, Clone)]
 	pub struct bytes(pub Vec<u8>);
 
 	/// Solidity doesn't have `void` type, however we have special implementation
@@ -185,73 +194,7 @@
 		#[must_use]
 		pub fn is_empty(&self) -> bool {
 			self.len() == 0
-		}
-	}
-
-	#[derive(Debug, Default)]
-	pub struct EthCrossAccount {
-		pub(crate) eth: address,
-		pub(crate) sub: uint256,
-	}
-
-	impl EthCrossAccount {
-		pub fn from_sub_cross_account<T>(cross_account_id: &T::CrossAccountId) -> Self
-		where
-			T: pallet_evm::Config,
-			T::AccountId: AsRef<[u8; 32]>,
-		{
-			if cross_account_id.is_canonical_substrate() {
-				Self {
-					eth: Default::default(),
-					sub: convert_cross_account_to_uint256::<T>(cross_account_id),
-				}
-			} else {
-				Self {
-					eth: *cross_account_id.as_eth(),
-					sub: Default::default(),
-				}
-			}
 		}
-
-		pub fn into_sub_cross_account<T>(&self) -> crate::execution::Result<T::CrossAccountId>
-		where
-			T: pallet_evm::Config,
-			T::AccountId: From<[u8; 32]>,
-		{
-			if self.eth == Default::default() && self.sub == Default::default() {
-				Err("All fields of cross account is zeroed".into())
-			} else if self.eth == Default::default() {
-				Ok(convert_uint256_to_cross_account::<T>(self.sub))
-			} else if self.sub == Default::default() {
-				Ok(T::CrossAccountId::from_eth(self.eth))
-			} else {
-				Err("All fields of cross account is non zeroed".into())
-			}
-		}
-	}
-
-	/// Convert `CrossAccountId` to `uint256`.
-	pub fn convert_cross_account_to_uint256<T: pallet_evm::Config>(
-		from: &T::CrossAccountId,
-	) -> uint256
-	where
-		T::AccountId: AsRef<[u8; 32]>,
-	{
-		let slice = from.as_sub().as_ref();
-		uint256::from_big_endian(slice)
-	}
-
-	/// Convert `uint256` to `CrossAccountId`.
-	pub fn convert_uint256_to_cross_account<T: pallet_evm::Config>(
-		from: uint256,
-	) -> T::CrossAccountId
-	where
-		T::AccountId: From<[u8; 32]>,
-	{
-		let mut new_admin_arr = [0_u8; 32];
-		from.to_big_endian(&mut new_admin_arr);
-		let account_id = T::AccountId::from(new_admin_arr);
-		T::CrossAccountId::from_sub(account_id)
 	}
 
 	#[derive(Debug, Default)]
deletedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ /dev/null
@@ -1,702 +0,0 @@
-// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
-// This file is part of Unique Network.
-
-// Unique Network is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Unique Network is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-
-// 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 [`crate::solidity_interface`] macro code-generation.
-//! You should not rely on any public item from this module, as it is only intended to be used
-//! by procedural macro, API and output format may be changed at any time.
-//!
-//! Purpose of this module is to receive solidity contract definition in module-specified
-//! format, and then output string, representing interface of this contract in solidity language
-
-#[cfg(not(feature = "std"))]
-use alloc::{string::String, vec::Vec, collections::BTreeMap, format};
-#[cfg(feature = "std")]
-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::*, custom_signature::SignatureUnit};
-
-#[derive(Default)]
-pub struct TypeCollector {
-	/// 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>,
-}
-impl TypeCollector {
-	pub fn new() -> Self {
-		Self::default()
-	}
-	pub fn collect(&self, item: string) {
-		let id = self.next_id();
-		self.structs.borrow_mut().insert(item, id);
-	}
-	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, "/// @dev 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 collect_struct<T: StructCollect>(&self) -> String {
-		self.collect(<T as StructCollect>::declaration());
-		<T as StructCollect>::name()
-	}
-	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()
-	}
-}
-
-pub trait StructCollect: 'static {
-	/// Structure name.
-	fn name() -> String;
-	/// Structure declaration.
-	fn declaration() -> String;
-}
-
-pub trait SolidityTypeName: 'static {
-	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
-	/// "simple" types are stored inline, no `memory` modifier should be used in solidity
-	fn is_simple() -> bool;
-	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
-	/// Specialization
-	fn is_void() -> bool {
-		false
-	}
-}
-macro_rules! solidity_type_name {
-    ($($ty:ty => $name:literal $simple:literal = $default:literal),* $(,)?) => {
-        $(
-            impl SolidityTypeName for $ty {
-                fn solidity_name(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {
-                    write!(writer, $name)
-                }
-				fn is_simple() -> bool {
-					$simple
-				}
-				fn solidity_default(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {
-					write!(writer, $default)
-				}
-            }
-        )*
-    };
-}
-
-solidity_type_name! {
-	uint8 => "uint8" true = "0",
-	uint32 => "uint32" true = "0",
-	uint64 => "uint64" true = "0",
-	uint128 => "uint128" true = "0",
-	uint256 => "uint256" true = "0",
-	bytes4 => "bytes4" true = "bytes4(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, _tc: &TypeCollector) -> fmt::Result {
-		Ok(())
-	}
-	fn is_simple() -> bool {
-		true
-	}
-	fn solidity_default(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
-		Ok(())
-	}
-	fn is_void() -> bool {
-		true
-	}
-}
-
-mod sealed {
-	/// Not every type should be directly placed in vec.
-	/// Vec encoding is not memory efficient, as every item will be padded
-	/// to 32 bytes.
-	/// Instead you should use specialized types (`bytes` in case of `Vec<u8>`)
-	pub trait CanBePlacedInVec {}
-}
-
-impl sealed::CanBePlacedInVec for uint256 {}
-impl sealed::CanBePlacedInVec for string {}
-impl sealed::CanBePlacedInVec for address {}
-impl sealed::CanBePlacedInVec for EthCrossAccount {}
-impl sealed::CanBePlacedInVec for Property {}
-
-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, "new ")?;
-		T::solidity_name(writer, tc)?;
-		write!(writer, "[](0)")
-	}
-}
-
-impl SolidityTupleType for EthCrossAccount {
-	fn names(tc: &TypeCollector) -> Vec<string> {
-		let mut collected = Vec::with_capacity(Self::len());
-		{
-			let mut out = string::new();
-			address::solidity_name(&mut out, tc).expect("no fmt error");
-			collected.push(out);
-		}
-		{
-			let mut out = string::new();
-			uint256::solidity_name(&mut out, tc).expect("no fmt error");
-			collected.push(out);
-		}
-		collected
-	}
-
-	fn len() -> usize {
-		2
-	}
-}
-
-impl SolidityTypeName for EthCrossAccount {
-	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
-		write!(writer, "{}", tc.collect_struct::<Self>())
-	}
-
-	fn is_simple() -> bool {
-		false
-	}
-
-	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
-		write!(writer, "{}(", tc.collect_struct::<Self>())?;
-		address::solidity_default(writer, tc)?;
-		write!(writer, ",")?;
-		uint256::solidity_default(writer, tc)?;
-		write!(writer, ")")
-	}
-}
-
-impl StructCollect for EthCrossAccount {
-	fn name() -> String {
-		"EthCrossAccount".into()
-	}
-
-	fn declaration() -> String {
-		let mut str = String::new();
-		writeln!(str, "/// @dev Cross account struct").unwrap();
-		writeln!(str, "struct {} {{", Self::name()).unwrap();
-		writeln!(str, "\taddress eth;").unwrap();
-		writeln!(str, "\tuint256 sub;").unwrap();
-		writeln!(str, "}}").unwrap();
-		str
-	}
-}
-
-impl StructCollect for Property {
-	fn name() -> String {
-		"Property".into()
-	}
-
-	fn declaration() -> String {
-		let mut str = String::new();
-		writeln!(str, "/// @dev Property struct").unwrap();
-		writeln!(str, "struct {} {{", Self::name()).unwrap();
-		writeln!(str, "\tstring key;").unwrap();
-		writeln!(str, "\tbytes value;").unwrap();
-		writeln!(str, "}}").unwrap();
-		str
-	}
-}
-
-impl SolidityTypeName for Property {
-	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
-		write!(writer, "{}", tc.collect_struct::<Self>())
-	}
-
-	fn is_simple() -> bool {
-		false
-	}
-
-	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
-		write!(writer, "{}(", tc.collect_struct::<Self>())?;
-		address::solidity_default(writer, tc)?;
-		write!(writer, ",")?;
-		uint256::solidity_default(writer, tc)?;
-		write!(writer, ")")
-	}
-}
-
-impl SolidityTupleType for Property {
-	fn names(tc: &TypeCollector) -> Vec<string> {
-		let mut collected = Vec::with_capacity(Self::len());
-		{
-			let mut out = string::new();
-			string::solidity_name(&mut out, tc).expect("no fmt error");
-			collected.push(out);
-		}
-		{
-			let mut out = string::new();
-			bytes::solidity_name(&mut out, tc).expect("no fmt error");
-			collected.push(out);
-		}
-		collected
-	}
-
-	fn len() -> usize {
-		2
-	}
-}
-
-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
-			}
-			#[allow(unused_assignments)]
-			fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
-				write!(writer, "{}(", tc.collect_tuple::<Self>())?;
-				let mut first = true;
-				$(
-					if !first {
-						write!(writer, ",")?;
-					} else {
-						first = false;
-					}
-					<$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, tc: &TypeCollector) -> fmt::Result;
-	fn solidity_get(&self, prefix: &str, 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
-	}
-	fn len(&self) -> usize;
-}
-
-#[derive(Default)]
-pub struct UnnamedArgument<T>(PhantomData<*const T>);
-
-impl<T: SolidityTypeName> SolidityArguments for UnnamedArgument<T> {
-	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
-		if !T::is_void() {
-			T::solidity_name(writer, tc)?;
-			if !T::is_simple() {
-				write!(writer, " memory")?;
-			}
-			Ok(())
-		} else {
-			Ok(())
-		}
-	}
-	fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result {
-		Ok(())
-	}
-	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() {
-			0
-		} else {
-			1
-		}
-	}
-}
-
-pub struct NamedArgument<T>(&'static str, PhantomData<*const T>);
-
-impl<T> NamedArgument<T> {
-	pub fn new(name: &'static str) -> Self {
-		Self(name, Default::default())
-	}
-}
-
-impl<T: SolidityTypeName> SolidityArguments for NamedArgument<T> {
-	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
-		if !T::is_void() {
-			T::solidity_name(writer, tc)?;
-			if !T::is_simple() {
-				write!(writer, " memory")?;
-			}
-			write!(writer, " {}", self.0)
-		} else {
-			Ok(())
-		}
-	}
-	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {
-		writeln!(writer, "\t{prefix}\t{};", self.0)
-	}
-	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() {
-			0
-		} else {
-			1
-		}
-	}
-}
-
-pub struct SolidityEventArgument<T>(pub bool, &'static str, PhantomData<*const T>);
-
-impl<T> SolidityEventArgument<T> {
-	pub fn new(indexed: bool, name: &'static str) -> Self {
-		Self(indexed, name, Default::default())
-	}
-}
-
-impl<T: SolidityTypeName> SolidityArguments for SolidityEventArgument<T> {
-	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
-		if !T::is_void() {
-			T::solidity_name(writer, tc)?;
-			if self.0 {
-				write!(writer, " indexed")?;
-			}
-			write!(writer, " {}", self.1)
-		} else {
-			Ok(())
-		}
-	}
-	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {
-		writeln!(writer, "\t{prefix}\t{};", self.1)
-	}
-	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() {
-			0
-		} else {
-			1
-		}
-	}
-}
-
-impl SolidityArguments for () {
-	fn solidity_name(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
-		Ok(())
-	}
-	fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result {
-		Ok(())
-	}
-	fn solidity_default(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
-		Ok(())
-	}
-	fn len(&self) -> usize {
-		0
-	}
-}
-
-#[impl_for_tuples(1, 12)]
-impl SolidityArguments for Tuple {
-	for_tuples!( where #( Tuple: SolidityArguments ),* );
-
-	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
-		let mut first = true;
-		for_tuples!( #(
-            if !Tuple.is_empty() {
-                if !first {
-                    write!(writer, ", ")?;
-                }
-                first = false;
-                Tuple.solidity_name(writer, tc)?;
-            }
-        )* );
-		Ok(())
-	}
-	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {
-		for_tuples!( #(
-            Tuple.solidity_get(prefix, writer)?;
-        )* );
-		Ok(())
-	}
-	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, tc)?;
-			)* );
-			Ok(())
-		} else {
-			write!(writer, "(")?;
-			let mut first = true;
-			for_tuples!( #(
-				if !Tuple.is_empty() {
-					if !first {
-						write!(writer, ", ")?;
-					}
-					first = false;
-					Tuple.solidity_default(writer, tc)?;
-				}
-			)* );
-			write!(writer, ")")?;
-			Ok(())
-		}
-	}
-	fn len(&self) -> usize {
-		for_tuples!( #( Tuple.len() )+* )
-	}
-}
-
-pub trait SolidityFunctions {
-	fn solidity_name(
-		&self,
-		is_impl: bool,
-		writer: &mut impl fmt::Write,
-		tc: &TypeCollector,
-	) -> fmt::Result;
-}
-
-pub enum SolidityMutability {
-	Pure,
-	View,
-	Mutable,
-}
-pub struct SolidityFunction<A, R> {
-	pub docs: &'static [&'static str],
-	pub selector: u32,
-	pub hide: bool,
-	pub custom_signature: SignatureUnit,
-	pub name: &'static str,
-	pub args: A,
-	pub result: R,
-	pub mutability: SolidityMutability,
-	pub is_payable: bool,
-}
-impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {
-	fn solidity_name(
-		&self,
-		is_impl: bool,
-		writer: &mut impl fmt::Write,
-		tc: &TypeCollector,
-	) -> fmt::Result {
-		let hide_comment = self.hide.then(|| "// ").unwrap_or("");
-		for doc in self.docs {
-			writeln!(writer, "\t{hide_comment}///{}", doc)?;
-		}
-		writeln!(
-			writer,
-			"\t{hide_comment}/// @dev EVM selector for this function is: 0x{:0>8x},",
-			self.selector
-		)?;
-		writeln!(
-			writer,
-			"\t{hide_comment}///  or in textual repr: {}",
-			self.custom_signature.as_str().expect("bad utf-8")
-		)?;
-		write!(writer, "\t{hide_comment}function {}(", self.name)?;
-		self.args.solidity_name(writer, tc)?;
-		write!(writer, ")")?;
-		if is_impl {
-			write!(writer, " public")?;
-		} else {
-			write!(writer, " external")?;
-		}
-		match &self.mutability {
-			SolidityMutability::Pure => write!(writer, " pure")?,
-			SolidityMutability::View => write!(writer, " view")?,
-			SolidityMutability::Mutable => {}
-		}
-		if self.is_payable {
-			write!(writer, " payable")?;
-		}
-		if !self.result.is_empty() {
-			write!(writer, " returns (")?;
-			self.result.solidity_name(writer, tc)?;
-			write!(writer, ")")?;
-		}
-		if is_impl {
-			writeln!(writer, " {{")?;
-			writeln!(writer, "\t{hide_comment}\trequire(false, stub_error);")?;
-			self.args.solidity_get(hide_comment, writer)?;
-			match &self.mutability {
-				SolidityMutability::Pure => {}
-				SolidityMutability::View => writeln!(writer, "\t{hide_comment}\tdummy;")?,
-				SolidityMutability::Mutable => writeln!(writer, "\t{hide_comment}\tdummy = 0;")?,
-			}
-			if !self.result.is_empty() {
-				write!(writer, "\t{hide_comment}\treturn ")?;
-				self.result.solidity_default(writer, tc)?;
-				writeln!(writer, ";")?;
-			}
-			writeln!(writer, "\t{hide_comment}}}")?;
-		} else {
-			writeln!(writer, ";")?;
-		}
-		if self.hide {
-			writeln!(writer, "// FORMATTING: FORCE NEWLINE")?;
-		}
-		Ok(())
-	}
-}
-
-#[impl_for_tuples(0, 48)]
-impl SolidityFunctions for Tuple {
-	for_tuples!( where #( Tuple: SolidityFunctions ),* );
-
-	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, tc)?;
-        )* );
-		Ok(())
-	}
-}
-
-pub struct SolidityInterface<F: SolidityFunctions> {
-	pub docs: &'static [&'static str],
-	pub selector: bytes4,
-	pub name: &'static str,
-	pub is: &'static [&'static str],
-	pub functions: F,
-}
-
-impl<F: SolidityFunctions> SolidityInterface<F> {
-	pub fn format(
-		&self,
-		is_impl: bool,
-		out: &mut impl fmt::Write,
-		tc: &TypeCollector,
-	) -> fmt::Result {
-		const ZERO_BYTES: [u8; 4] = [0; 4];
-		for doc in self.docs {
-			writeln!(out, "///{}", doc)?;
-		}
-		if self.selector != ZERO_BYTES {
-			writeln!(
-				out,
-				"/// @dev the ERC-165 identifier for this interface is 0x{:0>8x}",
-				u32::from_be_bytes(self.selector)
-			)?;
-		}
-		if is_impl {
-			write!(out, "contract ")?;
-		} else {
-			write!(out, "interface ")?;
-		}
-		write!(out, "{}", self.name)?;
-		if !self.is.is_empty() {
-			write!(out, " is")?;
-			for (i, n) in self.is.iter().enumerate() {
-				if i != 0 {
-					write!(out, ",")?;
-				}
-				write!(out, " {}", n)?;
-			}
-		}
-		writeln!(out, " {{")?;
-		self.functions.solidity_name(is_impl, out, tc)?;
-		writeln!(out, "}}")?;
-		Ok(())
-	}
-}
-
-pub struct SolidityEvent<A> {
-	pub name: &'static str,
-	pub args: A,
-}
-
-impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {
-	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, tc)?;
-		writeln!(writer, ");")
-	}
-}
addedcrates/evm-coder/src/solidity/impls.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/src/solidity/impls.rs
@@ -0,0 +1,190 @@
+use super::{TypeCollector, SolidityTypeName, SolidityType, StructCollect};
+use crate::{sealed, types::*};
+use core::fmt;
+use primitive_types::{U256, H160};
+
+macro_rules! solidity_type_name {
+    ($($ty:ty => $name:literal $simple:literal = $default:literal),* $(,)?) => {
+        $(
+            impl SolidityTypeName for $ty {
+                fn solidity_name(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {
+                    write!(writer, $name)
+                }
+				fn is_simple() -> bool {
+					$simple
+				}
+				fn solidity_default(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {
+					write!(writer, $default)
+				}
+            }
+
+			impl StructCollect for $ty {
+				fn name() -> String {
+					$name.to_string()
+				}
+
+				fn declaration() -> String {
+					String::default()
+				}
+			}
+        )*
+    };
+}
+
+solidity_type_name! {
+	u8 => "uint8" true = "0",
+	u32 => "uint32" true = "0",
+	u64 => "uint64" true = "0",
+	u128 => "uint128" true = "0",
+	U256 => "uint256" true = "0",
+	bytes4 => "bytes4" true = "bytes4(0)",
+	H160 => "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, _tc: &TypeCollector) -> fmt::Result {
+		Ok(())
+	}
+	fn is_simple() -> bool {
+		true
+	}
+	fn solidity_default(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
+		Ok(())
+	}
+	fn is_void() -> bool {
+		true
+	}
+}
+
+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, "new ")?;
+		T::solidity_name(writer, tc)?;
+		write!(writer, "[](0)")
+	}
+}
+
+macro_rules! count {
+    () => (0usize);
+    ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*));
+}
+
+macro_rules! impl_tuples {
+	($($ident:ident)+) => {
+		impl<$($ident: SolidityTypeName + 'static),+> SolidityType 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
+			}
+			#[allow(unused_assignments)]
+			fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+				write!(writer, "{}(", tc.collect_tuple::<Self>())?;
+				let mut first = true;
+				$(
+					if !first {
+						write!(writer, ",")?;
+					} else {
+						first = false;
+					}
+					<$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}
+
+impl StructCollect for Property {
+	fn name() -> String {
+		"Property".into()
+	}
+
+	fn declaration() -> String {
+		use std::fmt::Write;
+
+		let mut str = String::new();
+		writeln!(str, "/// @dev Property struct").unwrap();
+		writeln!(str, "struct {} {{", Self::name()).unwrap();
+		writeln!(str, "\tstring key;").unwrap();
+		writeln!(str, "\tbytes value;").unwrap();
+		writeln!(str, "}}").unwrap();
+		str
+	}
+}
+
+impl SolidityTypeName for Property {
+	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		write!(writer, "{}", tc.collect_struct::<Self>())
+	}
+
+	fn is_simple() -> bool {
+		false
+	}
+
+	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		write!(writer, "{}(", tc.collect_struct::<Self>())?;
+		address::solidity_default(writer, tc)?;
+		write!(writer, ",")?;
+		uint256::solidity_default(writer, tc)?;
+		write!(writer, ")")
+	}
+}
+
+impl SolidityType for Property {
+	fn names(tc: &TypeCollector) -> Vec<string> {
+		let mut collected = Vec::with_capacity(Self::len());
+		{
+			let mut out = string::new();
+			string::solidity_name(&mut out, tc).expect("no fmt error");
+			collected.push(out);
+		}
+		{
+			let mut out = string::new();
+			bytes::solidity_name(&mut out, tc).expect("no fmt error");
+			collected.push(out);
+		}
+		collected
+	}
+
+	fn len() -> usize {
+		2
+	}
+}
addedcrates/evm-coder/src/solidity/mod.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/src/solidity/mod.rs
@@ -0,0 +1,421 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// 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 [`crate::solidity_interface`] macro code-generation.
+//! You should not rely on any public item from this module, as it is only intended to be used
+//! by procedural macro, API and output format may be changed at any time.
+//!
+//! Purpose of this module is to receive solidity contract definition in module-specified
+//! format, and then output string, representing interface of this contract in solidity language
+
+mod traits;
+pub use traits::*;
+mod impls;
+
+#[cfg(not(feature = "std"))]
+use alloc::{string::String, vec::Vec, collections::BTreeMap, format};
+#[cfg(feature = "std")]
+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::*, custom_signature::SignatureUnit};
+
+#[derive(Default)]
+pub struct TypeCollector {
+	/// 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>,
+}
+impl TypeCollector {
+	pub fn new() -> Self {
+		Self::default()
+	}
+	pub fn collect(&self, item: string) {
+		let id = self.next_id();
+		self.structs.borrow_mut().insert(item, id);
+	}
+	pub fn next_id(&self) -> usize {
+		let v = self.id.get();
+		self.id.set(v + 1);
+		v
+	}
+	pub fn collect_tuple<T: SolidityType>(&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, "/// @dev 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 collect_struct<T: StructCollect>(&self) -> String {
+		self.collect(<T as StructCollect>::declaration());
+		<T as StructCollect>::name()
+	}
+	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()
+	}
+}
+#[derive(Default)]
+pub struct UnnamedArgument<T>(PhantomData<*const T>);
+
+impl<T: SolidityTypeName> SolidityArguments for UnnamedArgument<T> {
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		if !T::is_void() {
+			T::solidity_name(writer, tc)?;
+			if !T::is_simple() {
+				write!(writer, " memory")?;
+			}
+			Ok(())
+		} else {
+			Ok(())
+		}
+	}
+	fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result {
+		Ok(())
+	}
+	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() {
+			0
+		} else {
+			1
+		}
+	}
+}
+
+pub struct NamedArgument<T>(&'static str, PhantomData<*const T>);
+
+impl<T> NamedArgument<T> {
+	pub fn new(name: &'static str) -> Self {
+		Self(name, Default::default())
+	}
+}
+
+impl<T: SolidityTypeName> SolidityArguments for NamedArgument<T> {
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		if !T::is_void() {
+			T::solidity_name(writer, tc)?;
+			if !T::is_simple() {
+				write!(writer, " memory")?;
+			}
+			write!(writer, " {}", self.0)
+		} else {
+			Ok(())
+		}
+	}
+	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {
+		writeln!(writer, "\t{prefix}\t{};", self.0)
+	}
+	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() {
+			0
+		} else {
+			1
+		}
+	}
+}
+
+pub struct SolidityEventArgument<T>(pub bool, &'static str, PhantomData<*const T>);
+
+impl<T> SolidityEventArgument<T> {
+	pub fn new(indexed: bool, name: &'static str) -> Self {
+		Self(indexed, name, Default::default())
+	}
+}
+
+impl<T: SolidityTypeName> SolidityArguments for SolidityEventArgument<T> {
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		if !T::is_void() {
+			T::solidity_name(writer, tc)?;
+			if self.0 {
+				write!(writer, " indexed")?;
+			}
+			write!(writer, " {}", self.1)
+		} else {
+			Ok(())
+		}
+	}
+	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {
+		writeln!(writer, "\t{prefix}\t{};", self.1)
+	}
+	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() {
+			0
+		} else {
+			1
+		}
+	}
+}
+
+impl SolidityArguments for () {
+	fn solidity_name(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
+		Ok(())
+	}
+	fn solidity_get(&self, _prefix: &str, _writer: &mut impl fmt::Write) -> fmt::Result {
+		Ok(())
+	}
+	fn solidity_default(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
+		Ok(())
+	}
+	fn len(&self) -> usize {
+		0
+	}
+}
+
+#[impl_for_tuples(1, 12)]
+impl SolidityArguments for Tuple {
+	for_tuples!( where #( Tuple: SolidityArguments ),* );
+
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		let mut first = true;
+		for_tuples!( #(
+            if !Tuple.is_empty() {
+                if !first {
+                    write!(writer, ", ")?;
+                }
+                first = false;
+                Tuple.solidity_name(writer, tc)?;
+            }
+        )* );
+		Ok(())
+	}
+	fn solidity_get(&self, prefix: &str, writer: &mut impl fmt::Write) -> fmt::Result {
+		for_tuples!( #(
+            Tuple.solidity_get(prefix, writer)?;
+        )* );
+		Ok(())
+	}
+	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, tc)?;
+			)* );
+			Ok(())
+		} else {
+			write!(writer, "(")?;
+			let mut first = true;
+			for_tuples!( #(
+				if !Tuple.is_empty() {
+					if !first {
+						write!(writer, ", ")?;
+					}
+					first = false;
+					Tuple.solidity_default(writer, tc)?;
+				}
+			)* );
+			write!(writer, ")")?;
+			Ok(())
+		}
+	}
+	fn len(&self) -> usize {
+		for_tuples!( #( Tuple.len() )+* )
+	}
+}
+
+pub enum SolidityMutability {
+	Pure,
+	View,
+	Mutable,
+}
+pub struct SolidityFunction<A, R> {
+	pub docs: &'static [&'static str],
+	pub selector: u32,
+	pub hide: bool,
+	pub custom_signature: SignatureUnit,
+	pub name: &'static str,
+	pub args: A,
+	pub result: R,
+	pub mutability: SolidityMutability,
+	pub is_payable: bool,
+}
+impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {
+	fn solidity_name(
+		&self,
+		is_impl: bool,
+		writer: &mut impl fmt::Write,
+		tc: &TypeCollector,
+	) -> fmt::Result {
+		let hide_comment = self.hide.then_some("// ").unwrap_or("");
+		for doc in self.docs {
+			writeln!(writer, "\t{hide_comment}///{}", doc)?;
+		}
+		writeln!(
+			writer,
+			"\t{hide_comment}/// @dev EVM selector for this function is: 0x{:0>8x},",
+			self.selector
+		)?;
+		writeln!(
+			writer,
+			"\t{hide_comment}///  or in textual repr: {}",
+			self.custom_signature.as_str().expect("bad utf-8")
+		)?;
+		write!(writer, "\t{hide_comment}function {}(", self.name)?;
+		self.args.solidity_name(writer, tc)?;
+		write!(writer, ")")?;
+		if is_impl {
+			write!(writer, " public")?;
+		} else {
+			write!(writer, " external")?;
+		}
+		match &self.mutability {
+			SolidityMutability::Pure => write!(writer, " pure")?,
+			SolidityMutability::View => write!(writer, " view")?,
+			SolidityMutability::Mutable => {}
+		}
+		if self.is_payable {
+			write!(writer, " payable")?;
+		}
+		if !self.result.is_empty() {
+			write!(writer, " returns (")?;
+			self.result.solidity_name(writer, tc)?;
+			write!(writer, ")")?;
+		}
+		if is_impl {
+			writeln!(writer, " {{")?;
+			writeln!(writer, "\t{hide_comment}\trequire(false, stub_error);")?;
+			self.args.solidity_get(hide_comment, writer)?;
+			match &self.mutability {
+				SolidityMutability::Pure => {}
+				SolidityMutability::View => writeln!(writer, "\t{hide_comment}\tdummy;")?,
+				SolidityMutability::Mutable => writeln!(writer, "\t{hide_comment}\tdummy = 0;")?,
+			}
+			if !self.result.is_empty() {
+				write!(writer, "\t{hide_comment}\treturn ")?;
+				self.result.solidity_default(writer, tc)?;
+				writeln!(writer, ";")?;
+			}
+			writeln!(writer, "\t{hide_comment}}}")?;
+		} else {
+			writeln!(writer, ";")?;
+		}
+		if self.hide {
+			writeln!(writer, "// FORMATTING: FORCE NEWLINE")?;
+		}
+		Ok(())
+	}
+}
+
+#[impl_for_tuples(0, 48)]
+impl SolidityFunctions for Tuple {
+	for_tuples!( where #( Tuple: SolidityFunctions ),* );
+
+	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, tc)?;
+        )* );
+		Ok(())
+	}
+}
+
+pub struct SolidityInterface<F: SolidityFunctions> {
+	pub docs: &'static [&'static str],
+	pub selector: bytes4,
+	pub name: &'static str,
+	pub is: &'static [&'static str],
+	pub functions: F,
+}
+
+impl<F: SolidityFunctions> SolidityInterface<F> {
+	pub fn format(
+		&self,
+		is_impl: bool,
+		out: &mut impl fmt::Write,
+		tc: &TypeCollector,
+	) -> fmt::Result {
+		const ZERO_BYTES: [u8; 4] = [0; 4];
+		for doc in self.docs {
+			writeln!(out, "///{}", doc)?;
+		}
+		if self.selector != ZERO_BYTES {
+			writeln!(
+				out,
+				"/// @dev the ERC-165 identifier for this interface is 0x{:0>8x}",
+				u32::from_be_bytes(self.selector)
+			)?;
+		}
+		if is_impl {
+			write!(out, "contract ")?;
+		} else {
+			write!(out, "interface ")?;
+		}
+		write!(out, "{}", self.name)?;
+		if !self.is.is_empty() {
+			write!(out, " is")?;
+			for (i, n) in self.is.iter().enumerate() {
+				if i != 0 {
+					write!(out, ",")?;
+				}
+				write!(out, " {}", n)?;
+			}
+		}
+		writeln!(out, " {{")?;
+		self.functions.solidity_name(is_impl, out, tc)?;
+		writeln!(out, "}}")?;
+		Ok(())
+	}
+}
+
+pub struct SolidityEvent<A> {
+	pub name: &'static str,
+	pub args: A,
+}
+
+impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {
+	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, tc)?;
+		writeln!(writer, ");")
+	}
+}
addedcrates/evm-coder/src/solidity/traits.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/src/solidity/traits.rs
@@ -0,0 +1,48 @@
+use super::TypeCollector;
+use core::fmt;
+
+pub trait StructCollect: 'static {
+	/// Structure name.
+	fn name() -> String;
+	/// Structure declaration.
+	fn declaration() -> String;
+}
+
+pub trait SolidityEnum: 'static {
+	fn solidity_option(&self) -> &str;
+}
+
+pub trait SolidityTypeName: 'static {
+	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
+	/// "simple" types are stored inline, no `memory` modifier should be used in solidity
+	fn is_simple() -> bool;
+	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
+	/// Specialization
+	fn is_void() -> bool {
+		false
+	}
+}
+
+pub trait SolidityType {
+	fn names(tc: &TypeCollector) -> Vec<String>;
+	fn len() -> usize;
+}
+
+pub trait SolidityArguments {
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
+	fn solidity_get(&self, prefix: &str, 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
+	}
+	fn len(&self) -> usize;
+}
+
+pub trait SolidityFunctions {
+	fn solidity_name(
+		&self,
+		is_impl: bool,
+		writer: &mut impl fmt::Write,
+		tc: &TypeCollector,
+	) -> fmt::Result;
+}
addedcrates/evm-coder/tests/abi_derive_generation.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/tests/abi_derive_generation.rs
@@ -0,0 +1,841 @@
+mod test_struct {
+	use evm_coder_procedural::AbiCoder;
+	use evm_coder::types::bytes;
+
+	#[test]
+	fn empty_struct() {
+		let t = trybuild::TestCases::new();
+		t.compile_fail("tests/build_failed/abi_derive_struct_generation.rs");
+	}
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TypeStruct1SimpleParam {
+		_a: u8,
+	}
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TypeStruct1DynamicParam {
+		_a: String,
+	}
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TypeStruct2SimpleParam {
+		_a: u8,
+		_b: u32,
+	}
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TypeStruct2DynamicParam {
+		_a: String,
+		_b: bytes,
+	}
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TypeStruct2MixedParam {
+		_a: u8,
+		_b: bytes,
+	}
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TypeStruct1DerivedSimpleParam {
+		_a: TypeStruct1SimpleParam,
+	}
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TypeStruct2DerivedSimpleParam {
+		_a: TypeStruct1SimpleParam,
+		_b: TypeStruct2SimpleParam,
+	}
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TypeStruct1DerivedDynamicParam {
+		_a: TypeStruct1DynamicParam,
+	}
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TypeStruct2DerivedDynamicParam {
+		_a: TypeStruct1DynamicParam,
+		_b: TypeStruct2DynamicParam,
+	}
+
+	/// Some docs
+	/// At multi
+	/// line
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TypeStruct3DerivedMixedParam {
+		/// Docs for A
+		/// multi
+		/// line
+		_a: TypeStruct1SimpleParam,
+		/// Docs for B
+		_b: TypeStruct2DynamicParam,
+		/// Docs for C
+		_c: TypeStruct2MixedParam,
+	}
+
+	#[test]
+	#[cfg(feature = "stubgen")]
+	fn struct_collect_type_struct3_derived_mixed_param() {
+		assert_eq!(
+			<TypeStruct3DerivedMixedParam as ::evm_coder::solidity::StructCollect>::name(),
+			"TypeStruct3DerivedMixedParam"
+		);
+		similar_asserts::assert_eq!(
+			<TypeStruct3DerivedMixedParam as ::evm_coder::solidity::StructCollect>::declaration(),
+			r#"/// @dev Some docs
+/// At multi
+/// line
+struct TypeStruct3DerivedMixedParam {
+	/// @dev Docs for A
+	/// multi
+	/// line
+	TypeStruct1SimpleParam _a;
+	/// @dev Docs for B
+	TypeStruct2DynamicParam _b;
+	/// @dev Docs for C
+	TypeStruct2MixedParam _c;
+}
+"#
+		);
+	}
+
+	#[test]
+	fn impl_abi_type_signature() {
+		assert_eq!(
+			<TypeStruct1SimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			"(uint8)"
+		);
+		assert_eq!(
+			<TypeStruct1DynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			"(string)"
+		);
+		assert_eq!(
+			<TypeStruct2SimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			"(uint8,uint32)"
+		);
+		assert_eq!(
+			<TypeStruct2DynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			"(string,bytes)"
+		);
+		assert_eq!(
+			<TypeStruct2MixedParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			"(uint8,bytes)"
+		);
+		assert_eq!(
+			<TypeStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			"((uint8))"
+		);
+		assert_eq!(
+			<TypeStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			"((uint8),(uint8,uint32))"
+		);
+		assert_eq!(
+			<TypeStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			"((string))"
+		);
+		assert_eq!(
+			<TypeStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			"((string),(string,bytes))"
+		);
+		assert_eq!(
+			<TypeStruct3DerivedMixedParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			"((uint8),(string,bytes),(uint8,bytes))"
+		);
+	}
+
+	#[test]
+	fn impl_abi_type_is_dynamic() {
+		assert_eq!(
+			<TypeStruct1SimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+			false
+		);
+		assert_eq!(
+			<TypeStruct1DynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+			true
+		);
+		assert_eq!(
+			<TypeStruct2SimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+			false
+		);
+		assert_eq!(
+			<TypeStruct2DynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+			true
+		);
+		assert_eq!(
+			<TypeStruct2MixedParam as evm_coder::abi::AbiType>::is_dynamic(),
+			true
+		);
+		assert_eq!(
+			<TypeStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+			false
+		);
+		assert_eq!(
+			<TypeStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+			false
+		);
+		assert_eq!(
+			<TypeStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+			true
+		);
+		assert_eq!(
+			<TypeStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+			true
+		);
+		assert_eq!(
+			<TypeStruct3DerivedMixedParam as evm_coder::abi::AbiType>::is_dynamic(),
+			true
+		);
+	}
+
+	#[test]
+	fn impl_abi_type_size() {
+		const ABI_ALIGNMENT: usize = 32;
+		assert_eq!(
+			<TypeStruct1SimpleParam as evm_coder::abi::AbiType>::size(),
+			ABI_ALIGNMENT
+		);
+		assert_eq!(
+			<TypeStruct1DynamicParam as evm_coder::abi::AbiType>::size(),
+			ABI_ALIGNMENT
+		);
+		assert_eq!(
+			<TypeStruct2SimpleParam as evm_coder::abi::AbiType>::size(),
+			ABI_ALIGNMENT * 2
+		);
+		assert_eq!(
+			<TypeStruct2DynamicParam as evm_coder::abi::AbiType>::size(),
+			ABI_ALIGNMENT * 2
+		);
+		assert_eq!(
+			<TypeStruct2MixedParam as evm_coder::abi::AbiType>::size(),
+			ABI_ALIGNMENT * 2
+		);
+		assert_eq!(
+			<TypeStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::size(),
+			ABI_ALIGNMENT
+		);
+		assert_eq!(
+			<TypeStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::size(),
+			ABI_ALIGNMENT * 3
+		);
+		assert_eq!(
+			<TypeStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::size(),
+			ABI_ALIGNMENT
+		);
+		assert_eq!(
+			<TypeStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::size(),
+			ABI_ALIGNMENT * 3
+		);
+		assert_eq!(
+			<TypeStruct3DerivedMixedParam as evm_coder::abi::AbiType>::size(),
+			ABI_ALIGNMENT * 5
+		);
+	}
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TupleStruct1SimpleParam(u8);
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TupleStruct1DynamicParam(String);
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TupleStruct2SimpleParam(u8, u32);
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TupleStruct2DynamicParam(String, bytes);
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TupleStruct2MixedParam(u8, bytes);
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TupleStruct1DerivedSimpleParam(TupleStruct1SimpleParam);
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TupleStruct2DerivedSimpleParam(TupleStruct1SimpleParam, TupleStruct2SimpleParam);
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TupleStruct1DerivedDynamicParam(TupleStruct1DynamicParam);
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TupleStruct2DerivedDynamicParam(TupleStruct1DynamicParam, TupleStruct2DynamicParam);
+
+	/// Some docs
+	/// At multi
+	/// line
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TupleStruct3DerivedMixedParam(
+		/// Docs for A
+		/// multi
+		/// line
+		TupleStruct1SimpleParam,
+		TupleStruct2DynamicParam,
+		/// Docs for C
+		TupleStruct2MixedParam,
+	);
+
+	#[test]
+	#[cfg(feature = "stubgen")]
+	fn struct_collect_tuple_struct3_derived_mixed_param() {
+		assert_eq!(
+			<TupleStruct3DerivedMixedParam as ::evm_coder::solidity::StructCollect>::name(),
+			"TupleStruct3DerivedMixedParam"
+		);
+		similar_asserts::assert_eq!(
+			<TupleStruct3DerivedMixedParam as ::evm_coder::solidity::StructCollect>::declaration(),
+			r#"/// @dev Some docs
+/// At multi
+/// line
+struct TupleStruct3DerivedMixedParam {
+	/// @dev Docs for A
+	/// multi
+	/// line
+	TupleStruct1SimpleParam field0;
+	TupleStruct2DynamicParam field1;
+	/// @dev Docs for C
+	TupleStruct2MixedParam field2;
+}
+"#
+		);
+	}
+
+	#[test]
+	fn impl_abi_type_signature_same_for_structs() {
+		assert_eq!(
+			<TypeStruct1SimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			<TupleStruct1SimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap()
+		);
+		assert_eq!(
+			<TypeStruct1DynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			<TupleStruct1DynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap()
+		);
+		assert_eq!(
+			<TypeStruct2SimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			<TupleStruct2SimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap()
+		);
+		assert_eq!(
+			<TypeStruct2DynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			<TupleStruct2DynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap()
+		);
+		assert_eq!(
+			<TypeStruct2MixedParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			<TupleStruct2MixedParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+		);
+		assert_eq!(
+			<TypeStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			<TupleStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+		);
+		assert_eq!(
+			<TypeStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			<TupleStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+		);
+		assert_eq!(
+			<TypeStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			<TupleStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+		);
+		assert_eq!(
+			<TypeStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			<TupleStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+		);
+		assert_eq!(
+			<TypeStruct3DerivedMixedParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			<TupleStruct3DerivedMixedParam as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+		);
+	}
+
+	#[test]
+	fn impl_abi_type_is_dynamic_same_for_structs() {
+		assert_eq!(
+			<TypeStruct1SimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+			<TupleStruct1SimpleParam as evm_coder::abi::AbiType>::is_dynamic()
+		);
+		assert_eq!(
+			<TypeStruct1DynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+			<TupleStruct1DynamicParam as evm_coder::abi::AbiType>::is_dynamic()
+		);
+		assert_eq!(
+			<TypeStruct2SimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+			<TupleStruct2SimpleParam as evm_coder::abi::AbiType>::is_dynamic()
+		);
+		assert_eq!(
+			<TypeStruct2DynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+			<TupleStruct2DynamicParam as evm_coder::abi::AbiType>::is_dynamic()
+		);
+		assert_eq!(
+			<TypeStruct2MixedParam as evm_coder::abi::AbiType>::is_dynamic(),
+			<TupleStruct2MixedParam as evm_coder::abi::AbiType>::is_dynamic()
+		);
+		assert_eq!(
+			<TypeStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+			<TupleStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::is_dynamic()
+		);
+		assert_eq!(
+			<TypeStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::is_dynamic(),
+			<TupleStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::is_dynamic()
+		);
+		assert_eq!(
+			<TypeStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+			<TupleStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::is_dynamic()
+		);
+		assert_eq!(
+			<TypeStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::is_dynamic(),
+			<TupleStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::is_dynamic()
+		);
+		assert_eq!(
+			<TypeStruct3DerivedMixedParam as evm_coder::abi::AbiType>::is_dynamic(),
+			<TupleStruct3DerivedMixedParam as evm_coder::abi::AbiType>::is_dynamic()
+		);
+	}
+
+	#[test]
+	fn impl_abi_type_size_same_for_structs() {
+		assert_eq!(
+			<TypeStruct1SimpleParam as evm_coder::abi::AbiType>::size(),
+			<TupleStruct1SimpleParam as evm_coder::abi::AbiType>::size()
+		);
+		assert_eq!(
+			<TypeStruct1DynamicParam as evm_coder::abi::AbiType>::size(),
+			<TupleStruct1DynamicParam as evm_coder::abi::AbiType>::size()
+		);
+		assert_eq!(
+			<TypeStruct2SimpleParam as evm_coder::abi::AbiType>::size(),
+			<TupleStruct2SimpleParam as evm_coder::abi::AbiType>::size()
+		);
+		assert_eq!(
+			<TypeStruct2DynamicParam as evm_coder::abi::AbiType>::size(),
+			<TupleStruct2DynamicParam as evm_coder::abi::AbiType>::size()
+		);
+		assert_eq!(
+			<TypeStruct2MixedParam as evm_coder::abi::AbiType>::size(),
+			<TupleStruct2MixedParam as evm_coder::abi::AbiType>::size()
+		);
+		assert_eq!(
+			<TypeStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::size(),
+			<TupleStruct1DerivedSimpleParam as evm_coder::abi::AbiType>::size()
+		);
+		assert_eq!(
+			<TypeStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::size(),
+			<TupleStruct2DerivedSimpleParam as evm_coder::abi::AbiType>::size()
+		);
+		assert_eq!(
+			<TypeStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::size(),
+			<TupleStruct1DerivedDynamicParam as evm_coder::abi::AbiType>::size()
+		);
+		assert_eq!(
+			<TypeStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::size(),
+			<TupleStruct2DerivedDynamicParam as evm_coder::abi::AbiType>::size()
+		);
+		assert_eq!(
+			<TypeStruct3DerivedMixedParam as evm_coder::abi::AbiType>::size(),
+			<TupleStruct3DerivedMixedParam as evm_coder::abi::AbiType>::size()
+		);
+	}
+
+	const FUNCTION_IDENTIFIER: u32 = 0xdeadbeef;
+
+	fn test_impl<Tuple, TupleStruct, TypeStruct>(
+		tuple_data: Tuple,
+		tuple_struct_data: TupleStruct,
+		type_struct_data: TypeStruct,
+	) where
+		TypeStruct: evm_coder::abi::AbiWrite
+			+ evm_coder::abi::AbiRead
+			+ std::cmp::PartialEq
+			+ std::fmt::Debug,
+		TupleStruct: evm_coder::abi::AbiWrite
+			+ evm_coder::abi::AbiRead
+			+ std::cmp::PartialEq
+			+ std::fmt::Debug,
+		Tuple: evm_coder::abi::AbiWrite
+			+ evm_coder::abi::AbiRead
+			+ std::cmp::PartialEq
+			+ std::fmt::Debug,
+	{
+		let encoded_type_struct = test_abi_write_impl(&type_struct_data);
+		let encoded_tuple_struct = test_abi_write_impl(&tuple_struct_data);
+		let encoded_tuple = test_abi_write_impl(&tuple_data);
+
+		similar_asserts::assert_eq!(encoded_tuple, encoded_type_struct);
+		similar_asserts::assert_eq!(encoded_tuple, encoded_tuple_struct);
+
+		{
+			let (_, mut decoder) = evm_coder::abi::AbiReader::new_call(&encoded_tuple).unwrap();
+			let restored_struct_data = <TypeStruct>::abi_read(&mut decoder).unwrap();
+			assert_eq!(restored_struct_data, type_struct_data);
+		}
+		{
+			let (_, mut decoder) = evm_coder::abi::AbiReader::new_call(&encoded_tuple).unwrap();
+			let restored_struct_data = <TupleStruct>::abi_read(&mut decoder).unwrap();
+			assert_eq!(restored_struct_data, tuple_struct_data);
+		}
+
+		{
+			let (_, mut decoder) =
+				evm_coder::abi::AbiReader::new_call(&encoded_type_struct).unwrap();
+			let restored_tuple_data = <Tuple>::abi_read(&mut decoder).unwrap();
+			assert_eq!(restored_tuple_data, tuple_data);
+		}
+		{
+			let (_, mut decoder) =
+				evm_coder::abi::AbiReader::new_call(&encoded_tuple_struct).unwrap();
+			let restored_tuple_data = <Tuple>::abi_read(&mut decoder).unwrap();
+			assert_eq!(restored_tuple_data, tuple_data);
+		}
+	}
+
+	fn test_abi_write_impl<A>(data: &A) -> Vec<u8>
+	where
+		A: evm_coder::abi::AbiWrite
+			+ evm_coder::abi::AbiRead
+			+ std::cmp::PartialEq
+			+ std::fmt::Debug,
+	{
+		let mut writer = evm_coder::abi::AbiWriter::new_call(FUNCTION_IDENTIFIER);
+		data.abi_write(&mut writer);
+		let encoded_tuple = writer.finish();
+		encoded_tuple
+	}
+
+	#[test]
+	fn codec_struct_1_simple() {
+		let _a = 0xff;
+		test_impl::<(u8,), TupleStruct1SimpleParam, TypeStruct1SimpleParam>(
+			(_a,),
+			TupleStruct1SimpleParam(_a),
+			TypeStruct1SimpleParam { _a },
+		);
+	}
+
+	#[test]
+	fn codec_struct_1_dynamic() {
+		let _a: String = "some string".into();
+		test_impl::<(String,), TupleStruct1DynamicParam, TypeStruct1DynamicParam>(
+			(_a.clone(),),
+			TupleStruct1DynamicParam(_a.clone()),
+			TypeStruct1DynamicParam { _a },
+		);
+	}
+
+	#[test]
+	fn codec_struct_1_derived_simple() {
+		let _a: u8 = 0xff;
+		test_impl::<((u8,),), TupleStruct1DerivedSimpleParam, TypeStruct1DerivedSimpleParam>(
+			((_a,),),
+			TupleStruct1DerivedSimpleParam(TupleStruct1SimpleParam(_a)),
+			TypeStruct1DerivedSimpleParam {
+				_a: TypeStruct1SimpleParam { _a },
+			},
+		);
+	}
+
+	#[test]
+	fn codec_struct_1_derived_dynamic() {
+		let _a: String = "some string".into();
+		test_impl::<((String,),), TupleStruct1DerivedDynamicParam, TypeStruct1DerivedDynamicParam>(
+			((_a.clone(),),),
+			TupleStruct1DerivedDynamicParam(TupleStruct1DynamicParam(_a.clone())),
+			TypeStruct1DerivedDynamicParam {
+				_a: TypeStruct1DynamicParam { _a },
+			},
+		);
+	}
+
+	#[test]
+	fn codec_struct_2_simple() {
+		let _a = 0xff;
+		let _b = 0xbeefbaba;
+		test_impl::<(u8, u32), TupleStruct2SimpleParam, TypeStruct2SimpleParam>(
+			(_a, _b),
+			TupleStruct2SimpleParam(_a, _b),
+			TypeStruct2SimpleParam { _a, _b },
+		);
+	}
+
+	#[test]
+	fn codec_struct_2_dynamic() {
+		let _a: String = "some string".into();
+		let _b: bytes = bytes(vec![0x11, 0x22, 0x33]);
+		test_impl::<(String, bytes), TupleStruct2DynamicParam, TypeStruct2DynamicParam>(
+			(_a.clone(), _b.clone()),
+			TupleStruct2DynamicParam(_a.clone(), _b.clone()),
+			TypeStruct2DynamicParam { _a, _b },
+		);
+	}
+
+	#[test]
+	fn codec_struct_2_mixed() {
+		let _a: u8 = 0xff;
+		let _b: bytes = bytes(vec![0x11, 0x22, 0x33]);
+		test_impl::<(u8, bytes), TupleStruct2MixedParam, TypeStruct2MixedParam>(
+			(_a.clone(), _b.clone()),
+			TupleStruct2MixedParam(_a.clone(), _b.clone()),
+			TypeStruct2MixedParam { _a, _b },
+		);
+	}
+
+	#[test]
+	fn codec_struct_2_derived_simple() {
+		let _a = 0xff;
+		let _b = 0xbeefbaba;
+		test_impl::<
+			((u8,), (u8, u32)),
+			TupleStruct2DerivedSimpleParam,
+			TypeStruct2DerivedSimpleParam,
+		>(
+			((_a,), (_a, _b)),
+			TupleStruct2DerivedSimpleParam(
+				TupleStruct1SimpleParam(_a),
+				TupleStruct2SimpleParam(_a, _b),
+			),
+			TypeStruct2DerivedSimpleParam {
+				_a: TypeStruct1SimpleParam { _a },
+				_b: TypeStruct2SimpleParam { _a, _b },
+			},
+		);
+	}
+
+	#[test]
+	fn codec_struct_2_derived_dynamic() {
+		let _a = "some string".to_string();
+		let _b = bytes(vec![0x11, 0x22, 0x33]);
+		test_impl::<
+			((String,), (String, bytes)),
+			TupleStruct2DerivedDynamicParam,
+			TypeStruct2DerivedDynamicParam,
+		>(
+			((_a.clone(),), (_a.clone(), _b.clone())),
+			TupleStruct2DerivedDynamicParam(
+				TupleStruct1DynamicParam(_a.clone()),
+				TupleStruct2DynamicParam(_a.clone(), _b.clone()),
+			),
+			TypeStruct2DerivedDynamicParam {
+				_a: TypeStruct1DynamicParam { _a: _a.clone() },
+				_b: TypeStruct2DynamicParam { _a, _b },
+			},
+		);
+	}
+
+	#[test]
+	fn codec_struct_3_derived_mixed() {
+		let int = 0xff;
+		let by = bytes(vec![0x11, 0x22, 0x33]);
+		let string = "some string".to_string();
+		test_impl::<
+			((u8,), (String, bytes), (u8, bytes)),
+			TupleStruct3DerivedMixedParam,
+			TypeStruct3DerivedMixedParam,
+		>(
+			((int,), (string.clone(), by.clone()), (int, by.clone())),
+			TupleStruct3DerivedMixedParam(
+				TupleStruct1SimpleParam(int),
+				TupleStruct2DynamicParam(string.clone(), by.clone()),
+				TupleStruct2MixedParam(int, by.clone()),
+			),
+			TypeStruct3DerivedMixedParam {
+				_a: TypeStruct1SimpleParam { _a: int },
+				_b: TypeStruct2DynamicParam {
+					_a: string.clone(),
+					_b: by.clone(),
+				},
+				_c: TypeStruct2MixedParam { _a: int, _b: by },
+			},
+		);
+	}
+
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TypeStruct2SimpleStruct1Simple {
+		_a: TypeStruct2SimpleParam,
+		_b: TypeStruct2SimpleParam,
+		_c: u8,
+	}
+	#[derive(AbiCoder, PartialEq, Debug)]
+	struct TupleStruct2SimpleStruct1Simple(TupleStruct2SimpleParam, TupleStruct2SimpleParam, u8);
+
+	#[test]
+	fn codec_struct_2_struct_simple_1_simple() {
+		let _a = 0xff;
+		let _b = 0xbeefbaba;
+		test_impl::<
+			((u8, u32), (u8, u32), u8),
+			TupleStruct2SimpleStruct1Simple,
+			TypeStruct2SimpleStruct1Simple,
+		>(
+			((_a, _b), (_a, _b), _a),
+			TupleStruct2SimpleStruct1Simple(
+				TupleStruct2SimpleParam(_a, _b),
+				TupleStruct2SimpleParam(_a, _b),
+				_a,
+			),
+			TypeStruct2SimpleStruct1Simple {
+				_a: TypeStruct2SimpleParam { _a, _b },
+				_b: TypeStruct2SimpleParam { _a, _b },
+				_c: _a,
+			},
+		);
+	}
+}
+
+mod test_enum {
+	use evm_coder::AbiCoder;
+
+	/// Some docs
+	/// At multi
+	/// line
+	#[derive(AbiCoder, Debug, PartialEq, Default)]
+	#[repr(u8)]
+	enum Color {
+		/// Docs for Red
+		/// multi
+		/// line
+		Red,
+		Green,
+		/// Docs for Blue
+		#[default]
+		Blue,
+	}
+
+	#[test]
+	fn empty() {}
+
+	#[test]
+	fn bad_enums() {
+		let t = trybuild::TestCases::new();
+		t.compile_fail("tests/build_failed/abi_derive_enum_generation.rs");
+	}
+
+	#[test]
+	fn impl_abi_type_signature_same_for_structs() {
+		assert_eq!(
+			<Color as evm_coder::abi::AbiType>::SIGNATURE
+				.as_str()
+				.unwrap(),
+			<u8 as evm_coder::abi::AbiType>::SIGNATURE.as_str().unwrap()
+		);
+	}
+
+	#[test]
+	fn impl_abi_type_is_dynamic_same_for_structs() {
+		assert_eq!(
+			<Color as evm_coder::abi::AbiType>::is_dynamic(),
+			<u8 as evm_coder::abi::AbiType>::is_dynamic()
+		);
+	}
+
+	#[test]
+	fn impl_abi_type_size_same_for_structs() {
+		assert_eq!(
+			<Color as evm_coder::abi::AbiType>::size(),
+			<u8 as evm_coder::abi::AbiType>::size()
+		);
+	}
+
+	#[test]
+	fn test_coder() {
+		const FUNCTION_IDENTIFIER: u32 = 0xdeadbeef;
+
+		let encoded_enum = {
+			let mut writer = evm_coder::abi::AbiWriter::new_call(FUNCTION_IDENTIFIER);
+			<Color as evm_coder::abi::AbiWrite>::abi_write(&Color::Green, &mut writer);
+			writer.finish()
+		};
+
+		let encoded_u8 = {
+			let mut writer = evm_coder::abi::AbiWriter::new_call(FUNCTION_IDENTIFIER);
+			<u8 as evm_coder::abi::AbiWrite>::abi_write(&(Color::Green as u8), &mut writer);
+			writer.finish()
+		};
+
+		similar_asserts::assert_eq!(encoded_enum, encoded_u8);
+
+		{
+			let (_, mut decoder) = evm_coder::abi::AbiReader::new_call(&encoded_enum).unwrap();
+			let restored_enum_data =
+				<Color as evm_coder::abi::AbiRead>::abi_read(&mut decoder).unwrap();
+			assert_eq!(restored_enum_data, Color::Green);
+		}
+	}
+
+	#[test]
+	#[cfg(feature = "stubgen")]
+	fn struct_collect_enum() {
+		assert_eq!(
+			<Color as ::evm_coder::solidity::StructCollect>::name(),
+			"Color"
+		);
+		similar_asserts::assert_eq!(
+			<Color as ::evm_coder::solidity::StructCollect>::declaration(),
+			r#"/// @dev Some docs
+/// At multi
+/// line
+enum Color {
+	/// @dev Docs for Red
+	/// multi
+	/// line
+	Red,
+	Green,
+	/// @dev Docs for Blue
+	Blue
+}
+"#
+		);
+	}
+}
addedcrates/evm-coder/tests/build_failed/abi_derive_enum_generation.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/tests/build_failed/abi_derive_enum_generation.rs
@@ -0,0 +1,36 @@
+use evm_coder_procedural::AbiCoder;
+
+#[derive(AbiCoder)]
+enum NonRepr {
+	A,
+	B,
+	C,
+}
+
+#[derive(AbiCoder)]
+#[repr(u32)]
+enum NonReprU8 {
+	A,
+	B,
+	C,
+}
+
+#[derive(AbiCoder)]
+#[repr(u8)]
+enum RustEnum {
+	A(u128),
+	B,
+	C,
+}
+
+#[derive(AbiCoder)]
+#[repr(u8)]
+enum WithExplicit {
+	A = 128,
+	B,
+	C,
+}
+
+fn main() {
+	assert!(false);
+}
addedcrates/evm-coder/tests/build_failed/abi_derive_enum_generation.stderrdiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/tests/build_failed/abi_derive_enum_generation.stderr
@@ -0,0 +1,23 @@
+error: Enum is not "repr(u8)"
+ --> tests/build_failed/abi_derive_enum_generation.rs:4:6
+  |
+4 | enum NonRepr {
+  |      ^^^^^^^
+
+error: Enum is not "repr(u8)"
+  --> tests/build_failed/abi_derive_enum_generation.rs:11:8
+   |
+11 | #[repr(u32)]
+   |        ^^^
+
+error: Enumeration parameters should not have fields
+  --> tests/build_failed/abi_derive_enum_generation.rs:21:2
+   |
+21 |     A(u128),
+   |     ^
+
+error: Enumeration options should not have an explicit specified value
+  --> tests/build_failed/abi_derive_enum_generation.rs:29:2
+   |
+29 |     A = 128,
+   |     ^
addedcrates/evm-coder/tests/build_failed/abi_derive_struct_generation.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/tests/build_failed/abi_derive_struct_generation.rs
@@ -0,0 +1,11 @@
+use evm_coder_procedural::AbiCoder;
+
+#[derive(AbiCoder, PartialEq, Debug)]
+struct EmptyStruct {}
+
+#[derive(AbiCoder, PartialEq, Debug)]
+struct EmptyTupleStruct();
+
+fn main() {
+	assert!(false);
+}
addedcrates/evm-coder/tests/build_failed/abi_derive_struct_generation.stderrdiffbeforeafterboth
--- /dev/null
+++ b/crates/evm-coder/tests/build_failed/abi_derive_struct_generation.stderr
@@ -0,0 +1,11 @@
+error: Empty structs not supported
+ --> tests/build_failed/abi_derive_struct_generation.rs:4:8
+  |
+4 | struct EmptyStruct {}
+  |        ^^^^^^^^^^^
+
+error: Empty structs not supported
+ --> tests/build_failed/abi_derive_struct_generation.rs:7:8
+  |
+7 | struct EmptyTupleStruct();
+  |        ^^^^^^^^^^^^^^^^
modifiedpallets/common/src/erc.rsdiffbeforeafterboth
--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -16,6 +16,7 @@
 
 //! This module contains the implementation of pallet methods for evm.
 
+pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};
 use evm_coder::{
 	abi::AbiType,
 	solidity_interface, solidity, ToLog,
@@ -24,7 +25,6 @@
 	execution::{Result, Error},
 	weight,
 };
-pub use pallet_evm::{PrecompileOutput, PrecompileResult, PrecompileHandle, account::CrossAccountId};
 use pallet_evm_coder_substrate::dispatch_to_evm;
 use sp_std::vec::Vec;
 use up_data_structs::{
@@ -35,7 +35,8 @@
 
 use crate::{
 	Pallet, CollectionHandle, Config, CollectionProperties, SelfWeightOf,
-	eth::convert_cross_account_to_uint256, weights::WeightInfo,
+	eth::{EthCrossAccount, convert_cross_account_to_uint256},
+	weights::WeightInfo,
 };
 
 /// Events for ethereum collection helper.
modifiedpallets/common/src/eth.rsdiffbeforeafterboth
--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -16,7 +16,10 @@
 
 //! The module contains a number of functions for converting and checking ethereum identifiers.
 
-use evm_coder::types::{uint256, address};
+use evm_coder::{
+	AbiCoder,
+	types::{uint256, address},
+};
 pub use pallet_evm::{Config, account::CrossAccountId};
 use sp_core::H160;
 use up_data_structs::CollectionId;
@@ -109,3 +112,46 @@
 		Err("All fields of cross account is non zeroed".into())
 	}
 }
+
+/// Cross account struct
+#[derive(Debug, Default, AbiCoder)]
+pub struct EthCrossAccount {
+	pub(crate) eth: address,
+	pub(crate) sub: uint256,
+}
+
+impl EthCrossAccount {
+	pub fn from_sub_cross_account<T>(cross_account_id: &T::CrossAccountId) -> Self
+	where
+		T: pallet_evm::Config,
+		T::AccountId: AsRef<[u8; 32]>,
+	{
+		if cross_account_id.is_canonical_substrate() {
+			Self {
+				eth: Default::default(),
+				sub: convert_cross_account_to_uint256::<T>(cross_account_id),
+			}
+		} else {
+			Self {
+				eth: *cross_account_id.as_eth(),
+				sub: Default::default(),
+			}
+		}
+	}
+
+	pub fn into_sub_cross_account<T>(&self) -> evm_coder::execution::Result<T::CrossAccountId>
+	where
+		T: pallet_evm::Config,
+		T::AccountId: From<[u8; 32]>,
+	{
+		if self.eth == Default::default() && self.sub == Default::default() {
+			Err("All fields of cross account is zeroed".into())
+		} else if self.eth == Default::default() {
+			Ok(convert_uint256_to_cross_account::<T>(self.sub))
+		} else if self.sub == Default::default() {
+			Ok(T::CrossAccountId::from_eth(self.eth))
+		} else {
+			Err("All fields of cross account is non zeroed".into())
+		}
+	}
+}
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -376,6 +376,7 @@
 		type TreasuryAccountId: Get<Self::AccountId>;
 
 		/// Address under which the CollectionHelper contract would be available.
+		#[pallet::constant]
 		type ContractAddress: Get<H160>;
 
 		/// Mapper for token addresses to Ethereum addresses.
modifiedpallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -47,6 +47,7 @@
 		type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;
 
 		/// Address, under which magic contract will be available
+		#[pallet::constant]
 		type ContractAddress: Get<H160>;
 
 		/// In case of enabled sponsoring, but no sponsoring rate limit set,
modifiedpallets/evm-contract-helpers/src/stubs/ContractHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/fungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/fungible/src/erc.rs
+++ b/pallets/fungible/src/erc.rs
@@ -24,12 +24,16 @@
 	weight,
 };
 use up_data_structs::CollectionMode;
-use pallet_common::erc::{CommonEvmHandler, PrecompileResult};
+use pallet_common::{
+	CollectionHandle,
+	erc::{CommonEvmHandler, PrecompileResult, CollectionCall},
+	eth::EthCrossAccount,
+};
 use sp_std::vec::Vec;
 use pallet_evm::{account::CrossAccountId, PrecompileHandle};
 use pallet_evm_coder_substrate::{call, dispatch_to_evm};
 use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
-use pallet_common::{CollectionHandle, erc::CollectionCall};
+use sp_core::Get;
 
 use crate::{
 	Allowance, Balance, Config, FungibleHandle, Pallet, SelfWeightOf, TotalSupply,
@@ -132,6 +136,11 @@
 
 		Ok(<Allowance<T>>::get((self.id, owner, spender)).into())
 	}
+
+	/// @notice Returns collection helper contract address
+	fn collection_helper_address(&self) -> Result<address> {
+		Ok(T::ContractAddress::get())
+	}
 }
 
 #[solidity_interface(name = ERC20Mintable)]
modifiedpallets/fungible/src/stubs/UniqueFungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/fungible/src/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/fungible/src/stubs/UniqueFungible.sol
+++ b/pallets/fungible/src/stubs/UniqueFungible.sol
@@ -547,7 +547,7 @@
 	event Approval(address indexed owner, address indexed spender, uint256 value);
 }
 
-/// @dev the ERC-165 identifier for this interface is 0x942e8b22
+/// @dev the ERC-165 identifier for this interface is 0x8cb847c4
 contract ERC20 is Dummy, ERC165, ERC20Events {
 	/// @dev EVM selector for this function is: 0x06fdde03,
 	///  or in textual repr: name()
@@ -634,6 +634,15 @@
 		dummy;
 		return 0;
 	}
+
+	/// @notice Returns collection helper contract address
+	/// @dev EVM selector for this function is: 0x1896cce6,
+	///  or in textual repr: collectionHelperAddress()
+	function collectionHelperAddress() public view returns (address) {
+		require(false, stub_error);
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
 }
 
 contract UniqueFungible is Dummy, ERC165, ERC20, ERC20Mintable, ERC20UniqueExtensions, Collection {}
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -36,12 +36,14 @@
 use pallet_evm_coder_substrate::dispatch_to_evm;
 use sp_std::vec::Vec;
 use pallet_common::{
+	CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,
 	erc::{CommonEvmHandler, PrecompileResult, CollectionCall, static_property::key},
-	CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,
+	eth::EthCrossAccount,
 };
 use pallet_evm::{account::CrossAccountId, PrecompileHandle};
 use pallet_evm_coder_substrate::call;
 use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
+use sp_core::Get;
 
 use crate::{
 	AccountBalance, Config, CreateItemData, NonfungibleHandle, Pallet, TokenData, TokensMinted,
@@ -489,6 +491,11 @@
 		// TODO: Not implemetable
 		Err("not implemented".into())
 	}
+
+	/// @notice Returns collection helper contract address
+	fn collection_helper_address(&self) -> Result<address> {
+		Ok(T::ContractAddress::get())
+	}
 }
 
 /// @title ERC721 Token that can be irreversibly burned (destroyed).
modifiedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nonfungible/src/stubs/UniqueNFT.sol
+++ b/pallets/nonfungible/src/stubs/UniqueNFT.sol
@@ -920,7 +920,7 @@
 
 /// @title ERC-721 Non-Fungible Token Standard
 /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
-/// @dev the ERC-165 identifier for this interface is 0x80ac58cd
+/// @dev the ERC-165 identifier for this interface is 0x983a942b
 contract ERC721 is Dummy, ERC165, ERC721Events {
 	/// @notice Count all NFTs assigned to an owner
 	/// @dev NFTs assigned to the zero address are considered invalid, and this
@@ -1050,6 +1050,15 @@
 		dummy;
 		return 0x0000000000000000000000000000000000000000;
 	}
+
+	/// @notice Returns collection helper contract address
+	/// @dev EVM selector for this function is: 0x1896cce6,
+	///  or in textual repr: collectionHelperAddress()
+	function collectionHelperAddress() public view returns (address) {
+		require(false, stub_error);
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
 }
 
 contract UniqueNFT is
modifiedpallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth
--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -178,7 +178,7 @@
 
 use RmrkProperty::*;
 
-/// Maximum number of levels of depth in the token nesting tree.
+/// A maximum number of levels of depth in the token nesting tree.
 pub const NESTING_BUDGET: u32 = 5;
 
 type PendingTarget = (CollectionId, TokenId);
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -31,14 +31,14 @@
 };
 use frame_support::{BoundedBTreeMap, BoundedVec};
 use pallet_common::{
-	CollectionHandle, CollectionPropertyPermissions,
+	CollectionHandle, CollectionPropertyPermissions, CommonCollectionOperations,
 	erc::{CommonEvmHandler, CollectionCall, static_property::key},
-	CommonCollectionOperations,
+	eth::EthCrossAccount,
 };
 use pallet_evm::{account::CrossAccountId, PrecompileHandle};
 use pallet_evm_coder_substrate::{call, dispatch_to_evm};
 use pallet_structure::{SelfWeightOf as StructureWeight, weights::WeightInfo as _};
-use sp_core::H160;
+use sp_core::{H160, Get};
 use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};
 use up_data_structs::{
 	CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,
@@ -482,6 +482,11 @@
 		// TODO: Not implemetable
 		Err("not implemented".into())
 	}
+
+	/// @notice Returns collection helper contract address
+	fn collection_helper_address(&self) -> Result<address> {
+		Ok(T::ContractAddress::get())
+	}
 }
 
 /// Returns amount of pieces of `token` that `owner` have
modifiedpallets/refungible/src/stubs/UniqueRefungible.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/refungible/src/stubs/UniqueRefungible.soldiffbeforeafterboth
--- a/pallets/refungible/src/stubs/UniqueRefungible.sol
+++ b/pallets/refungible/src/stubs/UniqueRefungible.sol
@@ -919,7 +919,7 @@
 
 /// @title ERC-721 Non-Fungible Token Standard
 /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
-/// @dev the ERC-165 identifier for this interface is 0x58800161
+/// @dev the ERC-165 identifier for this interface is 0x4016cd87
 contract ERC721 is Dummy, ERC165, ERC721Events {
 	/// @notice Count all RFTs assigned to an owner
 	/// @dev RFTs assigned to the zero address are considered invalid, and this
@@ -1047,6 +1047,15 @@
 		dummy;
 		return 0x0000000000000000000000000000000000000000;
 	}
+
+	/// @notice Returns collection helper contract address
+	/// @dev EVM selector for this function is: 0x1896cce6,
+	///  or in textual repr: collectionHelperAddress()
+	function collectionHelperAddress() public view returns (address) {
+		require(false, stub_error);
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
 }
 
 contract UniqueRefungible is
modifiedpallets/refungible/src/stubs/UniqueRefungibleToken.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/unique/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -28,6 +28,7 @@
 	CollectionById,
 	dispatch::CollectionDispatch,
 	erc::{CollectionHelpersEvents, static_property::key},
+	eth::{map_eth_to_id, collection_id_to_address},
 	Pallet as PalletCommon,
 };
 use pallet_evm::{account::CrossAccountId, OnMethodCall, PrecompileHandle, PrecompileResult};
@@ -35,7 +36,7 @@
 use sp_std::vec;
 use up_data_structs::{
 	CollectionDescription, CollectionMode, CollectionName, CollectionTokenPrefix,
-	CreateCollectionData,
+	CreateCollectionData, CollectionId,
 };
 
 use crate::{weights::WeightInfo, Config, SelfWeightOf};
@@ -361,6 +362,25 @@
 			.expect("Collection creation price should be convertible to u128");
 		Ok(price.into())
 	}
+
+	/// Returns address of a collection.
+	/// @param collectionId  - CollectionId  of the collection
+	/// @return eth mirror address of the collection
+	fn collection_address(&self, collection_id: uint32) -> Result<address> {
+		Ok(collection_id_to_address(collection_id.into()))
+	}
+
+	/// Returns collectionId of a collection.
+	/// @param collectionAddress  - Eth address of the collection
+	/// @return collectionId of the collection
+	fn collection_id(&self, collection_address: address) -> Result<uint32> {
+		map_eth_to_id(&collection_address)
+			.map(|id| id.0)
+			.ok_or(Error::Revert(format!(
+				"failed to convert address {} into collectionId.",
+				collection_address
+			)))
+	}
 }
 
 /// Implements [`OnMethodCall`], which delegates call to [`EvmCollectionHelpers`]
modifiedpallets/unique/src/eth/stubs/CollectionHelpers.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/unique/src/eth/stubs/CollectionHelpers.soldiffbeforeafterboth
--- a/pallets/unique/src/eth/stubs/CollectionHelpers.sol
+++ b/pallets/unique/src/eth/stubs/CollectionHelpers.sol
@@ -24,7 +24,7 @@
 }
 
 /// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x7dea03b1
+/// @dev the ERC-165 identifier for this interface is 0xe65011aa
 contract CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
 	/// Create an NFT collection
 	/// @param name Name of the collection
@@ -130,4 +130,28 @@
 		dummy;
 		return 0;
 	}
+
+	/// Returns address of a collection.
+	/// @param collectionId  - CollectionId  of the collection
+	/// @return eth mirror address of the collection
+	/// @dev EVM selector for this function is: 0x2e716683,
+	///  or in textual repr: collectionAddress(uint32)
+	function collectionAddress(uint32 collectionId) public view returns (address) {
+		require(false, stub_error);
+		collectionId;
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+
+	/// Returns collectionId of a collection.
+	/// @param collectionAddress  - Eth address of the collection
+	/// @return collectionId of the collection
+	/// @dev EVM selector for this function is: 0xb5cb7498,
+	///  or in textual repr: collectionId(address)
+	function collectionId(address collectionAddress) public view returns (uint32) {
+		require(false, stub_error);
+		collectionAddress;
+		dummy;
+		return 0;
+	}
 }
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
before · pallets/unique/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Unique Pallet18//!19//! A pallet governing Unique transactions.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The Unique pallet's purpose is to be the primary interface between28//! external users and the inner structure of the Unique chains.29//!30//! It also contains an implementation of [`CollectionHelpers`][`eth`],31//! an Ethereum contract dealing with collection operations.32//!33//! ## Interface34//!35//! ### Dispatchables36//!37//! - `create_collection` - Create a collection of tokens. **Deprecated**, use `create_collection_ex`.38//! - `create_collection_ex` - Create a collection of tokens with explicit parameters.39//! - `destroy_collection` - Destroy a collection if no tokens exist within.40//! - `add_to_allow_list` - Add an address to allow list.41//! - `remove_from_allow_list` - Remove an address from allow list.42//! - `change_collection_owner` - Change the owner of the collection.43//! - `add_collection_admin` - Add an admin to a collection.44//! - `remove_collection_admin` - Remove admin of a collection.45//! - `set_collection_sponsor` - Invite a new collection sponsor.46//! - `confirm_sponsorship` - Confirm own sponsorship of a collection, becoming the sponsor.47//! - `remove_collection_sponsor` - Remove a sponsor from a collection.48//! - `create_item` - Create an item within a collection.49//! - `create_multiple_items` - Create multiple items within a collection.50//! - `set_collection_properties` - Add or change collection properties.51//! - `delete_collection_properties` - Delete specified collection properties.52//! - `set_token_properties` - Add or change token properties.53//! - `delete_token_properties` - Delete token properties.54//! - `set_token_property_permissions` - Add or change token property permissions of a collection.55//! - `create_multiple_items_ex` - Create multiple items within a collection with explicitly specified initial parameters.56//! - `set_transfers_enabled_flag` - Completely allow or disallow transfers for a particular collection.57//! - `burn_item` - Destroy an item.58//! - `burn_from` - Destroy an item on behalf of the owner as a non-owner account.59//! - `transfer` - Change ownership of the token.60//! - `transfer_from` - Change ownership of the token on behalf of the owner as a non-owner account.61//! - `approve` - Allow a non-permissioned address to transfer or burn an item.62//! - `set_collection_limits` - Set specific limits of a collection.63//! - `set_collection_permissions` - Set specific permissions of a collection.64//! - `repartition` - Re-partition a refungible token, while owning all of its parts.6566#![recursion_limit = "1024"]67#![cfg_attr(not(feature = "std"), no_std)]68#![allow(69	clippy::too_many_arguments,70	clippy::unnecessary_mut_passed,71	clippy::unused_unit72)]7374extern crate alloc;7576use frame_support::{77	decl_module, decl_storage, decl_error, decl_event,78	dispatch::DispatchResult,79	ensure, fail,80	weights::{Weight},81	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},82	BoundedVec,83};84use scale_info::TypeInfo;85use frame_system::{self as system, ensure_signed};86use sp_std::{vec, vec::Vec};87use up_data_structs::{88	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,89	CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,90	SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,91	PropertyKeyPermission,92};93use pallet_evm::account::CrossAccountId;94use pallet_common::{95	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,96	dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,97};98pub mod eth;99100#[cfg(feature = "runtime-benchmarks")]101pub mod benchmarking;102pub mod weights;103use weights::WeightInfo;104105/// Maximum number of levels of depth in the token nesting tree.106pub const NESTING_BUDGET: u32 = 5;107108decl_error! {109	/// Errors for the common Unique transactions.110	pub enum Error for Module<T: Config> {111		/// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].112		CollectionDecimalPointLimitExceeded,113		/// This address is not set as sponsor, use setCollectionSponsor first.114		ConfirmUnsetSponsorFail,115		/// Length of items properties must be greater than 0.116		EmptyArgument,117		/// Repertition is only supported by refungible collection.118		RepartitionCalledOnNonRefungibleCollection,119	}120}121122/// Configuration trait of this pallet.123pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {124	/// Overarching event type.125	type RuntimeEvent: From<Event<Self>> + Into<<Self as frame_system::Config>::RuntimeEvent>;126127	/// Weight information for extrinsics in this pallet.128	type WeightInfo: WeightInfo;129130	/// Weight information for common pallet operations.131	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;132133	/// Weight info information for extra refungible pallet operations.134	type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;135}136137decl_event! {138	pub enum Event<T>139	where140		<T as frame_system::Config>::AccountId,141		<T as pallet_evm::Config>::CrossAccountId,142	{143		/// Collection sponsor was removed144		///145		/// # Arguments146		/// * collection_id: ID of the affected collection.147		CollectionSponsorRemoved(CollectionId),148149		/// Collection admin was added150		///151		/// # Arguments152		/// * collection_id: ID of the affected collection.153		/// * admin: Admin address.154		CollectionAdminAdded(CollectionId, CrossAccountId),155156		/// Collection owned was changed157		///158		/// # Arguments159		/// * collection_id: ID of the affected collection.160		/// * owner: New owner address.161		CollectionOwnedChanged(CollectionId, AccountId),162163		/// Collection sponsor was set164		///165		/// # Arguments166		/// * collection_id: ID of the affected collection.167		/// * owner: New sponsor address.168		CollectionSponsorSet(CollectionId, AccountId),169170		/// New sponsor was confirm171		///172		/// # Arguments173		/// * collection_id: ID of the affected collection.174		/// * sponsor: New sponsor address.175		SponsorshipConfirmed(CollectionId, AccountId),176177		/// Collection admin was removed178		///179		/// # Arguments180		/// * collection_id: ID of the affected collection.181		/// * admin: Removed admin address.182		CollectionAdminRemoved(CollectionId, CrossAccountId),183184		/// Address was removed from the allow list185		///186		/// # Arguments187		/// * collection_id: ID of the affected collection.188		/// * user: Address of the removed account.189		AllowListAddressRemoved(CollectionId, CrossAccountId),190191		/// Address was added to the allow list192		///193		/// # Arguments194		/// * collection_id: ID of the affected collection.195		/// * user: Address of the added account.196		AllowListAddressAdded(CollectionId, CrossAccountId),197198		/// Collection limits were set199		///200		/// # Arguments201		/// * collection_id: ID of the affected collection.202		CollectionLimitSet(CollectionId),203204		/// Collection permissions were set205		///206		/// # Arguments207		/// * collection_id: ID of the affected collection.208		CollectionPermissionSet(CollectionId),209	}210}211212type SelfWeightOf<T> = <T as Config>::WeightInfo;213214// # Used definitions215//216// ## User control levels217//218// chain-controlled - key is uncontrolled by user219//                    i.e autoincrementing index220//                    can use non-cryptographic hash221// real - key is controlled by user222//        but it is hard to generate enough colliding values, i.e owner of signed txs223//        can use non-cryptographic hash224// controlled - key is completly controlled by users225//              i.e maps with mutable keys226//              should use cryptographic hash227//228// ## User control level downgrade reasons229//230// ?1 - chain-controlled -> controlled231//      collections/tokens can be destroyed, resulting in massive holes232// ?2 - chain-controlled -> controlled233//      same as ?1, but can be only added, resulting in easier exploitation234// ?3 - real -> controlled235//      no confirmation required, so addresses can be easily generated236decl_storage! {237	trait Store for Module<T: Config> as Unique {238239		//#region Private members240		/// Used for migrations241		ChainVersion: u64;242		//#endregion243244		//#region Tokens transfer sponosoring rate limit baskets245		/// (Collection id (controlled?2), who created (real))246		/// TODO: Off chain worker should remove from this map when collection gets removed247		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;248		/// Collection id (controlled?2), token id (controlled?2)249		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;250		/// Collection id (controlled?2), owning user (real)251		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;252		/// Collection id (controlled?2), token id (controlled?2)253		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;254		//#endregion255256		/// Variable metadata sponsoring257		/// Collection id (controlled?2), token id (controlled?2)258		#[deprecated]259		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;260		/// Last sponsoring of token property setting // todo:doc rephrase this and the following261		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;262263		/// Last sponsoring of NFT approval in a collection264		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;265		/// Last sponsoring of fungible tokens approval in a collection266		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;267		/// Last sponsoring of RFT approval in a collection268		pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;269	}270}271272decl_module! {273	/// Type alias to Pallet, to be used by construct_runtime.274	pub struct Module<T: Config> for enum Call275	where276		origin: T::RuntimeOrigin277	{278		type Error = Error<T>;279280		pub fn deposit_event() = default;281282		fn on_initialize(_now: T::BlockNumber) -> Weight {283			Weight::zero()284		}285286		fn on_runtime_upgrade() -> Weight {287			Weight::zero()288		}289290		/// Create a collection of tokens.291		///292		/// Each Token may have multiple properties encoded as an array of bytes293		/// of certain length. The initial owner of the collection is set294		/// to the address that signed the transaction and can be changed later.295		///296		/// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.297		///298		/// # Permissions299		///300		/// * Anyone - becomes the owner of the new collection.301		///302		/// # Arguments303		///304		/// * `collection_name`: Wide-character string with collection name305		/// (limit [`MAX_COLLECTION_NAME_LENGTH`]).306		/// * `collection_description`: Wide-character string with collection description307		/// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).308		/// * `token_prefix`: Byte string containing the token prefix to mark a collection309		/// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).310		/// * `mode`: Type of items stored in the collection and type dependent data.311		// returns collection ID312		#[weight = <SelfWeightOf<T>>::create_collection()]313		#[deprecated(note = "`create_collection_ex` is more up-to-date and advanced, prefer it instead")]314		pub fn create_collection(315			origin,316			collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,317			collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,318			token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,319			mode: CollectionMode320		) -> DispatchResult {321			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {322				name: collection_name,323				description: collection_description,324				token_prefix,325				mode,326				..Default::default()327			};328			Self::create_collection_ex(origin, data)329		}330331		/// Create a collection with explicit parameters.332		///333		/// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.334		///335		/// # Permissions336		///337		/// * Anyone - becomes the owner of the new collection.338		///339		/// # Arguments340		///341		/// * `data`: Explicit data of a collection used for its creation.342		#[weight = <SelfWeightOf<T>>::create_collection()]343		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {344			let sender = ensure_signed(origin)?;345346			// =========347			let sender = T::CrossAccountId::from_sub(sender);348			let _id = T::CollectionDispatch::create(sender.clone(), sender, data, Default::default())?;349350			Ok(())351		}352353		/// Destroy a collection if no tokens exist within.354		///355		/// # Permissions356		///357		/// * Collection owner358		///359		/// # Arguments360		///361		/// * `collection_id`: Collection to destroy.362		#[weight = <SelfWeightOf<T>>::destroy_collection()]363		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {364			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);365366			Self::destroy_collection_internal(sender, collection_id)367		}368369		/// Add an address to allow list.370		///371		/// # Permissions372		///373		/// * Collection owner374		/// * Collection admin375		///376		/// # Arguments377		///378		/// * `collection_id`: ID of the modified collection.379		/// * `address`: ID of the address to be added to the allowlist.380		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]381		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{382383			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);384			let collection = <CollectionHandle<T>>::try_get(collection_id)?;385			collection.check_is_internal()?;386387			<PalletCommon<T>>::toggle_allowlist(388				&collection,389				&sender,390				&address,391				true,392			)?;393394			Self::deposit_event(Event::<T>::AllowListAddressAdded(395				collection_id,396				address397			));398399			Ok(())400		}401402		/// Remove an address from allow list.403		///404		/// # Permissions405		///406		/// * Collection owner407		/// * Collection admin408		///409		/// # Arguments410		///411		/// * `collection_id`: ID of the modified collection.412		/// * `address`: ID of the address to be removed from the allowlist.413		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]414		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{415416			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);417			let collection = <CollectionHandle<T>>::try_get(collection_id)?;418			collection.check_is_internal()?;419420			<PalletCommon<T>>::toggle_allowlist(421				&collection,422				&sender,423				&address,424				false,425			)?;426427			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(428				collection_id,429				address430			));431432			Ok(())433		}434435		/// Change the owner of the collection.436		///437		/// # Permissions438		///439		/// * Collection owner440		///441		/// # Arguments442		///443		/// * `collection_id`: ID of the modified collection.444		/// * `new_owner`: ID of the account that will become the owner.445		#[weight = <SelfWeightOf<T>>::change_collection_owner()]446		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {447448			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);449450			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;451			target_collection.check_is_internal()?;452			target_collection.check_is_owner(&sender)?;453454			target_collection.owner = new_owner.clone();455			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(456				collection_id,457				new_owner458			));459460			target_collection.save()461		}462463		/// Add an admin to a collection.464		///465		/// NFT Collection can be controlled by multiple admin addresses466		/// (some which can also be servers, for example). Admins can issue467		/// and burn NFTs, as well as add and remove other admins,468		/// but cannot change NFT or Collection ownership.469		///470		/// # Permissions471		///472		/// * Collection owner473		/// * Collection admin474		///475		/// # Arguments476		///477		/// * `collection_id`: ID of the Collection to add an admin for.478		/// * `new_admin`: Address of new admin to add.479		#[weight = <SelfWeightOf<T>>::add_collection_admin()]480		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {481			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);482			let collection = <CollectionHandle<T>>::try_get(collection_id)?;483			collection.check_is_internal()?;484485			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(486				collection_id,487				new_admin_id.clone()488			));489490			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)491		}492493		/// Remove admin of a collection.494		///495		/// An admin address can remove itself. List of admins may become empty,496		/// in which case only Collection Owner will be able to add an Admin.497		///498		/// # Permissions499		///500		/// * Collection owner501		/// * Collection admin502		///503		/// # Arguments504		///505		/// * `collection_id`: ID of the collection to remove the admin for.506		/// * `account_id`: Address of the admin to remove.507		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]508		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {509			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);510			let collection = <CollectionHandle<T>>::try_get(collection_id)?;511			collection.check_is_internal()?;512513			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(514				collection_id,515				account_id.clone()516			));517518			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)519		}520521		/// Set (invite) a new collection sponsor.522		///523		/// If successful, confirmation from the sponsor-to-be will be pending.524		///525		/// # Permissions526		///527		/// * Collection owner528		/// * Collection admin529		///530		/// # Arguments531		///532		/// * `collection_id`: ID of the modified collection.533		/// * `new_sponsor`: ID of the account of the sponsor-to-be.534		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]535		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {536			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);537538			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;539			target_collection.check_is_owner_or_admin(&sender)?;540			target_collection.check_is_internal()?;541542			target_collection.set_sponsor(new_sponsor.clone())?;543544			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(545				collection_id,546				new_sponsor547			));548549			target_collection.save()550		}551552		/// Confirm own sponsorship of a collection, becoming the sponsor.553		///554		/// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].555		/// Sponsor can pay the fees of a transaction instead of the sender,556		/// but only within specified limits.557		///558		/// # Permissions559		///560		/// * Sponsor-to-be561		///562		/// # Arguments563		///564		/// * `collection_id`: ID of the collection with the pending sponsor.565		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]566		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {567			let sender = ensure_signed(origin)?;568569			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;570			target_collection.check_is_internal()?;571			ensure!(572				target_collection.confirm_sponsorship(&sender)?,573				Error::<T>::ConfirmUnsetSponsorFail574			);575576			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(577				collection_id,578				sender579			));580581			target_collection.save()582		}583584		/// Remove a collection's a sponsor, making everyone pay for their own transactions.585		///586		/// # Permissions587		///588		/// * Collection owner589		///590		/// # Arguments591		///592		/// * `collection_id`: ID of the collection with the sponsor to remove.593		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]594		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {595			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);596597			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;598			target_collection.check_is_internal()?;599			target_collection.check_is_owner(&sender)?;600601			target_collection.sponsorship = SponsorshipState::Disabled;602603			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(604				collection_id605			));606			target_collection.save()607		}608609		/// Mint an item within a collection.610		///611		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].612		///613		/// # Permissions614		///615		/// * Collection owner616		/// * Collection admin617		/// * Anyone if618		///     * Allow List is enabled, and619		///     * Address is added to allow list, and620		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])621		///622		/// # Arguments623		///624		/// * `collection_id`: ID of the collection to which an item would belong.625		/// * `owner`: Address of the initial owner of the item.626		/// * `data`: Token data describing the item to store on chain.627		#[weight = T::CommonWeightInfo::create_item()]628		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {629			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);630			let budget = budget::Value::new(NESTING_BUDGET);631632			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))633		}634635		/// Create multiple items within a collection.636		///637		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].638		///639		/// # Permissions640		///641		/// * Collection owner642		/// * Collection admin643		/// * Anyone if644		///     * Allow List is enabled, and645		///     * Address is added to the allow list, and646		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])647		///648		/// # Arguments649		///650		/// * `collection_id`: ID of the collection to which the tokens would belong.651		/// * `owner`: Address of the initial owner of the tokens.652		/// * `items_data`: Vector of data describing each item to be created.653		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]654		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {655			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);656			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);657			let budget = budget::Value::new(NESTING_BUDGET);658659			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))660		}661662		/// Add or change collection properties.663		///664		/// # Permissions665		///666		/// * Collection owner667		/// * Collection admin668		///669		/// # Arguments670		///671		/// * `collection_id`: ID of the modified collection.672		/// * `properties`: Vector of key-value pairs stored as the collection's metadata.673		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.674		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]675		pub fn set_collection_properties(676			origin,677			collection_id: CollectionId,678			properties: Vec<Property>679		) -> DispatchResultWithPostInfo {680			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);681682			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);683684			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))685		}686687		/// Delete specified collection properties.688		///689		/// # Permissions690		///691		/// * Collection Owner692		/// * Collection Admin693		///694		/// # Arguments695		///696		/// * `collection_id`: ID of the modified collection.697		/// * `property_keys`: Vector of keys of the properties to be deleted.698		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.699		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]700		pub fn delete_collection_properties(701			origin,702			collection_id: CollectionId,703			property_keys: Vec<PropertyKey>,704		) -> DispatchResultWithPostInfo {705			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);706707			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);708709			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))710		}711712		/// Add or change token properties according to collection's permissions.713		/// Currently properties only work with NFTs.714		///715		/// # Permissions716		///717		/// * Depends on collection's token property permissions and specified property mutability:718		/// 	* Collection owner719		/// 	* Collection admin720		/// 	* Token owner721		///722		/// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].723		///724		/// # Arguments725		///726		/// * `collection_id: ID of the collection to which the token belongs.727		/// * `token_id`: ID of the modified token.728		/// * `properties`: Vector of key-value pairs stored as the token's metadata.729		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.730		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]731		pub fn set_token_properties(732			origin,733			collection_id: CollectionId,734			token_id: TokenId,735			properties: Vec<Property>736		) -> DispatchResultWithPostInfo {737			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);738739			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);740			let budget = budget::Value::new(NESTING_BUDGET);741742			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))743		}744745		/// Delete specified token properties. Currently properties only work with NFTs.746		///747		/// # Permissions748		///749		/// * Depends on collection's token property permissions and specified property mutability:750		/// 	* Collection owner751		/// 	* Collection admin752		/// 	* Token owner753		///754		/// # Arguments755		///756		/// * `collection_id`: ID of the collection to which the token belongs.757		/// * `token_id`: ID of the modified token.758		/// * `property_keys`: Vector of keys of the properties to be deleted.759		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.760		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]761		pub fn delete_token_properties(762			origin,763			collection_id: CollectionId,764			token_id: TokenId,765			property_keys: Vec<PropertyKey>766		) -> DispatchResultWithPostInfo {767			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);768769			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);770			let budget = budget::Value::new(NESTING_BUDGET);771772			dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))773		}774775		/// Add or change token property permissions of a collection.776		///777		/// Without a permission for a particular key, a property with that key778		/// cannot be created in a token.779		///780		/// # Permissions781		///782		/// * Collection owner783		/// * Collection admin784		///785		/// # Arguments786		///787		/// * `collection_id`: ID of the modified collection.788		/// * `property_permissions`: Vector of permissions for property keys.789		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.790		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]791		pub fn set_token_property_permissions(792			origin,793			collection_id: CollectionId,794			property_permissions: Vec<PropertyKeyPermission>,795		) -> DispatchResultWithPostInfo {796			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);797798			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);799800			dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))801		}802803		/// Create multiple items within a collection with explicitly specified initial parameters.804		///805		/// # Permissions806		///807		/// * Collection owner808		/// * Collection admin809		/// * Anyone if810		///     * Allow List is enabled, and811		///     * Address is added to allow list, and812		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])813		///814		/// # Arguments815		///816		/// * `collection_id`: ID of the collection to which the tokens would belong.817		/// * `data`: Explicit item creation data.818		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]819		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {820			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);821			let budget = budget::Value::new(NESTING_BUDGET);822823			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))824		}825826		/// Completely allow or disallow transfers for a particular collection.827		///828		/// # Permissions829		///830		/// * Collection owner831		///832		/// # Arguments833		///834		/// * `collection_id`: ID of the collection.835		/// * `value`: New value of the flag, are transfers allowed?836		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]837		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {838			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);839			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;840			target_collection.check_is_internal()?;841			target_collection.check_is_owner(&sender)?;842843			// =========844845			target_collection.limits.transfers_enabled = Some(value);846			target_collection.save()847		}848849		/// Destroy an item.850		///851		/// # Permissions852		///853		/// * Collection owner854		/// * Collection admin855		/// * Current item owner856		///857		/// # Arguments858		///859		/// * `collection_id`: ID of the collection to which the item belongs.860		/// * `item_id`: ID of item to burn.861		/// * `value`: Number of pieces of the item to destroy.862		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.863		///     * Fungible Mode: The desired number of pieces to burn.864		///     * Re-Fungible Mode: The desired number of pieces to burn.865		#[weight = T::CommonWeightInfo::burn_item()]866		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {867			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);868869			let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;870			if value == 1 {871				<NftTransferBasket<T>>::remove(collection_id, item_id);872				<NftApproveBasket<T>>::remove(collection_id, item_id);873			}874			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?875			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());876			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));877			Ok(post_info)878		}879880		/// Destroy a token on behalf of the owner as a non-owner account.881		///882		/// See also: [`approve`][`Pallet::approve`].883		///884		/// After this method executes, one approval is removed from the total so that885		/// the approved address will not be able to transfer this item again from this owner.886		///887		/// # Permissions888		///889		/// * Collection owner890		/// * Collection admin891		/// * Current token owner892		/// * Address approved by current item owner893		///894		/// # Arguments895		///896		/// * `from`: The owner of the burning item.897		/// * `collection_id`: ID of the collection to which the item belongs.898		/// * `item_id`: ID of item to burn.899		/// * `value`: Number of pieces to burn.900		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.901		///     * Fungible Mode: The desired number of pieces to burn.902		///     * Re-Fungible Mode: The desired number of pieces to burn.903		#[weight = T::CommonWeightInfo::burn_from()]904		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {905			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);906			let budget = budget::Value::new(NESTING_BUDGET);907908			dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))909		}910911		/// Change ownership of the token.912		///913		/// # Permissions914		///915		/// * Collection owner916		/// * Collection admin917		/// * Current token owner918		///919		/// # Arguments920		///921		/// * `recipient`: Address of token recipient.922		/// * `collection_id`: ID of the collection the item belongs to.923		/// * `item_id`: ID of the item.924		///     * Non-Fungible Mode: Required.925		///     * Fungible Mode: Ignored.926		///     * Re-Fungible Mode: Required.927		///928		/// * `value`: Amount to transfer.929		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.930		///     * Fungible Mode: The desired number of pieces to transfer.931		///     * Re-Fungible Mode: The desired number of pieces to transfer.932		#[weight = T::CommonWeightInfo::transfer()]933		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {934			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);935			let budget = budget::Value::new(NESTING_BUDGET);936937			dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))938		}939940		/// Allow a non-permissioned address to transfer or burn an item.941		///942		/// # Permissions943		///944		/// * Collection owner945		/// * Collection admin946		/// * Current item owner947		///948		/// # Arguments949		///950		/// * `spender`: Account to be approved to make specific transactions on non-owned tokens.951		/// * `collection_id`: ID of the collection the item belongs to.952		/// * `item_id`: ID of the item transactions on which are now approved.953		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).954		/// Set to 0 to revoke the approval.955		#[weight = T::CommonWeightInfo::approve()]956		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {957			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);958959			dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))960		}961962		/// Change ownership of an item on behalf of the owner as a non-owner account.963		///964		/// See the [`approve`][`Pallet::approve`] method for additional information.965		///966		/// After this method executes, one approval is removed from the total so that967		/// the approved address will not be able to transfer this item again from this owner.968		///969		/// # Permissions970		///971		/// * Collection owner972		/// * Collection admin973		/// * Current item owner974		/// * Address approved by current item owner975		///976		/// # Arguments977		///978		/// * `from`: Address that currently owns the token.979		/// * `recipient`: Address of the new token-owner-to-be.980		/// * `collection_id`: ID of the collection the item.981		/// * `item_id`: ID of the item to be transferred.982		/// * `value`: Amount to transfer.983		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.984		///     * Fungible Mode: The desired number of pieces to transfer.985		///     * Re-Fungible Mode: The desired number of pieces to transfer.986		#[weight = T::CommonWeightInfo::transfer_from()]987		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {988			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);989			let budget = budget::Value::new(NESTING_BUDGET);990991			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))992		}993994		/// Set specific limits of a collection. Empty, or None fields mean chain default.995		///996		/// # Permissions997		///998		/// * Collection owner999		/// * Collection admin1000		///1001		/// # Arguments1002		///1003		/// * `collection_id`: ID of the modified collection.1004		/// * `new_limit`: New limits of the collection. Fields that are not set (None)1005		/// will not overwrite the old ones.1006		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1007		pub fn set_collection_limits(1008			origin,1009			collection_id: CollectionId,1010			new_limit: CollectionLimits,1011		) -> DispatchResult {1012			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1013			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1014			target_collection.check_is_internal()?;1015			target_collection.check_is_owner_or_admin(&sender)?;1016			let old_limit = &target_collection.limits;10171018			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10191020			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1021				collection_id1022			));10231024			target_collection.save()1025		}10261027		/// Set specific permissions of a collection. Empty, or None fields mean chain default.1028		///1029		/// # Permissions1030		///1031		/// * Collection owner1032		/// * Collection admin1033		///1034		/// # Arguments1035		///1036		/// * `collection_id`: ID of the modified collection.1037		/// * `new_permission`: New permissions of the collection. Fields that are not set (None)1038		/// will not overwrite the old ones.1039		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1040		pub fn set_collection_permissions(1041			origin,1042			collection_id: CollectionId,1043			new_permission: CollectionPermissions,1044		) -> DispatchResult {1045			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1046			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1047			target_collection.check_is_internal()?;1048			target_collection.check_is_owner_or_admin(&sender)?;1049			let old_limit = &target_collection.permissions;10501051			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10521053			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1054				collection_id1055			));10561057			target_collection.save()1058		}10591060		/// Re-partition a refungible token, while owning all of its parts/pieces.1061		///1062		/// # Permissions1063		///1064		/// * Token owner (must own every part)1065		///1066		/// # Arguments1067		///1068		/// * `collection_id`: ID of the collection the RFT belongs to.1069		/// * `token_id`: ID of the RFT.1070		/// * `amount`: New number of parts/pieces into which the token shall be partitioned.1071		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]1072		pub fn repartition(1073			origin,1074			collection_id: CollectionId,1075			token_id: TokenId,1076			amount: u128,1077		) -> DispatchResultWithPostInfo {1078			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1079			dispatch_tx::<T, _>(collection_id, |d| {1080				if let Some(refungible_extensions) = d.refungible_extensions() {1081					refungible_extensions.repartition(&sender, token_id, amount)1082				} else {1083					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1084				}1085			})1086		}1087	}1088}10891090impl<T: Config> Pallet<T> {1091	/// Force set `sponsor` for `collection`.1092	///1093	/// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1094	/// from the `sponsor` is not required.1095	///1096	/// # Arguments1097	///1098	/// * `sponsor`: ID of the account of the sponsor-to-be.1099	/// * `collection_id`: ID of the modified collection.1100	pub fn force_set_sponsor(sponsor: T::AccountId, collection_id: CollectionId) -> DispatchResult {1101		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1102		target_collection.check_is_internal()?;1103		target_collection.set_sponsor(sponsor.clone())?;11041105		Self::deposit_event(Event::<T>::CollectionSponsorSet(1106			collection_id,1107			sponsor.clone(),1108		));11091110		ensure!(1111			target_collection.confirm_sponsorship(&sponsor)?,1112			Error::<T>::ConfirmUnsetSponsorFail1113		);11141115		Self::deposit_event(Event::<T>::SponsorshipConfirmed(collection_id, sponsor));11161117		target_collection.save()1118	}11191120	/// Force remove `sponsor` for `collection`.1121	///1122	/// Differs from `remove_sponsor` in that1123	/// it doesn't require consent from the `owner` of the collection.1124	///1125	/// # Arguments1126	///1127	/// * `collection_id`: ID of the modified collection.1128	pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1129		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1130		target_collection.check_is_internal()?;1131		target_collection.sponsorship = SponsorshipState::Disabled;11321133		Self::deposit_event(Event::<T>::CollectionSponsorRemoved(collection_id));11341135		target_collection.save()1136	}11371138	#[inline(always)]1139	pub(crate) fn destroy_collection_internal(1140		sender: T::CrossAccountId,1141		collection_id: CollectionId,1142	) -> DispatchResult {1143		let collection = <CollectionHandle<T>>::try_get(collection_id)?;1144		collection.check_is_internal()?;11451146		T::CollectionDispatch::destroy(sender, collection)?;11471148		// TODO: basket cleanup should be moved elsewhere1149		// Maybe runtime dispatch.rs should perform it?11501151		let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1152		let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1153		let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);11541155		let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1156		let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1157		let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);11581159		Ok(())1160	}1161}
after · pallets/unique/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Unique Pallet18//!19//! A pallet governing Unique transactions.20//!21//! - [`Config`]22//! - [`Call`]23//! - [`Pallet`]24//!25//! ## Overview26//!27//! The Unique pallet's purpose is to be the primary interface between28//! external users and the inner structure of the Unique chains.29//!30//! It also contains an implementation of [`CollectionHelpers`][`eth`],31//! an Ethereum contract dealing with collection operations.32//!33//! ## Interface34//!35//! ### Dispatchables36//!37//! - `create_collection` - Create a collection of tokens. **Deprecated**, use `create_collection_ex`.38//! - `create_collection_ex` - Create a collection of tokens with explicit parameters.39//! - `destroy_collection` - Destroy a collection if no tokens exist within.40//! - `add_to_allow_list` - Add an address to allow list.41//! - `remove_from_allow_list` - Remove an address from allow list.42//! - `change_collection_owner` - Change the owner of the collection.43//! - `add_collection_admin` - Add an admin to a collection.44//! - `remove_collection_admin` - Remove admin of a collection.45//! - `set_collection_sponsor` - Invite a new collection sponsor.46//! - `confirm_sponsorship` - Confirm own sponsorship of a collection, becoming the sponsor.47//! - `remove_collection_sponsor` - Remove a sponsor from a collection.48//! - `create_item` - Create an item within a collection.49//! - `create_multiple_items` - Create multiple items within a collection.50//! - `set_collection_properties` - Add or change collection properties.51//! - `delete_collection_properties` - Delete specified collection properties.52//! - `set_token_properties` - Add or change token properties.53//! - `delete_token_properties` - Delete token properties.54//! - `set_token_property_permissions` - Add or change token property permissions of a collection.55//! - `create_multiple_items_ex` - Create multiple items within a collection with explicitly specified initial parameters.56//! - `set_transfers_enabled_flag` - Completely allow or disallow transfers for a particular collection.57//! - `burn_item` - Destroy an item.58//! - `burn_from` - Destroy an item on behalf of the owner as a non-owner account.59//! - `transfer` - Change ownership of the token.60//! - `transfer_from` - Change ownership of the token on behalf of the owner as a non-owner account.61//! - `approve` - Allow a non-permissioned address to transfer or burn an item.62//! - `set_collection_limits` - Set specific limits of a collection.63//! - `set_collection_permissions` - Set specific permissions of a collection.64//! - `repartition` - Re-partition a refungible token, while owning all of its parts.6566#![recursion_limit = "1024"]67#![cfg_attr(not(feature = "std"), no_std)]68#![allow(69	clippy::too_many_arguments,70	clippy::unnecessary_mut_passed,71	clippy::unused_unit72)]7374extern crate alloc;7576use frame_support::{77	decl_module, decl_storage, decl_error, decl_event,78	dispatch::DispatchResult,79	ensure, fail,80	weights::{Weight},81	pallet_prelude::{DispatchResultWithPostInfo, ConstU32},82	BoundedVec,83};84use scale_info::TypeInfo;85use frame_system::{self as system, ensure_signed};86use sp_std::{vec, vec::Vec};87use up_data_structs::{88	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,89	MAX_PROPERTIES_PER_ITEM, MAX_PROPERTY_KEY_LENGTH, MAX_PROPERTY_VALUE_LENGTH,90	MAX_COLLECTION_PROPERTIES_SIZE, COLLECTION_ADMINS_LIMIT, MAX_TOKEN_PROPERTIES_SIZE,91	CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,92	SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,93	PropertyKeyPermission,94};95use pallet_evm::account::CrossAccountId;96use pallet_common::{97	CollectionHandle, Pallet as PalletCommon, CommonWeightInfo, dispatch::dispatch_tx,98	dispatch::CollectionDispatch, RefungibleExtensionsWeightInfo,99};100pub mod eth;101102#[cfg(feature = "runtime-benchmarks")]103pub mod benchmarking;104pub mod weights;105use weights::WeightInfo;106107/// A maximum number of levels of depth in the token nesting tree.108pub const NESTING_BUDGET: u32 = 5;109110decl_error! {111	/// Errors for the common Unique transactions.112	pub enum Error for Module<T: Config> {113		/// Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].114		CollectionDecimalPointLimitExceeded,115		/// This address is not set as sponsor, use setCollectionSponsor first.116		ConfirmUnsetSponsorFail,117		/// Length of items properties must be greater than 0.118		EmptyArgument,119		/// Repertition is only supported by refungible collection.120		RepartitionCalledOnNonRefungibleCollection,121	}122}123124/// Configuration trait of this pallet.125pub trait Config: system::Config + pallet_common::Config + Sized + TypeInfo {126	/// Overarching event type.127	type RuntimeEvent: From<Event<Self>> + Into<<Self as frame_system::Config>::RuntimeEvent>;128129	/// Weight information for extrinsics in this pallet.130	type WeightInfo: WeightInfo;131132	/// Weight information for common pallet operations.133	type CommonWeightInfo: CommonWeightInfo<Self::CrossAccountId>;134135	/// Weight info information for extra refungible pallet operations.136	type RefungibleExtensionsWeightInfo: RefungibleExtensionsWeightInfo;137}138139decl_event! {140	pub enum Event<T>141	where142		<T as frame_system::Config>::AccountId,143		<T as pallet_evm::Config>::CrossAccountId,144	{145		/// Collection sponsor was removed146		///147		/// # Arguments148		/// * collection_id: ID of the affected collection.149		CollectionSponsorRemoved(CollectionId),150151		/// Collection admin was added152		///153		/// # Arguments154		/// * collection_id: ID of the affected collection.155		/// * admin: Admin address.156		CollectionAdminAdded(CollectionId, CrossAccountId),157158		/// Collection owned was changed159		///160		/// # Arguments161		/// * collection_id: ID of the affected collection.162		/// * owner: New owner address.163		CollectionOwnedChanged(CollectionId, AccountId),164165		/// Collection sponsor was set166		///167		/// # Arguments168		/// * collection_id: ID of the affected collection.169		/// * owner: New sponsor address.170		CollectionSponsorSet(CollectionId, AccountId),171172		/// New sponsor was confirm173		///174		/// # Arguments175		/// * collection_id: ID of the affected collection.176		/// * sponsor: New sponsor address.177		SponsorshipConfirmed(CollectionId, AccountId),178179		/// Collection admin was removed180		///181		/// # Arguments182		/// * collection_id: ID of the affected collection.183		/// * admin: Removed admin address.184		CollectionAdminRemoved(CollectionId, CrossAccountId),185186		/// Address was removed from the allow list187		///188		/// # Arguments189		/// * collection_id: ID of the affected collection.190		/// * user: Address of the removed account.191		AllowListAddressRemoved(CollectionId, CrossAccountId),192193		/// Address was added to the allow list194		///195		/// # Arguments196		/// * collection_id: ID of the affected collection.197		/// * user: Address of the added account.198		AllowListAddressAdded(CollectionId, CrossAccountId),199200		/// Collection limits were set201		///202		/// # Arguments203		/// * collection_id: ID of the affected collection.204		CollectionLimitSet(CollectionId),205206		/// Collection permissions were set207		///208		/// # Arguments209		/// * collection_id: ID of the affected collection.210		CollectionPermissionSet(CollectionId),211	}212}213214type SelfWeightOf<T> = <T as Config>::WeightInfo;215216// # Used definitions217//218// ## User control levels219//220// chain-controlled - key is uncontrolled by user221//                    i.e autoincrementing index222//                    can use non-cryptographic hash223// real - key is controlled by user224//        but it is hard to generate enough colliding values, i.e owner of signed txs225//        can use non-cryptographic hash226// controlled - key is completly controlled by users227//              i.e maps with mutable keys228//              should use cryptographic hash229//230// ## User control level downgrade reasons231//232// ?1 - chain-controlled -> controlled233//      collections/tokens can be destroyed, resulting in massive holes234// ?2 - chain-controlled -> controlled235//      same as ?1, but can be only added, resulting in easier exploitation236// ?3 - real -> controlled237//      no confirmation required, so addresses can be easily generated238decl_storage! {239	trait Store for Module<T: Config> as Unique {240241		//#region Private members242		/// Used for migrations243		ChainVersion: u64;244		//#endregion245246		//#region Tokens transfer sponosoring rate limit baskets247		/// (Collection id (controlled?2), who created (real))248		/// TODO: Off chain worker should remove from this map when collection gets removed249		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => Option<T::BlockNumber>;250		/// Collection id (controlled?2), token id (controlled?2)251		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;252		/// Collection id (controlled?2), owning user (real)253		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;254		/// Collection id (controlled?2), token id (controlled?2)255		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;256		//#endregion257258		/// Variable metadata sponsoring259		/// Collection id (controlled?2), token id (controlled?2)260		#[deprecated]261		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;262		/// Last sponsoring of token property setting // todo:doc rephrase this and the following263		pub TokenPropertyBasket get(fn token_property_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;264265		/// Last sponsoring of NFT approval in a collection266		pub NftApproveBasket get(fn nft_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber>;267		/// Last sponsoring of fungible tokens approval in a collection268		pub FungibleApproveBasket get(fn fungible_approve_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;269		/// Last sponsoring of RFT approval in a collection270		pub RefungibleApproveBasket get(fn refungible_approve_basket): nmap hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId, hasher(twox_64_concat) T::AccountId => Option<T::BlockNumber>;271	}272}273274decl_module! {275	/// Type alias to Pallet, to be used by construct_runtime.276	pub struct Module<T: Config> for enum Call277	where278		origin: T::RuntimeOrigin279	{280		type Error = Error<T>;281282		#[doc = "A maximum number of levels of depth in the token nesting tree."]283		const NESTING_BUDGET: u32 = NESTING_BUDGET;284285		#[doc = "Maximal length of a collection name."]286		const MAX_COLLECTION_NAME_LENGTH: u32 = MAX_COLLECTION_NAME_LENGTH;287288		#[doc = "Maximal length of a collection description."]289		const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = MAX_COLLECTION_DESCRIPTION_LENGTH;290291		#[doc = "Maximal length of a token prefix."]292		const MAX_TOKEN_PREFIX_LENGTH: u32 = MAX_TOKEN_PREFIX_LENGTH;293294		#[doc = "Maximum admins per collection."]295		const COLLECTION_ADMINS_LIMIT: u32 = COLLECTION_ADMINS_LIMIT;296297		#[doc = "Maximal length of a property key."]298		const MAX_PROPERTY_KEY_LENGTH: u32 = MAX_PROPERTY_KEY_LENGTH;299300		#[doc = "Maximal length of a property value."]301		const MAX_PROPERTY_VALUE_LENGTH: u32 = MAX_PROPERTY_VALUE_LENGTH;302303		#[doc = "A maximum number of token properties."]304		const MAX_PROPERTIES_PER_ITEM: u32 = MAX_PROPERTIES_PER_ITEM;305306		#[doc = "Maximum size for all collection properties."]307		const MAX_COLLECTION_PROPERTIES_SIZE: u32 = MAX_COLLECTION_PROPERTIES_SIZE;308309		#[doc = "Maximum size of all token properties."]310		const MAX_TOKEN_PROPERTIES_SIZE: u32 = MAX_TOKEN_PROPERTIES_SIZE;311312		#[doc = "Default NFT collection limit."]313		const NFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::NFT);314315		#[doc = "Default RFT collection limit."]316		const RFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::ReFungible);317318		#[doc = "Default FT collection limit."]319		const FT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::Fungible(0));320321322		pub fn deposit_event() = default;323324		fn on_initialize(_now: T::BlockNumber) -> Weight {325			Weight::zero()326		}327328		fn on_runtime_upgrade() -> Weight {329			Weight::zero()330		}331332		/// Create a collection of tokens.333		///334		/// Each Token may have multiple properties encoded as an array of bytes335		/// of certain length. The initial owner of the collection is set336		/// to the address that signed the transaction and can be changed later.337		///338		/// Prefer the more advanced [`create_collection_ex`][`Pallet::create_collection_ex`] instead.339		///340		/// # Permissions341		///342		/// * Anyone - becomes the owner of the new collection.343		///344		/// # Arguments345		///346		/// * `collection_name`: Wide-character string with collection name347		/// (limit [`MAX_COLLECTION_NAME_LENGTH`]).348		/// * `collection_description`: Wide-character string with collection description349		/// (limit [`MAX_COLLECTION_DESCRIPTION_LENGTH`]).350		/// * `token_prefix`: Byte string containing the token prefix to mark a collection351		/// to which a token belongs (limit [`MAX_TOKEN_PREFIX_LENGTH`]).352		/// * `mode`: Type of items stored in the collection and type dependent data.353		// returns collection ID354		#[weight = <SelfWeightOf<T>>::create_collection()]355		#[deprecated(note = "`create_collection_ex` is more up-to-date and advanced, prefer it instead")]356		pub fn create_collection(357			origin,358			collection_name: BoundedVec<u16, ConstU32<MAX_COLLECTION_NAME_LENGTH>>,359			collection_description: BoundedVec<u16, ConstU32<MAX_COLLECTION_DESCRIPTION_LENGTH>>,360			token_prefix: BoundedVec<u8, ConstU32<MAX_TOKEN_PREFIX_LENGTH>>,361			mode: CollectionMode362		) -> DispatchResult {363			let data: CreateCollectionData<T::AccountId> = CreateCollectionData {364				name: collection_name,365				description: collection_description,366				token_prefix,367				mode,368				..Default::default()369			};370			Self::create_collection_ex(origin, data)371		}372373		/// Create a collection with explicit parameters.374		///375		/// Prefer it to the deprecated [`create_collection`][`Pallet::create_collection`] method.376		///377		/// # Permissions378		///379		/// * Anyone - becomes the owner of the new collection.380		///381		/// # Arguments382		///383		/// * `data`: Explicit data of a collection used for its creation.384		#[weight = <SelfWeightOf<T>>::create_collection()]385		pub fn create_collection_ex(origin, data: CreateCollectionData<T::AccountId>) -> DispatchResult {386			let sender = ensure_signed(origin)?;387388			// =========389			let sender = T::CrossAccountId::from_sub(sender);390			let _id = T::CollectionDispatch::create(sender.clone(), sender, data, Default::default())?;391392			Ok(())393		}394395		/// Destroy a collection if no tokens exist within.396		///397		/// # Permissions398		///399		/// * Collection owner400		///401		/// # Arguments402		///403		/// * `collection_id`: Collection to destroy.404		#[weight = <SelfWeightOf<T>>::destroy_collection()]405		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {406			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);407408			Self::destroy_collection_internal(sender, collection_id)409		}410411		/// Add an address to allow list.412		///413		/// # Permissions414		///415		/// * Collection owner416		/// * Collection admin417		///418		/// # Arguments419		///420		/// * `collection_id`: ID of the modified collection.421		/// * `address`: ID of the address to be added to the allowlist.422		#[weight = <SelfWeightOf<T>>::add_to_allow_list()]423		pub fn add_to_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{424425			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);426			let collection = <CollectionHandle<T>>::try_get(collection_id)?;427			collection.check_is_internal()?;428429			<PalletCommon<T>>::toggle_allowlist(430				&collection,431				&sender,432				&address,433				true,434			)?;435436			Self::deposit_event(Event::<T>::AllowListAddressAdded(437				collection_id,438				address439			));440441			Ok(())442		}443444		/// Remove an address from allow list.445		///446		/// # Permissions447		///448		/// * Collection owner449		/// * Collection admin450		///451		/// # Arguments452		///453		/// * `collection_id`: ID of the modified collection.454		/// * `address`: ID of the address to be removed from the allowlist.455		#[weight = <SelfWeightOf<T>>::remove_from_allow_list()]456		pub fn remove_from_allow_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{457458			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);459			let collection = <CollectionHandle<T>>::try_get(collection_id)?;460			collection.check_is_internal()?;461462			<PalletCommon<T>>::toggle_allowlist(463				&collection,464				&sender,465				&address,466				false,467			)?;468469			<Pallet<T>>::deposit_event(Event::<T>::AllowListAddressRemoved(470				collection_id,471				address472			));473474			Ok(())475		}476477		/// Change the owner of the collection.478		///479		/// # Permissions480		///481		/// * Collection owner482		///483		/// # Arguments484		///485		/// * `collection_id`: ID of the modified collection.486		/// * `new_owner`: ID of the account that will become the owner.487		#[weight = <SelfWeightOf<T>>::change_collection_owner()]488		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {489490			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);491492			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;493			target_collection.check_is_internal()?;494			target_collection.check_is_owner(&sender)?;495496			target_collection.owner = new_owner.clone();497			<Pallet<T>>::deposit_event(Event::<T>::CollectionOwnedChanged(498				collection_id,499				new_owner500			));501502			target_collection.save()503		}504505		/// Add an admin to a collection.506		///507		/// NFT Collection can be controlled by multiple admin addresses508		/// (some which can also be servers, for example). Admins can issue509		/// and burn NFTs, as well as add and remove other admins,510		/// but cannot change NFT or Collection ownership.511		///512		/// # Permissions513		///514		/// * Collection owner515		/// * Collection admin516		///517		/// # Arguments518		///519		/// * `collection_id`: ID of the Collection to add an admin for.520		/// * `new_admin`: Address of new admin to add.521		#[weight = <SelfWeightOf<T>>::add_collection_admin()]522		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {523			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);524			let collection = <CollectionHandle<T>>::try_get(collection_id)?;525			collection.check_is_internal()?;526527			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminAdded(528				collection_id,529				new_admin_id.clone()530			));531532			<PalletCommon<T>>::toggle_admin(&collection, &sender, &new_admin_id, true)533		}534535		/// Remove admin of a collection.536		///537		/// An admin address can remove itself. List of admins may become empty,538		/// in which case only Collection Owner will be able to add an Admin.539		///540		/// # Permissions541		///542		/// * Collection owner543		/// * Collection admin544		///545		/// # Arguments546		///547		/// * `collection_id`: ID of the collection to remove the admin for.548		/// * `account_id`: Address of the admin to remove.549		#[weight = <SelfWeightOf<T>>::remove_collection_admin()]550		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {551			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);552			let collection = <CollectionHandle<T>>::try_get(collection_id)?;553			collection.check_is_internal()?;554555			<Pallet<T>>::deposit_event(Event::<T>::CollectionAdminRemoved(556				collection_id,557				account_id.clone()558			));559560			<PalletCommon<T>>::toggle_admin(&collection, &sender, &account_id, false)561		}562563		/// Set (invite) a new collection sponsor.564		///565		/// If successful, confirmation from the sponsor-to-be will be pending.566		///567		/// # Permissions568		///569		/// * Collection owner570		/// * Collection admin571		///572		/// # Arguments573		///574		/// * `collection_id`: ID of the modified collection.575		/// * `new_sponsor`: ID of the account of the sponsor-to-be.576		#[weight = <SelfWeightOf<T>>::set_collection_sponsor()]577		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {578			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);579580			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;581			target_collection.check_is_owner_or_admin(&sender)?;582			target_collection.check_is_internal()?;583584			target_collection.set_sponsor(new_sponsor.clone())?;585586			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(587				collection_id,588				new_sponsor589			));590591			target_collection.save()592		}593594		/// Confirm own sponsorship of a collection, becoming the sponsor.595		///596		/// An invitation must be pending, see [`set_collection_sponsor`][`Pallet::set_collection_sponsor`].597		/// Sponsor can pay the fees of a transaction instead of the sender,598		/// but only within specified limits.599		///600		/// # Permissions601		///602		/// * Sponsor-to-be603		///604		/// # Arguments605		///606		/// * `collection_id`: ID of the collection with the pending sponsor.607		#[weight = <SelfWeightOf<T>>::confirm_sponsorship()]608		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {609			let sender = ensure_signed(origin)?;610611			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;612			target_collection.check_is_internal()?;613			ensure!(614				target_collection.confirm_sponsorship(&sender)?,615				Error::<T>::ConfirmUnsetSponsorFail616			);617618			<Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(619				collection_id,620				sender621			));622623			target_collection.save()624		}625626		/// Remove a collection's a sponsor, making everyone pay for their own transactions.627		///628		/// # Permissions629		///630		/// * Collection owner631		///632		/// # Arguments633		///634		/// * `collection_id`: ID of the collection with the sponsor to remove.635		#[weight = <SelfWeightOf<T>>::remove_collection_sponsor()]636		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {637			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);638639			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;640			target_collection.check_is_internal()?;641			target_collection.check_is_owner(&sender)?;642643			target_collection.sponsorship = SponsorshipState::Disabled;644645			<Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(646				collection_id647			));648			target_collection.save()649		}650651		/// Mint an item within a collection.652		///653		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].654		///655		/// # Permissions656		///657		/// * Collection owner658		/// * Collection admin659		/// * Anyone if660		///     * Allow List is enabled, and661		///     * Address is added to allow list, and662		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])663		///664		/// # Arguments665		///666		/// * `collection_id`: ID of the collection to which an item would belong.667		/// * `owner`: Address of the initial owner of the item.668		/// * `data`: Token data describing the item to store on chain.669		#[weight = T::CommonWeightInfo::create_item()]670		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResultWithPostInfo {671			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);672			let budget = budget::Value::new(NESTING_BUDGET);673674			dispatch_tx::<T, _>(collection_id, |d| d.create_item(sender, owner, data, &budget))675		}676677		/// Create multiple items within a collection.678		///679		/// A collection must exist first, see [`create_collection_ex`][`Pallet::create_collection_ex`].680		///681		/// # Permissions682		///683		/// * Collection owner684		/// * Collection admin685		/// * Anyone if686		///     * Allow List is enabled, and687		///     * Address is added to the allow list, and688		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])689		///690		/// # Arguments691		///692		/// * `collection_id`: ID of the collection to which the tokens would belong.693		/// * `owner`: Address of the initial owner of the tokens.694		/// * `items_data`: Vector of data describing each item to be created.695		#[weight = T::CommonWeightInfo::create_multiple_items(&items_data)]696		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResultWithPostInfo {697			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);698			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);699			let budget = budget::Value::new(NESTING_BUDGET);700701			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items(sender, owner, items_data, &budget))702		}703704		/// Add or change collection properties.705		///706		/// # Permissions707		///708		/// * Collection owner709		/// * Collection admin710		///711		/// # Arguments712		///713		/// * `collection_id`: ID of the modified collection.714		/// * `properties`: Vector of key-value pairs stored as the collection's metadata.715		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.716		#[weight = T::CommonWeightInfo::set_collection_properties(properties.len() as u32)]717		pub fn set_collection_properties(718			origin,719			collection_id: CollectionId,720			properties: Vec<Property>721		) -> DispatchResultWithPostInfo {722			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);723724			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);725726			dispatch_tx::<T, _>(collection_id, |d| d.set_collection_properties(sender, properties))727		}728729		/// Delete specified collection properties.730		///731		/// # Permissions732		///733		/// * Collection Owner734		/// * Collection Admin735		///736		/// # Arguments737		///738		/// * `collection_id`: ID of the modified collection.739		/// * `property_keys`: Vector of keys of the properties to be deleted.740		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.741		#[weight = T::CommonWeightInfo::delete_collection_properties(property_keys.len() as u32)]742		pub fn delete_collection_properties(743			origin,744			collection_id: CollectionId,745			property_keys: Vec<PropertyKey>,746		) -> DispatchResultWithPostInfo {747			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);748749			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);750751			dispatch_tx::<T, _>(collection_id, |d| d.delete_collection_properties(&sender, property_keys))752		}753754		/// Add or change token properties according to collection's permissions.755		/// Currently properties only work with NFTs.756		///757		/// # Permissions758		///759		/// * Depends on collection's token property permissions and specified property mutability:760		/// 	* Collection owner761		/// 	* Collection admin762		/// 	* Token owner763		///764		/// See [`set_token_property_permissions`][`Pallet::set_token_property_permissions`].765		///766		/// # Arguments767		///768		/// * `collection_id: ID of the collection to which the token belongs.769		/// * `token_id`: ID of the modified token.770		/// * `properties`: Vector of key-value pairs stored as the token's metadata.771		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.772		#[weight = T::CommonWeightInfo::set_token_properties(properties.len() as u32)]773		pub fn set_token_properties(774			origin,775			collection_id: CollectionId,776			token_id: TokenId,777			properties: Vec<Property>778		) -> DispatchResultWithPostInfo {779			ensure!(!properties.is_empty(), Error::<T>::EmptyArgument);780781			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);782			let budget = budget::Value::new(NESTING_BUDGET);783784			dispatch_tx::<T, _>(collection_id, |d| d.set_token_properties(sender, token_id, properties, &budget))785		}786787		/// Delete specified token properties. Currently properties only work with NFTs.788		///789		/// # Permissions790		///791		/// * Depends on collection's token property permissions and specified property mutability:792		/// 	* Collection owner793		/// 	* Collection admin794		/// 	* Token owner795		///796		/// # Arguments797		///798		/// * `collection_id`: ID of the collection to which the token belongs.799		/// * `token_id`: ID of the modified token.800		/// * `property_keys`: Vector of keys of the properties to be deleted.801		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.802		#[weight = T::CommonWeightInfo::delete_token_properties(property_keys.len() as u32)]803		pub fn delete_token_properties(804			origin,805			collection_id: CollectionId,806			token_id: TokenId,807			property_keys: Vec<PropertyKey>808		) -> DispatchResultWithPostInfo {809			ensure!(!property_keys.is_empty(), Error::<T>::EmptyArgument);810811			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);812			let budget = budget::Value::new(NESTING_BUDGET);813814			dispatch_tx::<T, _>(collection_id, |d| d.delete_token_properties(sender, token_id, property_keys, &budget))815		}816817		/// Add or change token property permissions of a collection.818		///819		/// Without a permission for a particular key, a property with that key820		/// cannot be created in a token.821		///822		/// # Permissions823		///824		/// * Collection owner825		/// * Collection admin826		///827		/// # Arguments828		///829		/// * `collection_id`: ID of the modified collection.830		/// * `property_permissions`: Vector of permissions for property keys.831		/// Keys support Latin letters, `-`, `_`, and `.` as symbols.832		#[weight = T::CommonWeightInfo::set_token_property_permissions(property_permissions.len() as u32)]833		pub fn set_token_property_permissions(834			origin,835			collection_id: CollectionId,836			property_permissions: Vec<PropertyKeyPermission>,837		) -> DispatchResultWithPostInfo {838			ensure!(!property_permissions.is_empty(), Error::<T>::EmptyArgument);839840			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);841842			dispatch_tx::<T, _>(collection_id, |d| d.set_token_property_permissions(&sender, property_permissions))843		}844845		/// Create multiple items within a collection with explicitly specified initial parameters.846		///847		/// # Permissions848		///849		/// * Collection owner850		/// * Collection admin851		/// * Anyone if852		///     * Allow List is enabled, and853		///     * Address is added to allow list, and854		///     * MintPermission is enabled (see [`set_collection_permissions`][`Pallet::set_collection_permissions`])855		///856		/// # Arguments857		///858		/// * `collection_id`: ID of the collection to which the tokens would belong.859		/// * `data`: Explicit item creation data.860		#[weight = T::CommonWeightInfo::create_multiple_items_ex(&data)]861		pub fn create_multiple_items_ex(origin, collection_id: CollectionId, data: CreateItemExData<T::CrossAccountId>) -> DispatchResultWithPostInfo {862			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);863			let budget = budget::Value::new(NESTING_BUDGET);864865			dispatch_tx::<T, _>(collection_id, |d| d.create_multiple_items_ex(sender, data, &budget))866		}867868		/// Completely allow or disallow transfers for a particular collection.869		///870		/// # Permissions871		///872		/// * Collection owner873		///874		/// # Arguments875		///876		/// * `collection_id`: ID of the collection.877		/// * `value`: New value of the flag, are transfers allowed?878		#[weight = <SelfWeightOf<T>>::set_transfers_enabled_flag()]879		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {880			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);881			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;882			target_collection.check_is_internal()?;883			target_collection.check_is_owner(&sender)?;884885			// =========886887			target_collection.limits.transfers_enabled = Some(value);888			target_collection.save()889		}890891		/// Destroy an item.892		///893		/// # Permissions894		///895		/// * Collection owner896		/// * Collection admin897		/// * Current item owner898		///899		/// # Arguments900		///901		/// * `collection_id`: ID of the collection to which the item belongs.902		/// * `item_id`: ID of item to burn.903		/// * `value`: Number of pieces of the item to destroy.904		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.905		///     * Fungible Mode: The desired number of pieces to burn.906		///     * Re-Fungible Mode: The desired number of pieces to burn.907		#[weight = T::CommonWeightInfo::burn_item()]908		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {909			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);910911			let post_info = dispatch_tx::<T, _>(collection_id, |d| d.burn_item(sender, item_id, value))?;912			if value == 1 {913				<NftTransferBasket<T>>::remove(collection_id, item_id);914				<NftApproveBasket<T>>::remove(collection_id, item_id);915			}916			// Those maps should be cleared only if token disappears completly, need to move this part of logic to pallets?917			// <FungibleApproveBasket<T>>::remove(collection_id, sender.as_sub());918			// <RefungibleApproveBasket<T>>::remove((collection_id, item_id, sender.as_sub()));919			Ok(post_info)920		}921922		/// Destroy a token on behalf of the owner as a non-owner account.923		///924		/// See also: [`approve`][`Pallet::approve`].925		///926		/// After this method executes, one approval is removed from the total so that927		/// the approved address will not be able to transfer this item again from this owner.928		///929		/// # Permissions930		///931		/// * Collection owner932		/// * Collection admin933		/// * Current token owner934		/// * Address approved by current item owner935		///936		/// # Arguments937		///938		/// * `from`: The owner of the burning item.939		/// * `collection_id`: ID of the collection to which the item belongs.940		/// * `item_id`: ID of item to burn.941		/// * `value`: Number of pieces to burn.942		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.943		///     * Fungible Mode: The desired number of pieces to burn.944		///     * Re-Fungible Mode: The desired number of pieces to burn.945		#[weight = T::CommonWeightInfo::burn_from()]946		pub fn burn_from(origin, collection_id: CollectionId, from: T::CrossAccountId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {947			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);948			let budget = budget::Value::new(NESTING_BUDGET);949950			dispatch_tx::<T, _>(collection_id, |d| d.burn_from(sender, from, item_id, value, &budget))951		}952953		/// Change ownership of the token.954		///955		/// # Permissions956		///957		/// * Collection owner958		/// * Collection admin959		/// * Current token owner960		///961		/// # Arguments962		///963		/// * `recipient`: Address of token recipient.964		/// * `collection_id`: ID of the collection the item belongs to.965		/// * `item_id`: ID of the item.966		///     * Non-Fungible Mode: Required.967		///     * Fungible Mode: Ignored.968		///     * Re-Fungible Mode: Required.969		///970		/// * `value`: Amount to transfer.971		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.972		///     * Fungible Mode: The desired number of pieces to transfer.973		///     * Re-Fungible Mode: The desired number of pieces to transfer.974		#[weight = T::CommonWeightInfo::transfer()]975		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResultWithPostInfo {976			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);977			let budget = budget::Value::new(NESTING_BUDGET);978979			dispatch_tx::<T, _>(collection_id, |d| d.transfer(sender, recipient, item_id, value, &budget))980		}981982		/// Allow a non-permissioned address to transfer or burn an item.983		///984		/// # Permissions985		///986		/// * Collection owner987		/// * Collection admin988		/// * Current item owner989		///990		/// # Arguments991		///992		/// * `spender`: Account to be approved to make specific transactions on non-owned tokens.993		/// * `collection_id`: ID of the collection the item belongs to.994		/// * `item_id`: ID of the item transactions on which are now approved.995		/// * `amount`: Number of pieces of the item approved for a transaction (maximum of 1 for NFTs).996		/// Set to 0 to revoke the approval.997		#[weight = T::CommonWeightInfo::approve()]998		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResultWithPostInfo {999			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10001001			dispatch_tx::<T, _>(collection_id, |d| d.approve(sender, spender, item_id, amount))1002		}10031004		/// Change ownership of an item on behalf of the owner as a non-owner account.1005		///1006		/// See the [`approve`][`Pallet::approve`] method for additional information.1007		///1008		/// After this method executes, one approval is removed from the total so that1009		/// the approved address will not be able to transfer this item again from this owner.1010		///1011		/// # Permissions1012		///1013		/// * Collection owner1014		/// * Collection admin1015		/// * Current item owner1016		/// * Address approved by current item owner1017		///1018		/// # Arguments1019		///1020		/// * `from`: Address that currently owns the token.1021		/// * `recipient`: Address of the new token-owner-to-be.1022		/// * `collection_id`: ID of the collection the item.1023		/// * `item_id`: ID of the item to be transferred.1024		/// * `value`: Amount to transfer.1025		/// 	* Non-Fungible Mode: An NFT is indivisible, there is always 1 corresponding to an ID.1026		///     * Fungible Mode: The desired number of pieces to transfer.1027		///     * Re-Fungible Mode: The desired number of pieces to transfer.1028		#[weight = T::CommonWeightInfo::transfer_from()]1029		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResultWithPostInfo {1030			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1031			let budget = budget::Value::new(NESTING_BUDGET);10321033			dispatch_tx::<T, _>(collection_id, |d| d.transfer_from(sender, from, recipient, item_id, value, &budget))1034		}10351036		/// Set specific limits of a collection. Empty, or None fields mean chain default.1037		///1038		/// # Permissions1039		///1040		/// * Collection owner1041		/// * Collection admin1042		///1043		/// # Arguments1044		///1045		/// * `collection_id`: ID of the modified collection.1046		/// * `new_limit`: New limits of the collection. Fields that are not set (None)1047		/// will not overwrite the old ones.1048		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1049		pub fn set_collection_limits(1050			origin,1051			collection_id: CollectionId,1052			new_limit: CollectionLimits,1053		) -> DispatchResult {1054			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1055			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1056			target_collection.check_is_internal()?;1057			target_collection.check_is_owner_or_admin(&sender)?;1058			let old_limit = &target_collection.limits;10591060			target_collection.limits = <PalletCommon<T>>::clamp_limits(target_collection.mode.clone(), &old_limit, new_limit)?;10611062			<Pallet<T>>::deposit_event(Event::<T>::CollectionLimitSet(1063				collection_id1064			));10651066			target_collection.save()1067		}10681069		/// Set specific permissions of a collection. Empty, or None fields mean chain default.1070		///1071		/// # Permissions1072		///1073		/// * Collection owner1074		/// * Collection admin1075		///1076		/// # Arguments1077		///1078		/// * `collection_id`: ID of the modified collection.1079		/// * `new_permission`: New permissions of the collection. Fields that are not set (None)1080		/// will not overwrite the old ones.1081		#[weight = <SelfWeightOf<T>>::set_collection_limits()]1082		pub fn set_collection_permissions(1083			origin,1084			collection_id: CollectionId,1085			new_permission: CollectionPermissions,1086		) -> DispatchResult {1087			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1088			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1089			target_collection.check_is_internal()?;1090			target_collection.check_is_owner_or_admin(&sender)?;1091			let old_limit = &target_collection.permissions;10921093			target_collection.permissions = <PalletCommon<T>>::clamp_permissions(target_collection.mode.clone(), &old_limit, new_permission)?;10941095			<Pallet<T>>::deposit_event(Event::<T>::CollectionPermissionSet(1096				collection_id1097			));10981099			target_collection.save()1100		}11011102		/// Re-partition a refungible token, while owning all of its parts/pieces.1103		///1104		/// # Permissions1105		///1106		/// * Token owner (must own every part)1107		///1108		/// # Arguments1109		///1110		/// * `collection_id`: ID of the collection the RFT belongs to.1111		/// * `token_id`: ID of the RFT.1112		/// * `amount`: New number of parts/pieces into which the token shall be partitioned.1113		#[weight = T::RefungibleExtensionsWeightInfo::repartition()]1114		pub fn repartition(1115			origin,1116			collection_id: CollectionId,1117			token_id: TokenId,1118			amount: u128,1119		) -> DispatchResultWithPostInfo {1120			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1121			dispatch_tx::<T, _>(collection_id, |d| {1122				if let Some(refungible_extensions) = d.refungible_extensions() {1123					refungible_extensions.repartition(&sender, token_id, amount)1124				} else {1125					fail!(<Error<T>>::RepartitionCalledOnNonRefungibleCollection)1126				}1127			})1128		}1129	}1130}11311132impl<T: Config> Pallet<T> {1133	/// Force set `sponsor` for `collection`.1134	///1135	/// Differs from [`set_collection_sponsor`][`Pallet::set_collection_sponsor`] in that confirmation1136	/// from the `sponsor` is not required.1137	///1138	/// # Arguments1139	///1140	/// * `sponsor`: ID of the account of the sponsor-to-be.1141	/// * `collection_id`: ID of the modified collection.1142	pub fn force_set_sponsor(sponsor: T::AccountId, collection_id: CollectionId) -> DispatchResult {1143		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1144		target_collection.check_is_internal()?;1145		target_collection.set_sponsor(sponsor.clone())?;11461147		Self::deposit_event(Event::<T>::CollectionSponsorSet(1148			collection_id,1149			sponsor.clone(),1150		));11511152		ensure!(1153			target_collection.confirm_sponsorship(&sponsor)?,1154			Error::<T>::ConfirmUnsetSponsorFail1155		);11561157		Self::deposit_event(Event::<T>::SponsorshipConfirmed(collection_id, sponsor));11581159		target_collection.save()1160	}11611162	/// Force remove `sponsor` for `collection`.1163	///1164	/// Differs from `remove_sponsor` in that1165	/// it doesn't require consent from the `owner` of the collection.1166	///1167	/// # Arguments1168	///1169	/// * `collection_id`: ID of the modified collection.1170	pub fn force_remove_collection_sponsor(collection_id: CollectionId) -> DispatchResult {1171		let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;1172		target_collection.check_is_internal()?;1173		target_collection.sponsorship = SponsorshipState::Disabled;11741175		Self::deposit_event(Event::<T>::CollectionSponsorRemoved(collection_id));11761177		target_collection.save()1178	}11791180	#[inline(always)]1181	pub(crate) fn destroy_collection_internal(1182		sender: T::CrossAccountId,1183		collection_id: CollectionId,1184	) -> DispatchResult {1185		let collection = <CollectionHandle<T>>::try_get(collection_id)?;1186		collection.check_is_internal()?;11871188		T::CollectionDispatch::destroy(sender, collection)?;11891190		// TODO: basket cleanup should be moved elsewhere1191		// Maybe runtime dispatch.rs should perform it?11921193		let _ = <NftTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1194		let _ = <FungibleTransferBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1195		let _ = <ReFungibleTransferBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);11961197		let _ = <NftApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1198		let _ = <FungibleApproveBasket<T>>::clear_prefix(collection_id, u32::MAX, None);1199		let _ = <RefungibleApproveBasket<T>>::clear_prefix((collection_id,), u32::MAX, None);12001201		Ok(())1202	}1203}
modifiedprimitives/data-structs/CHANGELOG.mddiffbeforeafterboth
--- a/primitives/data-structs/CHANGELOG.md
+++ b/primitives/data-structs/CHANGELOG.md
@@ -3,6 +3,7 @@
 All notable changes to this project will be documented in this file.
 
 <!-- bureaucrate goes here -->
+
 ## [v0.2.2] 2022-08-16
 
 ### Other changes
@@ -28,12 +29,19 @@
 multiple users into `RefungibleMultipleItems` call.
 
 ## [v0.2.0] - 2022-08-01
+
 ### Deprecated
+
 - `CreateReFungibleData::const_data`
 
 ## [v0.1.2] - 2022-07-25
+
 ### Added
+
 - Type aliases `CollectionName`, `CollectionDescription`, `CollectionTokenPrefix`
+
 ## [v0.1.1] - 2022-07-22
+
 ### Added
-- Аields with properties to `CreateReFungibleData` and `CreateRefungibleExData`.
\ No newline at end of file
+
+- Fields with properties to `CreateReFungibleData` and `CreateRefungibleExData`.
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -120,22 +120,22 @@
 // TODO: not used. Delete?
 pub const COLLECTION_FIELD_LIMIT: u32 = CONST_ON_CHAIN_SCHEMA_LIMIT;
 
-/// Maximum length for collection name.
+/// Maximal length of a collection name.
 pub const MAX_COLLECTION_NAME_LENGTH: u32 = 64;
 
-/// Maximum length for collection description.
+/// Maximal length of a collection description.
 pub const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = 256;
 
-/// Maximal token prefix length.
+/// Maximal length of a token prefix.
 pub const MAX_TOKEN_PREFIX_LENGTH: u32 = 16;
 
-/// Maximal lenght of property key.
+/// Maximal length of a property key.
 pub const MAX_PROPERTY_KEY_LENGTH: u32 = 256;
 
-/// Maximal lenght of property value.
+/// Maximal length of a property value.
 pub const MAX_PROPERTY_VALUE_LENGTH: u32 = 32768;
 
-/// Maximum properties that can be assigned to token.
+/// A maximum number of token properties.
 pub const MAX_PROPERTIES_PER_ITEM: u32 = 64;
 
 /// Maximal lenght of extended property value.
@@ -144,7 +144,7 @@
 /// Maximum size for all collection properties.
 pub const MAX_COLLECTION_PROPERTIES_SIZE: u32 = 40960;
 
-/// Maximum size for all token properties.
+/// Maximum size of all token properties.
 pub const MAX_TOKEN_PROPERTIES_SIZE: u32 = 32768;
 
 /// How much items can be created per single
@@ -609,6 +609,24 @@
 }
 
 impl CollectionLimits {
+	pub fn with_default_limits(collection_type: CollectionMode) -> Self {
+		CollectionLimits {
+			account_token_ownership_limit: Some(ACCOUNT_TOKEN_OWNERSHIP_LIMIT),
+			sponsored_data_size: Some(CUSTOM_DATA_LIMIT),
+			sponsored_data_rate_limit: Some(SponsoringRateLimit::SponsoringDisabled),
+			token_limit: Some(COLLECTION_TOKEN_LIMIT),
+			sponsor_transfer_timeout: match collection_type {
+				CollectionMode::NFT => Some(NFT_SPONSOR_TRANSFER_TIMEOUT),
+				CollectionMode::ReFungible => Some(REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT),
+				CollectionMode::Fungible(_) => Some(FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT),
+			},
+			sponsor_approve_timeout: Some(SPONSOR_APPROVE_TIMEOUT),
+			owner_can_transfer: Some(false),
+			owner_can_destroy: Some(true),
+			transfers_enabled: Some(true),
+		}
+	}
+
 	/// Get effective value for [`account_token_ownership_limit`](self.account_token_ownership_limit).
 	pub fn account_token_ownership_limit(&self) -> u32 {
 		self.account_token_ownership_limit
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -102,6 +102,7 @@
     "testXcmTransferStatemine": "mocha --timeout 9999999 -r ts-node/register ./**/xcm/xcmTransferStatemine.test.ts statemineId=1000 uniqueId=5000",
     "testXcmTransferMoonbeam": "mocha --timeout 9999999 -r ts-node/register ./**/xcm/xcmTransferMoonbeam.test.ts",
     "benchMintingFee": "ts-node src/benchmarks/mintFee/benchmark.ts",
+    "testApiConsts": "mocha --timeout 9999999 -r ts-node/register ./**/apiConsts.test.ts",
     "load": "mocha --timeout 9999999 -r ts-node/register './**/*.load.ts'",
     "loadTransfer": "ts-node src/transfer.nload.ts",
     "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json",
addedtests/src/apiConsts.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/apiConsts.test.ts
@@ -0,0 +1,120 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {ApiPromise} from '@polkadot/api';
+import {ApiBase} from '@polkadot/api/base';
+import {usingPlaygrounds, itSub, expect} from './util';
+
+
+const MAX_COLLECTION_DESCRIPTION_LENGTH = 256n;
+const MAX_COLLECTION_NAME_LENGTH = 64n;
+const COLLECTION_ADMINS_LIMIT = 5n;
+const MAX_COLLECTION_PROPERTIES_SIZE = 40960n;
+const MAX_TOKEN_PREFIX_LENGTH = 16n;
+const MAX_PROPERTY_KEY_LENGTH = 256n;
+const MAX_PROPERTY_VALUE_LENGTH = 32768n;
+const MAX_PROPERTIES_PER_ITEM = 64n;
+const MAX_TOKEN_PROPERTIES_SIZE = 32768n;
+const NESTING_BUDGET = 5n;
+
+const DEFAULT_COLLETCTION_LIMIT = {
+  accountTokenOwnershipLimit: '1,000,000',
+  sponsoredDataSize: '2,048',
+  sponsoredDataRateLimit: 'SponsoringDisabled',
+  tokenLimit: '4,294,967,295',
+  sponsorTransferTimeout: '5',
+  sponsorApproveTimeout: '5',
+  ownerCanTransfer: false,
+  ownerCanDestroy: true,
+  transfersEnabled: true,
+};
+
+const EVM_COLLECTION_HELPERS_ADDRESS = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f';
+const HELPERS_CONTRACT_ADDRESS = '0x842899ECF380553E8a4de75bF534cdf6fBF64049';
+
+describe('integration test: API UNIQUE consts', () => {
+  let api: ApiPromise;
+  
+  before(async () => {
+    await usingPlaygrounds(async (helper) => {
+      api = await helper.getApi();
+    });
+  });
+  
+  itSub('DEFAULT_NFT_COLLECTION_LIMITS', () => {
+    expect(api.consts.unique.nftDefaultCollectionLimits.toHuman()).to.deep.equal(DEFAULT_COLLETCTION_LIMIT);
+  });
+  
+  itSub('DEFAULT_RFT_COLLECTION_LIMITS', () => {
+    expect(api.consts.unique.rftDefaultCollectionLimits.toHuman()).to.deep.equal(DEFAULT_COLLETCTION_LIMIT);
+  });
+  
+  itSub('DEFAULT_FT_COLLECTION_LIMITS', () => {
+    expect(api.consts.unique.ftDefaultCollectionLimits.toHuman()).to.deep.equal(DEFAULT_COLLETCTION_LIMIT);
+  });
+  
+  itSub('MAX_COLLECTION_NAME_LENGTH', () => {
+    checkConst(api.consts.unique.maxCollectionNameLength, MAX_COLLECTION_NAME_LENGTH);
+  });
+  
+  itSub('MAX_COLLECTION_DESCRIPTION_LENGTH', () => {
+    checkConst(api.consts.unique.maxCollectionDescriptionLength, MAX_COLLECTION_DESCRIPTION_LENGTH);
+  });
+  
+  itSub('MAX_COLLECTION_PROPERTIES_SIZE', () => {
+    checkConst(api.consts.unique.maxCollectionPropertiesSize, MAX_COLLECTION_PROPERTIES_SIZE);
+  });
+
+  itSub('MAX_TOKEN_PREFIX_LENGTH', () => {
+    checkConst(api.consts.unique.maxTokenPrefixLength, MAX_TOKEN_PREFIX_LENGTH);
+  });
+
+  itSub('MAX_PROPERTY_KEY_LENGTH', () => {
+    checkConst(api.consts.unique.maxPropertyKeyLength, MAX_PROPERTY_KEY_LENGTH);
+  });
+  
+  itSub('MAX_PROPERTY_VALUE_LENGTH', () => {
+    checkConst(api.consts.unique.maxPropertyValueLength, MAX_PROPERTY_VALUE_LENGTH);
+  });
+  
+  itSub('MAX_PROPERTIES_PER_ITEM', () => {
+    checkConst(api.consts.unique.maxPropertiesPerItem, MAX_PROPERTIES_PER_ITEM);
+  });
+  
+  itSub('NESTING_BUDGET', () => {
+    checkConst(api.consts.unique.nestingBudget, NESTING_BUDGET);
+  });
+  
+  itSub('MAX_TOKEN_PROPERTIES_SIZE', () => {
+    checkConst(api.consts.unique.maxTokenPropertiesSize, MAX_TOKEN_PROPERTIES_SIZE);
+  });
+  
+  itSub('COLLECTION_ADMINS_LIMIT', () => {
+    checkConst(api.consts.unique.collectionAdminsLimit, COLLECTION_ADMINS_LIMIT);
+  });
+  
+  itSub('HELPERS_CONTRACT_ADDRESS', () => {
+    expect(api.consts.evmContractHelpers.contractAddress.toString().toLowerCase()).to.be.equal(HELPERS_CONTRACT_ADDRESS.toLowerCase());
+  });
+  
+  itSub('EVM_COLLECTION_HELPERS_ADDRESS', () => {
+    expect(api.consts.common.contractAddress.toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS.toLowerCase());
+  });
+});
+
+function checkConst<T>(constValue: any, expectedValue: T) {
+  expect(constValue.toBigInt()).equal(expectedValue);
+}
\ No newline at end of file
modifiedtests/src/eth/abi/collectionHelpers.jsondiffbeforeafterboth
--- a/tests/src/eth/abi/collectionHelpers.json
+++ b/tests/src/eth/abi/collectionHelpers.json
@@ -32,6 +32,15 @@
     "type": "event"
   },
   {
+    "inputs": [
+      { "internalType": "uint32", "name": "collectionId", "type": "uint32" }
+    ],
+    "name": "collectionAddress",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
     "inputs": [],
     "name": "collectionCreationFee",
     "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
@@ -40,6 +49,19 @@
   },
   {
     "inputs": [
+      {
+        "internalType": "address",
+        "name": "collectionAddress",
+        "type": "address"
+      }
+    ],
+    "name": "collectionId",
+    "outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
       { "internalType": "string", "name": "name", "type": "string" },
       { "internalType": "uint8", "name": "decimals", "type": "uint8" },
       { "internalType": "string", "name": "description", "type": "string" },
modifiedtests/src/eth/abi/fungible.jsondiffbeforeafterboth
--- a/tests/src/eth/abi/fungible.json
+++ b/tests/src/eth/abi/fungible.json
@@ -201,6 +201,13 @@
   },
   {
     "inputs": [],
+    "name": "collectionHelperAddress",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
     "name": "collectionOwner",
     "outputs": [
       {
modifiedtests/src/eth/abi/nonFungible.jsondiffbeforeafterboth
--- a/tests/src/eth/abi/nonFungible.json
+++ b/tests/src/eth/abi/nonFungible.json
@@ -231,6 +231,13 @@
   },
   {
     "inputs": [],
+    "name": "collectionHelperAddress",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
     "name": "collectionOwner",
     "outputs": [
       {
modifiedtests/src/eth/abi/reFungible.jsondiffbeforeafterboth
--- a/tests/src/eth/abi/reFungible.json
+++ b/tests/src/eth/abi/reFungible.json
@@ -213,6 +213,13 @@
   },
   {
     "inputs": [],
+    "name": "collectionHelperAddress",
+    "outputs": [{ "internalType": "address", "name": "", "type": "address" }],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
     "name": "collectionOwner",
     "outputs": [
       {
modifiedtests/src/eth/api/CollectionHelpers.soldiffbeforeafterboth
--- a/tests/src/eth/api/CollectionHelpers.sol
+++ b/tests/src/eth/api/CollectionHelpers.sol
@@ -19,7 +19,7 @@
 }
 
 /// @title Contract, which allows users to operate with collections
-/// @dev the ERC-165 identifier for this interface is 0x7dea03b1
+/// @dev the ERC-165 identifier for this interface is 0xe65011aa
 interface CollectionHelpers is Dummy, ERC165, CollectionHelpersEvents {
 	/// Create an NFT collection
 	/// @param name Name of the collection
@@ -78,4 +78,18 @@
 	/// @dev EVM selector for this function is: 0xd23a7ab1,
 	///  or in textual repr: collectionCreationFee()
 	function collectionCreationFee() external view returns (uint256);
+
+	/// Returns address of a collection.
+	/// @param collectionId  - CollectionId  of the collection
+	/// @return eth mirror address of the collection
+	/// @dev EVM selector for this function is: 0x2e716683,
+	///  or in textual repr: collectionAddress(uint32)
+	function collectionAddress(uint32 collectionId) external view returns (address);
+
+	/// Returns collectionId of a collection.
+	/// @param collectionAddress  - Eth address of the collection
+	/// @return collectionId of the collection
+	/// @dev EVM selector for this function is: 0xb5cb7498,
+	///  or in textual repr: collectionId(address)
+	function collectionId(address collectionAddress) external view returns (uint32);
 }
modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -354,7 +354,7 @@
 	event Approval(address indexed owner, address indexed spender, uint256 value);
 }
 
-/// @dev the ERC-165 identifier for this interface is 0x942e8b22
+/// @dev the ERC-165 identifier for this interface is 0x8cb847c4
 interface ERC20 is Dummy, ERC165, ERC20Events {
 	/// @dev EVM selector for this function is: 0x06fdde03,
 	///  or in textual repr: name()
@@ -395,6 +395,11 @@
 	/// @dev EVM selector for this function is: 0xdd62ed3e,
 	///  or in textual repr: allowance(address,address)
 	function allowance(address owner, address spender) external view returns (uint256);
+
+	/// @notice Returns collection helper contract address
+	/// @dev EVM selector for this function is: 0x1896cce6,
+	///  or in textual repr: collectionHelperAddress()
+	function collectionHelperAddress() external view returns (address);
 }
 
 interface UniqueFungible is Dummy, ERC165, ERC20, ERC20Mintable, ERC20UniqueExtensions, Collection {}
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -605,7 +605,7 @@
 
 /// @title ERC-721 Non-Fungible Token Standard
 /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
-/// @dev the ERC-165 identifier for this interface is 0x80ac58cd
+/// @dev the ERC-165 identifier for this interface is 0x983a942b
 interface ERC721 is Dummy, ERC165, ERC721Events {
 	/// @notice Count all NFTs assigned to an owner
 	/// @dev NFTs assigned to the zero address are considered invalid, and this
@@ -685,6 +685,11 @@
 	/// @dev EVM selector for this function is: 0xe985e9c5,
 	///  or in textual repr: isApprovedForAll(address,address)
 	function isApprovedForAll(address owner, address operator) external view returns (address);
+
+	/// @notice Returns collection helper contract address
+	/// @dev EVM selector for this function is: 0x1896cce6,
+	///  or in textual repr: collectionHelperAddress()
+	function collectionHelperAddress() external view returns (address);
 }
 
 interface UniqueNFT is
modifiedtests/src/eth/api/UniqueRefungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueRefungible.sol
+++ b/tests/src/eth/api/UniqueRefungible.sol
@@ -604,7 +604,7 @@
 
 /// @title ERC-721 Non-Fungible Token Standard
 /// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
-/// @dev the ERC-165 identifier for this interface is 0x58800161
+/// @dev the ERC-165 identifier for this interface is 0x4016cd87
 interface ERC721 is Dummy, ERC165, ERC721Events {
 	/// @notice Count all RFTs assigned to an owner
 	/// @dev RFTs assigned to the zero address are considered invalid, and this
@@ -682,6 +682,11 @@
 	/// @dev EVM selector for this function is: 0xe985e9c5,
 	///  or in textual repr: isApprovedForAll(address,address)
 	function isApprovedForAll(address owner, address operator) external view returns (address);
+
+	/// @notice Returns collection helper contract address
+	/// @dev EVM selector for this function is: 0x1896cce6,
+	///  or in textual repr: collectionHelperAddress()
+	function collectionHelperAddress() external view returns (address);
 }
 
 interface UniqueRefungible is
addedtests/src/eth/collectionHelperAddress.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/eth/collectionHelperAddress.test.ts
@@ -0,0 +1,72 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {itEth, usingEthPlaygrounds, expect} from './util';
+import {IKeyringPair} from '@polkadot/types/types';
+import {Pallets} from '../util';
+
+const EVM_COLLECTION_HELPERS_ADDRESS = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f';
+
+describe('[eth]CollectionHelperAddress test: ERC20/ERC721 ', () => {
+  let donor: IKeyringPair;
+
+  before(async function() {
+    await usingEthPlaygrounds(async (helper, privateKey) => {
+      donor = await privateKey({filename: __filename});
+    });
+  });
+
+  itEth('NFT', async ({helper}) => {
+    const owner =  await helper.eth.createAccountWithBalance(donor);
+    
+    const {collectionAddress: nftCollectionAddress} = await helper.eth.createNFTCollection(owner, 'Sponsor', 'absolutely anything', 'ROC');
+    const nftCollection = helper.ethNativeContract.collection(nftCollectionAddress, 'nft', owner);
+    
+    expect((await nftCollection.methods.collectionHelperAddress().call())
+      .toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS);
+  });
+  
+  itEth.ifWithPallets('RFT ', [Pallets.ReFungible], async ({helper}) => {
+    const owner =  await helper.eth.createAccountWithBalance(donor);
+
+    const {collectionAddress: rftCollectionAddress} = await helper.eth.createRFTCollection(owner, 'Sponsor', 'absolutely anything', 'ROC');
+
+    const rftCollection = helper.ethNativeContract.collection(rftCollectionAddress, 'rft', owner);
+    expect((await rftCollection.methods.collectionHelperAddress().call())
+      .toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS);
+  });
+  
+  itEth('FT', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+
+    const {collectionAddress} = await helper.eth.createFungibleCollection(owner, 'Sponsor', 18, 'absolutely anything', 'ROC');
+    const collection = helper.ethNativeContract.collection(collectionAddress, 'ft', owner);
+    
+    expect((await collection.methods.collectionHelperAddress().call())
+      .toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS);
+  });
+  
+  itEth('[collectionHelpers] convert collectionId into address', async ({helper}) => {
+    const owner = await helper.eth.createAccountWithBalance(donor);
+    const collectionId = 7;
+    const collectionAddress = helper.ethAddress.fromCollectionId(collectionId);
+    const helperContract = helper.ethNativeContract.collectionHelpers(owner);
+    
+    expect(await helperContract.methods.collectionAddress(collectionId).call()).to.be.equal(collectionAddress);
+    expect(parseInt(await helperContract.methods.collectionId(collectionAddress).call())).to.be.equal(collectionId);
+  });
+ 
+});
modifiedtests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth
--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -103,12 +103,12 @@
 
   contractHelpers(caller: string): Contract {
     const web3 = this.helper.getWeb3();
-    return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, gas: this.helper.eth.DEFAULT_GAS});
+    return new web3.eth.Contract(contractHelpersAbi as any, this.helper.getApi().consts.evmContractHelpers.contractAddress.toString(), {from: caller, gas: this.helper.eth.DEFAULT_GAS});
   }
 
   collectionHelpers(caller: string) {
     const web3 = this.helper.getWeb3();
-    return new web3.eth.Contract(collectionHelpersAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, gas: this.helper.eth.DEFAULT_GAS});
+    return new web3.eth.Contract(collectionHelpersAbi as any, this.helper.getApi().consts.common.contractAddress.toString(), {from: caller, gas: this.helper.eth.DEFAULT_GAS});
   }
 
   collection(address: string, mode: TCollectionMode, caller?: string, mergeDeprecated = false): Contract {
modifiedtests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-consts.ts
+++ b/tests/src/interfaces/augment-api-consts.ts
@@ -8,8 +8,8 @@
 import type { ApiTypes, AugmentedConst } from '@polkadot/api-base/types';
 import type { Option, u128, u16, u32, u64, u8 } from '@polkadot/types-codec';
 import type { Codec } from '@polkadot/types-codec/types';
-import type { Perbill, Permill, Weight } from '@polkadot/types/interfaces/runtime';
-import type { FrameSupportPalletId, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, SpVersionRuntimeVersion, SpWeightsRuntimeDbWeight, XcmV1MultiLocation } from '@polkadot/types/lookup';
+import type { H160, Perbill, Permill, Weight } from '@polkadot/types/interfaces/runtime';
+import type { FrameSupportPalletId, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, SpVersionRuntimeVersion, SpWeightsRuntimeDbWeight, UpDataStructsCollectionLimits, XcmV1MultiLocation } from '@polkadot/types/lookup';
 
 export type __AugmentedConst<ApiType extends ApiTypes> = AugmentedConst<ApiType>;
 
@@ -70,6 +70,10 @@
        **/
       collectionCreationPrice: u128 & AugmentedConst<ApiType>;
       /**
+       * Address under which the CollectionHelper contract would be available.
+       **/
+      contractAddress: H160 & AugmentedConst<ApiType>;
+      /**
        * Generic const
        **/
       [key: string]: Codec;
@@ -82,6 +86,16 @@
        **/
       [key: string]: Codec;
     };
+    evmContractHelpers: {
+      /**
+       * Address, under which magic contract will be available
+       **/
+      contractAddress: H160 & AugmentedConst<ApiType>;
+      /**
+       * Generic const
+       **/
+      [key: string]: Codec;
+    };
     inflation: {
       /**
        * Number of blocks that pass between treasury balance updates due to inflation
@@ -231,6 +245,64 @@
        **/
       [key: string]: Codec;
     };
+    unique: {
+      /**
+       * Maximum admins per collection.
+       **/
+      collectionAdminsLimit: u32 & AugmentedConst<ApiType>;
+      /**
+       * Default FT collection limit.
+       **/
+      ftDefaultCollectionLimits: UpDataStructsCollectionLimits & AugmentedConst<ApiType>;
+      /**
+       * Maximal length of a collection description.
+       **/
+      maxCollectionDescriptionLength: u32 & AugmentedConst<ApiType>;
+      /**
+       * Maximal length of a collection name.
+       **/
+      maxCollectionNameLength: u32 & AugmentedConst<ApiType>;
+      /**
+       * Maximum size for all collection properties.
+       **/
+      maxCollectionPropertiesSize: u32 & AugmentedConst<ApiType>;
+      /**
+       * A maximum number of token properties.
+       **/
+      maxPropertiesPerItem: u32 & AugmentedConst<ApiType>;
+      /**
+       * Maximal length of a property key.
+       **/
+      maxPropertyKeyLength: u32 & AugmentedConst<ApiType>;
+      /**
+       * Maximal length of a property value.
+       **/
+      maxPropertyValueLength: u32 & AugmentedConst<ApiType>;
+      /**
+       * Maximal length of a token prefix.
+       **/
+      maxTokenPrefixLength: u32 & AugmentedConst<ApiType>;
+      /**
+       * Maximum size of all token properties.
+       **/
+      maxTokenPropertiesSize: u32 & AugmentedConst<ApiType>;
+      /**
+       * A maximum number of levels of depth in the token nesting tree.
+       **/
+      nestingBudget: u32 & AugmentedConst<ApiType>;
+      /**
+       * Default NFT collection limit.
+       **/
+      nftDefaultCollectionLimits: UpDataStructsCollectionLimits & AugmentedConst<ApiType>;
+      /**
+       * Default RFT collection limit.
+       **/
+      rftDefaultCollectionLimits: UpDataStructsCollectionLimits & AugmentedConst<ApiType>;
+      /**
+       * Generic const
+       **/
+      [key: string]: Codec;
+    };
     vesting: {
       /**
        * The minimum amount transferred to call `vested_transfer`.