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
before · pallets/common/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//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63	ensure,64	traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65	dispatch::Pays,66	transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70	COLLECTION_NUMBER_LIMIT,71	Collection,72	RpcCollection,73	CollectionFlags,74	RpcCollectionFlags,75	CollectionId,76	CreateItemData,77	MAX_TOKEN_PREFIX_LENGTH,78	COLLECTION_ADMINS_LIMIT,79	TokenId,80	TokenChild,81	CollectionStats,82	MAX_TOKEN_OWNERSHIP,83	CollectionMode,84	NFT_SPONSOR_TRANSFER_TIMEOUT,85	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87	MAX_SPONSOR_TIMEOUT,88	CUSTOM_DATA_LIMIT,89	CollectionLimits,90	CreateCollectionData,91	SponsorshipState,92	CreateItemExData,93	SponsoringRateLimit,94	budget::Budget,95	PhantomType,96	Property,97	Properties,98	PropertiesPermissionMap,99	PropertyKey,100	PropertyValue,101	PropertyPermission,102	PropertiesError,103	PropertyKeyPermission,104	TokenData,105	TrySetProperty,106	PropertyScope,107	// RMRK108	RmrkCollectionInfo,109	RmrkInstanceInfo,110	RmrkResourceInfo,111	RmrkPropertyInfo,112	RmrkBaseInfo,113	RmrkPartType,114	RmrkBoundedTheme,115	RmrkNftChild,116	CollectionPermissions,117};118119pub use pallet::*;120use sp_core::H160;121use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};122#[cfg(feature = "runtime-benchmarks")]123pub mod benchmarking;124pub mod dispatch;125pub mod erc;126pub mod eth;127pub mod weights;128129/// Weight info.130pub type SelfWeightOf<T> = <T as Config>::WeightInfo;131132/// Collection handle contains information about collection data and id.133/// Also provides functionality to count consumed gas.134///135/// CollectionHandle is used as a generic wrapper for collections of all types.136/// It allows to perform common operations and queries on any collection type,137/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].138#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]139pub struct CollectionHandle<T: Config> {140	/// Collection id141	pub id: CollectionId,142	collection: Collection<T::AccountId>,143	/// Substrate recorder for counting consumed gas144	pub recorder: SubstrateRecorder<T>,145}146147impl<T: Config> WithRecorder<T> for CollectionHandle<T> {148	fn recorder(&self) -> &SubstrateRecorder<T> {149		&self.recorder150	}151	fn into_recorder(self) -> SubstrateRecorder<T> {152		self.recorder153	}154}155156impl<T: Config> CollectionHandle<T> {157	/// Same as [CollectionHandle::new] but with an explicit gas limit.158	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {159		<CollectionById<T>>::get(id).map(|collection| Self {160			id,161			collection,162			recorder: SubstrateRecorder::new(gas_limit),163		})164	}165166	/// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].167	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {168		<CollectionById<T>>::get(id).map(|collection| Self {169			id,170			collection,171			recorder,172		})173	}174175	/// Retrives collection data from storage and creates collection handle with default parameters.176	/// If collection not found return `None`177	pub fn new(id: CollectionId) -> Option<Self> {178		Self::new_with_gas_limit(id, u64::MAX)179	}180181	/// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.182	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {183		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)184	}185186	/// Consume gas for reading.187	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {188		self.recorder189			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(190				<T as frame_system::Config>::DbWeight::get()191					.read192					.saturating_mul(reads),193			)))194	}195196	/// Consume gas for writing.197	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {198		self.recorder199			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(200				<T as frame_system::Config>::DbWeight::get()201					.write202					.saturating_mul(writes),203			)))204	}205206	/// Consume gas for reading and writing.207	pub fn consume_store_reads_and_writes(208		&self,209		reads: u64,210		writes: u64,211	) -> evm_coder::execution::Result<()> {212		let weight = <T as frame_system::Config>::DbWeight::get();213		let reads = weight.read.saturating_mul(reads);214		let writes = weight.read.saturating_mul(writes);215		self.recorder216			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(217				reads.saturating_add(writes),218			)))219	}220221	/// Save collection to storage.222	pub fn save(&self) -> DispatchResult {223		<CollectionById<T>>::insert(self.id, &self.collection);224		Ok(())225	}226227	/// Set collection sponsor.228	///229	/// Unique collections allows sponsoring for certain actions.230	/// This method allows you to set the sponsor of the collection.231	/// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].232	pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {233		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);234		Ok(())235	}236237	/// Confirm sponsorship238	///239	/// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.240	/// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].241	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {242		if self.collection.sponsorship.pending_sponsor() != Some(sender) {243			return Ok(false);244		}245246		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());247		Ok(true)248	}249250	/// Remove collection sponsor.251	pub fn remove_sponsor(&mut self) -> DispatchResult {252		self.collection.sponsorship = SponsorshipState::Disabled;253		Ok(())254	}255256	/// Checks that the collection was created with, and must be operated upon through **Unique API**.257	/// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.258	pub fn check_is_internal(&self) -> DispatchResult {259		if self.flags.external {260			return Err(<Error<T>>::CollectionIsExternal)?;261		}262263		Ok(())264	}265266	/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.267	/// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.268	pub fn check_is_external(&self) -> DispatchResult {269		if !self.flags.external {270			return Err(<Error<T>>::CollectionIsInternal)?;271		}272273		Ok(())274	}275}276277impl<T: Config> Deref for CollectionHandle<T> {278	type Target = Collection<T::AccountId>;279280	fn deref(&self) -> &Self::Target {281		&self.collection282	}283}284285impl<T: Config> DerefMut for CollectionHandle<T> {286	fn deref_mut(&mut self) -> &mut Self::Target {287		&mut self.collection288	}289}290291impl<T: Config> CollectionHandle<T> {292	/// Checks if the `user` is the owner of the collection.293	pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {294		ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);295		Ok(())296	}297298	/// Returns **true** if the `user` is the owner or administrator of the collection.299	pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {300		*user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))301	}302303	/// Checks if the `user` is the owner or administrator of the collection.304	pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {305		ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);306		Ok(())307	}308309	/// Return **true** if `user` was not allowed to have tokens, and he can ignore such restrictions.310	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {311		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)312	}313314	/// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.315	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {316		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)317	}318319	/// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.320	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {321		ensure!(322			<Allowlist<T>>::get((self.id, user)),323			<Error<T>>::AddressNotInAllowlist324		);325		Ok(())326	}327328	/// Changes collection owner to another account329	/// #### Store read/writes330	/// 1 writes331	fn set_owner_internal(332		&mut self,333		caller: T::CrossAccountId,334		new_owner: T::CrossAccountId,335	) -> DispatchResult {336		self.check_is_owner(&caller)?;337		self.collection.owner = new_owner.as_sub().clone();338		self.save()339	}340}341342#[frame_support::pallet]343pub mod pallet {344	use super::*;345	use dispatch::CollectionDispatch;346	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};347	use frame_system::pallet_prelude::*;348	use frame_support::traits::Currency;349	use up_data_structs::{TokenId, mapping::TokenAddressMapping};350	use scale_info::TypeInfo;351	use weights::WeightInfo;352353	#[pallet::config]354	pub trait Config:355		frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo356	{357		/// Weight information for functions of this pallet.358		type WeightInfo: WeightInfo;359360		/// Events compatible with [`frame_system::Config::Event`].361		type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;362363		/// Handler of accounts and payment.364		type Currency: Currency<Self::AccountId>;365366		/// Set price to create a collection.367		#[pallet::constant]368		type CollectionCreationPrice: Get<369			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,370		>;371372		/// Dispatcher of operations on collections.373		type CollectionDispatch: CollectionDispatch<Self>;374375		/// Account which holds the chain's treasury.376		type TreasuryAccountId: Get<Self::AccountId>;377378		/// Address under which the CollectionHelper contract would be available.379		type ContractAddress: Get<H160>;380381		/// Mapper for token addresses to Ethereum addresses.382		type EvmTokenAddressMapping: TokenAddressMapping<H160>;383384		/// Mapper for token addresses to [`CrossAccountId`].385		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;386	}387388	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);389390	#[pallet::pallet]391	#[pallet::storage_version(STORAGE_VERSION)]392	#[pallet::generate_store(pub(super) trait Store)]393	pub struct Pallet<T>(_);394395	#[pallet::extra_constants]396	impl<T: Config> Pallet<T> {397		/// Maximum admins per collection.398		pub fn collection_admins_limit() -> u32 {399			COLLECTION_ADMINS_LIMIT400		}401	}402403	#[pallet::event]404	#[pallet::generate_deposit(pub fn deposit_event)]405	pub enum Event<T: Config> {406		/// New collection was created407		CollectionCreated(408			/// Globally unique identifier of newly created collection.409			CollectionId,410			/// [`CollectionMode`] converted into _u8_.411			u8,412			/// Collection owner.413			T::AccountId,414		),415416		/// New collection was destroyed417		CollectionDestroyed(418			/// Globally unique identifier of collection.419			CollectionId,420		),421422		/// New item was created.423		ItemCreated(424			/// Id of the collection where item was created.425			CollectionId,426			/// Id of an item. Unique within the collection.427			TokenId,428			/// Owner of newly created item429			T::CrossAccountId,430			/// Always 1 for NFT431			u128,432		),433434		/// Collection item was burned.435		ItemDestroyed(436			/// Id of the collection where item was destroyed.437			CollectionId,438			/// Identifier of burned NFT.439			TokenId,440			/// Which user has destroyed its tokens.441			T::CrossAccountId,442			/// Amount of token pieces destroed. Always 1 for NFT.443			u128,444		),445446		/// Item was transferred447		Transfer(448			/// Id of collection to which item is belong.449			CollectionId,450			/// Id of an item.451			TokenId,452			/// Original owner of item.453			T::CrossAccountId,454			/// New owner of item.455			T::CrossAccountId,456			/// Amount of token pieces transfered. Always 1 for NFT.457			u128,458		),459460		/// Amount pieces of token owned by `sender` was approved for `spender`.461		Approved(462			/// Id of collection to which item is belong.463			CollectionId,464			/// Id of an item.465			TokenId,466			/// Original owner of item.467			T::CrossAccountId,468			/// Id for which the approval was granted.469			T::CrossAccountId,470			/// Amount of token pieces transfered. Always 1 for NFT.471			u128,472		),473474		/// The colletion property has been added or edited.475		CollectionPropertySet(476			/// Id of collection to which property has been set.477			CollectionId,478			/// The property that was set.479			PropertyKey,480		),481482		/// The property has been deleted.483		CollectionPropertyDeleted(484			/// Id of collection to which property has been deleted.485			CollectionId,486			/// The property that was deleted.487			PropertyKey,488		),489490		/// The token property has been added or edited.491		TokenPropertySet(492			/// Identifier of the collection whose token has the property set.493			CollectionId,494			/// The token for which the property was set.495			TokenId,496			/// The property that was set.497			PropertyKey,498		),499500		/// The token property has been deleted.501		TokenPropertyDeleted(502			/// Identifier of the collection whose token has the property deleted.503			CollectionId,504			/// The token for which the property was deleted.505			TokenId,506			/// The property that was deleted.507			PropertyKey,508		),509510		/// The token property permission of a collection has been set.511		PropertyPermissionSet(512			/// ID of collection to which property permission has been set.513			CollectionId,514			/// The property permission that was set.515			PropertyKey,516		),517	}518519	#[pallet::error]520	pub enum Error<T> {521		/// This collection does not exist.522		CollectionNotFound,523		/// Sender parameter and item owner must be equal.524		MustBeTokenOwner,525		/// No permission to perform action526		NoPermission,527		/// Destroying only empty collections is allowed528		CantDestroyNotEmptyCollection,529		/// Collection is not in mint mode.530		PublicMintingNotAllowed,531		/// Address is not in allow list.532		AddressNotInAllowlist,533534		/// Collection name can not be longer than 63 char.535		CollectionNameLimitExceeded,536		/// Collection description can not be longer than 255 char.537		CollectionDescriptionLimitExceeded,538		/// Token prefix can not be longer than 15 char.539		CollectionTokenPrefixLimitExceeded,540		/// Total collections bound exceeded.541		TotalCollectionsLimitExceeded,542		/// Exceeded max admin count543		CollectionAdminCountExceeded,544		/// Collection limit bounds per collection exceeded545		CollectionLimitBoundsExceeded,546		/// Tried to enable permissions which are only permitted to be disabled547		OwnerPermissionsCantBeReverted,548		/// Collection settings not allowing items transferring549		TransferNotAllowed,550		/// Account token limit exceeded per collection551		AccountTokenLimitExceeded,552		/// Collection token limit exceeded553		CollectionTokenLimitExceeded,554		/// Metadata flag frozen555		MetadataFlagFrozen,556557		/// Item does not exist558		TokenNotFound,559		/// Item is balance not enough560		TokenValueTooLow,561		/// Requested value is more than the approved562		ApprovedValueTooLow,563		/// Tried to approve more than owned564		CantApproveMoreThanOwned,565566		/// Can't transfer tokens to ethereum zero address567		AddressIsZero,568569		/// The operation is not supported570		UnsupportedOperation,571572		/// Insufficient funds to perform an action573		NotSufficientFounds,574575		/// User does not satisfy the nesting rule576		UserIsNotAllowedToNest,577		/// Only tokens from specific collections may nest tokens under this one578		SourceCollectionIsNotAllowedToNest,579580		/// Tried to store more data than allowed in collection field581		CollectionFieldSizeExceeded,582583		/// Tried to store more property data than allowed584		NoSpaceForProperty,585586		/// Tried to store more property keys than allowed587		PropertyLimitReached,588589		/// Property key is too long590		PropertyKeyIsTooLong,591592		/// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed593		InvalidCharacterInPropertyKey,594595		/// Empty property keys are forbidden596		EmptyPropertyKey,597598		/// Tried to access an external collection with an internal API599		CollectionIsExternal,600601		/// Tried to access an internal collection with an external API602		CollectionIsInternal,603	}604605	/// Storage of the count of created collections. Essentially contains the last collection ID.606	#[pallet::storage]607	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;608609	/// Storage of the count of deleted collections.610	#[pallet::storage]611	pub type DestroyedCollectionCount<T> =612		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;613614	/// Storage of collection info.615	#[pallet::storage]616	pub type CollectionById<T> = StorageMap<617		Hasher = Blake2_128Concat,618		Key = CollectionId,619		Value = Collection<<T as frame_system::Config>::AccountId>,620		QueryKind = OptionQuery,621	>;622623	/// Storage of collection properties.624	#[pallet::storage]625	#[pallet::getter(fn collection_properties)]626	pub type CollectionProperties<T> = StorageMap<627		Hasher = Blake2_128Concat,628		Key = CollectionId,629		Value = Properties,630		QueryKind = ValueQuery,631		OnEmpty = up_data_structs::CollectionProperties,632	>;633634	/// Storage of token property permissions of a collection.635	#[pallet::storage]636	#[pallet::getter(fn property_permissions)]637	pub type CollectionPropertyPermissions<T> = StorageMap<638		Hasher = Blake2_128Concat,639		Key = CollectionId,640		Value = PropertiesPermissionMap,641		QueryKind = ValueQuery,642	>;643644	/// Storage of the amount of collection admins.645	#[pallet::storage]646	pub type AdminAmount<T> = StorageMap<647		Hasher = Blake2_128Concat,648		Key = CollectionId,649		Value = u32,650		QueryKind = ValueQuery,651	>;652653	/// List of collection admins.654	#[pallet::storage]655	pub type IsAdmin<T: Config> = StorageNMap<656		Key = (657			Key<Blake2_128Concat, CollectionId>,658			Key<Blake2_128Concat, T::CrossAccountId>,659		),660		Value = bool,661		QueryKind = ValueQuery,662	>;663664	/// Allowlisted collection users.665	#[pallet::storage]666	pub type Allowlist<T: Config> = StorageNMap<667		Key = (668			Key<Blake2_128Concat, CollectionId>,669			Key<Blake2_128Concat, T::CrossAccountId>,670		),671		Value = bool,672		QueryKind = ValueQuery,673	>;674675	/// Not used by code, exists only to provide some types to metadata.676	#[pallet::storage]677	pub type DummyStorageValue<T: Config> = StorageValue<678		Value = (679			CollectionStats,680			CollectionId,681			TokenId,682			TokenChild,683			PhantomType<(684				TokenData<T::CrossAccountId>,685				RpcCollection<T::AccountId>,686				// RMRK687				RmrkCollectionInfo<T::AccountId>,688				RmrkInstanceInfo<T::AccountId>,689				RmrkResourceInfo,690				RmrkPropertyInfo,691				RmrkBaseInfo<T::AccountId>,692				RmrkPartType,693				RmrkBoundedTheme,694				RmrkNftChild,695			)>,696		),697		QueryKind = OptionQuery,698	>;699700	#[pallet::hooks]701	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {702		fn on_runtime_upgrade() -> Weight {703			StorageVersion::new(1).put::<Pallet<T>>();704705			Weight::zero()706		}707	}708}709710impl<T: Config> Pallet<T> {711	/// Enshure that receiver address is correct.712	///713	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.714	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {715		ensure!(716			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,717			<Error<T>>::AddressIsZero718		);719		Ok(())720	}721722	/// Get a vector of collection admins.723	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {724		<IsAdmin<T>>::iter_prefix((collection,))725			.map(|(a, _)| a)726			.collect()727	}728729	/// Get a vector of users allowed to mint tokens.730	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {731		<Allowlist<T>>::iter_prefix((collection,))732			.map(|(a, _)| a)733			.collect()734	}735736	/// Is `user` allowed to mint token in `collection`.737	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {738		<Allowlist<T>>::get((collection, user))739	}740741	/// Get statistics of collections.742	pub fn collection_stats() -> CollectionStats {743		let created = <CreatedCollectionCount<T>>::get();744		let destroyed = <DestroyedCollectionCount<T>>::get();745		CollectionStats {746			created: created.0,747			destroyed: destroyed.0,748			alive: created.0 - destroyed.0,749		}750	}751752	/// Get the effective limits for the collection.753	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {754		let collection = <CollectionById<T>>::get(collection)?;755		let limits = collection.limits;756		let effective_limits = CollectionLimits {757			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),758			sponsored_data_size: Some(limits.sponsored_data_size()),759			sponsored_data_rate_limit: Some(760				limits761					.sponsored_data_rate_limit762					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),763			),764			token_limit: Some(limits.token_limit()),765			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(766				match collection.mode {767					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,768					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,769					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,770				},771			)),772			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),773			owner_can_transfer: Some(limits.owner_can_transfer()),774			owner_can_destroy: Some(limits.owner_can_destroy()),775			transfers_enabled: Some(limits.transfers_enabled()),776		};777778		Some(effective_limits)779	}780781	/// Returns information about the `collection` adapted for rpc.782	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {783		let Collection {784			name,785			description,786			owner,787			mode,788			token_prefix,789			sponsorship,790			limits,791			permissions,792			flags,793		} = <CollectionById<T>>::get(collection)?;794795		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)796			.into_iter()797			.map(|(key, permission)| PropertyKeyPermission { key, permission })798			.collect();799800		let properties = <CollectionProperties<T>>::get(collection)801			.into_iter()802			.map(|(key, value)| Property { key, value })803			.collect();804805		let permissions = CollectionPermissions {806			access: Some(permissions.access()),807			mint_mode: Some(permissions.mint_mode()),808			nesting: Some(permissions.nesting().clone()),809		};810811		Some(RpcCollection {812			name: name.into_inner(),813			description: description.into_inner(),814			owner,815			mode,816			token_prefix: token_prefix.into_inner(),817			sponsorship,818			limits,819			permissions,820			token_property_permissions,821			properties,822			read_only: flags.external,823824			flags: RpcCollectionFlags {825				foreign: flags.foreign,826				erc721metadata: flags.erc721metadata,827			},828		})829	}830}831832macro_rules! limit_default {833	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{834		$(835			if let Some($new) = $new.$field {836				let $old = $old.$field($($arg)?);837				let _ = $new;838				let _ = $old;839				$check840			} else {841				$new.$field = $old.$field842			}843		)*844	}};845}846macro_rules! limit_default_clone {847	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{848		$(849			if let Some($new) = $new.$field.clone() {850				let $old = $old.$field($($arg)?);851				let _ = $new;852				let _ = $old;853				$check854			} else {855				$new.$field = $old.$field.clone()856			}857		)*858	}};859}860861impl<T: Config> Pallet<T> {862	/// Create new collection.863	///864	/// * `owner` - The owner of the collection.865	/// * `data` - Description of the created collection.866	/// * `flags` - Extra flags to store.867	pub fn init_collection(868		owner: T::CrossAccountId,869		payer: T::CrossAccountId,870		data: CreateCollectionData<T::AccountId>,871		flags: CollectionFlags,872	) -> Result<CollectionId, DispatchError> {873		{874			ensure!(875				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,876				Error::<T>::CollectionTokenPrefixLimitExceeded877			);878		}879880		let created_count = <CreatedCollectionCount<T>>::get()881			.0882			.checked_add(1)883			.ok_or(ArithmeticError::Overflow)?;884		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;885		let id = CollectionId(created_count);886887		// bound Total number of collections888		ensure!(889			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,890			<Error<T>>::TotalCollectionsLimitExceeded891		);892893		// =========894895		let collection = Collection {896			owner: owner.as_sub().clone(),897			name: data.name,898			mode: data.mode.clone(),899			description: data.description,900			token_prefix: data.token_prefix,901			sponsorship: data902				.pending_sponsor903				.map(SponsorshipState::Unconfirmed)904				.unwrap_or_default(),905			limits: data906				.limits907				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))908				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,909			permissions: data910				.permissions911				.map(|permissions| {912					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)913				})914				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,915			flags,916		};917918		let mut collection_properties = up_data_structs::CollectionProperties::get();919		collection_properties920			.try_set_from_iter(data.properties.into_iter())921			.map_err(<Error<T>>::from)?;922923		CollectionProperties::<T>::insert(id, collection_properties);924925		let mut token_props_permissions = PropertiesPermissionMap::new();926		token_props_permissions927			.try_set_from_iter(data.token_property_permissions.into_iter())928			.map_err(<Error<T>>::from)?;929930		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);931932		// Take a (non-refundable) deposit of collection creation933		{934			let mut imbalance =935				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();936			imbalance.subsume(937				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(938					&T::TreasuryAccountId::get(),939					T::CollectionCreationPrice::get(),940				),941			);942			<T as Config>::Currency::settle(943				payer.as_sub(),944				imbalance,945				WithdrawReasons::TRANSFER,946				ExistenceRequirement::KeepAlive,947			)948			.map_err(|_| Error::<T>::NotSufficientFounds)?;949		}950951		<CreatedCollectionCount<T>>::put(created_count);952		<Pallet<T>>::deposit_event(Event::CollectionCreated(953			id,954			data.mode.id(),955			owner.as_sub().clone(),956		));957		<PalletEvm<T>>::deposit_log(958			erc::CollectionHelpersEvents::CollectionCreated {959				owner: *owner.as_eth(),960				collection_id: eth::collection_id_to_address(id),961			}962			.to_log(T::ContractAddress::get()),963		);964		<CollectionById<T>>::insert(id, collection);965		Ok(id)966	}967968	/// Destroy collection.969	///970	/// * `collection` - Collection handler.971	/// * `sender` - The owner or administrator of the collection.972	pub fn destroy_collection(973		collection: CollectionHandle<T>,974		sender: &T::CrossAccountId,975	) -> DispatchResult {976		ensure!(977			collection.limits.owner_can_destroy(),978			<Error<T>>::NoPermission,979		);980		collection.check_is_owner(sender)?;981982		let destroyed_collections = <DestroyedCollectionCount<T>>::get()983			.0984			.checked_add(1)985			.ok_or(ArithmeticError::Overflow)?;986987		// =========988989		<DestroyedCollectionCount<T>>::put(destroyed_collections);990		<CollectionById<T>>::remove(collection.id);991		<AdminAmount<T>>::remove(collection.id);992		let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);993		let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);994		<CollectionProperties<T>>::remove(collection.id);995996		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));997998		<PalletEvm<T>>::deposit_log(999			erc::CollectionHelpersEvents::CollectionDestroyed {1000				collection_id: eth::collection_id_to_address(collection.id),1001			}1002			.to_log(T::ContractAddress::get()),1003		);1004		Ok(())1005	}10061007	/// Set collection property.1008	///1009	/// * `collection` - Collection handler.1010	/// * `sender` - The owner or administrator of the collection.1011	/// * `property` - The property to set.1012	pub fn set_collection_property(1013		collection: &CollectionHandle<T>,1014		sender: &T::CrossAccountId,1015		property: Property,1016	) -> DispatchResult {1017		collection.check_is_owner_or_admin(sender)?;10181019		CollectionProperties::<T>::try_mutate(collection.id, |properties| {1020			let property = property.clone();1021			properties.try_set(property.key, property.value)1022		})1023		.map_err(<Error<T>>::from)?;10241025		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10261027		Ok(())1028	}10291030	/// Set scouped collection property.1031	///1032	/// * `collection_id` - ID of the collection for which the property is being set.1033	/// * `scope` - Property scope.1034	/// * `property` - The property to set.1035	pub fn set_scoped_collection_property(1036		collection_id: CollectionId,1037		scope: PropertyScope,1038		property: Property,1039	) -> DispatchResult {1040		CollectionProperties::<T>::try_mutate(collection_id, |properties| {1041			properties.try_scoped_set(scope, property.key, property.value)1042		})1043		.map_err(<Error<T>>::from)?;10441045		Ok(())1046	}10471048	/// Set scouped collection properties.1049	///1050	/// * `collection_id` - ID of the collection for which the properties is being set.1051	/// * `scope` - Property scope.1052	/// * `properties` - The properties to set.1053	pub fn set_scoped_collection_properties(1054		collection_id: CollectionId,1055		scope: PropertyScope,1056		properties: impl Iterator<Item = Property>,1057	) -> DispatchResult {1058		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1059			stored_properties.try_scoped_set_from_iter(scope, properties)1060		})1061		.map_err(<Error<T>>::from)?;10621063		Ok(())1064	}10651066	/// Set collection properties.1067	///1068	/// * `collection` - Collection handler.1069	/// * `sender` - The owner or administrator of the collection.1070	/// * `properties` - The properties to set.1071	#[transactional]1072	pub fn set_collection_properties(1073		collection: &CollectionHandle<T>,1074		sender: &T::CrossAccountId,1075		properties: Vec<Property>,1076	) -> DispatchResult {1077		for property in properties {1078			Self::set_collection_property(collection, sender, property)?;1079		}10801081		Ok(())1082	}10831084	/// Delete collection property.1085	///1086	/// * `collection` - Collection handler.1087	/// * `sender` - The owner or administrator of the collection.1088	/// * `property` - The property to delete.1089	pub fn delete_collection_property(1090		collection: &CollectionHandle<T>,1091		sender: &T::CrossAccountId,1092		property_key: PropertyKey,1093	) -> DispatchResult {1094		collection.check_is_owner_or_admin(sender)?;10951096		CollectionProperties::<T>::try_mutate(collection.id, |properties| {1097			properties.remove(&property_key)1098		})1099		.map_err(<Error<T>>::from)?;11001101		Self::deposit_event(Event::CollectionPropertyDeleted(1102			collection.id,1103			property_key,1104		));11051106		Ok(())1107	}11081109	/// Delete collection properties.1110	///1111	/// * `collection` - Collection handler.1112	/// * `sender` - The owner or administrator of the collection.1113	/// * `properties` - The properties to delete.1114	#[transactional]1115	pub fn delete_collection_properties(1116		collection: &CollectionHandle<T>,1117		sender: &T::CrossAccountId,1118		property_keys: Vec<PropertyKey>,1119	) -> DispatchResult {1120		for key in property_keys {1121			Self::delete_collection_property(collection, sender, key)?;1122		}11231124		Ok(())1125	}11261127	/// Set collection propetry permission without any checks.1128	///1129	/// Used for migrations.1130	///1131	/// * `collection` - Collection handler.1132	/// * `property_permissions` - Property permissions.1133	pub fn set_property_permission_unchecked(1134		collection: CollectionId,1135		property_permission: PropertyKeyPermission,1136	) -> DispatchResult {1137		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1138			permissions.try_set(property_permission.key, property_permission.permission)1139		})1140		.map_err(<Error<T>>::from)?;1141		Ok(())1142	}11431144	/// Set collection property permission.1145	///1146	/// * `collection` - Collection handler.1147	/// * `sender` - The owner or administrator of the collection.1148	/// * `property_permission` - Property permission.1149	pub fn set_property_permission(1150		collection: &CollectionHandle<T>,1151		sender: &T::CrossAccountId,1152		property_permission: PropertyKeyPermission,1153	) -> DispatchResult {1154		Self::set_scoped_property_permission(1155			collection,1156			sender,1157			PropertyScope::None,1158			property_permission,1159		)1160	}11611162	/// Set collection property permission with scope.1163	///1164	/// * `collection` - Collection handler.1165	/// * `sender` - The owner or administrator of the collection.1166	/// * `scope` - Property scope.1167	/// * `property_permission` - Property permission.1168	pub fn set_scoped_property_permission(1169		collection: &CollectionHandle<T>,1170		sender: &T::CrossAccountId,1171		scope: PropertyScope,1172		property_permission: PropertyKeyPermission,1173	) -> DispatchResult {1174		collection.check_is_owner_or_admin(sender)?;11751176		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1177		let current_permission = all_permissions.get(&property_permission.key);1178		if matches![1179			current_permission,1180			Some(PropertyPermission { mutable: false, .. })1181		] {1182			return Err(<Error<T>>::NoPermission.into());1183		}11841185		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1186			let property_permission = property_permission.clone();1187			permissions.try_scoped_set(1188				scope,1189				property_permission.key,1190				property_permission.permission,1191			)1192		})1193		.map_err(<Error<T>>::from)?;11941195		Self::deposit_event(Event::PropertyPermissionSet(1196			collection.id,1197			property_permission.key,1198		));11991200		Ok(())1201	}12021203	/// Set token property permission.1204	///1205	/// * `collection` - Collection handler.1206	/// * `sender` - The owner or administrator of the collection.1207	/// * `property_permissions` - Property permissions.1208	#[transactional]1209	pub fn set_token_property_permissions(1210		collection: &CollectionHandle<T>,1211		sender: &T::CrossAccountId,1212		property_permissions: Vec<PropertyKeyPermission>,1213	) -> DispatchResult {1214		Self::set_scoped_token_property_permissions(1215			collection,1216			sender,1217			PropertyScope::None,1218			property_permissions,1219		)1220	}12211222	/// Set token property permission with scope.1223	///1224	/// * `collection` - Collection handler.1225	/// * `sender` - The owner or administrator of the collection.1226	/// * `scope` - Property scope.1227	/// * `property_permissions` - Property permissions.1228	#[transactional]1229	pub fn set_scoped_token_property_permissions(1230		collection: &CollectionHandle<T>,1231		sender: &T::CrossAccountId,1232		scope: PropertyScope,1233		property_permissions: Vec<PropertyKeyPermission>,1234	) -> DispatchResult {1235		for prop_pemission in property_permissions {1236			Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1237		}12381239		Ok(())1240	}12411242	/// Get collection property.1243	pub fn get_collection_property(1244		collection_id: CollectionId,1245		key: &PropertyKey,1246	) -> Option<PropertyValue> {1247		Self::collection_properties(collection_id).get(key).cloned()1248	}12491250	/// Convert byte vector to property key vector.1251	pub fn bytes_keys_to_property_keys(1252		keys: Vec<Vec<u8>>,1253	) -> Result<Vec<PropertyKey>, DispatchError> {1254		keys.into_iter()1255			.map(|key| -> Result<PropertyKey, DispatchError> {1256				key.try_into()1257					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1258			})1259			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1260	}12611262	/// Get properties according to given keys.1263	pub fn filter_collection_properties(1264		collection_id: CollectionId,1265		keys: Option<Vec<PropertyKey>>,1266	) -> Result<Vec<Property>, DispatchError> {1267		let properties = Self::collection_properties(collection_id);12681269		let properties = keys1270			.map(|keys| {1271				keys.into_iter()1272					.filter_map(|key| {1273						properties.get(&key).map(|value| Property {1274							key,1275							value: value.clone(),1276						})1277					})1278					.collect()1279			})1280			.unwrap_or_else(|| {1281				properties1282					.into_iter()1283					.map(|(key, value)| Property { key, value })1284					.collect()1285			});12861287		Ok(properties)1288	}12891290	/// Get property permissions according to given keys.1291	pub fn filter_property_permissions(1292		collection_id: CollectionId,1293		keys: Option<Vec<PropertyKey>>,1294	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1295		let permissions = Self::property_permissions(collection_id);12961297		let key_permissions = keys1298			.map(|keys| {1299				keys.into_iter()1300					.filter_map(|key| {1301						permissions1302							.get(&key)1303							.map(|permission| PropertyKeyPermission {1304								key,1305								permission: permission.clone(),1306							})1307					})1308					.collect()1309			})1310			.unwrap_or_else(|| {1311				permissions1312					.into_iter()1313					.map(|(key, permission)| PropertyKeyPermission { key, permission })1314					.collect()1315			});13161317		Ok(key_permissions)1318	}13191320	/// Toggle `user` participation in the `collection`'s allow list.1321	/// #### Store read/writes1322	/// 1 writes1323	pub fn toggle_allowlist(1324		collection: &CollectionHandle<T>,1325		sender: &T::CrossAccountId,1326		user: &T::CrossAccountId,1327		allowed: bool,1328	) -> DispatchResult {1329		collection.check_is_owner_or_admin(sender)?;13301331		// =========13321333		if allowed {1334			<Allowlist<T>>::insert((collection.id, user), true);1335		} else {1336			<Allowlist<T>>::remove((collection.id, user));1337		}13381339		Ok(())1340	}13411342	/// Toggle `user` participation in the `collection`'s admin list.1343	/// #### Store read/writes1344	/// 2 writes1345	pub fn toggle_admin(1346		collection: &CollectionHandle<T>,1347		sender: &T::CrossAccountId,1348		user: &T::CrossAccountId,1349		admin: bool,1350	) -> DispatchResult {1351		collection.check_is_owner(sender)?;13521353		let was_admin = <IsAdmin<T>>::get((collection.id, user));1354		if was_admin == admin {1355			return Ok(());1356		}1357		let amount = <AdminAmount<T>>::get(collection.id);13581359		if admin {1360			let amount = amount1361				.checked_add(1)1362				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1363			ensure!(1364				amount <= Self::collection_admins_limit(),1365				<Error<T>>::CollectionAdminCountExceeded,1366			);13671368			// =========13691370			<AdminAmount<T>>::insert(collection.id, amount);1371			<IsAdmin<T>>::insert((collection.id, user), true);1372		} else {1373			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1374			<IsAdmin<T>>::remove((collection.id, user));1375		}13761377		Ok(())1378	}13791380	/// Merge set fields from `new_limit` to `old_limit`.1381	pub fn clamp_limits(1382		mode: CollectionMode,1383		old_limit: &CollectionLimits,1384		mut new_limit: CollectionLimits,1385	) -> Result<CollectionLimits, DispatchError> {1386		let limits = old_limit;1387		limit_default!(old_limit, new_limit,1388			account_token_ownership_limit => ensure!(1389				new_limit <= MAX_TOKEN_OWNERSHIP,1390				<Error<T>>::CollectionLimitBoundsExceeded,1391			),1392			sponsored_data_size => ensure!(1393				new_limit <= CUSTOM_DATA_LIMIT,1394				<Error<T>>::CollectionLimitBoundsExceeded,1395			),13961397			sponsored_data_rate_limit => {},1398			token_limit => ensure!(1399				old_limit >= new_limit && new_limit > 0,1400				<Error<T>>::CollectionTokenLimitExceeded1401			),14021403			sponsor_transfer_timeout(match mode {1404				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1405				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1406				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1407			}) => ensure!(1408				new_limit <= MAX_SPONSOR_TIMEOUT,1409				<Error<T>>::CollectionLimitBoundsExceeded,1410			),1411			sponsor_approve_timeout => {},1412			owner_can_transfer => ensure!(1413				!limits.owner_can_transfer_instaled() ||1414				old_limit || !new_limit,1415				<Error<T>>::OwnerPermissionsCantBeReverted,1416			),1417			owner_can_destroy => ensure!(1418				old_limit || !new_limit,1419				<Error<T>>::OwnerPermissionsCantBeReverted,1420			),1421			transfers_enabled => {},1422		);1423		Ok(new_limit)1424	}14251426	/// Merge set fields from `new_permission` to `old_permission`.1427	pub fn clamp_permissions(1428		_mode: CollectionMode,1429		old_permission: &CollectionPermissions,1430		mut new_permission: CollectionPermissions,1431	) -> Result<CollectionPermissions, DispatchError> {1432		limit_default_clone!(old_permission, new_permission,1433			access => {},1434			mint_mode => {},1435			nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1436		);1437		Ok(new_permission)1438	}1439}14401441/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1442#[macro_export]1443macro_rules! unsupported {1444	($runtime:path) => {1445		Err($crate::Error::<$runtime>::UnsupportedOperation.into())1446	};1447}14481449/// Return weights for various worst-case operations.1450pub trait CommonWeightInfo<CrossAccountId> {1451	/// Weight of item creation.1452	fn create_item() -> Weight;14531454	/// Weight of items creation.1455	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14561457	/// Weight of items creation.1458	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14591460	/// The weight of the burning item.1461	fn burn_item() -> Weight;14621463	/// Property setting weight.1464	///1465	/// * `amount`- The number of properties to set.1466	fn set_collection_properties(amount: u32) -> Weight;14671468	/// Collection property deletion weight.1469	///1470	/// * `amount`- The number of properties to set.1471	fn delete_collection_properties(amount: u32) -> Weight;14721473	/// Token property setting weight.1474	///1475	/// * `amount`- The number of properties to set.1476	fn set_token_properties(amount: u32) -> Weight;14771478	/// Token property deletion weight.1479	///1480	/// * `amount`- The number of properties to delete.1481	fn delete_token_properties(amount: u32) -> Weight;14821483	/// Token property permissions set weight.1484	///1485	/// * `amount`- The number of property permissions to set.1486	fn set_token_property_permissions(amount: u32) -> Weight;14871488	/// Transfer price of the token or its parts.1489	fn transfer() -> Weight;14901491	/// The price of setting the permission of the operation from another user.1492	fn approve() -> Weight;14931494	/// Transfer price from another user.1495	fn transfer_from() -> Weight;14961497	/// The price of burning a token from another user.1498	fn burn_from() -> Weight;14991500	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1501	/// whole users's balance.1502	///1503	/// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1504	fn burn_recursively_self_raw() -> Weight;15051506	/// Cost of iterating over `amount` children while burning, without counting child burning itself.1507	///1508	/// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1509	fn burn_recursively_breadth_raw(amount: u32) -> Weight;15101511	/// The price of recursive burning a token.1512	///1513	/// `max_selfs` - The maximum burning weight of the token itself.1514	/// `max_breadth` - The maximum number of nested tokens to burn.1515	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1516		Self::burn_recursively_self_raw()1517			.saturating_mul(max_selfs.max(1) as u64)1518			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1519	}15201521	/// The price of retrieving token owner1522	fn token_owner() -> Weight;1523}15241525/// Weight info extension trait for refungible pallet.1526pub trait RefungibleExtensionsWeightInfo {1527	/// Weight of token repartition.1528	fn repartition() -> Weight;1529}15301531/// Common collection operations.1532///1533/// It wraps methods in Fungible, Nonfungible and Refungible pallets1534/// and adds weight info.1535pub trait CommonCollectionOperations<T: Config> {1536	/// Create token.1537	///1538	/// * `sender` - The user who mint the token and pays for the transaction.1539	/// * `to` - The user who will own the token.1540	/// * `data` - Token data.1541	/// * `nesting_budget` - A budget that can be spent on nesting tokens.1542	fn create_item(1543		&self,1544		sender: T::CrossAccountId,1545		to: T::CrossAccountId,1546		data: CreateItemData,1547		nesting_budget: &dyn Budget,1548	) -> DispatchResultWithPostInfo;15491550	/// Create multiple tokens.1551	///1552	/// * `sender` - The user who mint the token and pays for the transaction.1553	/// * `to` - The user who will own the token.1554	/// * `data` - Token data.1555	/// * `nesting_budget` - A budget that can be spent on nesting tokens.1556	fn create_multiple_items(1557		&self,1558		sender: T::CrossAccountId,1559		to: T::CrossAccountId,1560		data: Vec<CreateItemData>,1561		nesting_budget: &dyn Budget,1562	) -> DispatchResultWithPostInfo;15631564	/// Create multiple tokens.1565	///1566	/// * `sender` - The user who mint the token and pays for the transaction.1567	/// * `to` - The user who will own the token.1568	/// * `data` - Token data.1569	/// * `nesting_budget` - A budget that can be spent on nesting tokens.1570	fn create_multiple_items_ex(1571		&self,1572		sender: T::CrossAccountId,1573		data: CreateItemExData<T::CrossAccountId>,1574		nesting_budget: &dyn Budget,1575	) -> DispatchResultWithPostInfo;15761577	/// Burn token.1578	///1579	/// * `sender` - The user who owns the token.1580	/// * `token` - Token id that will burned.1581	/// * `amount` - The number of parts of the token that will be burned.1582	fn burn_item(1583		&self,1584		sender: T::CrossAccountId,1585		token: TokenId,1586		amount: u128,1587	) -> DispatchResultWithPostInfo;15881589	/// Burn token and all nested tokens recursievly.1590	///1591	/// * `sender` - The user who owns the token.1592	/// * `token` - Token id that will burned.1593	/// * `self_budget` - The budget that can be spent on burning tokens.1594	/// * `breadth_budget` - The budget that can be spent on burning nested tokens.1595	fn burn_item_recursively(1596		&self,1597		sender: T::CrossAccountId,1598		token: TokenId,1599		self_budget: &dyn Budget,1600		breadth_budget: &dyn Budget,1601	) -> DispatchResultWithPostInfo;16021603	/// Set collection properties.1604	///1605	/// * `sender` - Must be either the owner of the collection or its admin.1606	/// * `properties` - Properties to be set.1607	fn set_collection_properties(1608		&self,1609		sender: T::CrossAccountId,1610		properties: Vec<Property>,1611	) -> DispatchResultWithPostInfo;16121613	/// Delete collection properties.1614	///1615	/// * `sender` - Must be either the owner of the collection or its admin.1616	/// * `properties` - The properties to be removed.1617	fn delete_collection_properties(1618		&self,1619		sender: &T::CrossAccountId,1620		property_keys: Vec<PropertyKey>,1621	) -> DispatchResultWithPostInfo;16221623	/// Set token properties.1624	///1625	/// The appropriate [`PropertyPermission`] for the token property1626	/// must be set with [`Self::set_token_property_permissions`].1627	///1628	/// * `sender` - Must be either the owner of the token or its admin.1629	/// * `token_id` - The token for which the properties are being set.1630	/// * `properties` - Properties to be set.1631	/// * `budget` - Budget for setting properties.1632	fn set_token_properties(1633		&self,1634		sender: T::CrossAccountId,1635		token_id: TokenId,1636		properties: Vec<Property>,1637		budget: &dyn Budget,1638	) -> DispatchResultWithPostInfo;16391640	/// Remove token properties.1641	///1642	/// The appropriate [`PropertyPermission`] for the token property1643	/// must be set with [`Self::set_token_property_permissions`].1644	///1645	/// * `sender` - Must be either the owner of the token or its admin.1646	/// * `token_id` - The token for which the properties are being remove.1647	/// * `property_keys` - Keys to remove corresponding properties.1648	/// * `budget` - Budget for removing properties.1649	fn delete_token_properties(1650		&self,1651		sender: T::CrossAccountId,1652		token_id: TokenId,1653		property_keys: Vec<PropertyKey>,1654		budget: &dyn Budget,1655	) -> DispatchResultWithPostInfo;16561657	/// Set token property permissions.1658	///1659	/// * `sender` - Must be either the owner of the token or its admin.1660	/// * `token_id` - The token for which the properties are being set.1661	/// * `property_permissions` - Property permissions to be set.1662	/// * `budget` - Budget for setting properties.1663	fn set_token_property_permissions(1664		&self,1665		sender: &T::CrossAccountId,1666		property_permissions: Vec<PropertyKeyPermission>,1667	) -> DispatchResultWithPostInfo;16681669	/// Transfer amount of token pieces.1670	///1671	/// * `sender` - Donor user.1672	/// * `to` - Recepient user.1673	/// * `token` - The token of which parts are being sent.1674	/// * `amount` - The number of parts of the token that will be transferred.1675	/// * `budget` - The maximum budget that can be spent on the transfer.1676	fn transfer(1677		&self,1678		sender: T::CrossAccountId,1679		to: T::CrossAccountId,1680		token: TokenId,1681		amount: u128,1682		budget: &dyn Budget,1683	) -> DispatchResultWithPostInfo;16841685	/// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].1686	///1687	/// * `sender` - The user who grants access to the token.1688	/// * `spender` - The user to whom the rights are granted.1689	/// * `token` - The token to which access is granted.1690	/// * `amount` - The amount of pieces that another user can dispose of.1691	fn approve(1692		&self,1693		sender: T::CrossAccountId,1694		spender: T::CrossAccountId,1695		token: TokenId,1696		amount: u128,1697	) -> DispatchResultWithPostInfo;16981699	/// Send parts of a token owned by another user.1700	///1701	/// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1702	///1703	/// * `sender` - The user who must have access to the token (see [`Self::approve`]).1704	/// * `from` - The user who owns the token.1705	/// * `to` - Recepient user.1706	/// * `token` - The token of which parts are being sent.1707	/// * `amount` - The number of parts of the token that will be transferred.1708	/// * `budget` - The maximum budget that can be spent on the transfer.1709	fn transfer_from(1710		&self,1711		sender: T::CrossAccountId,1712		from: T::CrossAccountId,1713		to: T::CrossAccountId,1714		token: TokenId,1715		amount: u128,1716		budget: &dyn Budget,1717	) -> DispatchResultWithPostInfo;17181719	/// Burn parts of a token owned by another user.1720	///1721	/// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1722	///1723	/// * `sender` - The user who must have access to the token (see [`Self::approve`]).1724	/// * `from` - The user who owns the token.1725	/// * `token` - The token of which parts are being sent.1726	/// * `amount` - The number of parts of the token that will be transferred.1727	/// * `budget` - The maximum budget that can be spent on the burn.1728	fn burn_from(1729		&self,1730		sender: T::CrossAccountId,1731		from: T::CrossAccountId,1732		token: TokenId,1733		amount: u128,1734		budget: &dyn Budget,1735	) -> DispatchResultWithPostInfo;17361737	/// Check permission to nest token.1738	///1739	/// * `sender` - The user who initiated the check.1740	/// * `from` - The token that is checked for embedding.1741	/// * `under` - Token under which to check.1742	/// * `budget` - The maximum budget that can be spent on the check.1743	fn check_nesting(1744		&self,1745		sender: T::CrossAccountId,1746		from: (CollectionId, TokenId),1747		under: TokenId,1748		budget: &dyn Budget,1749	) -> DispatchResult;17501751	/// Nest one token into another.1752	///1753	/// * `under` - Token holder.1754	/// * `to_nest` - Nested token.1755	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17561757	/// Unnest token.1758	///1759	/// * `under` - Token holder.1760	/// * `to_nest` - Token to unnest.1761	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17621763	/// Get all user tokens.1764	///1765	/// * `account` - Account for which you need to get tokens.1766	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17671768	/// Get all the tokens in the collection.1769	fn collection_tokens(&self) -> Vec<TokenId>;17701771	/// Check if the token exists.1772	///1773	/// * `token` - Id token to check.1774	fn token_exists(&self, token: TokenId) -> bool;17751776	/// Get the id of the last minted token.1777	fn last_token_id(&self) -> TokenId;17781779	/// Get the owner of the token.1780	///1781	/// * `token` - The token for which you need to find out the owner.1782	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17831784	/// Returns 10 tokens owners in no particular order.1785	///1786	/// * `token` - The token for which you need to find out the owners.1787	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17881789	/// Get the value of the token property by key.1790	///1791	/// * `token` - Token with the property to get.1792	/// * `key` - Property name.1793	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17941795	/// Get a set of token properties by key vector.1796	///1797	/// * `token` - Token with the property to get.1798	/// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),1799	/// then all properties are returned.1800	fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18011802	/// Amount of unique collection tokens1803	fn total_supply(&self) -> u32;18041805	/// Amount of different tokens account has.1806	///1807	/// * `account` - The account for which need to get the balance.1808	fn account_balance(&self, account: T::CrossAccountId) -> u32;18091810	/// Amount of specific token account have.1811	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18121813	/// Amount of token pieces1814	fn total_pieces(&self, token: TokenId) -> Option<u128>;18151816	/// Get the number of parts of the token that a trusted user can manage.1817	///1818	/// * `sender` - Trusted user.1819	/// * `spender` - Owner of the token.1820	/// * `token` - The token for which to get the value.1821	fn allowance(1822		&self,1823		sender: T::CrossAccountId,1824		spender: T::CrossAccountId,1825		token: TokenId,1826	) -> u128;18271828	/// Get extension for RFT collection.1829	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1830}18311832/// Extension for RFT collection.1833pub trait RefungibleExtensions<T>1834where1835	T: Config,1836{1837	/// Change the number of parts of the token.1838	///1839	/// When the value changes down, this function is equivalent to burning parts of the token.1840	///1841	/// * `sender` - The user calling the repartition operation. Must be the owner of the token.1842	/// * `token` - The token for which you want to change the number of parts.1843	/// * `amount` - The new value of the parts of the token.1844	fn repartition(1845		&self,1846		sender: &T::CrossAccountId,1847		token: TokenId,1848		amount: u128,1849	) -> DispatchResultWithPostInfo;1850}18511852/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].1853///1854/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.1855pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1856	let post_info = PostDispatchInfo {1857		actual_weight: Some(weight),1858		pays_fee: Pays::Yes,1859	};1860	match res {1861		Ok(()) => Ok(post_info),1862		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1863	}1864}18651866impl<T: Config> From<PropertiesError> for Error<T> {1867	fn from(error: PropertiesError) -> Self {1868		match error {1869			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1870			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1871			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1872			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1873			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1874		}1875	}1876}
after · pallets/common/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//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63	ensure,64	traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65	dispatch::Pays,66	transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70	COLLECTION_NUMBER_LIMIT,71	Collection,72	RpcCollection,73	CollectionFlags,74	RpcCollectionFlags,75	CollectionId,76	CreateItemData,77	MAX_TOKEN_PREFIX_LENGTH,78	COLLECTION_ADMINS_LIMIT,79	TokenId,80	TokenChild,81	CollectionStats,82	MAX_TOKEN_OWNERSHIP,83	CollectionMode,84	NFT_SPONSOR_TRANSFER_TIMEOUT,85	FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86	REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87	MAX_SPONSOR_TIMEOUT,88	CUSTOM_DATA_LIMIT,89	CollectionLimits,90	CreateCollectionData,91	SponsorshipState,92	CreateItemExData,93	SponsoringRateLimit,94	budget::Budget,95	PhantomType,96	Property,97	Properties,98	PropertiesPermissionMap,99	PropertyKey,100	PropertyValue,101	PropertyPermission,102	PropertiesError,103	PropertyKeyPermission,104	TokenData,105	TrySetProperty,106	PropertyScope,107	// RMRK108	RmrkCollectionInfo,109	RmrkInstanceInfo,110	RmrkResourceInfo,111	RmrkPropertyInfo,112	RmrkBaseInfo,113	RmrkPartType,114	RmrkBoundedTheme,115	RmrkNftChild,116	CollectionPermissions,117};118119pub use pallet::*;120use sp_core::H160;121use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};122#[cfg(feature = "runtime-benchmarks")]123pub mod benchmarking;124pub mod dispatch;125pub mod erc;126pub mod eth;127pub mod weights;128129/// Weight info.130pub type SelfWeightOf<T> = <T as Config>::WeightInfo;131132/// Collection handle contains information about collection data and id.133/// Also provides functionality to count consumed gas.134///135/// CollectionHandle is used as a generic wrapper for collections of all types.136/// It allows to perform common operations and queries on any collection type,137/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].138#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]139pub struct CollectionHandle<T: Config> {140	/// Collection id141	pub id: CollectionId,142	collection: Collection<T::AccountId>,143	/// Substrate recorder for counting consumed gas144	pub recorder: SubstrateRecorder<T>,145}146147impl<T: Config> WithRecorder<T> for CollectionHandle<T> {148	fn recorder(&self) -> &SubstrateRecorder<T> {149		&self.recorder150	}151	fn into_recorder(self) -> SubstrateRecorder<T> {152		self.recorder153	}154}155156impl<T: Config> CollectionHandle<T> {157	/// Same as [CollectionHandle::new] but with an explicit gas limit.158	pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {159		<CollectionById<T>>::get(id).map(|collection| Self {160			id,161			collection,162			recorder: SubstrateRecorder::new(gas_limit),163		})164	}165166	/// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].167	pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {168		<CollectionById<T>>::get(id).map(|collection| Self {169			id,170			collection,171			recorder,172		})173	}174175	/// Retrives collection data from storage and creates collection handle with default parameters.176	/// If collection not found return `None`177	pub fn new(id: CollectionId) -> Option<Self> {178		Self::new_with_gas_limit(id, u64::MAX)179	}180181	/// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.182	pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {183		Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)184	}185186	/// Consume gas for reading.187	pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {188		self.recorder189			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(190				<T as frame_system::Config>::DbWeight::get()191					.read192					.saturating_mul(reads),193			)))194	}195196	/// Consume gas for writing.197	pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {198		self.recorder199			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(200				<T as frame_system::Config>::DbWeight::get()201					.write202					.saturating_mul(writes),203			)))204	}205206	/// Consume gas for reading and writing.207	pub fn consume_store_reads_and_writes(208		&self,209		reads: u64,210		writes: u64,211	) -> evm_coder::execution::Result<()> {212		let weight = <T as frame_system::Config>::DbWeight::get();213		let reads = weight.read.saturating_mul(reads);214		let writes = weight.read.saturating_mul(writes);215		self.recorder216			.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(217				reads.saturating_add(writes),218			)))219	}220221	/// Save collection to storage.222	pub fn save(&self) -> DispatchResult {223		<CollectionById<T>>::insert(self.id, &self.collection);224		Ok(())225	}226227	/// Set collection sponsor.228	///229	/// Unique collections allows sponsoring for certain actions.230	/// This method allows you to set the sponsor of the collection.231	/// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].232	pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {233		self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);234		Ok(())235	}236237	/// Confirm sponsorship238	///239	/// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.240	/// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].241	pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {242		if self.collection.sponsorship.pending_sponsor() != Some(sender) {243			return Ok(false);244		}245246		self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());247		Ok(true)248	}249250	/// Remove collection sponsor.251	pub fn remove_sponsor(&mut self) -> DispatchResult {252		self.collection.sponsorship = SponsorshipState::Disabled;253		Ok(())254	}255256	/// Checks that the collection was created with, and must be operated upon through **Unique API**.257	/// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.258	pub fn check_is_internal(&self) -> DispatchResult {259		if self.flags.external {260			return Err(<Error<T>>::CollectionIsExternal)?;261		}262263		Ok(())264	}265266	/// Checks that the collection was created with, and must be operated upon through an **assimilated API**.267	/// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.268	pub fn check_is_external(&self) -> DispatchResult {269		if !self.flags.external {270			return Err(<Error<T>>::CollectionIsInternal)?;271		}272273		Ok(())274	}275}276277impl<T: Config> Deref for CollectionHandle<T> {278	type Target = Collection<T::AccountId>;279280	fn deref(&self) -> &Self::Target {281		&self.collection282	}283}284285impl<T: Config> DerefMut for CollectionHandle<T> {286	fn deref_mut(&mut self) -> &mut Self::Target {287		&mut self.collection288	}289}290291impl<T: Config> CollectionHandle<T> {292	/// Checks if the `user` is the owner of the collection.293	pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {294		ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);295		Ok(())296	}297298	/// Returns **true** if the `user` is the owner or administrator of the collection.299	pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {300		*user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))301	}302303	/// Checks if the `user` is the owner or administrator of the collection.304	pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {305		ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);306		Ok(())307	}308309	/// Return **true** if `user` was not allowed to have tokens, and he can ignore such restrictions.310	pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {311		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)312	}313314	/// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.315	pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {316		self.limits.owner_can_transfer() && self.is_owner_or_admin(user)317	}318319	/// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.320	pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {321		ensure!(322			<Allowlist<T>>::get((self.id, user)),323			<Error<T>>::AddressNotInAllowlist324		);325		Ok(())326	}327328	/// Changes collection owner to another account329	/// #### Store read/writes330	/// 1 writes331	fn set_owner_internal(332		&mut self,333		caller: T::CrossAccountId,334		new_owner: T::CrossAccountId,335	) -> DispatchResult {336		self.check_is_owner(&caller)?;337		self.collection.owner = new_owner.as_sub().clone();338		self.save()339	}340}341342#[frame_support::pallet]343pub mod pallet {344	use super::*;345	use dispatch::CollectionDispatch;346	use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};347	use frame_system::pallet_prelude::*;348	use frame_support::traits::Currency;349	use up_data_structs::{TokenId, mapping::TokenAddressMapping};350	use scale_info::TypeInfo;351	use weights::WeightInfo;352353	#[pallet::config]354	pub trait Config:355		frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo356	{357		/// Weight information for functions of this pallet.358		type WeightInfo: WeightInfo;359360		/// Events compatible with [`frame_system::Config::Event`].361		type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;362363		/// Handler of accounts and payment.364		type Currency: Currency<Self::AccountId>;365366		/// Set price to create a collection.367		#[pallet::constant]368		type CollectionCreationPrice: Get<369			<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,370		>;371372		/// Dispatcher of operations on collections.373		type CollectionDispatch: CollectionDispatch<Self>;374375		/// Account which holds the chain's treasury.376		type TreasuryAccountId: Get<Self::AccountId>;377378		/// Address under which the CollectionHelper contract would be available.379		#[pallet::constant]380		type ContractAddress: Get<H160>;381382		/// Mapper for token addresses to Ethereum addresses.383		type EvmTokenAddressMapping: TokenAddressMapping<H160>;384385		/// Mapper for token addresses to [`CrossAccountId`].386		type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;387	}388389	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);390391	#[pallet::pallet]392	#[pallet::storage_version(STORAGE_VERSION)]393	#[pallet::generate_store(pub(super) trait Store)]394	pub struct Pallet<T>(_);395396	#[pallet::extra_constants]397	impl<T: Config> Pallet<T> {398		/// Maximum admins per collection.399		pub fn collection_admins_limit() -> u32 {400			COLLECTION_ADMINS_LIMIT401		}402	}403404	#[pallet::event]405	#[pallet::generate_deposit(pub fn deposit_event)]406	pub enum Event<T: Config> {407		/// New collection was created408		CollectionCreated(409			/// Globally unique identifier of newly created collection.410			CollectionId,411			/// [`CollectionMode`] converted into _u8_.412			u8,413			/// Collection owner.414			T::AccountId,415		),416417		/// New collection was destroyed418		CollectionDestroyed(419			/// Globally unique identifier of collection.420			CollectionId,421		),422423		/// New item was created.424		ItemCreated(425			/// Id of the collection where item was created.426			CollectionId,427			/// Id of an item. Unique within the collection.428			TokenId,429			/// Owner of newly created item430			T::CrossAccountId,431			/// Always 1 for NFT432			u128,433		),434435		/// Collection item was burned.436		ItemDestroyed(437			/// Id of the collection where item was destroyed.438			CollectionId,439			/// Identifier of burned NFT.440			TokenId,441			/// Which user has destroyed its tokens.442			T::CrossAccountId,443			/// Amount of token pieces destroed. Always 1 for NFT.444			u128,445		),446447		/// Item was transferred448		Transfer(449			/// Id of collection to which item is belong.450			CollectionId,451			/// Id of an item.452			TokenId,453			/// Original owner of item.454			T::CrossAccountId,455			/// New owner of item.456			T::CrossAccountId,457			/// Amount of token pieces transfered. Always 1 for NFT.458			u128,459		),460461		/// Amount pieces of token owned by `sender` was approved for `spender`.462		Approved(463			/// Id of collection to which item is belong.464			CollectionId,465			/// Id of an item.466			TokenId,467			/// Original owner of item.468			T::CrossAccountId,469			/// Id for which the approval was granted.470			T::CrossAccountId,471			/// Amount of token pieces transfered. Always 1 for NFT.472			u128,473		),474475		/// The colletion property has been added or edited.476		CollectionPropertySet(477			/// Id of collection to which property has been set.478			CollectionId,479			/// The property that was set.480			PropertyKey,481		),482483		/// The property has been deleted.484		CollectionPropertyDeleted(485			/// Id of collection to which property has been deleted.486			CollectionId,487			/// The property that was deleted.488			PropertyKey,489		),490491		/// The token property has been added or edited.492		TokenPropertySet(493			/// Identifier of the collection whose token has the property set.494			CollectionId,495			/// The token for which the property was set.496			TokenId,497			/// The property that was set.498			PropertyKey,499		),500501		/// The token property has been deleted.502		TokenPropertyDeleted(503			/// Identifier of the collection whose token has the property deleted.504			CollectionId,505			/// The token for which the property was deleted.506			TokenId,507			/// The property that was deleted.508			PropertyKey,509		),510511		/// The token property permission of a collection has been set.512		PropertyPermissionSet(513			/// ID of collection to which property permission has been set.514			CollectionId,515			/// The property permission that was set.516			PropertyKey,517		),518	}519520	#[pallet::error]521	pub enum Error<T> {522		/// This collection does not exist.523		CollectionNotFound,524		/// Sender parameter and item owner must be equal.525		MustBeTokenOwner,526		/// No permission to perform action527		NoPermission,528		/// Destroying only empty collections is allowed529		CantDestroyNotEmptyCollection,530		/// Collection is not in mint mode.531		PublicMintingNotAllowed,532		/// Address is not in allow list.533		AddressNotInAllowlist,534535		/// Collection name can not be longer than 63 char.536		CollectionNameLimitExceeded,537		/// Collection description can not be longer than 255 char.538		CollectionDescriptionLimitExceeded,539		/// Token prefix can not be longer than 15 char.540		CollectionTokenPrefixLimitExceeded,541		/// Total collections bound exceeded.542		TotalCollectionsLimitExceeded,543		/// Exceeded max admin count544		CollectionAdminCountExceeded,545		/// Collection limit bounds per collection exceeded546		CollectionLimitBoundsExceeded,547		/// Tried to enable permissions which are only permitted to be disabled548		OwnerPermissionsCantBeReverted,549		/// Collection settings not allowing items transferring550		TransferNotAllowed,551		/// Account token limit exceeded per collection552		AccountTokenLimitExceeded,553		/// Collection token limit exceeded554		CollectionTokenLimitExceeded,555		/// Metadata flag frozen556		MetadataFlagFrozen,557558		/// Item does not exist559		TokenNotFound,560		/// Item is balance not enough561		TokenValueTooLow,562		/// Requested value is more than the approved563		ApprovedValueTooLow,564		/// Tried to approve more than owned565		CantApproveMoreThanOwned,566567		/// Can't transfer tokens to ethereum zero address568		AddressIsZero,569570		/// The operation is not supported571		UnsupportedOperation,572573		/// Insufficient funds to perform an action574		NotSufficientFounds,575576		/// User does not satisfy the nesting rule577		UserIsNotAllowedToNest,578		/// Only tokens from specific collections may nest tokens under this one579		SourceCollectionIsNotAllowedToNest,580581		/// Tried to store more data than allowed in collection field582		CollectionFieldSizeExceeded,583584		/// Tried to store more property data than allowed585		NoSpaceForProperty,586587		/// Tried to store more property keys than allowed588		PropertyLimitReached,589590		/// Property key is too long591		PropertyKeyIsTooLong,592593		/// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed594		InvalidCharacterInPropertyKey,595596		/// Empty property keys are forbidden597		EmptyPropertyKey,598599		/// Tried to access an external collection with an internal API600		CollectionIsExternal,601602		/// Tried to access an internal collection with an external API603		CollectionIsInternal,604	}605606	/// Storage of the count of created collections. Essentially contains the last collection ID.607	#[pallet::storage]608	pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;609610	/// Storage of the count of deleted collections.611	#[pallet::storage]612	pub type DestroyedCollectionCount<T> =613		StorageValue<Value = CollectionId, QueryKind = ValueQuery>;614615	/// Storage of collection info.616	#[pallet::storage]617	pub type CollectionById<T> = StorageMap<618		Hasher = Blake2_128Concat,619		Key = CollectionId,620		Value = Collection<<T as frame_system::Config>::AccountId>,621		QueryKind = OptionQuery,622	>;623624	/// Storage of collection properties.625	#[pallet::storage]626	#[pallet::getter(fn collection_properties)]627	pub type CollectionProperties<T> = StorageMap<628		Hasher = Blake2_128Concat,629		Key = CollectionId,630		Value = Properties,631		QueryKind = ValueQuery,632		OnEmpty = up_data_structs::CollectionProperties,633	>;634635	/// Storage of token property permissions of a collection.636	#[pallet::storage]637	#[pallet::getter(fn property_permissions)]638	pub type CollectionPropertyPermissions<T> = StorageMap<639		Hasher = Blake2_128Concat,640		Key = CollectionId,641		Value = PropertiesPermissionMap,642		QueryKind = ValueQuery,643	>;644645	/// Storage of the amount of collection admins.646	#[pallet::storage]647	pub type AdminAmount<T> = StorageMap<648		Hasher = Blake2_128Concat,649		Key = CollectionId,650		Value = u32,651		QueryKind = ValueQuery,652	>;653654	/// List of collection admins.655	#[pallet::storage]656	pub type IsAdmin<T: Config> = StorageNMap<657		Key = (658			Key<Blake2_128Concat, CollectionId>,659			Key<Blake2_128Concat, T::CrossAccountId>,660		),661		Value = bool,662		QueryKind = ValueQuery,663	>;664665	/// Allowlisted collection users.666	#[pallet::storage]667	pub type Allowlist<T: Config> = StorageNMap<668		Key = (669			Key<Blake2_128Concat, CollectionId>,670			Key<Blake2_128Concat, T::CrossAccountId>,671		),672		Value = bool,673		QueryKind = ValueQuery,674	>;675676	/// Not used by code, exists only to provide some types to metadata.677	#[pallet::storage]678	pub type DummyStorageValue<T: Config> = StorageValue<679		Value = (680			CollectionStats,681			CollectionId,682			TokenId,683			TokenChild,684			PhantomType<(685				TokenData<T::CrossAccountId>,686				RpcCollection<T::AccountId>,687				// RMRK688				RmrkCollectionInfo<T::AccountId>,689				RmrkInstanceInfo<T::AccountId>,690				RmrkResourceInfo,691				RmrkPropertyInfo,692				RmrkBaseInfo<T::AccountId>,693				RmrkPartType,694				RmrkBoundedTheme,695				RmrkNftChild,696			)>,697		),698		QueryKind = OptionQuery,699	>;700701	#[pallet::hooks]702	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {703		fn on_runtime_upgrade() -> Weight {704			StorageVersion::new(1).put::<Pallet<T>>();705706			Weight::zero()707		}708	}709}710711impl<T: Config> Pallet<T> {712	/// Enshure that receiver address is correct.713	///714	/// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.715	pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {716		ensure!(717			&T::CrossAccountId::from_eth(H160([0; 20])) != receiver,718			<Error<T>>::AddressIsZero719		);720		Ok(())721	}722723	/// Get a vector of collection admins.724	pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {725		<IsAdmin<T>>::iter_prefix((collection,))726			.map(|(a, _)| a)727			.collect()728	}729730	/// Get a vector of users allowed to mint tokens.731	pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {732		<Allowlist<T>>::iter_prefix((collection,))733			.map(|(a, _)| a)734			.collect()735	}736737	/// Is `user` allowed to mint token in `collection`.738	pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {739		<Allowlist<T>>::get((collection, user))740	}741742	/// Get statistics of collections.743	pub fn collection_stats() -> CollectionStats {744		let created = <CreatedCollectionCount<T>>::get();745		let destroyed = <DestroyedCollectionCount<T>>::get();746		CollectionStats {747			created: created.0,748			destroyed: destroyed.0,749			alive: created.0 - destroyed.0,750		}751	}752753	/// Get the effective limits for the collection.754	pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {755		let collection = <CollectionById<T>>::get(collection)?;756		let limits = collection.limits;757		let effective_limits = CollectionLimits {758			account_token_ownership_limit: Some(limits.account_token_ownership_limit()),759			sponsored_data_size: Some(limits.sponsored_data_size()),760			sponsored_data_rate_limit: Some(761				limits762					.sponsored_data_rate_limit763					.unwrap_or(SponsoringRateLimit::SponsoringDisabled),764			),765			token_limit: Some(limits.token_limit()),766			sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(767				match collection.mode {768					CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,769					CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,770					CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,771				},772			)),773			sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),774			owner_can_transfer: Some(limits.owner_can_transfer()),775			owner_can_destroy: Some(limits.owner_can_destroy()),776			transfers_enabled: Some(limits.transfers_enabled()),777		};778779		Some(effective_limits)780	}781782	/// Returns information about the `collection` adapted for rpc.783	pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {784		let Collection {785			name,786			description,787			owner,788			mode,789			token_prefix,790			sponsorship,791			limits,792			permissions,793			flags,794		} = <CollectionById<T>>::get(collection)?;795796		let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)797			.into_iter()798			.map(|(key, permission)| PropertyKeyPermission { key, permission })799			.collect();800801		let properties = <CollectionProperties<T>>::get(collection)802			.into_iter()803			.map(|(key, value)| Property { key, value })804			.collect();805806		let permissions = CollectionPermissions {807			access: Some(permissions.access()),808			mint_mode: Some(permissions.mint_mode()),809			nesting: Some(permissions.nesting().clone()),810		};811812		Some(RpcCollection {813			name: name.into_inner(),814			description: description.into_inner(),815			owner,816			mode,817			token_prefix: token_prefix.into_inner(),818			sponsorship,819			limits,820			permissions,821			token_property_permissions,822			properties,823			read_only: flags.external,824825			flags: RpcCollectionFlags {826				foreign: flags.foreign,827				erc721metadata: flags.erc721metadata,828			},829		})830	}831}832833macro_rules! limit_default {834	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{835		$(836			if let Some($new) = $new.$field {837				let $old = $old.$field($($arg)?);838				let _ = $new;839				let _ = $old;840				$check841			} else {842				$new.$field = $old.$field843			}844		)*845	}};846}847macro_rules! limit_default_clone {848	($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{849		$(850			if let Some($new) = $new.$field.clone() {851				let $old = $old.$field($($arg)?);852				let _ = $new;853				let _ = $old;854				$check855			} else {856				$new.$field = $old.$field.clone()857			}858		)*859	}};860}861862impl<T: Config> Pallet<T> {863	/// Create new collection.864	///865	/// * `owner` - The owner of the collection.866	/// * `data` - Description of the created collection.867	/// * `flags` - Extra flags to store.868	pub fn init_collection(869		owner: T::CrossAccountId,870		payer: T::CrossAccountId,871		data: CreateCollectionData<T::AccountId>,872		flags: CollectionFlags,873	) -> Result<CollectionId, DispatchError> {874		{875			ensure!(876				data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,877				Error::<T>::CollectionTokenPrefixLimitExceeded878			);879		}880881		let created_count = <CreatedCollectionCount<T>>::get()882			.0883			.checked_add(1)884			.ok_or(ArithmeticError::Overflow)?;885		let destroyed_count = <DestroyedCollectionCount<T>>::get().0;886		let id = CollectionId(created_count);887888		// bound Total number of collections889		ensure!(890			created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,891			<Error<T>>::TotalCollectionsLimitExceeded892		);893894		// =========895896		let collection = Collection {897			owner: owner.as_sub().clone(),898			name: data.name,899			mode: data.mode.clone(),900			description: data.description,901			token_prefix: data.token_prefix,902			sponsorship: data903				.pending_sponsor904				.map(SponsorshipState::Unconfirmed)905				.unwrap_or_default(),906			limits: data907				.limits908				.map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))909				.unwrap_or_else(|| Ok(CollectionLimits::default()))?,910			permissions: data911				.permissions912				.map(|permissions| {913					Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)914				})915				.unwrap_or_else(|| Ok(CollectionPermissions::default()))?,916			flags,917		};918919		let mut collection_properties = up_data_structs::CollectionProperties::get();920		collection_properties921			.try_set_from_iter(data.properties.into_iter())922			.map_err(<Error<T>>::from)?;923924		CollectionProperties::<T>::insert(id, collection_properties);925926		let mut token_props_permissions = PropertiesPermissionMap::new();927		token_props_permissions928			.try_set_from_iter(data.token_property_permissions.into_iter())929			.map_err(<Error<T>>::from)?;930931		CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);932933		// Take a (non-refundable) deposit of collection creation934		{935			let mut imbalance =936				<<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();937			imbalance.subsume(938				<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(939					&T::TreasuryAccountId::get(),940					T::CollectionCreationPrice::get(),941				),942			);943			<T as Config>::Currency::settle(944				payer.as_sub(),945				imbalance,946				WithdrawReasons::TRANSFER,947				ExistenceRequirement::KeepAlive,948			)949			.map_err(|_| Error::<T>::NotSufficientFounds)?;950		}951952		<CreatedCollectionCount<T>>::put(created_count);953		<Pallet<T>>::deposit_event(Event::CollectionCreated(954			id,955			data.mode.id(),956			owner.as_sub().clone(),957		));958		<PalletEvm<T>>::deposit_log(959			erc::CollectionHelpersEvents::CollectionCreated {960				owner: *owner.as_eth(),961				collection_id: eth::collection_id_to_address(id),962			}963			.to_log(T::ContractAddress::get()),964		);965		<CollectionById<T>>::insert(id, collection);966		Ok(id)967	}968969	/// Destroy collection.970	///971	/// * `collection` - Collection handler.972	/// * `sender` - The owner or administrator of the collection.973	pub fn destroy_collection(974		collection: CollectionHandle<T>,975		sender: &T::CrossAccountId,976	) -> DispatchResult {977		ensure!(978			collection.limits.owner_can_destroy(),979			<Error<T>>::NoPermission,980		);981		collection.check_is_owner(sender)?;982983		let destroyed_collections = <DestroyedCollectionCount<T>>::get()984			.0985			.checked_add(1)986			.ok_or(ArithmeticError::Overflow)?;987988		// =========989990		<DestroyedCollectionCount<T>>::put(destroyed_collections);991		<CollectionById<T>>::remove(collection.id);992		<AdminAmount<T>>::remove(collection.id);993		let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);994		let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);995		<CollectionProperties<T>>::remove(collection.id);996997		<Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));998999		<PalletEvm<T>>::deposit_log(1000			erc::CollectionHelpersEvents::CollectionDestroyed {1001				collection_id: eth::collection_id_to_address(collection.id),1002			}1003			.to_log(T::ContractAddress::get()),1004		);1005		Ok(())1006	}10071008	/// Set collection property.1009	///1010	/// * `collection` - Collection handler.1011	/// * `sender` - The owner or administrator of the collection.1012	/// * `property` - The property to set.1013	pub fn set_collection_property(1014		collection: &CollectionHandle<T>,1015		sender: &T::CrossAccountId,1016		property: Property,1017	) -> DispatchResult {1018		collection.check_is_owner_or_admin(sender)?;10191020		CollectionProperties::<T>::try_mutate(collection.id, |properties| {1021			let property = property.clone();1022			properties.try_set(property.key, property.value)1023		})1024		.map_err(<Error<T>>::from)?;10251026		Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10271028		Ok(())1029	}10301031	/// Set scouped collection property.1032	///1033	/// * `collection_id` - ID of the collection for which the property is being set.1034	/// * `scope` - Property scope.1035	/// * `property` - The property to set.1036	pub fn set_scoped_collection_property(1037		collection_id: CollectionId,1038		scope: PropertyScope,1039		property: Property,1040	) -> DispatchResult {1041		CollectionProperties::<T>::try_mutate(collection_id, |properties| {1042			properties.try_scoped_set(scope, property.key, property.value)1043		})1044		.map_err(<Error<T>>::from)?;10451046		Ok(())1047	}10481049	/// Set scouped collection properties.1050	///1051	/// * `collection_id` - ID of the collection for which the properties is being set.1052	/// * `scope` - Property scope.1053	/// * `properties` - The properties to set.1054	pub fn set_scoped_collection_properties(1055		collection_id: CollectionId,1056		scope: PropertyScope,1057		properties: impl Iterator<Item = Property>,1058	) -> DispatchResult {1059		CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1060			stored_properties.try_scoped_set_from_iter(scope, properties)1061		})1062		.map_err(<Error<T>>::from)?;10631064		Ok(())1065	}10661067	/// Set collection properties.1068	///1069	/// * `collection` - Collection handler.1070	/// * `sender` - The owner or administrator of the collection.1071	/// * `properties` - The properties to set.1072	#[transactional]1073	pub fn set_collection_properties(1074		collection: &CollectionHandle<T>,1075		sender: &T::CrossAccountId,1076		properties: Vec<Property>,1077	) -> DispatchResult {1078		for property in properties {1079			Self::set_collection_property(collection, sender, property)?;1080		}10811082		Ok(())1083	}10841085	/// Delete collection property.1086	///1087	/// * `collection` - Collection handler.1088	/// * `sender` - The owner or administrator of the collection.1089	/// * `property` - The property to delete.1090	pub fn delete_collection_property(1091		collection: &CollectionHandle<T>,1092		sender: &T::CrossAccountId,1093		property_key: PropertyKey,1094	) -> DispatchResult {1095		collection.check_is_owner_or_admin(sender)?;10961097		CollectionProperties::<T>::try_mutate(collection.id, |properties| {1098			properties.remove(&property_key)1099		})1100		.map_err(<Error<T>>::from)?;11011102		Self::deposit_event(Event::CollectionPropertyDeleted(1103			collection.id,1104			property_key,1105		));11061107		Ok(())1108	}11091110	/// Delete collection properties.1111	///1112	/// * `collection` - Collection handler.1113	/// * `sender` - The owner or administrator of the collection.1114	/// * `properties` - The properties to delete.1115	#[transactional]1116	pub fn delete_collection_properties(1117		collection: &CollectionHandle<T>,1118		sender: &T::CrossAccountId,1119		property_keys: Vec<PropertyKey>,1120	) -> DispatchResult {1121		for key in property_keys {1122			Self::delete_collection_property(collection, sender, key)?;1123		}11241125		Ok(())1126	}11271128	/// Set collection propetry permission without any checks.1129	///1130	/// Used for migrations.1131	///1132	/// * `collection` - Collection handler.1133	/// * `property_permissions` - Property permissions.1134	pub fn set_property_permission_unchecked(1135		collection: CollectionId,1136		property_permission: PropertyKeyPermission,1137	) -> DispatchResult {1138		<CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1139			permissions.try_set(property_permission.key, property_permission.permission)1140		})1141		.map_err(<Error<T>>::from)?;1142		Ok(())1143	}11441145	/// Set collection property permission.1146	///1147	/// * `collection` - Collection handler.1148	/// * `sender` - The owner or administrator of the collection.1149	/// * `property_permission` - Property permission.1150	pub fn set_property_permission(1151		collection: &CollectionHandle<T>,1152		sender: &T::CrossAccountId,1153		property_permission: PropertyKeyPermission,1154	) -> DispatchResult {1155		Self::set_scoped_property_permission(1156			collection,1157			sender,1158			PropertyScope::None,1159			property_permission,1160		)1161	}11621163	/// Set collection property permission with scope.1164	///1165	/// * `collection` - Collection handler.1166	/// * `sender` - The owner or administrator of the collection.1167	/// * `scope` - Property scope.1168	/// * `property_permission` - Property permission.1169	pub fn set_scoped_property_permission(1170		collection: &CollectionHandle<T>,1171		sender: &T::CrossAccountId,1172		scope: PropertyScope,1173		property_permission: PropertyKeyPermission,1174	) -> DispatchResult {1175		collection.check_is_owner_or_admin(sender)?;11761177		let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1178		let current_permission = all_permissions.get(&property_permission.key);1179		if matches![1180			current_permission,1181			Some(PropertyPermission { mutable: false, .. })1182		] {1183			return Err(<Error<T>>::NoPermission.into());1184		}11851186		CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1187			let property_permission = property_permission.clone();1188			permissions.try_scoped_set(1189				scope,1190				property_permission.key,1191				property_permission.permission,1192			)1193		})1194		.map_err(<Error<T>>::from)?;11951196		Self::deposit_event(Event::PropertyPermissionSet(1197			collection.id,1198			property_permission.key,1199		));12001201		Ok(())1202	}12031204	/// Set token property permission.1205	///1206	/// * `collection` - Collection handler.1207	/// * `sender` - The owner or administrator of the collection.1208	/// * `property_permissions` - Property permissions.1209	#[transactional]1210	pub fn set_token_property_permissions(1211		collection: &CollectionHandle<T>,1212		sender: &T::CrossAccountId,1213		property_permissions: Vec<PropertyKeyPermission>,1214	) -> DispatchResult {1215		Self::set_scoped_token_property_permissions(1216			collection,1217			sender,1218			PropertyScope::None,1219			property_permissions,1220		)1221	}12221223	/// Set token property permission with scope.1224	///1225	/// * `collection` - Collection handler.1226	/// * `sender` - The owner or administrator of the collection.1227	/// * `scope` - Property scope.1228	/// * `property_permissions` - Property permissions.1229	#[transactional]1230	pub fn set_scoped_token_property_permissions(1231		collection: &CollectionHandle<T>,1232		sender: &T::CrossAccountId,1233		scope: PropertyScope,1234		property_permissions: Vec<PropertyKeyPermission>,1235	) -> DispatchResult {1236		for prop_pemission in property_permissions {1237			Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1238		}12391240		Ok(())1241	}12421243	/// Get collection property.1244	pub fn get_collection_property(1245		collection_id: CollectionId,1246		key: &PropertyKey,1247	) -> Option<PropertyValue> {1248		Self::collection_properties(collection_id).get(key).cloned()1249	}12501251	/// Convert byte vector to property key vector.1252	pub fn bytes_keys_to_property_keys(1253		keys: Vec<Vec<u8>>,1254	) -> Result<Vec<PropertyKey>, DispatchError> {1255		keys.into_iter()1256			.map(|key| -> Result<PropertyKey, DispatchError> {1257				key.try_into()1258					.map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1259			})1260			.collect::<Result<Vec<PropertyKey>, DispatchError>>()1261	}12621263	/// Get properties according to given keys.1264	pub fn filter_collection_properties(1265		collection_id: CollectionId,1266		keys: Option<Vec<PropertyKey>>,1267	) -> Result<Vec<Property>, DispatchError> {1268		let properties = Self::collection_properties(collection_id);12691270		let properties = keys1271			.map(|keys| {1272				keys.into_iter()1273					.filter_map(|key| {1274						properties.get(&key).map(|value| Property {1275							key,1276							value: value.clone(),1277						})1278					})1279					.collect()1280			})1281			.unwrap_or_else(|| {1282				properties1283					.into_iter()1284					.map(|(key, value)| Property { key, value })1285					.collect()1286			});12871288		Ok(properties)1289	}12901291	/// Get property permissions according to given keys.1292	pub fn filter_property_permissions(1293		collection_id: CollectionId,1294		keys: Option<Vec<PropertyKey>>,1295	) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1296		let permissions = Self::property_permissions(collection_id);12971298		let key_permissions = keys1299			.map(|keys| {1300				keys.into_iter()1301					.filter_map(|key| {1302						permissions1303							.get(&key)1304							.map(|permission| PropertyKeyPermission {1305								key,1306								permission: permission.clone(),1307							})1308					})1309					.collect()1310			})1311			.unwrap_or_else(|| {1312				permissions1313					.into_iter()1314					.map(|(key, permission)| PropertyKeyPermission { key, permission })1315					.collect()1316			});13171318		Ok(key_permissions)1319	}13201321	/// Toggle `user` participation in the `collection`'s allow list.1322	/// #### Store read/writes1323	/// 1 writes1324	pub fn toggle_allowlist(1325		collection: &CollectionHandle<T>,1326		sender: &T::CrossAccountId,1327		user: &T::CrossAccountId,1328		allowed: bool,1329	) -> DispatchResult {1330		collection.check_is_owner_or_admin(sender)?;13311332		// =========13331334		if allowed {1335			<Allowlist<T>>::insert((collection.id, user), true);1336		} else {1337			<Allowlist<T>>::remove((collection.id, user));1338		}13391340		Ok(())1341	}13421343	/// Toggle `user` participation in the `collection`'s admin list.1344	/// #### Store read/writes1345	/// 2 writes1346	pub fn toggle_admin(1347		collection: &CollectionHandle<T>,1348		sender: &T::CrossAccountId,1349		user: &T::CrossAccountId,1350		admin: bool,1351	) -> DispatchResult {1352		collection.check_is_owner(sender)?;13531354		let was_admin = <IsAdmin<T>>::get((collection.id, user));1355		if was_admin == admin {1356			return Ok(());1357		}1358		let amount = <AdminAmount<T>>::get(collection.id);13591360		if admin {1361			let amount = amount1362				.checked_add(1)1363				.ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1364			ensure!(1365				amount <= Self::collection_admins_limit(),1366				<Error<T>>::CollectionAdminCountExceeded,1367			);13681369			// =========13701371			<AdminAmount<T>>::insert(collection.id, amount);1372			<IsAdmin<T>>::insert((collection.id, user), true);1373		} else {1374			<AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1375			<IsAdmin<T>>::remove((collection.id, user));1376		}13771378		Ok(())1379	}13801381	/// Merge set fields from `new_limit` to `old_limit`.1382	pub fn clamp_limits(1383		mode: CollectionMode,1384		old_limit: &CollectionLimits,1385		mut new_limit: CollectionLimits,1386	) -> Result<CollectionLimits, DispatchError> {1387		let limits = old_limit;1388		limit_default!(old_limit, new_limit,1389			account_token_ownership_limit => ensure!(1390				new_limit <= MAX_TOKEN_OWNERSHIP,1391				<Error<T>>::CollectionLimitBoundsExceeded,1392			),1393			sponsored_data_size => ensure!(1394				new_limit <= CUSTOM_DATA_LIMIT,1395				<Error<T>>::CollectionLimitBoundsExceeded,1396			),13971398			sponsored_data_rate_limit => {},1399			token_limit => ensure!(1400				old_limit >= new_limit && new_limit > 0,1401				<Error<T>>::CollectionTokenLimitExceeded1402			),14031404			sponsor_transfer_timeout(match mode {1405				CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1406				CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1407				CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1408			}) => ensure!(1409				new_limit <= MAX_SPONSOR_TIMEOUT,1410				<Error<T>>::CollectionLimitBoundsExceeded,1411			),1412			sponsor_approve_timeout => {},1413			owner_can_transfer => ensure!(1414				!limits.owner_can_transfer_instaled() ||1415				old_limit || !new_limit,1416				<Error<T>>::OwnerPermissionsCantBeReverted,1417			),1418			owner_can_destroy => ensure!(1419				old_limit || !new_limit,1420				<Error<T>>::OwnerPermissionsCantBeReverted,1421			),1422			transfers_enabled => {},1423		);1424		Ok(new_limit)1425	}14261427	/// Merge set fields from `new_permission` to `old_permission`.1428	pub fn clamp_permissions(1429		_mode: CollectionMode,1430		old_permission: &CollectionPermissions,1431		mut new_permission: CollectionPermissions,1432	) -> Result<CollectionPermissions, DispatchError> {1433		limit_default_clone!(old_permission, new_permission,1434			access => {},1435			mint_mode => {},1436			nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1437		);1438		Ok(new_permission)1439	}1440}14411442/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1443#[macro_export]1444macro_rules! unsupported {1445	($runtime:path) => {1446		Err($crate::Error::<$runtime>::UnsupportedOperation.into())1447	};1448}14491450/// Return weights for various worst-case operations.1451pub trait CommonWeightInfo<CrossAccountId> {1452	/// Weight of item creation.1453	fn create_item() -> Weight;14541455	/// Weight of items creation.1456	fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14571458	/// Weight of items creation.1459	fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14601461	/// The weight of the burning item.1462	fn burn_item() -> Weight;14631464	/// Property setting weight.1465	///1466	/// * `amount`- The number of properties to set.1467	fn set_collection_properties(amount: u32) -> Weight;14681469	/// Collection property deletion weight.1470	///1471	/// * `amount`- The number of properties to set.1472	fn delete_collection_properties(amount: u32) -> Weight;14731474	/// Token property setting weight.1475	///1476	/// * `amount`- The number of properties to set.1477	fn set_token_properties(amount: u32) -> Weight;14781479	/// Token property deletion weight.1480	///1481	/// * `amount`- The number of properties to delete.1482	fn delete_token_properties(amount: u32) -> Weight;14831484	/// Token property permissions set weight.1485	///1486	/// * `amount`- The number of property permissions to set.1487	fn set_token_property_permissions(amount: u32) -> Weight;14881489	/// Transfer price of the token or its parts.1490	fn transfer() -> Weight;14911492	/// The price of setting the permission of the operation from another user.1493	fn approve() -> Weight;14941495	/// Transfer price from another user.1496	fn transfer_from() -> Weight;14971498	/// The price of burning a token from another user.1499	fn burn_from() -> Weight;15001501	/// Differs from burn_item in case of Fungible and Refungible, as it should burn1502	/// whole users's balance.1503	///1504	/// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1505	fn burn_recursively_self_raw() -> Weight;15061507	/// Cost of iterating over `amount` children while burning, without counting child burning itself.1508	///1509	/// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1510	fn burn_recursively_breadth_raw(amount: u32) -> Weight;15111512	/// The price of recursive burning a token.1513	///1514	/// `max_selfs` - The maximum burning weight of the token itself.1515	/// `max_breadth` - The maximum number of nested tokens to burn.1516	fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1517		Self::burn_recursively_self_raw()1518			.saturating_mul(max_selfs.max(1) as u64)1519			.saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1520	}15211522	/// The price of retrieving token owner1523	fn token_owner() -> Weight;1524}15251526/// Weight info extension trait for refungible pallet.1527pub trait RefungibleExtensionsWeightInfo {1528	/// Weight of token repartition.1529	fn repartition() -> Weight;1530}15311532/// Common collection operations.1533///1534/// It wraps methods in Fungible, Nonfungible and Refungible pallets1535/// and adds weight info.1536pub trait CommonCollectionOperations<T: Config> {1537	/// Create token.1538	///1539	/// * `sender` - The user who mint the token and pays for the transaction.1540	/// * `to` - The user who will own the token.1541	/// * `data` - Token data.1542	/// * `nesting_budget` - A budget that can be spent on nesting tokens.1543	fn create_item(1544		&self,1545		sender: T::CrossAccountId,1546		to: T::CrossAccountId,1547		data: CreateItemData,1548		nesting_budget: &dyn Budget,1549	) -> DispatchResultWithPostInfo;15501551	/// Create multiple tokens.1552	///1553	/// * `sender` - The user who mint the token and pays for the transaction.1554	/// * `to` - The user who will own the token.1555	/// * `data` - Token data.1556	/// * `nesting_budget` - A budget that can be spent on nesting tokens.1557	fn create_multiple_items(1558		&self,1559		sender: T::CrossAccountId,1560		to: T::CrossAccountId,1561		data: Vec<CreateItemData>,1562		nesting_budget: &dyn Budget,1563	) -> DispatchResultWithPostInfo;15641565	/// Create multiple tokens.1566	///1567	/// * `sender` - The user who mint the token and pays for the transaction.1568	/// * `to` - The user who will own the token.1569	/// * `data` - Token data.1570	/// * `nesting_budget` - A budget that can be spent on nesting tokens.1571	fn create_multiple_items_ex(1572		&self,1573		sender: T::CrossAccountId,1574		data: CreateItemExData<T::CrossAccountId>,1575		nesting_budget: &dyn Budget,1576	) -> DispatchResultWithPostInfo;15771578	/// Burn token.1579	///1580	/// * `sender` - The user who owns the token.1581	/// * `token` - Token id that will burned.1582	/// * `amount` - The number of parts of the token that will be burned.1583	fn burn_item(1584		&self,1585		sender: T::CrossAccountId,1586		token: TokenId,1587		amount: u128,1588	) -> DispatchResultWithPostInfo;15891590	/// Burn token and all nested tokens recursievly.1591	///1592	/// * `sender` - The user who owns the token.1593	/// * `token` - Token id that will burned.1594	/// * `self_budget` - The budget that can be spent on burning tokens.1595	/// * `breadth_budget` - The budget that can be spent on burning nested tokens.1596	fn burn_item_recursively(1597		&self,1598		sender: T::CrossAccountId,1599		token: TokenId,1600		self_budget: &dyn Budget,1601		breadth_budget: &dyn Budget,1602	) -> DispatchResultWithPostInfo;16031604	/// Set collection properties.1605	///1606	/// * `sender` - Must be either the owner of the collection or its admin.1607	/// * `properties` - Properties to be set.1608	fn set_collection_properties(1609		&self,1610		sender: T::CrossAccountId,1611		properties: Vec<Property>,1612	) -> DispatchResultWithPostInfo;16131614	/// Delete collection properties.1615	///1616	/// * `sender` - Must be either the owner of the collection or its admin.1617	/// * `properties` - The properties to be removed.1618	fn delete_collection_properties(1619		&self,1620		sender: &T::CrossAccountId,1621		property_keys: Vec<PropertyKey>,1622	) -> DispatchResultWithPostInfo;16231624	/// Set token properties.1625	///1626	/// The appropriate [`PropertyPermission`] for the token property1627	/// must be set with [`Self::set_token_property_permissions`].1628	///1629	/// * `sender` - Must be either the owner of the token or its admin.1630	/// * `token_id` - The token for which the properties are being set.1631	/// * `properties` - Properties to be set.1632	/// * `budget` - Budget for setting properties.1633	fn set_token_properties(1634		&self,1635		sender: T::CrossAccountId,1636		token_id: TokenId,1637		properties: Vec<Property>,1638		budget: &dyn Budget,1639	) -> DispatchResultWithPostInfo;16401641	/// Remove token properties.1642	///1643	/// The appropriate [`PropertyPermission`] for the token property1644	/// must be set with [`Self::set_token_property_permissions`].1645	///1646	/// * `sender` - Must be either the owner of the token or its admin.1647	/// * `token_id` - The token for which the properties are being remove.1648	/// * `property_keys` - Keys to remove corresponding properties.1649	/// * `budget` - Budget for removing properties.1650	fn delete_token_properties(1651		&self,1652		sender: T::CrossAccountId,1653		token_id: TokenId,1654		property_keys: Vec<PropertyKey>,1655		budget: &dyn Budget,1656	) -> DispatchResultWithPostInfo;16571658	/// Set token property permissions.1659	///1660	/// * `sender` - Must be either the owner of the token or its admin.1661	/// * `token_id` - The token for which the properties are being set.1662	/// * `property_permissions` - Property permissions to be set.1663	/// * `budget` - Budget for setting properties.1664	fn set_token_property_permissions(1665		&self,1666		sender: &T::CrossAccountId,1667		property_permissions: Vec<PropertyKeyPermission>,1668	) -> DispatchResultWithPostInfo;16691670	/// Transfer amount of token pieces.1671	///1672	/// * `sender` - Donor user.1673	/// * `to` - Recepient user.1674	/// * `token` - The token of which parts are being sent.1675	/// * `amount` - The number of parts of the token that will be transferred.1676	/// * `budget` - The maximum budget that can be spent on the transfer.1677	fn transfer(1678		&self,1679		sender: T::CrossAccountId,1680		to: T::CrossAccountId,1681		token: TokenId,1682		amount: u128,1683		budget: &dyn Budget,1684	) -> DispatchResultWithPostInfo;16851686	/// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].1687	///1688	/// * `sender` - The user who grants access to the token.1689	/// * `spender` - The user to whom the rights are granted.1690	/// * `token` - The token to which access is granted.1691	/// * `amount` - The amount of pieces that another user can dispose of.1692	fn approve(1693		&self,1694		sender: T::CrossAccountId,1695		spender: T::CrossAccountId,1696		token: TokenId,1697		amount: u128,1698	) -> DispatchResultWithPostInfo;16991700	/// Send parts of a token owned by another user.1701	///1702	/// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1703	///1704	/// * `sender` - The user who must have access to the token (see [`Self::approve`]).1705	/// * `from` - The user who owns the token.1706	/// * `to` - Recepient user.1707	/// * `token` - The token of which parts are being sent.1708	/// * `amount` - The number of parts of the token that will be transferred.1709	/// * `budget` - The maximum budget that can be spent on the transfer.1710	fn transfer_from(1711		&self,1712		sender: T::CrossAccountId,1713		from: T::CrossAccountId,1714		to: T::CrossAccountId,1715		token: TokenId,1716		amount: u128,1717		budget: &dyn Budget,1718	) -> DispatchResultWithPostInfo;17191720	/// Burn parts of a token owned by another user.1721	///1722	/// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1723	///1724	/// * `sender` - The user who must have access to the token (see [`Self::approve`]).1725	/// * `from` - The user who owns the token.1726	/// * `token` - The token of which parts are being sent.1727	/// * `amount` - The number of parts of the token that will be transferred.1728	/// * `budget` - The maximum budget that can be spent on the burn.1729	fn burn_from(1730		&self,1731		sender: T::CrossAccountId,1732		from: T::CrossAccountId,1733		token: TokenId,1734		amount: u128,1735		budget: &dyn Budget,1736	) -> DispatchResultWithPostInfo;17371738	/// Check permission to nest token.1739	///1740	/// * `sender` - The user who initiated the check.1741	/// * `from` - The token that is checked for embedding.1742	/// * `under` - Token under which to check.1743	/// * `budget` - The maximum budget that can be spent on the check.1744	fn check_nesting(1745		&self,1746		sender: T::CrossAccountId,1747		from: (CollectionId, TokenId),1748		under: TokenId,1749		budget: &dyn Budget,1750	) -> DispatchResult;17511752	/// Nest one token into another.1753	///1754	/// * `under` - Token holder.1755	/// * `to_nest` - Nested token.1756	fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17571758	/// Unnest token.1759	///1760	/// * `under` - Token holder.1761	/// * `to_nest` - Token to unnest.1762	fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17631764	/// Get all user tokens.1765	///1766	/// * `account` - Account for which you need to get tokens.1767	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17681769	/// Get all the tokens in the collection.1770	fn collection_tokens(&self) -> Vec<TokenId>;17711772	/// Check if the token exists.1773	///1774	/// * `token` - Id token to check.1775	fn token_exists(&self, token: TokenId) -> bool;17761777	/// Get the id of the last minted token.1778	fn last_token_id(&self) -> TokenId;17791780	/// Get the owner of the token.1781	///1782	/// * `token` - The token for which you need to find out the owner.1783	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17841785	/// Returns 10 tokens owners in no particular order.1786	///1787	/// * `token` - The token for which you need to find out the owners.1788	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17891790	/// Get the value of the token property by key.1791	///1792	/// * `token` - Token with the property to get.1793	/// * `key` - Property name.1794	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17951796	/// Get a set of token properties by key vector.1797	///1798	/// * `token` - Token with the property to get.1799	/// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),1800	/// then all properties are returned.1801	fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18021803	/// Amount of unique collection tokens1804	fn total_supply(&self) -> u32;18051806	/// Amount of different tokens account has.1807	///1808	/// * `account` - The account for which need to get the balance.1809	fn account_balance(&self, account: T::CrossAccountId) -> u32;18101811	/// Amount of specific token account have.1812	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18131814	/// Amount of token pieces1815	fn total_pieces(&self, token: TokenId) -> Option<u128>;18161817	/// Get the number of parts of the token that a trusted user can manage.1818	///1819	/// * `sender` - Trusted user.1820	/// * `spender` - Owner of the token.1821	/// * `token` - The token for which to get the value.1822	fn allowance(1823		&self,1824		sender: T::CrossAccountId,1825		spender: T::CrossAccountId,1826		token: TokenId,1827	) -> u128;18281829	/// Get extension for RFT collection.1830	fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1831}18321833/// Extension for RFT collection.1834pub trait RefungibleExtensions<T>1835where1836	T: Config,1837{1838	/// Change the number of parts of the token.1839	///1840	/// When the value changes down, this function is equivalent to burning parts of the token.1841	///1842	/// * `sender` - The user calling the repartition operation. Must be the owner of the token.1843	/// * `token` - The token for which you want to change the number of parts.1844	/// * `amount` - The new value of the parts of the token.1845	fn repartition(1846		&self,1847		sender: &T::CrossAccountId,1848		token: TokenId,1849		amount: u128,1850	) -> DispatchResultWithPostInfo;1851}18521853/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].1854///1855/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.1856pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1857	let post_info = PostDispatchInfo {1858		actual_weight: Some(weight),1859		pays_fee: Pays::Yes,1860	};1861	match res {1862		Ok(()) => Ok(post_info),1863		Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1864	}1865}18661867impl<T: Config> From<PropertiesError> for Error<T> {1868	fn from(error: PropertiesError) -> Self {1869		match error {1870			PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1871			PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1872			PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1873			PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1874			PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1875		}1876	}1877}
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
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -86,6 +86,8 @@
 use sp_std::{vec, vec::Vec};
 use up_data_structs::{
 	MAX_COLLECTION_NAME_LENGTH, MAX_COLLECTION_DESCRIPTION_LENGTH, MAX_TOKEN_PREFIX_LENGTH,
+	MAX_PROPERTIES_PER_ITEM, MAX_PROPERTY_KEY_LENGTH, MAX_PROPERTY_VALUE_LENGTH,
+	MAX_COLLECTION_PROPERTIES_SIZE, COLLECTION_ADMINS_LIMIT, MAX_TOKEN_PROPERTIES_SIZE,
 	CreateItemData, CollectionLimits, CollectionPermissions, CollectionId, CollectionMode, TokenId,
 	SponsorshipState, CreateCollectionData, CreateItemExData, budget, Property, PropertyKey,
 	PropertyKeyPermission,
@@ -102,7 +104,7 @@
 pub mod weights;
 use weights::WeightInfo;
 
-/// 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;
 
 decl_error! {
@@ -277,6 +279,46 @@
 	{
 		type Error = Error<T>;
 
+		#[doc = "A maximum number of levels of depth in the token nesting tree."]
+		const NESTING_BUDGET: u32 = NESTING_BUDGET;
+
+		#[doc = "Maximal length of a collection name."]
+		const MAX_COLLECTION_NAME_LENGTH: u32 = MAX_COLLECTION_NAME_LENGTH;
+
+		#[doc = "Maximal length of a collection description."]
+		const MAX_COLLECTION_DESCRIPTION_LENGTH: u32 = MAX_COLLECTION_DESCRIPTION_LENGTH;
+
+		#[doc = "Maximal length of a token prefix."]
+		const MAX_TOKEN_PREFIX_LENGTH: u32 = MAX_TOKEN_PREFIX_LENGTH;
+
+		#[doc = "Maximum admins per collection."]
+		const COLLECTION_ADMINS_LIMIT: u32 = COLLECTION_ADMINS_LIMIT;
+
+		#[doc = "Maximal length of a property key."]
+		const MAX_PROPERTY_KEY_LENGTH: u32 = MAX_PROPERTY_KEY_LENGTH;
+
+		#[doc = "Maximal length of a property value."]
+		const MAX_PROPERTY_VALUE_LENGTH: u32 = MAX_PROPERTY_VALUE_LENGTH;
+
+		#[doc = "A maximum number of token properties."]
+		const MAX_PROPERTIES_PER_ITEM: u32 = MAX_PROPERTIES_PER_ITEM;
+
+		#[doc = "Maximum size for all collection properties."]
+		const MAX_COLLECTION_PROPERTIES_SIZE: u32 = MAX_COLLECTION_PROPERTIES_SIZE;
+
+		#[doc = "Maximum size of all token properties."]
+		const MAX_TOKEN_PROPERTIES_SIZE: u32 = MAX_TOKEN_PROPERTIES_SIZE;
+
+		#[doc = "Default NFT collection limit."]
+		const NFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::NFT);
+
+		#[doc = "Default RFT collection limit."]
+		const RFT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::ReFungible);
+
+		#[doc = "Default FT collection limit."]
+		const FT_DEFAULT_COLLECTION_LIMITS: CollectionLimits = CollectionLimits::with_default_limits(CollectionMode::Fungible(0));
+
+
 		pub fn deposit_event() = default;
 
 		fn on_initialize(_now: T::BlockNumber) -> Weight {
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`.