git.delta.rocks / unique-network / refs/commits / d6d1ff906912

difftreelog

Merge branch 'develop' into feature/CORE-37

Yaroslav Bolyukin2021-09-01parents: #1e2758b #4d7077f.patch.diff
in: master

21 files changed

modifiedcrates/evm-coder-macros/src/solidity_interface.rsdiffbeforeafterboth
--- a/crates/evm-coder-macros/src/solidity_interface.rs
+++ b/crates/evm-coder-macros/src/solidity_interface.rs
@@ -1,16 +1,16 @@
 #![allow(dead_code)]
 
 use quote::quote;
-use darling::FromMeta;
+use darling::{FromMeta, ToTokens};
 use inflector::cases;
 use std::fmt::Write;
 use syn::{
-	FnArg, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Meta, NestedMeta, PatType, Path,
-	ReturnType, Type, spanned::Spanned,
+	Expr, FnArg, GenericArgument, Generics, Ident, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta,
+	NestedMeta, PatType, Path, PathArguments, ReturnType, Type, spanned::Spanned,
 };
 
 use crate::{
-	fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_ident_from_type,
+	fn_selector_str, parse_ident_from_pat, parse_ident_from_path, parse_path, parse_path_segment,
 	parse_result_ok, pascal_ident_to_call, pascal_ident_to_snake_call, snake_ident_to_pascal,
 	snake_ident_to_screaming,
 };
@@ -77,14 +77,14 @@
 	fn expand_generator(&self) -> proc_macro2::TokenStream {
 		let pascal_call_name = &self.pascal_call_name;
 		quote! {
-			#pascal_call_name::generate_solidity_interface(out_set, is_impl);
+			#pascal_call_name::generate_solidity_interface(tc, is_impl);
 		}
 	}
 
 	fn expand_event_generator(&self) -> proc_macro2::TokenStream {
 		let name = &self.name;
 		quote! {
-			#name::generate_solidity_interface(out_set, is_impl);
+			#name::generate_solidity_interface(tc, is_impl);
 		}
 	}
 }
@@ -121,10 +121,161 @@
 	rename_selector: Option<String>,
 }
 
+enum AbiType {
+	// type
+	Plain(Ident),
+	// (type1,type2)
+	Tuple(Vec<AbiType>),
+	// type[]
+	Vec(Box<AbiType>),
+	// type[20]
+	Array(Box<AbiType>, usize),
+}
+impl AbiType {
+	fn try_from(value: &Type) -> syn::Result<Self> {
+		let value = Self::try_maybe_special_from(value)?;
+		if value.is_special() {
+			return Err(syn::Error::new(value.span(), "unexpected special type"));
+		}
+		Ok(value)
+	}
+	fn try_maybe_special_from(value: &Type) -> syn::Result<Self> {
+		match value {
+			Type::Array(arr) => {
+				let wrapped = AbiType::try_from(&arr.elem)?;
+				match &arr.len {
+					Expr::Lit(l) => match &l.lit {
+						Lit::Int(i) => {
+							let num = i.base10_parse::<usize>()?;
+							Ok(AbiType::Array(Box::new(wrapped), num as usize))
+						}
+						_ => Err(syn::Error::new(arr.len.span(), "should be int literal")),
+					},
+					_ => Err(syn::Error::new(arr.len.span(), "should be literal")),
+				}
+			}
+			Type::Path(_) => {
+				let path = parse_path(value)?;
+				let segment = parse_path_segment(path)?;
+				if segment.ident == "Vec" {
+					let args = match &segment.arguments {
+						PathArguments::AngleBracketed(e) => e,
+						_ => {
+							return Err(syn::Error::new(
+								segment.arguments.span(),
+								"missing Vec generic",
+							))
+						}
+					};
+					let args = &args.args;
+					if args.len() != 1 {
+						return Err(syn::Error::new(
+							args.span(),
+							"expected only one generic for vec",
+						));
+					}
+					let arg = args.first().unwrap();
+
+					let ty = match arg {
+						GenericArgument::Type(ty) => ty,
+						_ => {
+							return Err(syn::Error::new(
+								arg.span(),
+								"expected first generic to be type",
+							))
+						}
+					};
+
+					let wrapped = AbiType::try_from(ty)?;
+					Ok(Self::Vec(Box::new(wrapped)))
+				} else {
+					if !segment.arguments.is_empty() {
+						return Err(syn::Error::new(
+							segment.arguments.span(),
+							"unexpected generic arguments for non-vec type",
+						));
+					}
+					Ok(Self::Plain(segment.ident.clone()))
+				}
+			}
+			Type::Tuple(t) => {
+				let mut out = Vec::with_capacity(t.elems.len());
+				for el in t.elems.iter() {
+					out.push(AbiType::try_from(el)?)
+				}
+				Ok(Self::Tuple(out))
+			}
+			_ => Err(syn::Error::new(
+				value.span(),
+				"unexpected type, only arrays, plain types and tuples are supported",
+			)),
+		}
+	}
+	fn is_value(&self) -> bool {
+		match self {
+			Self::Plain(v) if v == "value" => true,
+			_ => false,
+		}
+	}
+	fn is_caller(&self) -> bool {
+		match self {
+			Self::Plain(v) if v == "caller" => true,
+			_ => false,
+		}
+	}
+	fn is_special(&self) -> bool {
+		self.is_caller() || self.is_value()
+	}
+	fn selector_ty_buf(&self, buf: &mut String) -> std::fmt::Result {
+		match self {
+			AbiType::Plain(t) => {
+				write!(buf, "{}", t)
+			}
+			AbiType::Tuple(t) => {
+				write!(buf, "(")?;
+				for (i, t) in t.iter().enumerate() {
+					if i != 0 {
+						write!(buf, ",")?;
+					}
+					t.selector_ty_buf(buf)?;
+				}
+				write!(buf, ")")
+			}
+			AbiType::Vec(v) => {
+				v.selector_ty_buf(buf)?;
+				write!(buf, "[]")
+			}
+			AbiType::Array(v, len) => {
+				v.selector_ty_buf(buf)?;
+				write!(buf, "[{}]", len)
+			}
+		}
+	}
+	fn selector_ty(&self) -> String {
+		let mut out = String::new();
+		self.selector_ty_buf(&mut out).expect("no fmt error");
+		out
+	}
+}
+impl ToTokens for AbiType {
+	fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+		match self {
+			AbiType::Plain(t) => tokens.extend(quote! {#t}),
+			AbiType::Tuple(t) => {
+				tokens.extend(quote! {(
+					#(#t),*
+				)});
+			}
+			AbiType::Vec(v) => tokens.extend(quote! {Vec<#v>}),
+			AbiType::Array(v, l) => tokens.extend(quote! {[#v; #l]}),
+		}
+	}
+}
+
 struct MethodArg {
 	name: Ident,
 	camel_name: String,
-	ty: Ident,
+	ty: AbiType,
 }
 impl MethodArg {
 	fn try_from(value: &PatType) -> syn::Result<Self> {
@@ -132,21 +283,21 @@
 		Ok(Self {
 			camel_name: cases::camelcase::to_camel_case(&name.to_string()),
 			name,
-			ty: parse_ident_from_type(&value.ty, false)?.clone(),
+			ty: AbiType::try_maybe_special_from(&value.ty)?,
 		})
 	}
 	fn is_value(&self) -> bool {
-		self.ty == "value"
+		self.ty.is_value()
 	}
 	fn is_caller(&self) -> bool {
-		self.ty == "caller"
+		self.ty.is_caller()
 	}
 	fn is_special(&self) -> bool {
-		self.is_value() || self.is_caller()
+		self.ty.is_special()
 	}
-	fn selector_ty(&self) -> &Ident {
+	fn selector_ty(&self) -> String {
 		assert!(!self.is_special());
-		&self.ty
+		self.ty.selector_ty()
 	}
 
 	fn expand_call_def(&self) -> proc_macro2::TokenStream {
@@ -419,9 +570,11 @@
 			.iter()
 			.filter(|a| !a.is_special())
 			.map(MethodArg::expand_solidity_argument);
+		let selector = format!("{} {:0>8x}", self.selector_str, self.selector);
 
 		quote! {
 			SolidityFunction {
+				selector: #selector,
 				name: #camel_name,
 				mutability: #mutability,
 				args: (
@@ -544,7 +697,7 @@
 						)*
 					)
 				}
-				pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {
+				pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {
 					use evm_coder::solidity::*;
 					use core::fmt::Write;
 					let interface = SolidityInterface {
@@ -559,9 +712,9 @@
 						)*),
 					};
 					if is_impl {
-						out_set.insert("// Common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\n".into());
+						tc.collect("// Common stubs holder\ncontract Dummy {\n\tuint8 dummy;\n\tstring stub_error = \"this contract is implemented in native\";\n}\n".into());
 					} else {
-						out_set.insert("// Common stubs holder\ninterface Dummy {\n}\n".into());
+						tc.collect("// Common stubs holder\ninterface Dummy {\n}\n".into());
 					}
 					#(
 						#solidity_generators
@@ -576,8 +729,8 @@
 					if #solidity_name.starts_with("Inline") {
 						out.push_str("// Inline\n");
 					}
-					let _ = interface.format(is_impl, &mut out);
-					out_set.insert(out);
+					let _ = interface.format(is_impl, &mut out, tc);
+					tc.collect(out);
 				}
 			}
 			impl ::evm_coder::Call for #call_name {
modifiedcrates/evm-coder-macros/src/to_log.rsdiffbeforeafterboth
--- a/crates/evm-coder-macros/src/to_log.rs
+++ b/crates/evm-coder-macros/src/to_log.rs
@@ -179,7 +179,7 @@
 					#consts
 				)*
 
-				pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {
+				pub fn generate_solidity_interface(tc: &evm_coder::solidity::TypeCollector, is_impl: bool) {
 					use evm_coder::solidity::*;
 					use core::fmt::Write;
 					let interface = SolidityInterface {
@@ -191,8 +191,8 @@
 					};
 					let mut out = string::new();
 					out.push_str("// Inline\n");
-					let _ = interface.format(is_impl, &mut out);
-					out_set.insert(out);
+					let _ = interface.format(is_impl, &mut out, tc);
+					tc.collect(out);
 				}
 			}
 
modifiedcrates/evm-coder/src/abi.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/abi.rs
+++ b/crates/evm-coder/src/abi.rs
@@ -211,6 +211,10 @@
 		self.memory(value.as_bytes())
 	}
 
+	pub fn bytes(&mut self, value: &[u8]) {
+		self.memory(value)
+	}
+
 	pub fn finish(mut self) -> Vec<u8> {
 		for (static_offset, part) in self.dynamic_part {
 			let part_offset = self.static_part.len();
@@ -247,6 +251,59 @@
 impl_abi_readable!(bool, bool);
 impl_abi_readable!(string, string);
 
+mod sealed {
+	/// Not all types can be placed in vec, i.e `Vec<u8>` is restricted, `bytes` should be used instead
+	pub trait CanBePlacedInVec {}
+}
+
+impl sealed::CanBePlacedInVec for U256 {}
+impl sealed::CanBePlacedInVec for string {}
+impl sealed::CanBePlacedInVec for H160 {}
+
+impl<R: sealed::CanBePlacedInVec> AbiRead<Vec<R>> for AbiReader<'_>
+where
+	Self: AbiRead<R>,
+{
+	fn abi_read(&mut self) -> Result<Vec<R>> {
+		let mut sub = self.subresult()?;
+		let size = sub.read_usize()?;
+		sub.subresult_offset = sub.offset;
+		let mut out = Vec::with_capacity(size);
+		for _ in 0..size {
+			out.push(<Self as AbiRead<R>>::abi_read(&mut sub)?);
+		}
+		Ok(out)
+	}
+}
+
+macro_rules! impl_tuples {
+	($($ident:ident)+) => {
+		impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}
+		impl<$($ident),+> AbiRead<($($ident,)+)> for AbiReader<'_>
+		where
+			$(Self: AbiRead<$ident>),+
+		{
+			fn abi_read(&mut self) -> Result<($($ident,)+)> {
+				let mut subresult = self.subresult()?;
+				Ok((
+					$(<Self as AbiRead<$ident>>::abi_read(&mut subresult)?,)+
+				))
+			}
+		}
+	};
+}
+
+impl_tuples! {A}
+impl_tuples! {A B}
+impl_tuples! {A B C}
+impl_tuples! {A B C D}
+impl_tuples! {A B C D E}
+impl_tuples! {A B C D E F}
+impl_tuples! {A B C D E F G}
+impl_tuples! {A B C D E F G H}
+impl_tuples! {A B C D E F G H I}
+impl_tuples! {A B C D E F G H I J}
+
 pub trait AbiWrite {
 	fn abi_write(&self, writer: &mut AbiWriter);
 }
@@ -273,6 +330,11 @@
 		writer.string(self)
 	}
 }
+impl AbiWrite for &Vec<u8> {
+	fn abi_write(&self, writer: &mut AbiWriter) {
+		writer.bytes(self)
+	}
+}
 
 impl AbiWrite for () {
 	fn abi_write(&self, _writer: &mut AbiWriter) {}
@@ -308,6 +370,11 @@
 
 #[cfg(test)]
 pub mod test {
+	use crate::{
+		abi::AbiRead,
+		types::{string, uint256},
+	};
+
 	use super::{AbiReader, AbiWriter};
 	use hex_literal::hex;
 
@@ -355,4 +422,48 @@
 		assert_eq!(decoder.uint32().unwrap(), 1);
 		assert_eq!(decoder.string().unwrap(), "Test URI");
 	}
+
+	#[test]
+	fn mint_bulk() {
+		let (call, mut decoder) = AbiReader::new_call(&hex!(
+			"
+				36543006
+				00000000000000000000000053744e6da587ba10b32a2554d2efdcd985bc27a3 // address
+				0000000000000000000000000000000000000000000000000000000000000040 // 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
+			"
+		))
+		.unwrap();
+		assert_eq!(call, 0x36543006);
+		let _ = decoder.address().unwrap();
+		let data =
+			<AbiReader<'_> as AbiRead<Vec<(uint256, string)>>>::abi_read(&mut decoder).unwrap();
+		assert_eq!(
+			data,
+			vec![
+				(1.into(), "Test URI 0".to_string()),
+				(11.into(), "Test URI 1".to_string()),
+				(12.into(), "Test URI 2".to_string())
+			]
+		);
+	}
 }
modifiedcrates/evm-coder/src/lib.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/lib.rs
+++ b/crates/evm-coder/src/lib.rs
@@ -67,8 +67,8 @@
 		#[test]
 		#[ignore]
 		fn $name() {
-			use sp_std::collections::btree_set::BTreeSet;
-			let mut out = BTreeSet::new();
+			use evm_coder::solidity::TypeCollector;
+			let mut out = TypeCollector::new();
 			$decl::generate_solidity_interface(&mut out, $is_impl);
 			println!("=== SNIP START ===");
 			println!("// SPDX-License-Identifier: OTHER");
@@ -76,7 +76,7 @@
 			println!();
 			println!("pragma solidity >=0.8.0 <0.9.0;");
 			println!();
-			for b in out {
+			for b in out.finish() {
 				println!("{}", b);
 			}
 			println!("=== SNIP END ===");
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -1,25 +1,79 @@
 #[cfg(not(feature = "std"))]
-use alloc::{string::String};
-use core::{fmt, marker::PhantomData};
+use alloc::{
+	string::String,
+	vec::Vec,
+	collections::{BTreeSet, BTreeMap},
+	format,
+};
+#[cfg(feature = "std")]
+use std::collections::{BTreeSet, BTreeMap};
+use core::{
+	fmt::{self, Write},
+	marker::PhantomData,
+	cell::{Cell, RefCell},
+};
 use impl_trait_for_tuples::impl_for_tuples;
 use crate::types::*;
 
+#[derive(Default)]
+pub struct TypeCollector {
+	structs: RefCell<BTreeSet<string>>,
+	anonymous: RefCell<BTreeMap<Vec<string>, usize>>,
+	id: Cell<usize>,
+}
+impl TypeCollector {
+	pub fn new() -> Self {
+		Self::default()
+	}
+	pub fn collect(&self, item: string) {
+		self.structs.borrow_mut().insert(item);
+	}
+	pub fn next_id(&self) -> usize {
+		let v = self.id.get();
+		self.id.set(v + 1);
+		v
+	}
+	pub fn collect_tuple<T: SolidityTupleType>(&self) -> String {
+		let names = T::names(self);
+		if let Some(id) = self.anonymous.borrow().get(&names).cloned() {
+			return format!("Tuple{}", id);
+		}
+		let id = self.next_id();
+		let mut str = String::new();
+		writeln!(str, "// Anonymous struct").unwrap();
+		writeln!(str, "struct Tuple{} {{", id).unwrap();
+		for (i, name) in names.iter().enumerate() {
+			writeln!(str, "\t{} field_{};", name, i).unwrap();
+		}
+		writeln!(str, "}}").unwrap();
+		self.collect(str);
+		self.anonymous.borrow_mut().insert(names, id);
+		format!("Tuple{}", id)
+	}
+	pub fn finish(self) -> BTreeSet<string> {
+		self.structs.into_inner()
+	}
+}
+
 pub trait SolidityTypeName: 'static {
-	fn solidity_name(writer: &mut impl fmt::Write) -> fmt::Result;
-	fn solidity_default(writer: &mut impl fmt::Write) -> fmt::Result;
+	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
+	fn is_simple() -> bool;
+	fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
 	fn is_void() -> bool {
 		false
 	}
 }
-
 macro_rules! solidity_type_name {
-    ($($ty:ident => $name:literal = $default:literal),* $(,)?) => {
+    ($($ty:ty => $name:literal $simple:literal = $default:literal),* $(,)?) => {
         $(
             impl SolidityTypeName for $ty {
-                fn solidity_name(writer: &mut impl core::fmt::Write) -> core::fmt::Result {
+                fn solidity_name(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {
                     write!(writer, $name)
                 }
-				fn solidity_default(writer: &mut impl core::fmt::Write) -> core::fmt::Result {
+				fn is_simple() -> bool {
+					$simple
+				}
+				fn solidity_default(writer: &mut impl core::fmt::Write, _tc: &TypeCollector) -> core::fmt::Result {
 					write!(writer, $default)
 				}
             }
@@ -28,20 +82,23 @@
 }
 
 solidity_type_name! {
-	uint8 => "uint8" = "0",
-	uint32 => "uint32" = "0",
-	uint128 => "uint128" = "0",
-	uint256 => "uint256" = "0",
-	address => "address" = "0x0000000000000000000000000000000000000000",
-	string => "string memory" = "\"\"",
-	bytes => "bytes memory" = "hex\"\"",
-	bool => "bool" = "false",
+	uint8 => "uint8" true = "0",
+	uint32 => "uint32" true = "0",
+	uint128 => "uint128" true = "0",
+	uint256 => "uint256" true = "0",
+	address => "address" true = "0x0000000000000000000000000000000000000000",
+	string => "string" false = "\"\"",
+	bytes => "bytes" false = "hex\"\"",
+	bool => "bool" true = "false",
 }
 impl SolidityTypeName for void {
-	fn solidity_name(_writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
 		Ok(())
 	}
-	fn solidity_default(_writer: &mut impl fmt::Write) -> fmt::Result {
+	fn is_simple() -> bool {
+		true
+	}
+	fn solidity_default(_writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
 		Ok(())
 	}
 	fn is_void() -> bool {
@@ -49,10 +106,88 @@
 	}
 }
 
+mod sealed {
+	pub trait CanBePlacedInVec {}
+}
+
+impl sealed::CanBePlacedInVec for uint256 {}
+impl sealed::CanBePlacedInVec for string {}
+impl sealed::CanBePlacedInVec for address {}
+
+impl<T: SolidityTypeName + sealed::CanBePlacedInVec> SolidityTypeName for Vec<T> {
+	fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		T::solidity_name(writer, tc)?;
+		write!(writer, "[]")
+	}
+	fn is_simple() -> bool {
+		false
+	}
+	fn solidity_default(writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
+		write!(writer, "[]")
+	}
+}
+
+pub trait SolidityTupleType {
+	fn names(tc: &TypeCollector) -> Vec<String>;
+	fn len() -> usize;
+}
+
+macro_rules! count {
+    () => (0usize);
+    ( $x:tt $($xs:tt)* ) => (1usize + count!($($xs)*));
+}
+
+macro_rules! impl_tuples {
+	($($ident:ident)+) => {
+		impl<$($ident),+> sealed::CanBePlacedInVec for ($($ident,)+) {}
+		impl<$($ident: SolidityTypeName + 'static),+> SolidityTupleType for ($($ident,)+) {
+			fn names(tc: &TypeCollector) -> Vec<string> {
+				let mut collected = Vec::with_capacity(Self::len());
+				$({
+					let mut out = string::new();
+					$ident::solidity_name(&mut out, tc).expect("no fmt error");
+					collected.push(out);
+				})*;
+				collected
+			}
+
+			fn len() -> usize {
+				count!($($ident)*)
+			}
+		}
+		impl<$($ident: SolidityTypeName + 'static),+> SolidityTypeName for ($($ident,)+) {
+			fn solidity_name(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+				write!(writer, "{}", tc.collect_tuple::<Self>())
+			}
+			fn is_simple() -> bool {
+				false
+			}
+			fn solidity_default(writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+				write!(writer, "{}(", tc.collect_tuple::<Self>())?;
+				$(
+					<$ident>::solidity_default(writer, tc)?;
+				)*
+				write!(writer, ")")
+			}
+		}
+	};
+}
+
+impl_tuples! {A}
+impl_tuples! {A B}
+impl_tuples! {A B C}
+impl_tuples! {A B C D}
+impl_tuples! {A B C D E}
+impl_tuples! {A B C D E F}
+impl_tuples! {A B C D E F G}
+impl_tuples! {A B C D E F G H}
+impl_tuples! {A B C D E F G H I}
+impl_tuples! {A B C D E F G H I J}
+
 pub trait SolidityArguments {
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result;
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
 	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result;
-	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result;
+	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result;
 	fn is_empty(&self) -> bool {
 		self.len() == 0
 	}
@@ -63,9 +198,13 @@
 pub struct UnnamedArgument<T>(PhantomData<*const T>);
 
 impl<T: SolidityTypeName> SolidityArguments for UnnamedArgument<T> {
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
 		if !T::is_void() {
-			T::solidity_name(writer)
+			T::solidity_name(writer, tc)?;
+			if !T::is_simple() {
+				write!(writer, " memory")?;
+			}
+			Ok(())
 		} else {
 			Ok(())
 		}
@@ -73,8 +212,8 @@
 	fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
 		Ok(())
 	}
-	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
-		T::solidity_default(writer)
+	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		T::solidity_default(writer, tc)
 	}
 	fn len(&self) -> usize {
 		if T::is_void() {
@@ -94,9 +233,12 @@
 }
 
 impl<T: SolidityTypeName> SolidityArguments for NamedArgument<T> {
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
 		if !T::is_void() {
-			T::solidity_name(writer)?;
+			T::solidity_name(writer, tc)?;
+			if !T::is_simple() {
+				write!(writer, " memory")?;
+			}
 			write!(writer, " {}", self.0)
 		} else {
 			Ok(())
@@ -105,8 +247,8 @@
 	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {
 		writeln!(writer, "\t\t{};", self.0)
 	}
-	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
-		T::solidity_default(writer)
+	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		T::solidity_default(writer, tc)
 	}
 	fn len(&self) -> usize {
 		if T::is_void() {
@@ -126,9 +268,9 @@
 }
 
 impl<T: SolidityTypeName> SolidityArguments for SolidityEventArgument<T> {
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
 		if !T::is_void() {
-			T::solidity_name(writer)?;
+			T::solidity_name(writer, tc)?;
 			if self.0 {
 				write!(writer, " indexed")?;
 			}
@@ -140,8 +282,8 @@
 	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {
 		writeln!(writer, "\t\t{};", self.1)
 	}
-	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
-		T::solidity_default(writer)
+	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
+		T::solidity_default(writer, tc)
 	}
 	fn len(&self) -> usize {
 		if T::is_void() {
@@ -153,13 +295,13 @@
 }
 
 impl SolidityArguments for () {
-	fn solidity_name(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
 		Ok(())
 	}
 	fn solidity_get(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
 		Ok(())
 	}
-	fn solidity_default(&self, _writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_default(&self, _writer: &mut impl fmt::Write, _tc: &TypeCollector) -> fmt::Result {
 		Ok(())
 	}
 	fn len(&self) -> usize {
@@ -171,7 +313,7 @@
 impl SolidityArguments for Tuple {
 	for_tuples!( where #( Tuple: SolidityArguments ),* );
 
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
 		let mut first = true;
 		for_tuples!( #(
             if !Tuple.is_empty() {
@@ -179,7 +321,7 @@
                     write!(writer, ", ")?;
                 }
                 first = false;
-                Tuple.solidity_name(writer)?;
+                Tuple.solidity_name(writer, tc)?;
             }
         )* );
 		Ok(())
@@ -190,12 +332,12 @@
         )* );
 		Ok(())
 	}
-	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_default(&self, writer: &mut impl fmt::Write, tc: &TypeCollector) -> fmt::Result {
 		if self.is_empty() {
 			Ok(())
 		} else if self.len() == 1 {
 			for_tuples!( #(
-				Tuple.solidity_default(writer)?;
+				Tuple.solidity_default(writer, tc)?;
 			)* );
 			Ok(())
 		} else {
@@ -207,7 +349,7 @@
 						write!(writer, ", ")?;
 					}
 					first = false;
-					Tuple.solidity_name(writer)?;
+					Tuple.solidity_default(writer, tc)?;
 				}
 			)* );
 			write!(writer, ")")?;
@@ -220,7 +362,12 @@
 }
 
 pub trait SolidityFunctions {
-	fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result;
+	fn solidity_name(
+		&self,
+		is_impl: bool,
+		writer: &mut impl fmt::Write,
+		tc: &TypeCollector,
+	) -> fmt::Result;
 }
 
 pub enum SolidityMutability {
@@ -229,15 +376,22 @@
 	Mutable,
 }
 pub struct SolidityFunction<A, R> {
+	pub selector: &'static str,
 	pub name: &'static str,
 	pub args: A,
 	pub result: R,
 	pub mutability: SolidityMutability,
 }
 impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {
-	fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(
+		&self,
+		is_impl: bool,
+		writer: &mut impl fmt::Write,
+		tc: &TypeCollector,
+	) -> fmt::Result {
+		writeln!(writer, "\t// Selector: {}", self.selector)?;
 		write!(writer, "\tfunction {}(", self.name)?;
-		self.args.solidity_name(writer)?;
+		self.args.solidity_name(writer, tc)?;
 		write!(writer, ")")?;
 		if is_impl {
 			write!(writer, " public")?;
@@ -251,7 +405,7 @@
 		}
 		if !self.result.is_empty() {
 			write!(writer, " returns (")?;
-			self.result.solidity_name(writer)?;
+			self.result.solidity_name(writer, tc)?;
 			write!(writer, ")")?;
 		}
 		if is_impl {
@@ -265,7 +419,7 @@
 			}
 			if !self.result.is_empty() {
 				write!(writer, "\t\treturn ")?;
-				self.result.solidity_default(writer)?;
+				self.result.solidity_default(writer, tc)?;
 				writeln!(writer, ";")?;
 			}
 			writeln!(writer, "\t}}")?;
@@ -280,10 +434,15 @@
 impl SolidityFunctions for Tuple {
 	for_tuples!( where #( Tuple: SolidityFunctions ),* );
 
-	fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(
+		&self,
+		is_impl: bool,
+		writer: &mut impl fmt::Write,
+		tc: &TypeCollector,
+	) -> fmt::Result {
 		let mut first = false;
 		for_tuples!( #(
-            Tuple.solidity_name(is_impl, writer)?;
+            Tuple.solidity_name(is_impl, writer, tc)?;
         )* );
 		Ok(())
 	}
@@ -296,7 +455,12 @@
 }
 
 impl<F: SolidityFunctions> SolidityInterface<F> {
-	pub fn format(&self, is_impl: bool, out: &mut impl fmt::Write) -> fmt::Result {
+	pub fn format(
+		&self,
+		is_impl: bool,
+		out: &mut impl fmt::Write,
+		tc: &TypeCollector,
+	) -> fmt::Result {
 		if is_impl {
 			write!(out, "contract ")?;
 		} else {
@@ -313,7 +477,7 @@
 			}
 		}
 		writeln!(out, " {{")?;
-		self.functions.solidity_name(is_impl, out)?;
+		self.functions.solidity_name(is_impl, out, tc)?;
 		writeln!(out, "}}")?;
 		Ok(())
 	}
@@ -325,9 +489,14 @@
 }
 
 impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {
-	fn solidity_name(&self, _is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(
+		&self,
+		_is_impl: bool,
+		writer: &mut impl fmt::Write,
+		tc: &TypeCollector,
+	) -> fmt::Result {
 		write!(writer, "\tevent {}(", self.name)?;
-		self.args.solidity_name(writer)?;
+		self.args.solidity_name(writer, tc)?;
 		writeln!(writer, ");")
 	}
 }
modifiedpallets/evm-contract-helpers/src/stubs/ContractHelpers.soldiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol
+++ b/pallets/evm-contract-helpers/src/stubs/ContractHelpers.sol
@@ -10,6 +10,7 @@
 }
 
 contract ContractHelpers is Dummy {
+	// Selector: contractOwner(address) 5152b14c
 	function contractOwner(address contractAddress)
 		public
 		view
@@ -21,6 +22,7 @@
 		return 0x0000000000000000000000000000000000000000;
 	}
 
+	// Selector: sponsoringEnabled(address) 6027dc61
 	function sponsoringEnabled(address contractAddress)
 		public
 		view
@@ -32,6 +34,7 @@
 		return false;
 	}
 
+	// Selector: toggleSponsoring(address,bool) fcac6d86
 	function toggleSponsoring(address contractAddress, bool enabled) public {
 		require(false, stub_error);
 		contractAddress;
@@ -39,6 +42,7 @@
 		dummy = 0;
 	}
 
+	// Selector: setSponsoringRateLimit(address,uint32) 77b6c908
 	function setSponsoringRateLimit(address contractAddress, uint32 rateLimit)
 		public
 	{
@@ -48,6 +52,7 @@
 		dummy = 0;
 	}
 
+	// Selector: allowed(address,address) 5c658165
 	function allowed(address contractAddress, address user)
 		public
 		view
@@ -60,6 +65,7 @@
 		return false;
 	}
 
+	// Selector: allowlistEnabled(address) c772ef6c
 	function allowlistEnabled(address contractAddress)
 		public
 		view
@@ -71,6 +77,7 @@
 		return false;
 	}
 
+	// Selector: toggleAllowlist(address,bool) 36de20f5
 	function toggleAllowlist(address contractAddress, bool enabled) public {
 		require(false, stub_error);
 		contractAddress;
@@ -78,6 +85,7 @@
 		dummy = 0;
 	}
 
+	// Selector: toggleAllowed(address,address,bool) 4706cc1c
 	function toggleAllowed(
 		address contractAddress,
 		address user,
modifiedpallets/nft/src/eth/erc.rsdiffbeforeafterboth
--- a/pallets/nft/src/eth/erc.rs
+++ b/pallets/nft/src/eth/erc.rs
@@ -8,6 +8,7 @@
 };
 use frame_support::storage::{StorageMap, StorageDoubleMap};
 use pallet_evm::AddressMapping;
+use pallet_evm_coder_substrate::dispatch_to_evm;
 use super::account::CrossAccountId;
 use sp_std::{vec, vec::Vec};
 
@@ -299,6 +300,90 @@
 			.ok_or("item id overflow")?
 			.into())
 	}
+
+	fn set_variable_metadata(
+		&mut self,
+		caller: caller,
+		token_id: uint256,
+		data: bytes,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let token_id = token_id.try_into().map_err(|_| "token id overflow")?;
+
+		<Module<T>>::set_variable_meta_data_internal(&caller, self, token_id, data)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
+	fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {
+		let token_id = token_id.try_into().map_err(|_| "token id overflow")?;
+
+		<Module<T>>::get_variable_metadata(self, token_id).map_err(dispatch_to_evm::<T>)
+	}
+
+	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let mut expected_index = <ItemListIndex>::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?;
+
+		let total_tokens = token_ids.len();
+		for id in token_ids.into_iter() {
+			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;
+			if id != expected_index {
+				return Err("item id should be next".into());
+			}
+			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;
+		}
+
+		let data = (0..total_tokens)
+			.map(|_| {
+				CreateItemData::NFT(CreateNftData {
+					const_data: vec![].try_into().unwrap(),
+					variable_data: vec![].try_into().unwrap(),
+				})
+			})
+			.collect();
+
+		<Module<T>>::create_multiple_items_internal(&caller, self, &to, data)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+
+	#[solidity(rename_selector = "mintBulkWithTokenURI")]
+	fn mint_bulk_with_token_uri(
+		&mut self,
+		caller: caller,
+		to: address,
+		tokens: Vec<(uint256, string)>,
+	) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let mut expected_index = <ItemListIndex>::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?;
+
+		let mut data = Vec::with_capacity(tokens.len());
+		for (id, token_uri) in tokens {
+			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;
+			if id != expected_index {
+				panic!("item id should be next ({}) but got {}", expected_index, id);
+			}
+			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;
+
+			data.push(CreateItemData::NFT(CreateNftData {
+				const_data: Vec::<u8>::from(token_uri)
+					.try_into()
+					.map_err(|_| "token uri is too long")?,
+				variable_data: vec![].try_into().unwrap(),
+			}));
+		}
+
+		<Module<T>>::create_multiple_items_internal(&caller, self, &to, data)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
 }
 
 #[solidity_interface(
modifiedpallets/nft/src/eth/stubs/UniqueFungible.soldiffbeforeafterboth
--- a/pallets/nft/src/eth/stubs/UniqueFungible.sol
+++ b/pallets/nft/src/eth/stubs/UniqueFungible.sol
@@ -21,12 +21,14 @@
 
 // Inline
 contract InlineNameSymbol is Dummy {
+	// Selector: name() 06fdde03
 	function name() public view returns (string memory) {
 		require(false, stub_error);
 		dummy;
 		return "";
 	}
 
+	// Selector: symbol() 95d89b41
 	function symbol() public view returns (string memory) {
 		require(false, stub_error);
 		dummy;
@@ -36,6 +38,7 @@
 
 // Inline
 contract InlineTotalSupply is Dummy {
+	// Selector: totalSupply() 18160ddd
 	function totalSupply() public view returns (uint256) {
 		require(false, stub_error);
 		dummy;
@@ -44,6 +47,7 @@
 }
 
 contract ERC165 is Dummy {
+	// Selector: supportsInterface(bytes4) 01ffc9a7
 	function supportsInterface(uint32 interfaceId) public view returns (bool) {
 		require(false, stub_error);
 		interfaceId;
@@ -53,12 +57,14 @@
 }
 
 contract ERC20 is Dummy, InlineNameSymbol, InlineTotalSupply, ERC20Events {
+	// Selector: decimals() 313ce567
 	function decimals() public view returns (uint8) {
 		require(false, stub_error);
 		dummy;
 		return 0;
 	}
 
+	// Selector: balanceOf(address) 70a08231
 	function balanceOf(address owner) public view returns (uint256) {
 		require(false, stub_error);
 		owner;
@@ -66,6 +72,7 @@
 		return 0;
 	}
 
+	// Selector: transfer(address,uint256) a9059cbb
 	function transfer(address to, uint256 amount) public returns (bool) {
 		require(false, stub_error);
 		to;
@@ -74,6 +81,7 @@
 		return false;
 	}
 
+	// Selector: transferFrom(address,address,uint256) 23b872dd
 	function transferFrom(
 		address from,
 		address to,
@@ -87,6 +95,7 @@
 		return false;
 	}
 
+	// Selector: approve(address,uint256) 095ea7b3
 	function approve(address spender, uint256 amount) public returns (bool) {
 		require(false, stub_error);
 		spender;
@@ -95,6 +104,7 @@
 		return false;
 	}
 
+	// Selector: allowance(address,address) dd62ed3e
 	function allowance(address owner, address spender)
 		public
 		view
modifiedpallets/nft/src/eth/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

modifiedpallets/nft/src/eth/stubs/UniqueNFT.soldiffbeforeafterboth
--- a/pallets/nft/src/eth/stubs/UniqueNFT.sol
+++ b/pallets/nft/src/eth/stubs/UniqueNFT.sol
@@ -3,6 +3,12 @@
 
 pragma solidity >=0.8.0 <0.9.0;
 
+// Anonymous struct
+struct Tuple0 {
+	uint256 field_0;
+	string field_1;
+}
+
 // Common stubs holder
 contract Dummy {
 	uint8 dummy;
@@ -35,12 +41,14 @@
 
 // Inline
 contract InlineNameSymbol is Dummy {
+	// Selector: name() 06fdde03
 	function name() public view returns (string memory) {
 		require(false, stub_error);
 		dummy;
 		return "";
 	}
 
+	// Selector: symbol() 95d89b41
 	function symbol() public view returns (string memory) {
 		require(false, stub_error);
 		dummy;
@@ -50,6 +58,7 @@
 
 // Inline
 contract InlineTotalSupply is Dummy {
+	// Selector: totalSupply() 18160ddd
 	function totalSupply() public view returns (uint256) {
 		require(false, stub_error);
 		dummy;
@@ -58,6 +67,7 @@
 }
 
 contract ERC165 is Dummy {
+	// Selector: supportsInterface(bytes4) 01ffc9a7
 	function supportsInterface(uint32 interfaceId) public view returns (bool) {
 		require(false, stub_error);
 		interfaceId;
@@ -67,6 +77,7 @@
 }
 
 contract ERC721 is Dummy, ERC165, ERC721Events {
+	// Selector: balanceOf(address) 70a08231
 	function balanceOf(address owner) public view returns (uint256) {
 		require(false, stub_error);
 		owner;
@@ -74,6 +85,7 @@
 		return 0;
 	}
 
+	// Selector: ownerOf(uint256) 6352211e
 	function ownerOf(uint256 tokenId) public view returns (address) {
 		require(false, stub_error);
 		tokenId;
@@ -81,6 +93,7 @@
 		return 0x0000000000000000000000000000000000000000;
 	}
 
+	// Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672
 	function safeTransferFromWithData(
 		address from,
 		address to,
@@ -95,6 +108,7 @@
 		dummy = 0;
 	}
 
+	// Selector: safeTransferFrom(address,address,uint256) 42842e0e
 	function safeTransferFrom(
 		address from,
 		address to,
@@ -107,6 +121,7 @@
 		dummy = 0;
 	}
 
+	// Selector: transferFrom(address,address,uint256) 23b872dd
 	function transferFrom(
 		address from,
 		address to,
@@ -119,6 +134,7 @@
 		dummy = 0;
 	}
 
+	// Selector: approve(address,uint256) 095ea7b3
 	function approve(address approved, uint256 tokenId) public {
 		require(false, stub_error);
 		approved;
@@ -126,6 +142,7 @@
 		dummy = 0;
 	}
 
+	// Selector: setApprovalForAll(address,bool) a22cb465
 	function setApprovalForAll(address operator, bool approved) public {
 		require(false, stub_error);
 		operator;
@@ -133,6 +150,7 @@
 		dummy = 0;
 	}
 
+	// Selector: getApproved(uint256) 081812fc
 	function getApproved(uint256 tokenId) public view returns (address) {
 		require(false, stub_error);
 		tokenId;
@@ -140,6 +158,7 @@
 		return 0x0000000000000000000000000000000000000000;
 	}
 
+	// Selector: isApprovedForAll(address,address) e985e9c5
 	function isApprovedForAll(address owner, address operator)
 		public
 		view
@@ -154,6 +173,7 @@
 }
 
 contract ERC721Burnable is Dummy {
+	// Selector: burn(uint256) 42966c68
 	function burn(uint256 tokenId) public {
 		require(false, stub_error);
 		tokenId;
@@ -162,6 +182,7 @@
 }
 
 contract ERC721Enumerable is Dummy, InlineTotalSupply {
+	// Selector: tokenByIndex(uint256) 4f6ccce7
 	function tokenByIndex(uint256 index) public view returns (uint256) {
 		require(false, stub_error);
 		index;
@@ -169,6 +190,7 @@
 		return 0;
 	}
 
+	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
 	function tokenOfOwnerByIndex(address owner, uint256 index)
 		public
 		view
@@ -183,6 +205,7 @@
 }
 
 contract ERC721Metadata is Dummy, InlineNameSymbol {
+	// Selector: tokenURI(uint256) c87b56dd
 	function tokenURI(uint256 tokenId) public view returns (string memory) {
 		require(false, stub_error);
 		tokenId;
@@ -192,12 +215,14 @@
 }
 
 contract ERC721Mintable is Dummy, ERC721MintableEvents {
+	// Selector: mintingFinished() 05d2035b
 	function mintingFinished() public view returns (bool) {
 		require(false, stub_error);
 		dummy;
 		return false;
 	}
 
+	// Selector: mint(address,uint256) 40c10f19
 	function mint(address to, uint256 tokenId) public returns (bool) {
 		require(false, stub_error);
 		to;
@@ -206,6 +231,7 @@
 		return false;
 	}
 
+	// Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f
 	function mintWithTokenURI(
 		address to,
 		uint256 tokenId,
@@ -219,6 +245,7 @@
 		return false;
 	}
 
+	// Selector: finishMinting() 7d64bcb4
 	function finishMinting() public returns (bool) {
 		require(false, stub_error);
 		dummy = 0;
@@ -227,6 +254,7 @@
 }
 
 contract ERC721UniqueExtensions is Dummy {
+	// Selector: transfer(address,uint256) a9059cbb
 	function transfer(address to, uint256 tokenId) public {
 		require(false, stub_error);
 		to;
@@ -234,11 +262,56 @@
 		dummy = 0;
 	}
 
+	// Selector: nextTokenId() 75794a3c
 	function nextTokenId() public view returns (uint256) {
 		require(false, stub_error);
 		dummy;
 		return 0;
 	}
+
+	// Selector: setVariableMetadata(uint256,bytes) d4eac26d
+	function setVariableMetadata(uint256 tokenId, bytes memory data) public {
+		require(false, stub_error);
+		tokenId;
+		data;
+		dummy = 0;
+	}
+
+	// Selector: getVariableMetadata(uint256) e6c5ce6f
+	function getVariableMetadata(uint256 tokenId)
+		public
+		view
+		returns (bytes memory)
+	{
+		require(false, stub_error);
+		tokenId;
+		dummy;
+		return hex"";
+	}
+
+	// Selector: mintBulk(address,uint256[]) 44a9945e
+	function mintBulk(address to, uint256[] memory tokenIds)
+		public
+		returns (bool)
+	{
+		require(false, stub_error);
+		to;
+		tokenIds;
+		dummy = 0;
+		return false;
+	}
+
+	// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
+	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
+		public
+		returns (bool)
+	{
+		require(false, stub_error);
+		to;
+		tokens;
+		dummy = 0;
+		return false;
+	}
 }
 
 contract UniqueNFT is
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
1559 Ok(())1559 Ok(())
1560 }1560 }
1561
1562 pub fn get_variable_metadata(
1563 collection: &CollectionHandle<T>,
1564 item_id: TokenId,
1565 ) -> Result<Vec<u8>, DispatchError> {
1566 Ok(match collection.mode {
1567 CollectionMode::NFT => {
1568 <NftItemList<T>>::get(collection.id, item_id)
1569 .ok_or(Error::<T>::TokenNotFound)?
1570 .variable_data
1571 }
1572 CollectionMode::ReFungible => {
1573 <ReFungibleItemList<T>>::get(collection.id, item_id)
1574 .ok_or(Error::<T>::TokenNotFound)?
1575 .variable_data
1576 }
1577 _ => fail!(Error::<T>::UnexpectedCollectionType),
1578 })
1579 }
15611580
1562 pub fn create_multiple_items_internal(1581 pub fn create_multiple_items_internal(
1563 sender: &T::CrossAccountId,1582 sender: &T::CrossAccountId,
modifiedtests/src/eth/api/ContractHelpers.soldiffbeforeafterboth
--- a/tests/src/eth/api/ContractHelpers.sol
+++ b/tests/src/eth/api/ContractHelpers.sol
@@ -9,33 +9,41 @@
 }
 
 interface ContractHelpers is Dummy {
+	// Selector: contractOwner(address) 5152b14c
 	function contractOwner(address contractAddress)
 		external
 		view
 		returns (address);
 
+	// Selector: sponsoringEnabled(address) 6027dc61
 	function sponsoringEnabled(address contractAddress)
 		external
 		view
 		returns (bool);
 
+	// Selector: toggleSponsoring(address,bool) fcac6d86
 	function toggleSponsoring(address contractAddress, bool enabled) external;
 
+	// Selector: setSponsoringRateLimit(address,uint32) 77b6c908
 	function setSponsoringRateLimit(address contractAddress, uint32 rateLimit)
 		external;
 
+	// Selector: allowed(address,address) 5c658165
 	function allowed(address contractAddress, address user)
 		external
 		view
 		returns (bool);
 
+	// Selector: allowlistEnabled(address) c772ef6c
 	function allowlistEnabled(address contractAddress)
 		external
 		view
 		returns (bool);
 
+	// Selector: toggleAllowlist(address,bool) 36de20f5
 	function toggleAllowlist(address contractAddress, bool enabled) external;
 
+	// Selector: toggleAllowed(address,address,bool) 4706cc1c
 	function toggleAllowed(
 		address contractAddress,
 		address user,
modifiedtests/src/eth/api/UniqueFungible.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueFungible.sol
+++ b/tests/src/eth/api/UniqueFungible.sol
@@ -20,35 +20,45 @@
 
 // Inline
 interface InlineNameSymbol is Dummy {
+	// Selector: name() 06fdde03
 	function name() external view returns (string memory);
 
+	// Selector: symbol() 95d89b41
 	function symbol() external view returns (string memory);
 }
 
 // Inline
 interface InlineTotalSupply is Dummy {
+	// Selector: totalSupply() 18160ddd
 	function totalSupply() external view returns (uint256);
 }
 
 interface ERC165 is Dummy {
+	// Selector: supportsInterface(bytes4) 01ffc9a7
 	function supportsInterface(uint32 interfaceId) external view returns (bool);
 }
 
 interface ERC20 is Dummy, InlineNameSymbol, InlineTotalSupply, ERC20Events {
+	// Selector: decimals() 313ce567
 	function decimals() external view returns (uint8);
 
+	// Selector: balanceOf(address) 70a08231
 	function balanceOf(address owner) external view returns (uint256);
 
+	// Selector: transfer(address,uint256) a9059cbb
 	function transfer(address to, uint256 amount) external returns (bool);
 
+	// Selector: transferFrom(address,address,uint256) 23b872dd
 	function transferFrom(
 		address from,
 		address to,
 		uint256 amount
 	) external returns (bool);
 
+	// Selector: approve(address,uint256) 095ea7b3
 	function approve(address spender, uint256 amount) external returns (bool);
 
+	// Selector: allowance(address,address) dd62ed3e
 	function allowance(address owner, address spender)
 		external
 		view
modifiedtests/src/eth/api/UniqueNFT.soldiffbeforeafterboth
--- a/tests/src/eth/api/UniqueNFT.sol
+++ b/tests/src/eth/api/UniqueNFT.sol
@@ -3,6 +3,12 @@
 
 pragma solidity >=0.8.0 <0.9.0;
 
+// Anonymous struct
+struct Tuple0 {
+	uint256 field_0;
+	string field_1;
+}
+
 // Common stubs holder
 interface Dummy {
 
@@ -34,25 +40,32 @@
 
 // Inline
 interface InlineNameSymbol is Dummy {
+	// Selector: name() 06fdde03
 	function name() external view returns (string memory);
 
+	// Selector: symbol() 95d89b41
 	function symbol() external view returns (string memory);
 }
 
 // Inline
 interface InlineTotalSupply is Dummy {
+	// Selector: totalSupply() 18160ddd
 	function totalSupply() external view returns (uint256);
 }
 
 interface ERC165 is Dummy {
+	// Selector: supportsInterface(bytes4) 01ffc9a7
 	function supportsInterface(uint32 interfaceId) external view returns (bool);
 }
 
 interface ERC721 is Dummy, ERC165, ERC721Events {
+	// Selector: balanceOf(address) 70a08231
 	function balanceOf(address owner) external view returns (uint256);
 
+	// Selector: ownerOf(uint256) 6352211e
 	function ownerOf(uint256 tokenId) external view returns (address);
 
+	// Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a11672
 	function safeTransferFromWithData(
 		address from,
 		address to,
@@ -60,24 +73,30 @@
 		bytes memory data
 	) external;
 
+	// Selector: safeTransferFrom(address,address,uint256) 42842e0e
 	function safeTransferFrom(
 		address from,
 		address to,
 		uint256 tokenId
 	) external;
 
+	// Selector: transferFrom(address,address,uint256) 23b872dd
 	function transferFrom(
 		address from,
 		address to,
 		uint256 tokenId
 	) external;
 
+	// Selector: approve(address,uint256) 095ea7b3
 	function approve(address approved, uint256 tokenId) external;
 
+	// Selector: setApprovalForAll(address,bool) a22cb465
 	function setApprovalForAll(address operator, bool approved) external;
 
+	// Selector: getApproved(uint256) 081812fc
 	function getApproved(uint256 tokenId) external view returns (address);
 
+	// Selector: isApprovedForAll(address,address) e985e9c5
 	function isApprovedForAll(address owner, address operator)
 		external
 		view
@@ -85,12 +104,15 @@
 }
 
 interface ERC721Burnable is Dummy {
+	// Selector: burn(uint256) 42966c68
 	function burn(uint256 tokenId) external;
 }
 
 interface ERC721Enumerable is Dummy, InlineTotalSupply {
+	// Selector: tokenByIndex(uint256) 4f6ccce7
 	function tokenByIndex(uint256 index) external view returns (uint256);
 
+	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59
 	function tokenOfOwnerByIndex(address owner, uint256 index)
 		external
 		view
@@ -98,27 +120,53 @@
 }
 
 interface ERC721Metadata is Dummy, InlineNameSymbol {
+	// Selector: tokenURI(uint256) c87b56dd
 	function tokenURI(uint256 tokenId) external view returns (string memory);
 }
 
 interface ERC721Mintable is Dummy, ERC721MintableEvents {
+	// Selector: mintingFinished() 05d2035b
 	function mintingFinished() external view returns (bool);
 
+	// Selector: mint(address,uint256) 40c10f19
 	function mint(address to, uint256 tokenId) external returns (bool);
 
+	// Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f
 	function mintWithTokenURI(
 		address to,
 		uint256 tokenId,
 		string memory tokenUri
 	) external returns (bool);
 
+	// Selector: finishMinting() 7d64bcb4
 	function finishMinting() external returns (bool);
 }
 
 interface ERC721UniqueExtensions is Dummy {
+	// Selector: transfer(address,uint256) a9059cbb
 	function transfer(address to, uint256 tokenId) external;
 
+	// Selector: nextTokenId() 75794a3c
 	function nextTokenId() external view returns (uint256);
+
+	// Selector: setVariableMetadata(uint256,bytes) d4eac26d
+	function setVariableMetadata(uint256 tokenId, bytes memory data) external;
+
+	// Selector: getVariableMetadata(uint256) e6c5ce6f
+	function getVariableMetadata(uint256 tokenId)
+		external
+		view
+		returns (bytes memory);
+
+	// Selector: mintBulk(address,uint256[]) 44a9945e
+	function mintBulk(address to, uint256[] memory tokenIds)
+		external
+		returns (bool);
+
+	// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006
+	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
+		external
+		returns (bool);
 }
 
 interface UniqueNFT is
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -4,7 +4,7 @@
 //
 
 import privateKey from '../substrate/privateKey';
-import { approveExpectSuccess, burnItemExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE } from '../util/helpers';
+import { approveExpectSuccess, burnItemExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess, setVariableMetaDataExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE } from '../util/helpers';
 import { collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth } from './util/helpers';
 import nonFungibleAbi from './nonFungibleAbi.json';
 import { expect } from 'chai';
@@ -105,6 +105,69 @@
       expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
     }
   });
+  itWeb3('Can perform mintBulk()', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+
+    const caller = await createEthAccountWithBalance(api, web3);
+    const changeAdminTx = api.tx.nft.addCollectionAdmin(collection, { ethereum: caller });
+    await submitTransactionAsync(alice, changeAdminTx);
+    const receiver = createEthAccount(web3);
+
+    const address = collectionIdToAddress(collection);
+    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, {from: caller, ...GAS_ARGS});
+
+    {
+      const nextTokenId = await contract.methods.nextTokenId().call();
+      expect(nextTokenId).to.be.equal('1');
+      const result = await contract.methods.mintBulkWithTokenURI(
+        receiver,
+        [
+          [nextTokenId, 'Test URI 0'],
+          [+nextTokenId + 1, 'Test URI 1'],
+          [+nextTokenId + 2, 'Test URI 2'],
+        ],
+      ).send({ from: caller });
+      const events = normalizeEvents(result.events);
+
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: receiver,
+            tokenId: nextTokenId,
+          },
+        },
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: receiver,
+            tokenId: String(+nextTokenId + 1),
+          },
+        },
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: receiver,
+            tokenId: String(+nextTokenId + 2),
+          },
+        },
+      ]);
+
+      await waitNewBlocks(api, 1);
+      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');
+      expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');
+      expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');
+    }
+  });
 
   itWeb3('Can perform burn()', async ({ web3, api }) => {
     const collection = await createCollectionExpectSuccess({
@@ -263,6 +326,41 @@
       expect(+balance).to.equal(1);
     }
   });
+
+  itWeb3('Can perform getVariableMetadata', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+
+    const owner = await createEthAccountWithBalance(api, web3);
+
+    const item = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });
+    await setVariableMetaDataExpectSuccess(alice, collection, item, [1, 2, 3]);
+
+    const address = collectionIdToAddress(collection);
+    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, { from: owner, ...GAS_ARGS });
+    
+    expect(await contract.methods.getVariableMetadata(item).call()).to.be.equal('0x010203');
+  });
+
+  itWeb3('Can perform setVariableMetadata', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+
+    const owner = await createEthAccountWithBalance(api, web3);
+
+    const item = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: owner });
+
+    const address = collectionIdToAddress(collection);
+    const contract = new web3.eth.Contract(nonFungibleAbi as any, address, { from: owner, ...GAS_ARGS });
+    
+    expect(await contract.methods.setVariableMetadata(item, '0x010203').send({ from: owner }));
+    await waitNewBlocks(api, 1);
+    expect(await contract.methods.getVariableMetadata(item).call()).to.be.equal('0x010203');
+  });
 });
 
 describe('NFT: Fees', () => {
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -50,7 +50,7 @@
         "type": "event"
     },
     {
-        "anonymous": true,
+        "anonymous": false,
         "inputs": [],
         "name": "MintingFinished",
         "type": "event"
@@ -165,6 +165,25 @@
     {
         "inputs": [
             {
+                "internalType": "uint256",
+                "name": "tokenId",
+                "type": "uint256"
+            }
+        ],
+        "name": "getVariableMetadata",
+        "outputs": [
+            {
+                "internalType": "bytes",
+                "name": "",
+                "type": "bytes"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
                 "internalType": "address",
                 "name": "owner",
                 "type": "address"
@@ -218,13 +237,73 @@
                 "type": "address"
             },
             {
+                "internalType": "uint256[]",
+                "name": "tokenIds",
+                "type": "uint256[]"
+            }
+        ],
+        "name": "mintBulk",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "to",
+                "type": "address"
+            },
+            {
+                "components": [
+                    {
+                        "internalType": "uint256",
+                        "name": "field_0",
+                        "type": "uint256"
+                    },
+                    {
+                        "internalType": "string",
+                        "name": "field_1",
+                        "type": "string"
+                    }
+                ],
+                "internalType": "struct Tuple0[]",
+                "name": "tokens",
+                "type": "tuple[]"
+            }
+        ],
+        "name": "mintBulkWithTokenURI",
+        "outputs": [
+            {
+                "internalType": "bool",
+                "name": "",
+                "type": "bool"
+            }
+        ],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
+                "internalType": "address",
+                "name": "to",
+                "type": "address"
+            },
+            {
                 "internalType": "uint256",
                 "name": "tokenId",
                 "type": "uint256"
             },
             {
                 "internalType": "string",
-                "name": "tokenURI",
+                "name": "tokenUri",
                 "type": "string"
             }
         ],
@@ -369,6 +448,24 @@
     {
         "inputs": [
             {
+                "internalType": "uint256",
+                "name": "tokenId",
+                "type": "uint256"
+            },
+            {
+                "internalType": "bytes",
+                "name": "data",
+                "type": "bytes"
+            }
+        ],
+        "name": "setVariableMetadata",
+        "outputs": [],
+        "stateMutability": "nonpayable",
+        "type": "function"
+    },
+    {
+        "inputs": [
+            {
                 "internalType": "uint32",
                 "name": "interfaceId",
                 "type": "uint32"
@@ -514,4 +611,4 @@
         "stateMutability": "nonpayable",
         "type": "function"
     }
-]
\ No newline at end of file
+]
modifiedtests/src/eth/proxy/UniqueNFTProxy.abidiffbeforeafterboth
--- a/tests/src/eth/proxy/UniqueNFTProxy.abi
+++ b/tests/src/eth/proxy/UniqueNFTProxy.abi
@@ -1 +1 @@
-[{"inputs":[{"internalType":"address","name":"_proxied","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[],"name":"MintingFinished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"approved","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"finishMinting","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"tokenUri","type":"string"}],"name":"mintWithTokenURI","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"mintingFinished","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextTokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFromWithData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"interfaceId","type":"uint32"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}]
\ No newline at end of file
+[{"inputs":[{"internalType":"address","name":"_proxied","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[],"name":"MintingFinished","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"approved","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"finishMinting","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getVariableMetadata","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"mint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"mintBulk","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"components":[{"internalType":"uint256","name":"field_0","type":"uint256"},{"internalType":"string","name":"field_1","type":"string"}],"internalType":"struct Tuple0[]","name":"tokens","type":"tuple[]"}],"name":"mintBulkWithTokenURI","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"tokenUri","type":"string"}],"name":"mintWithTokenURI","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"mintingFinished","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextTokenId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeTransferFromWithData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setVariableMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"interfaceId","type":"uint32"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"}]
\ No newline at end of file
modifiedtests/src/eth/proxy/UniqueNFTProxy.bindiffbeforeafterboth
--- a/tests/src/eth/proxy/UniqueNFTProxy.bin
+++ b/tests/src/eth/proxy/UniqueNFTProxy.bin
@@ -1 +1 @@
-608060405234801561001057600080fd5b506040516111e03803806111e083398101604081905261002f91610054565b600080546001600160a01b0319166001600160a01b0392909216919091179055610084565b60006020828403121561006657600080fd5b81516001600160a01b038116811461007d57600080fd5b9392505050565b61114d806100936000396000f3fe608060405234801561001057600080fd5b506004361061014d5760003560e01c806350bb4e7f116100c357806395d89b411161007c57806395d89b41146102a8578063a22cb465146102b0578063a9059cbb146102c3578063c87b56dd146102d6578063e985e9c5146102e9578063f4f4b500146102fc57600080fd5b806350bb4e7f1461024c57806360a116721461025f5780636352211e1461027257806370a082311461028557806375794a3c146102985780637d64bcb4146102a057600080fd5b806323b872dd1161011557806323b872dd146101da5780632f745c59146101ed57806340c10f191461020057806342842e0e1461021357806342966c68146102265780634f6ccce71461023957600080fd5b806305d2035b1461015257806306fdde031461016f578063081812fc14610184578063095ea7b3146101af57806318160ddd146101c4575b600080fd5b61015a61030f565b60405190151581526020015b60405180910390f35b61017761039b565b6040516101669190611043565b610197610192366004610f5b565b61041b565b6040516001600160a01b039091168152602001610166565b6101c26101bd366004610e2e565b61049f565b005b6101cc61050a565b604051908152602001610166565b6101c26101e8366004610d3f565b610591565b6101cc6101fb366004610e2e565b610605565b61015a61020e366004610e2e565b610691565b6101c2610221366004610d3f565b610718565b6101c2610234366004610f5b565b610759565b6101cc610247366004610f5b565b6107ba565b61015a61025a366004610e5a565b610838565b6101c261026d366004610d80565b6108c7565b610197610280366004610f5b565b610936565b6101cc610293366004610ccc565b610968565b6101cc61099b565b61015a6109ea565b610177610a4f565b6101c26102be366004610e00565b610a93565b6101c26102d1366004610e2e565b610acd565b6101776102e4366004610f5b565b610b06565b6101976102f7366004610d06565b610b87565b61015a61030a366004610f8d565b610c0d565b60008060009054906101000a90046001600160a01b03166001600160a01b03166305d2035b6040518163ffffffff1660e01b815260040160206040518083038186803b15801561035e57600080fd5b505afa158015610372573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103969190610ec7565b905090565b60008054604080516306fdde0360e01b815290516060936001600160a01b03909316926306fdde039260048082019391829003018186803b1580156103df57600080fd5b505afa1580156103f3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526103969190810190610ee4565b6000805460405163020604bf60e21b8152600481018490526001600160a01b039091169063081812fc906024015b60206040518083038186803b15801561046157600080fd5b505afa158015610475573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104999190610ce9565b92915050565b60005460405163095ea7b360e01b81526001600160a01b038481166004830152602482018490529091169063095ea7b3906044015b600060405180830381600087803b1580156104ee57600080fd5b505af1158015610502573d6000803e3d6000fd5b505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561055957600080fd5b505afa15801561056d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103969190610f74565b6000546040516323b872dd60e01b81526001600160a01b038581166004830152848116602483015260448201849052909116906323b872dd906064015b600060405180830381600087803b1580156105e857600080fd5b505af11580156105fc573d6000803e3d6000fd5b50505050505050565b60008054604051632f745c5960e01b81526001600160a01b0385811660048301526024820185905290911690632f745c599060440160206040518083038186803b15801561065257600080fd5b505afa158015610666573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061068a9190610f74565b9392505050565b600080546040516340c10f1960e01b81526001600160a01b03858116600483015260248201859052909116906340c10f1990604401602060405180830381600087803b1580156106e057600080fd5b505af11580156106f4573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061068a9190610ec7565b600054604051632142170760e11b81526001600160a01b038581166004830152848116602483015260448201849052909116906342842e0e906064016105ce565b600054604051630852cd8d60e31b8152600481018390526001600160a01b03909116906342966c6890602401600060405180830381600087803b15801561079f57600080fd5b505af11580156107b3573d6000803e3d6000fd5b5050505050565b60008054604051634f6ccce760e01b8152600481018490526001600160a01b0390911690634f6ccce7906024015b60206040518083038186803b15801561080057600080fd5b505afa158015610814573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104999190610f74565b600080546040516350bb4e7f60e01b81526001600160a01b03909116906350bb4e7f9061086d9087908790879060040161101c565b602060405180830381600087803b15801561088757600080fd5b505af115801561089b573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108bf9190610ec7565b949350505050565b6000546040516330508b3960e11b81526001600160a01b03909116906360a11672906108fd908790879087908790600401610fdf565b600060405180830381600087803b15801561091757600080fd5b505af115801561092b573d6000803e3d6000fd5b505050505b50505050565b600080546040516331a9108f60e11b8152600481018490526001600160a01b0390911690636352211e90602401610449565b600080546040516370a0823160e01b81526001600160a01b038481166004830152909116906370a08231906024016107e8565b60008060009054906101000a90046001600160a01b03166001600160a01b03166375794a3c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561055957600080fd5b60008060009054906101000a90046001600160a01b03166001600160a01b0316637d64bcb46040518163ffffffff1660e01b8152600401602060405180830381600087803b158015610a3b57600080fd5b505af1158015610372573d6000803e3d6000fd5b60008054604080516395d89b4160e01b815290516060936001600160a01b03909316926395d89b419260048082019391829003018186803b1580156103df57600080fd5b60005460405163a22cb46560e01b81526001600160a01b03848116600483015283151560248301529091169063a22cb465906044016104d4565b60005460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016104d4565b60005460405163c87b56dd60e01b8152600481018390526060916001600160a01b03169063c87b56dd9060240160006040518083038186803b158015610b4b57600080fd5b505afa158015610b5f573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526104999190810190610ee4565b6000805460405163e985e9c560e01b81526001600160a01b03858116600483015284811660248301529091169063e985e9c59060440160206040518083038186803b158015610bd557600080fd5b505afa158015610be9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061068a9190610ce9565b6000805460405162f4f4b560e81b815263ffffffff841660048201526001600160a01b039091169063f4f4b5009060240160206040518083038186803b158015610c5657600080fd5b505afa158015610c6a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104999190610ec7565b6000610ca1610c9c84611087565b611056565b9050828152838383011115610cb557600080fd5b828260208301376000602084830101529392505050565b600060208284031215610cde57600080fd5b813561068a816110f1565b600060208284031215610cfb57600080fd5b815161068a816110f1565b60008060408385031215610d1957600080fd5b8235610d24816110f1565b91506020830135610d34816110f1565b809150509250929050565b600080600060608486031215610d5457600080fd5b8335610d5f816110f1565b92506020840135610d6f816110f1565b929592945050506040919091013590565b60008060008060808587031215610d9657600080fd5b8435610da1816110f1565b93506020850135610db1816110f1565b925060408501359150606085013567ffffffffffffffff811115610dd457600080fd5b8501601f81018713610de557600080fd5b610df487823560208401610c8e565b91505092959194509250565b60008060408385031215610e1357600080fd5b8235610e1e816110f1565b91506020830135610d3481611109565b60008060408385031215610e4157600080fd5b8235610e4c816110f1565b946020939093013593505050565b600080600060608486031215610e6f57600080fd5b8335610e7a816110f1565b925060208401359150604084013567ffffffffffffffff811115610e9d57600080fd5b8401601f81018613610eae57600080fd5b610ebd86823560208401610c8e565b9150509250925092565b600060208284031215610ed957600080fd5b815161068a81611109565b600060208284031215610ef657600080fd5b815167ffffffffffffffff811115610f0d57600080fd5b8201601f81018413610f1e57600080fd5b8051610f2c610c9c82611087565b818152856020838501011115610f4157600080fd5b610f528260208301602086016110af565b95945050505050565b600060208284031215610f6d57600080fd5b5035919050565b600060208284031215610f8657600080fd5b5051919050565b600060208284031215610f9f57600080fd5b813563ffffffff8116811461068a57600080fd5b60008151808452610fcb8160208601602086016110af565b601f01601f19169290920160200192915050565b6001600160a01b038581168252841660208201526040810183905260806060820181905260009061101290830184610fb3565b9695505050505050565b60018060a01b0384168152826020820152606060408201526000610f526060830184610fb3565b60208152600061068a6020830184610fb3565b604051601f8201601f1916810167ffffffffffffffff8111828210171561107f5761107f6110db565b604052919050565b600067ffffffffffffffff8211156110a1576110a16110db565b50601f01601f191660200190565b60005b838110156110ca5781810151838201526020016110b2565b838111156109305750506000910152565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b038116811461110657600080fd5b50565b801515811461110657600080fdfea2646970667358221220cd50bb20b48b73eddb390585af8699a01cd5f49b79ab50aa532df41df639f36764736f6c63430008070033
\ No newline at end of file
+608060405234801561001057600080fd5b5060405161168d38038061168d83398101604081905261002f91610054565b600080546001600160a01b0319166001600160a01b0392909216919091179055610084565b60006020828403121561006657600080fd5b81516001600160a01b038116811461007d57600080fd5b9392505050565b6115fa806100936000396000f3fe608060405234801561001057600080fd5b50600436106101a95760003560e01c806350bb4e7f116100f9578063a22cb46511610097578063d4eac26d11610071578063d4eac26d1461036b578063e6c5ce6f1461037e578063e985e9c514610391578063f4f4b500146103a457600080fd5b8063a22cb46514610332578063a9059cbb14610345578063c87b56dd1461035857600080fd5b806370a08231116100d357806370a082311461030757806375794a3c1461031a5780637d64bcb41461032257806395d89b411461032a57600080fd5b806350bb4e7f146102ce57806360a11672146102e15780636352211e146102f457600080fd5b80632f745c591161016657806342842e0e1161014057806342842e0e1461028257806342966c681461029557806344a9945e146102a85780634f6ccce7146102bb57600080fd5b80632f745c5914610249578063365430061461025c57806340c10f191461026f57600080fd5b806305d2035b146101ae57806306fdde03146101cb578063081812fc146101e0578063095ea7b31461020b57806318160ddd1461022057806323b872dd14610236575b600080fd5b6101b66103b7565b60405190151581526020015b60405180910390f35b6101d3610443565b6040516101c2919061148a565b6101f36101ee366004611277565b6104c3565b6040516001600160a01b0390911681526020016101c2565b61021e61021936600461118c565b610547565b005b6102286105b2565b6040519081526020016101c2565b61021e610244366004610eff565b610639565b61022861025736600461118c565b6106ad565b6101b661026a366004610fac565b610739565b6101b661027d36600461118c565b6107be565b61021e610290366004610eff565b6107f8565b61021e6102a3366004611277565b610839565b6101b66102b63660046110b1565b61089a565b6102286102c9366004611277565b6108cd565b6101b66102dc3660046111b8565b61094b565b61021e6102ef366004610f40565b6109da565b6101f3610302366004611277565b610a49565b610228610315366004610e8c565b610a7b565b610228610aae565b6101b6610afd565b6101d3610b62565b61021e61034036600461115e565b610ba6565b61021e61035336600461118c565b610be0565b6101d3610366366004611277565b610c19565b61021e6103793660046112a9565b610c9b565b6101d361038c366004611277565b610ccd565b6101f361039f366004610ec6565b610cff565b6101b66103b23660046112f0565b610d85565b60008060009054906101000a90046001600160a01b03166001600160a01b03166305d2035b6040518163ffffffff1660e01b815260040160206040518083038186803b15801561040657600080fd5b505afa15801561041a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061043e9190611211565b905090565b60008054604080516306fdde0360e01b815290516060936001600160a01b03909316926306fdde039260048082019391829003018186803b15801561048757600080fd5b505afa15801561049b573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261043e919081019061122e565b6000805460405163020604bf60e21b8152600481018490526001600160a01b039091169063081812fc906024015b60206040518083038186803b15801561050957600080fd5b505afa15801561051d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105419190610ea9565b92915050565b60005460405163095ea7b360e01b81526001600160a01b038481166004830152602482018490529091169063095ea7b3906044015b600060405180830381600087803b15801561059657600080fd5b505af11580156105aa573d6000803e3d6000fd5b505050505050565b60008060009054906101000a90046001600160a01b03166001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561060157600080fd5b505afa158015610615573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061043e9190611290565b6000546040516323b872dd60e01b81526001600160a01b038581166004830152848116602483015260448201849052909116906323b872dd906064015b600060405180830381600087803b15801561069057600080fd5b505af11580156106a4573d6000803e3d6000fd5b50505050505050565b60008054604051632f745c5960e01b81526001600160a01b0385811660048301526024820185905290911690632f745c599060440160206040518083038186803b1580156106fa57600080fd5b505afa15801561070e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107329190611290565b9392505050565b60008054604051631b2a180360e11b81526001600160a01b039091169063365430069061076c908690869060040161137f565b602060405180830381600087803b15801561078657600080fd5b505af115801561079a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107329190611211565b600080546040516340c10f1960e01b81526001600160a01b03858116600483015260248201859052909116906340c10f199060440161076c565b600054604051632142170760e11b81526001600160a01b038581166004830152848116602483015260448201849052909116906342842e0e90606401610676565b600054604051630852cd8d60e31b8152600481018390526001600160a01b03909116906342966c6890602401600060405180830381600087803b15801561087f57600080fd5b505af1158015610893573d6000803e3d6000fd5b5050505050565b60008054604051632254ca2f60e11b81526001600160a01b03909116906344a9945e9061076c9086908690600401611404565b60008054604051634f6ccce760e01b8152600481018490526001600160a01b0390911690634f6ccce7906024015b60206040518083038186803b15801561091357600080fd5b505afa158015610927573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105419190611290565b600080546040516350bb4e7f60e01b81526001600160a01b03909116906350bb4e7f906109809087908790879060040161145a565b602060405180830381600087803b15801561099a57600080fd5b505af11580156109ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109d29190611211565b949350505050565b6000546040516330508b3960e11b81526001600160a01b03909116906360a1167290610a10908790879087908790600401611342565b600060405180830381600087803b158015610a2a57600080fd5b505af1158015610a3e573d6000803e3d6000fd5b505050505b50505050565b600080546040516331a9108f60e11b8152600481018490526001600160a01b0390911690636352211e906024016104f1565b600080546040516370a0823160e01b81526001600160a01b038481166004830152909116906370a08231906024016108fb565b60008060009054906101000a90046001600160a01b03166001600160a01b03166375794a3c6040518163ffffffff1660e01b815260040160206040518083038186803b15801561060157600080fd5b60008060009054906101000a90046001600160a01b03166001600160a01b0316637d64bcb46040518163ffffffff1660e01b8152600401602060405180830381600087803b158015610b4e57600080fd5b505af115801561041a573d6000803e3d6000fd5b60008054604080516395d89b4160e01b815290516060936001600160a01b03909316926395d89b419260048082019391829003018186803b15801561048757600080fd5b60005460405163a22cb46560e01b81526001600160a01b03848116600483015283151560248301529091169063a22cb4659060440161057c565b60005460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb9060440161057c565b60005460405163c87b56dd60e01b8152600481018390526060916001600160a01b03169063c87b56dd906024015b60006040518083038186803b158015610c5f57600080fd5b505afa158015610c73573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052610541919081019061122e565b60005460405163d4eac26d60e01b81526001600160a01b039091169063d4eac26d9061057c908590859060040161149d565b60005460405163e6c5ce6f60e01b8152600481018390526060916001600160a01b03169063e6c5ce6f90602401610c47565b6000805460405163e985e9c560e01b81526001600160a01b03858116600483015284811660248301529091169063e985e9c59060440160206040518083038186803b158015610d4d57600080fd5b505afa158015610d61573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107329190610ea9565b6000805460405162f4f4b560e81b815263ffffffff841660048201526001600160a01b039091169063f4f4b5009060240160206040518083038186803b158015610dce57600080fd5b505afa158015610de2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105419190611211565b6000610e19610e1484611534565b6114df565b9050828152838383011115610e2d57600080fd5b61073283602083018461155c565b600082601f830112610e4c57600080fd5b8135610e5a610e1482611534565b818152846020838601011115610e6f57600080fd5b816020850160208301376000918101602001919091529392505050565b600060208284031215610e9e57600080fd5b81356107328161159e565b600060208284031215610ebb57600080fd5b81516107328161159e565b60008060408385031215610ed957600080fd5b8235610ee48161159e565b91506020830135610ef48161159e565b809150509250929050565b600080600060608486031215610f1457600080fd5b8335610f1f8161159e565b92506020840135610f2f8161159e565b929592945050506040919091013590565b60008060008060808587031215610f5657600080fd5b8435610f618161159e565b93506020850135610f718161159e565b925060408501359150606085013567ffffffffffffffff811115610f9457600080fd5b610fa087828801610e3b565b91505092959194509250565b60008060408385031215610fbf57600080fd5b8235610fca8161159e565b915060208381013567ffffffffffffffff80821115610fe857600080fd5b818601915086601f830112610ffc57600080fd5b813561100a610e1482611510565b8082825285820191508585018a878560051b880101111561102a57600080fd5b60005b848110156110a05781358681111561104457600080fd5b87016040818e03601f1901121561105a57600080fd5b6110626114b6565b89820135815260408201358881111561107a57600080fd5b6110888f8c83860101610e3b565b828c015250855250928701929087019060010161102d565b50979a909950975050505050505050565b600080604083850312156110c457600080fd5b82356110cf8161159e565b915060208381013567ffffffffffffffff8111156110ec57600080fd5b8401601f810186136110fd57600080fd5b803561110b610e1482611510565b80828252848201915084840189868560051b870101111561112b57600080fd5b600094505b8385101561114e578035835260019490940193918501918501611130565b5080955050505050509250929050565b6000806040838503121561117157600080fd5b823561117c8161159e565b91506020830135610ef4816115b6565b6000806040838503121561119f57600080fd5b82356111aa8161159e565b946020939093013593505050565b6000806000606084860312156111cd57600080fd5b83356111d88161159e565b925060208401359150604084013567ffffffffffffffff8111156111fb57600080fd5b61120786828701610e3b565b9150509250925092565b60006020828403121561122357600080fd5b8151610732816115b6565b60006020828403121561124057600080fd5b815167ffffffffffffffff81111561125757600080fd5b8201601f8101841361126857600080fd5b6109d284825160208401610e06565b60006020828403121561128957600080fd5b5035919050565b6000602082840312156112a257600080fd5b5051919050565b600080604083850312156112bc57600080fd5b82359150602083013567ffffffffffffffff8111156112da57600080fd5b6112e685828601610e3b565b9150509250929050565b60006020828403121561130257600080fd5b813563ffffffff8116811461073257600080fd5b6000815180845261132e81602086016020860161155c565b601f01601f19169290920160200192915050565b6001600160a01b038581168252841660208201526040810183905260806060820181905260009061137590830184611316565b9695505050505050565b6001600160a01b0383168152604060208083018290528351828401819052600092916060600583901b860181019290860190878301865b828110156113f557888603605f190184528151805187528501518587018890526113e288880182611316565b96505092840192908401906001016113b6565b50939998505050505050505050565b6001600160a01b038316815260406020808301829052835191830182905260009184820191906060850190845b8181101561144d57845183529383019391830191600101611431565b5090979650505050505050565b60018060a01b03841681528260208201526060604082015260006114816060830184611316565b95945050505050565b6020815260006107326020830184611316565b8281526040602082015260006109d26040830184611316565b6040805190810167ffffffffffffffff811182821017156114d9576114d9611588565b60405290565b604051601f8201601f1916810167ffffffffffffffff8111828210171561150857611508611588565b604052919050565b600067ffffffffffffffff82111561152a5761152a611588565b5060051b60200190565b600067ffffffffffffffff82111561154e5761154e611588565b50601f01601f191660200190565b60005b8381101561157757818101518382015260200161155f565b83811115610a435750506000910152565b634e487b7160e01b600052604160045260246000fd5b6001600160a01b03811681146115b357600080fd5b50565b80151581146115b357600080fdfea2646970667358221220669a75a3efcdc6b60606caa5c7e41cab1d727e89a03fd231d1cda27f4de159a064736f6c63430008070033
\ No newline at end of file
modifiedtests/src/eth/proxy/UniqueNFTProxy.soldiffbeforeafterboth
--- a/tests/src/eth/proxy/UniqueNFTProxy.sol
+++ b/tests/src/eth/proxy/UniqueNFTProxy.sol
@@ -156,4 +156,36 @@
     {
         return proxied.supportsInterface(interfaceId);
     }
+
+    function setVariableMetadata(uint256 tokenId, bytes memory data)
+        external
+        override
+    {
+        return proxied.setVariableMetadata(tokenId, data);
+    }
+
+    function getVariableMetadata(uint256 tokenId)
+        external
+        view
+        override
+        returns (bytes memory)
+    {
+        return proxied.getVariableMetadata(tokenId);
+    }
+
+    function mintBulk(address to, uint256[] memory tokenIds)
+        external
+        override
+        returns (bool)
+    {
+        return proxied.mintBulk(to, tokenIds);
+    }
+
+    function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)
+        external
+        override
+        returns (bool)
+    {
+        return proxied.mintBulkWithTokenURI(to, tokens);
+    }
 }
modifiedtests/src/eth/proxy/nonFungibleProxy.test.tsdiffbeforeafterboth
--- a/tests/src/eth/proxy/nonFungibleProxy.test.ts
+++ b/tests/src/eth/proxy/nonFungibleProxy.test.ts
@@ -4,7 +4,7 @@
 //
 
 import privateKey from '../../substrate/privateKey';
-import { createCollectionExpectSuccess, createItemExpectSuccess } from '../../util/helpers';
+import { createCollectionExpectSuccess, createItemExpectSuccess, setVariableMetaDataExpectSuccess } from '../../util/helpers';
 import { collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents } from '../util/helpers';
 import nonFungibleAbi from '../nonFungibleAbi.json';
 import { expect } from 'chai';
@@ -96,13 +96,11 @@
     {
       const nextTokenId = await contract.methods.nextTokenId().call();
       expect(nextTokenId).to.be.equal('1');
-      console.log('Before mint');
       const result = await contract.methods.mintWithTokenURI(
         receiver,
         nextTokenId,
         'Test URI',
       ).send({ from: caller });
-      console.log('After mint');
       const events = normalizeEvents(result.events);
 
       expect(events).to.be.deep.equal([
@@ -121,6 +119,69 @@
       expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
     }
   });
+  itWeb3('Can perform mintBulk()', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+
+    const caller = await createEthAccountWithBalance(api, web3);
+    const receiver = createEthAccount(web3);
+
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, { from: caller, ...GAS_ARGS }));
+    const changeAdminTx = api.tx.nft.addCollectionAdmin(collection, { ethereum: contract.options.address });
+    await submitTransactionAsync(alice, changeAdminTx);
+
+    {
+      const nextTokenId = await contract.methods.nextTokenId().call();
+      expect(nextTokenId).to.be.equal('1');
+      const result = await contract.methods.mintBulkWithTokenURI(
+        receiver,
+        [
+          [nextTokenId, 'Test URI 0'],
+          [+nextTokenId + 1, 'Test URI 1'],
+          [+nextTokenId + 2, 'Test URI 2'],
+        ],
+      ).send({ from: caller });
+      const events = normalizeEvents(result.events);
+
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: receiver,
+            tokenId: nextTokenId,
+          },
+        },
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: receiver,
+            tokenId: String(+nextTokenId + 1),
+          },
+        },
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: receiver,
+            tokenId: String(+nextTokenId + 2),
+          },
+        },
+      ]);
+
+      await waitNewBlocks(api, 1);
+      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI 0');
+      expect(await contract.methods.tokenURI(+nextTokenId + 1).call()).to.be.equal('Test URI 1');
+      expect(await contract.methods.tokenURI(+nextTokenId + 2).call()).to.be.equal('Test URI 2');
+    }
+  });
 
   itWeb3('Can perform burn()', async ({ web3, api }) => {
     const collection = await createCollectionExpectSuccess({
@@ -267,4 +328,35 @@
       expect(+balance).to.equal(1);
     }
   });
+
+  itWeb3('Can perform getVariableMetadata', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+    const caller = await createEthAccountWithBalance(api, web3);
+
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, { from: caller, ...GAS_ARGS }));
+    const item = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: contract.options.address });
+    await setVariableMetaDataExpectSuccess(alice, collection, item, [1, 2, 3]);
+    
+    expect(await contract.methods.getVariableMetadata(item).call()).to.be.equal('0x010203');
+  });
+
+  itWeb3('Can perform setVariableMetadata', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+    const caller = await createEthAccountWithBalance(api, web3);
+
+    const address = collectionIdToAddress(collection);
+    const contract = await proxyWrap(api, web3, new web3.eth.Contract(nonFungibleAbi as any, address, { from: caller, ...GAS_ARGS }));
+    const item = await createItemExpectSuccess(alice, collection, 'NFT', { ethereum: contract.options.address });
+    
+    expect(await contract.methods.setVariableMetadata(item, '0x010203').send({ from: caller }));
+    await waitNewBlocks(api, 1);
+    expect(await contract.methods.getVariableMetadata(item).call()).to.be.equal('0x010203');
+  });
 });
modifiedtests/src/eth/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/eth/util/helpers.ts
+++ b/tests/src/eth/util/helpers.ts
@@ -269,4 +269,4 @@
   expect(after < before).to.be.true;
 
   return before - after;
-}
\ No newline at end of file
+}