git.delta.rocks / unique-network / refs/commits / 8e4698bc6eb3

difftreelog

Merge pull request #160 from UniqueNetwork/feature/erc-mintable-burnable

kozyrevdev2021-08-30parents: #da8dccd #ac8c84f.patch.diff
in: master
Implement ERC721Burnable/ERC721Mintable

13 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5852,34 +5852,34 @@
 [[package]]
 name = "pallet-scheduler"
 version = "3.0.0"
+source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.8#74101dc21cfffb4c2d014fcc28edc166d5ca1b16"
 dependencies = [
  "frame-benchmarking",
  "frame-support",
  "frame-system",
  "log",
  "parity-scale-codec",
- "serde",
- "sp-core",
  "sp-io",
  "sp-runtime",
  "sp-std",
- "substrate-test-utils",
- "up-sponsorship",
 ]
 
 [[package]]
 name = "pallet-scheduler"
 version = "3.0.0"
-source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.8#74101dc21cfffb4c2d014fcc28edc166d5ca1b16"
 dependencies = [
  "frame-benchmarking",
  "frame-support",
  "frame-system",
  "log",
  "parity-scale-codec",
+ "serde",
+ "sp-core",
  "sp-io",
  "sp-runtime",
  "sp-std",
+ "substrate-test-utils",
+ "up-sponsorship",
 ]
 
 [[package]]
@@ -10695,13 +10695,13 @@
 [[package]]
 name = "substrate-wasm-builder"
 version = "4.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93a3d51ad6abbc408b03ea962062bfcc959b438a318d7d4bedd181e1effd0610"
+source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.8#74101dc21cfffb4c2d014fcc28edc166d5ca1b16"
 dependencies = [
  "ansi_term 0.12.1",
  "atty",
  "build-helper",
- "cargo_metadata 0.12.3",
+ "cargo_metadata 0.13.1",
+ "sp-maybe-compressed-blob",
  "tempfile",
  "toml",
  "walkdir",
@@ -10711,13 +10711,13 @@
 [[package]]
 name = "substrate-wasm-builder"
 version = "4.0.0"
-source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.8#74101dc21cfffb4c2d014fcc28edc166d5ca1b16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93a3d51ad6abbc408b03ea962062bfcc959b438a318d7d4bedd181e1effd0610"
 dependencies = [
  "ansi_term 0.12.1",
  "atty",
  "build-helper",
- "cargo_metadata 0.13.1",
- "sp-maybe-compressed-blob",
+ "cargo_metadata 0.12.3",
  "tempfile",
  "toml",
  "walkdir",
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
@@ -73,6 +73,20 @@
 			}
 		}
 	}
+
+	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);
+		}
+	}
+
+	fn expand_event_generator(&self) -> proc_macro2::TokenStream {
+		let name = &self.name;
+		quote! {
+			#name::generate_solidity_interface(out_set, is_impl);
+		}
+	}
 }
 
 #[derive(Default)]
@@ -109,12 +123,15 @@
 
 struct MethodArg {
 	name: Ident,
+	camel_name: String,
 	ty: Ident,
 }
 impl MethodArg {
 	fn try_from(value: &PatType) -> syn::Result<Self> {
+		let name = parse_ident_from_pat(&value.pat)?.clone();
 		Ok(Self {
-			name: parse_ident_from_pat(&value.pat)?.clone(),
+			camel_name: cases::camelcase::to_camel_case(&name.to_string()),
+			name,
 			ty: parse_ident_from_type(&value.ty, false)?.clone(),
 		})
 	}
@@ -168,10 +185,10 @@
 	}
 
 	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {
-		let name = &self.name.to_string();
+		let camel_name = &self.camel_name.to_string();
 		let ty = &self.ty;
 		quote! {
-			<NamedArgument<#ty>>::new(#name)
+			<NamedArgument<#ty>>::new(#camel_name)
 		}
 	}
 }
@@ -397,7 +414,11 @@
 		};
 		let result = &self.result;
 
-		let args = self.args.iter().map(MethodArg::expand_solidity_argument);
+		let args = self
+			.args
+			.iter()
+			.filter(|a| !a.is_special())
+			.map(MethodArg::expand_solidity_argument);
 
 		quote! {
 			SolidityFunction {
@@ -475,6 +496,24 @@
 		let call_variants_this = self.methods.iter().map(Method::expand_variant_call);
 		let solidity_functions = self.methods.iter().map(Method::expand_solidity_function);
 
+		// TODO: Inline inline_is
+		let solidity_is = self
+			.info
+			.is
+			.0
+			.iter()
+			.chain(self.info.inline_is.0.iter())
+			.map(|is| is.name.to_string());
+		let solidity_events_is = self.info.events.0.iter().map(|is| is.name.to_string());
+		let solidity_generators = self
+			.info
+			.is
+			.0
+			.iter()
+			.chain(self.info.inline_is.0.iter())
+			.map(Is::expand_generator);
+		let solidity_event_generators = self.info.events.0.iter().map(Is::expand_event_generator);
+
 		// let methods = self.methods.iter().map(Method::solidity_def);
 
 		quote! {
@@ -505,18 +544,40 @@
 						)*
 					)
 				}
-				pub fn generate_solidity_interface() -> string {
+				pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {
 					use evm_coder::solidity::*;
 					use core::fmt::Write;
 					let interface = SolidityInterface {
 						name: #solidity_name,
+						is: &["Dummy", #(
+							#solidity_is,
+						)* #(
+							#solidity_events_is,
+						)* ],
 						functions: (#(
 							#solidity_functions,
 						)*),
 					};
+					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());
+					} else {
+						out_set.insert("// Common stubs holder\ninterface Dummy {\n}\n".into());
+					}
+					#(
+						#solidity_generators
+					)*
+					#(
+						#solidity_event_generators
+					)*
+
 					let mut out = string::new();
-					let _ = interface.format(&mut out);
-					out
+					// In solidity interface usage (is) should be preceeded by interface definition
+					// This comment helps to sort it in a set
+					if #solidity_name.starts_with("Inline") {
+						out.push_str("// Inline\n");
+					}
+					let _ = interface.format(is_impl, &mut out);
+					out_set.insert(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
@@ -1,3 +1,4 @@
+use inflector::cases;
 use syn::{Data, DeriveInput, Field, Fields, Ident, Variant, spanned::Spanned};
 use std::fmt::Write;
 use quote::quote;
@@ -6,6 +7,7 @@
 
 struct EventField {
 	name: Ident,
+	camel_name: String,
 	ty: Ident,
 	indexed: bool,
 }
@@ -24,10 +26,18 @@
 		}
 		Ok(Self {
 			name: name.to_owned(),
+			camel_name: cases::camelcase::to_camel_case(&name.to_string()),
 			ty: ty.to_owned(),
 			indexed,
 		})
 	}
+	fn expand_solidity_argument(&self) -> proc_macro2::TokenStream {
+		let camel_name = &self.camel_name;
+		let ty = &self.ty;
+		quote! {
+			<NamedArgument<#ty>>::new(#camel_name)
+		}
+	}
 }
 
 struct Event {
@@ -116,6 +126,21 @@
 			)*];
 		}
 	}
+
+	fn expand_solidity_function(&self) -> proc_macro2::TokenStream {
+		let name = self.name.to_string();
+		let args = self.fields.iter().map(EventField::expand_solidity_argument);
+		quote! {
+			SolidityEvent {
+				name: #name,
+				args: (
+					#(
+						#args,
+					)*
+				),
+			}
+		}
+	}
 }
 
 pub struct Events {
@@ -144,12 +169,30 @@
 
 		let consts = self.events.iter().map(Event::expand_consts);
 		let serializers = self.events.iter().map(Event::expand_serializers);
+		let solidity_name = self.name.to_string();
+		let solidity_functions = self.events.iter().map(Event::expand_solidity_function);
 
 		quote! {
 			impl #name {
 				#(
 					#consts
 				)*
+
+				pub fn generate_solidity_interface(out_set: &mut sp_std::collections::btree_set::BTreeSet<string>, is_impl: bool) {
+					use evm_coder::solidity::*;
+					use core::fmt::Write;
+					let interface = SolidityInterface {
+						name: #solidity_name,
+						is: &[],
+						functions: (#(
+							#solidity_functions,
+						)*),
+					};
+					let mut out = string::new();
+					out.push_str("// Inline\n");
+					let _ = interface.format(is_impl, &mut out);
+					out_set.insert(out);
+				}
 			}
 
 			#[automatically_derived]
modifiedcrates/evm-coder/src/abi.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/abi.rs
+++ b/crates/evm-coder/src/abi.rs
@@ -19,11 +19,16 @@
 #[derive(Clone)]
 pub struct AbiReader<'i> {
 	buf: &'i [u8],
+	subresult_offset: usize,
 	offset: usize,
 }
 impl<'i> AbiReader<'i> {
 	pub fn new(buf: &'i [u8]) -> Self {
-		Self { buf, offset: 0 }
+		Self {
+			buf,
+			subresult_offset: 0,
+			offset: 0,
+		}
 	}
 	pub fn new_call(buf: &'i [u8]) -> Result<(u32, Self)> {
 		if buf.len() < 4 {
@@ -32,11 +37,18 @@
 		let mut method_id = [0; 4];
 		method_id.copy_from_slice(&buf[0..4]);
 
-		Ok((u32::from_be_bytes(method_id), Self { buf, offset: 4 }))
+		Ok((
+			u32::from_be_bytes(method_id),
+			Self {
+				buf,
+				subresult_offset: 4,
+				offset: 4,
+			},
+		))
 	}
 
 	fn read_padleft<const S: usize>(&mut self) -> Result<[u8; S]> {
-		if self.buf.len() - self.offset < 32 {
+		if self.buf.len() - self.offset < ABI_ALIGNMENT {
 			return Err(Error::Error(ExitError::OutOfOffset));
 		}
 		let mut block = [0; S];
@@ -79,6 +91,9 @@
 		}
 		Ok(subresult.buf[subresult.offset..subresult.offset + length].into())
 	}
+	pub fn string(&mut self) -> Result<string> {
+		string::from_utf8(self.bytes()?).map_err(|_| Error::Error(ExitError::InvalidRange))
+	}
 
 	pub fn uint32(&mut self) -> Result<u32> {
 		Ok(u32::from_be_bytes(self.read_padleft()?))
@@ -103,9 +118,13 @@
 
 	fn subresult(&mut self) -> Result<AbiReader<'i>> {
 		let offset = self.read_usize()?;
+		if offset + self.subresult_offset > self.buf.len() {
+			return Err(Error::Error(ExitError::InvalidRange));
+		}
 		Ok(AbiReader {
 			buf: self.buf,
-			offset: offset + self.offset,
+			subresult_offset: offset + self.subresult_offset,
+			offset: offset + self.subresult_offset,
 		})
 	}
 
@@ -196,7 +215,7 @@
 		for (static_offset, part) in self.dynamic_part {
 			let part_offset = self.static_part.len();
 
-			let encoded_dynamic_offset = usize::to_be_bytes(part_offset - static_offset);
+			let encoded_dynamic_offset = usize::to_be_bytes(part_offset);
 			self.static_part[static_offset + ABI_ALIGNMENT - encoded_dynamic_offset.len()
 				..static_offset + ABI_ALIGNMENT]
 				.copy_from_slice(&encoded_dynamic_offset);
@@ -226,6 +245,7 @@
 impl_abi_readable!(H160, address);
 impl_abi_readable!(Vec<u8>, bytes);
 impl_abi_readable!(bool, bool);
+impl_abi_readable!(string, string);
 
 pub trait AbiWrite {
 	fn abi_write(&self, writer: &mut AbiWriter);
@@ -285,3 +305,54 @@
 		writer
 	}}
 }
+
+#[cfg(test)]
+pub mod test {
+	use super::{AbiReader, AbiWriter};
+	use hex_literal::hex;
+
+	#[test]
+	fn dynamic_after_static() {
+		let mut encoder = AbiWriter::new();
+		encoder.bool(&true);
+		encoder.string("test");
+		let encoded = encoder.finish();
+
+		let mut encoder = AbiWriter::new();
+		encoder.bool(&true);
+		// Offset to subresult
+		encoder.uint32(&(32 * 2));
+		// Len of "test"
+		encoder.uint32(&4);
+		encoder.write_padright(&[b't', b'e', b's', b't']);
+		let alternative_encoded = encoder.finish();
+
+		assert_eq!(encoded, alternative_encoded);
+
+		let mut decoder = AbiReader::new(&encoded);
+		assert_eq!(decoder.bool().unwrap(), true);
+		assert_eq!(decoder.string().unwrap(), "test");
+	}
+
+	#[test]
+	fn mint_sample() {
+		let (call, mut decoder) = AbiReader::new_call(&hex!(
+			"
+				50bb4e7f
+				000000000000000000000000ad2c0954693c2b5404b7e50967d3481bea432374
+				0000000000000000000000000000000000000000000000000000000000000001
+				0000000000000000000000000000000000000000000000000000000000000060
+				0000000000000000000000000000000000000000000000000000000000000008
+				5465737420555249000000000000000000000000000000000000000000000000
+			"
+		))
+		.unwrap();
+		assert_eq!(call, 0x50bb4e7f);
+		assert_eq!(
+			format!("{:?}", decoder.address().unwrap()),
+			"0xad2c0954693c2b5404b7e50967d3481bea432374"
+		);
+		assert_eq!(decoder.uint32().unwrap(), 1);
+		assert_eq!(decoder.string().unwrap(), "Test URI");
+	}
+}
modifiedcrates/evm-coder/src/solidity.rsdiffbeforeafterboth
--- a/crates/evm-coder/src/solidity.rs
+++ b/crates/evm-coder/src/solidity.rs
@@ -6,37 +6,44 @@
 
 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 is_void() -> bool {
 		false
 	}
 }
 
 macro_rules! solidity_type_name {
-    ($($ty:ident => $name:expr),* $(,)?) => {
+    ($($ty:ident => $name:literal = $default:literal),* $(,)?) => {
         $(
             impl SolidityTypeName for $ty {
                 fn solidity_name(writer: &mut impl core::fmt::Write) -> core::fmt::Result {
                     write!(writer, $name)
                 }
+				fn solidity_default(writer: &mut impl core::fmt::Write) -> core::fmt::Result {
+					write!(writer, $default)
+				}
             }
         )*
     };
 }
 
 solidity_type_name! {
-	uint8 => "uint8",
-	uint32 => "uint32",
-	uint128 => "uint128",
-	uint256 => "uint256",
-	address => "address",
-	string => "memory string",
-	bytes => "memory bytes",
-	bool => "bool",
+	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",
 }
 impl SolidityTypeName for void {
 	fn solidity_name(_writer: &mut impl fmt::Write) -> fmt::Result {
 		Ok(())
 	}
+	fn solidity_default(_writer: &mut impl fmt::Write) -> fmt::Result {
+		Ok(())
+	}
 	fn is_void() -> bool {
 		true
 	}
@@ -44,6 +51,8 @@
 
 pub trait SolidityArguments {
 	fn solidity_name(&self, writer: &mut impl fmt::Write) -> 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 is_empty(&self) -> bool {
 		self.len() == 0
 	}
@@ -61,6 +70,12 @@
 			Ok(())
 		}
 	}
+	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 len(&self) -> usize {
 		if T::is_void() {
 			0
@@ -87,6 +102,12 @@
 			Ok(())
 		}
 	}
+	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 len(&self) -> usize {
 		if T::is_void() {
 			0
@@ -100,6 +121,12 @@
 	fn solidity_name(&self, _writer: &mut impl fmt::Write) -> 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 {
+		Ok(())
+	}
 	fn len(&self) -> usize {
 		0
 	}
@@ -122,13 +149,43 @@
         )* );
 		Ok(())
 	}
+	fn solidity_get(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+		for_tuples!( #(
+            Tuple.solidity_get(writer)?;
+        )* );
+		Ok(())
+	}
+	fn solidity_default(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+		if self.is_empty() {
+			Ok(())
+		} else if self.len() == 1 {
+			for_tuples!( #(
+				Tuple.solidity_default(writer)?;
+			)* );
+			Ok(())
+		} else {
+			write!(writer, "(")?;
+			let mut first = true;
+			for_tuples!( #(
+				if !Tuple.is_empty() {
+					if !first {
+						write!(writer, ", ")?;
+					}
+					first = false;
+					Tuple.solidity_name(writer)?;
+				}
+			)* );
+			write!(writer, ")")?;
+			Ok(())
+		}
+	}
 	fn len(&self) -> usize {
 		for_tuples!( #( Tuple.len() )+* )
 	}
 }
 
 pub trait SolidityFunctions {
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result;
+	fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result;
 }
 
 pub enum SolidityMutability {
@@ -143,10 +200,15 @@
 	pub mutability: SolidityMutability,
 }
 impl<A: SolidityArguments, R: SolidityArguments> SolidityFunctions for SolidityFunction<A, R> {
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
-		write!(writer, "function {}(", self.name)?;
+	fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
+		write!(writer, "\tfunction {}(", self.name)?;
 		self.args.solidity_name(writer)?;
-		write!(writer, ") external")?;
+		write!(writer, ")")?;
+		if is_impl {
+			write!(writer, " public")?;
+		} else {
+			write!(writer, " external")?;
+		}
 		match &self.mutability {
 			SolidityMutability::Pure => write!(writer, " pure")?,
 			SolidityMutability::View => write!(writer, " view")?,
@@ -157,7 +219,25 @@
 			self.result.solidity_name(writer)?;
 			write!(writer, ")")?;
 		}
-		writeln!(writer, ";")
+		if is_impl {
+			writeln!(writer, " {{")?;
+			writeln!(writer, "\t\trequire(false, stub_error);")?;
+			self.args.solidity_get(writer)?;
+			match &self.mutability {
+				SolidityMutability::Pure => {}
+				SolidityMutability::View => writeln!(writer, "\t\tdummy;")?,
+				SolidityMutability::Mutable => writeln!(writer, "\t\tdummy = 0;")?,
+			}
+			if !self.result.is_empty() {
+				write!(writer, "\t\treturn ")?;
+				self.result.solidity_default(writer)?;
+				writeln!(writer, ";")?;
+			}
+			writeln!(writer, "\t}}")?;
+		} else {
+			writeln!(writer, ";")?;
+		}
+		Ok(())
 	}
 }
 
@@ -165,10 +245,10 @@
 impl SolidityFunctions for Tuple {
 	for_tuples!( where #( Tuple: SolidityFunctions ),* );
 
-	fn solidity_name(&self, writer: &mut impl fmt::Write) -> fmt::Result {
+	fn solidity_name(&self, is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
 		let mut first = false;
 		for_tuples!( #(
-            Tuple.solidity_name(writer)?;
+            Tuple.solidity_name(is_impl, writer)?;
         )* );
 		Ok(())
 	}
@@ -176,14 +256,43 @@
 
 pub struct SolidityInterface<F: SolidityFunctions> {
 	pub name: &'static str,
+	pub is: &'static [&'static str],
 	pub functions: F,
 }
 
 impl<F: SolidityFunctions> SolidityInterface<F> {
-	pub fn format(&self, out: &mut impl fmt::Write) -> fmt::Result {
-		writeln!(out, "interface {} {{", self.name)?;
-		self.functions.solidity_name(out)?;
+	pub fn format(&self, is_impl: bool, out: &mut impl fmt::Write) -> fmt::Result {
+		if is_impl {
+			write!(out, "contract ")?;
+		} else {
+			write!(out, "interface ")?;
+		}
+		write!(out, "{}", self.name)?;
+		if !self.is.is_empty() {
+			write!(out, " is")?;
+			for (i, n) in self.is.iter().enumerate() {
+				if i != 0 {
+					write!(out, ",")?;
+				}
+				write!(out, " {}", n)?;
+			}
+		}
+		writeln!(out, " {{")?;
+		self.functions.solidity_name(is_impl, out)?;
 		writeln!(out, "}}")?;
 		Ok(())
 	}
 }
+
+pub struct SolidityEvent<A> {
+	pub name: &'static str,
+	pub args: A,
+}
+
+impl<A: SolidityArguments> SolidityFunctions for SolidityEvent<A> {
+	fn solidity_name(&self, _is_impl: bool, writer: &mut impl fmt::Write) -> fmt::Result {
+		write!(writer, "\tevent {}(", self.name)?;
+		self.args.solidity_name(writer)?;
+		writeln!(writer, ");")
+	}
+}
modifiedpallets/nft/src/eth/erc.rsdiffbeforeafterboth
--- a/pallets/nft/src/eth/erc.rs
+++ b/pallets/nft/src/eth/erc.rs
@@ -1,12 +1,15 @@
 use core::char::{decode_utf16, REPLACEMENT_CHARACTER};
 use evm_coder::{ToLog, execution::Result, solidity, solidity_interface, types::*};
+use nft_data_structs::{CreateItemData, CreateNftData};
 use core::convert::TryInto;
-use alloc::format;
-use crate::{Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList};
-use frame_support::storage::StorageDoubleMap;
+use crate::{
+	Allowances, Module, Balance, CollectionHandle, CollectionMode, Config, NftItemList,
+	ItemListIndex,
+};
+use frame_support::storage::{StorageMap, StorageDoubleMap};
 use pallet_evm::AddressMapping;
 use super::account::CrossAccountId;
-use sp_std::vec::Vec;
+use sp_std::{vec, vec::Vec};
 
 #[solidity_interface(name = "ERC165")]
 impl<T: Config> CollectionHandle<T> {
@@ -42,9 +45,15 @@
 
 #[solidity_interface(name = "ERC721Metadata", inline_is(InlineNameSymbol))]
 impl<T: Config> CollectionHandle<T> {
+	#[solidity(rename_selector = "tokenURI")]
 	fn token_uri(&self, token_id: uint256) -> Result<string> {
-		// TODO: We should standartize url prefix, maybe via offchain schema?
-		Ok(format!("unique.network/{}/{}", self.id, token_id))
+		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
+		Ok(string::from_utf8_lossy(
+			&<NftItemList<T>>::get(self.id, token_id)
+				.ok_or("token not found")?
+				.const_data,
+		)
+		.into())
 	}
 }
 
@@ -178,6 +187,93 @@
 	}
 }
 
+#[solidity_interface(name = "ERC721Burnable")]
+impl<T: Config> CollectionHandle<T> {
+	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let token_id = token_id.try_into().map_err(|_| "amount overflow")?;
+
+		<Module<T>>::burn_item_internal(&caller, &self, token_id, 1).map_err(|_| "burn error")?;
+		Ok(())
+	}
+}
+
+#[derive(ToLog)]
+pub enum ERC721MintableEvents {
+	#[allow(dead_code)]
+	MintingFinished {},
+}
+
+#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]
+impl<T: Config> CollectionHandle<T> {
+	fn minting_finished(&self) -> Result<bool> {
+		Ok(false)
+	}
+
+	fn mint(&mut self, caller: caller, to: address, token_id: uint256) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;
+		if <ItemListIndex>::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?
+			!= token_id
+		{
+			return Err("item id should be next".into());
+		}
+
+		<Module<T>>::create_item_internal(
+			&caller,
+			&self,
+			&to,
+			CreateItemData::NFT(CreateNftData {
+				const_data: vec![].try_into().unwrap(),
+				variable_data: vec![].try_into().unwrap(),
+			}),
+		)
+		.map_err(|_| "mint error")?;
+		Ok(true)
+	}
+
+	#[solidity(rename_selector = "mintWithTokenURI")]
+	fn mint_with_token_uri(
+		&mut self,
+		caller: caller,
+		to: address,
+		token_id: uint256,
+		token_uri: string,
+	) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let token_id: u32 = token_id.try_into().map_err(|_| "amount overflow")?;
+		if <ItemListIndex>::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?
+			!= token_id
+		{
+			return Err("item id should be next".into());
+		}
+
+		<Module<T>>::create_item_internal(
+			&caller,
+			&self,
+			&to,
+			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(),
+			}),
+		)
+		.map_err(|_| "mint error")?;
+		Ok(true)
+	}
+
+	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {
+		Err("not implementable".into())
+	}
+}
+
 #[solidity_interface(name = "ERC721UniqueExtensions")]
 impl<T: Config> CollectionHandle<T> {
 	#[solidity(rename_selector = "transfer")]
@@ -196,6 +292,13 @@
 			.map_err(|_| "transfer error")?;
 		Ok(())
 	}
+
+	fn next_token_id(&self) -> Result<uint256> {
+		Ok(ItemListIndex::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?
+			.into())
+	}
 }
 
 #[solidity_interface(
@@ -205,7 +308,9 @@
 		ERC721,
 		ERC721Metadata,
 		ERC721Enumerable,
-		ERC721UniqueExtensions
+		ERC721UniqueExtensions,
+		ERC721Mintable,
+		ERC721Burnable,
 	)
 )]
 impl<T: Config> CollectionHandle<T> {}
@@ -297,3 +402,32 @@
 
 #[solidity_interface(name = "UniqueFungible", is(ERC165, ERC20))]
 impl<T: Config> CollectionHandle<T> {}
+
+macro_rules! generate_code {
+	($name:ident, $decl:ident, $is_impl:literal) => {
+		#[test]
+		#[ignore]
+		fn $name() {
+			use sp_std::collections::btree_set::BTreeSet;
+			let mut out = BTreeSet::new();
+			$decl::generate_solidity_interface(&mut out, $is_impl);
+			println!("=== SNIP START ===");
+			println!("// SPDX-License-Identifier: OTHER");
+			println!("// This code is automatically generated with `cargo test --package pallet-nft -- eth::erc::{} --exact --nocapture --ignored`", stringify!(name));
+			println!();
+			println!("pragma solidity >=0.8.0 <0.9.0;");
+			println!();
+			for b in out {
+				println!("{}", b);
+			}
+			println!("=== SNIP END ===");
+		}
+	};
+}
+
+// Not a tests, but code generators
+generate_code!(nft_impl, UniqueNFTCall, true);
+generate_code!(nft_iface, UniqueNFTCall, false);
+
+generate_code!(fungible_impl, UniqueFungibleCall, true);
+generate_code!(fungible_iface, UniqueFungibleCall, false);
modifiedpallets/nft/src/eth/stubs/ERC20.bindiffbeforeafterboth

binary blob — no preview

modifiedpallets/nft/src/eth/stubs/ERC20.soldiffbeforeafterboth
--- a/pallets/nft/src/eth/stubs/ERC20.sol
+++ b/pallets/nft/src/eth/stubs/ERC20.sol
@@ -1,69 +1,94 @@
 // SPDX-License-Identifier: OTHER
+// This code is automatically generated with `cargo test --package pallet-nft -- eth::erc::name --exact --nocapture --ignored`
 
 pragma solidity >=0.8.0 <0.9.0;
 
-contract ERC20 {
-	uint8 _dummy = 0;
-	string stub_error = "this contract does not exists, code for collections is implemented at pallet side";
+// Common stubs holder
+contract Dummy {
+	uint8 dummy;
+	string stub_error = "this contract is implemented in native";
+}
 
-	// 0x18160ddd
-	function totalSupply() external view returns (uint256) {
+// Inline
+contract ERC20Events {
+	event Transfer(address from, address to, uint256 value);
+	event Approval(address owner, address spender, uint256 value);
+}
+
+// Inline
+contract InlineNameSymbol is Dummy {
+	function name() public view returns (string memory) {
 		require(false, stub_error);
-		_dummy;
-		return 0;
+		dummy;
+		return "";
 	}
+	function symbol() public view returns (string memory) {
+		require(false, stub_error);
+		dummy;
+		return "";
+	}
+}
 
-	// 0x70a08231
-	function balanceOf(address account) external view returns (uint256) {
+// Inline
+contract InlineTotalSupply is Dummy {
+	function totalSupply() public view returns (uint256) {
 		require(false, stub_error);
-		account;
-		_dummy;
+		dummy;
 		return 0;
 	}
+}
 
-	// 0xa9059cbb
-	function transfer(address recipient, uint256 amount) external returns (bool) {
+contract ERC165 is Dummy {
+	function supportsInterface(uint32 interfaceId) public view returns (bool) {
 		require(false, stub_error);
-		recipient;
-		amount;
-		_dummy = 0;
+		interfaceId;
+		dummy;
 		return false;
 	}
+}
 
-	// 0xdd62ed3e
-	function allowance(address owner, address spender) external view returns (uint256) {
+contract ERC20 is Dummy, InlineNameSymbol, InlineTotalSupply, ERC20Events {
+	function decimals() public view returns (uint8) {
 		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+	function balanceOf(address owner) public view returns (uint256) {
+		require(false, stub_error);
 		owner;
-		spender;
-		return _dummy;
+		dummy;
+		return 0;
+	}
+	function transfer(address to, uint256 amount) public returns (bool) {
+		require(false, stub_error);
+		to;
+		amount;
+		dummy = 0;
+		return false;
 	}
-
-	// 0x095ea7b3
-	function approve(address spender, uint256 amount) external returns (bool) {
+	function transferFrom(address from, address to, uint256 amount) public returns (bool) {
 		require(false, stub_error);
-		spender;
+		from;
+		to;
 		amount;
-		_dummy = 0;
+		dummy = 0;
 		return false;
 	}
-
-	// 0x23b872dd
-	function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
+	function approve(address spender, uint256 amount) public returns (bool) {
 		require(false, stub_error);
-		sender;
-		recipient;
+		spender;
 		amount;
-		_dummy = 0;
+		dummy = 0;
 		return false;
 	}
+	function allowance(address owner, address spender) public view returns (uint256) {
+		require(false, stub_error);
+		owner;
+		spender;
+		dummy;
+		return 0;
+	}
+}
 
-	// While ERC165 is not required by spec of ERC20, better implement it
-	// 0x01ffc9a7
-	function supportsInterface(bytes4 interfaceID) public pure returns (bool) {
-		return 
-			// ERC20
-			interfaceID == 0x36372b07 || 
-			// ERC165
-			interfaceID == 0x01ffc9a7;
-	}
+contract UniqueFungible is Dummy, ERC165, ERC20 {
 }
\ No newline at end of file
modifiedpallets/nft/src/eth/stubs/ERC721.bindiffbeforeafterboth

binary blob — no preview

modifiedpallets/nft/src/eth/stubs/ERC721.soldiffbeforeafterboth
--- a/pallets/nft/src/eth/stubs/ERC721.sol
+++ b/pallets/nft/src/eth/stubs/ERC721.sol
@@ -1,163 +1,194 @@
 // SPDX-License-Identifier: OTHER
+// This code is automatically generated with `cargo test --package pallet-nft -- eth::erc::name --exact --nocapture --ignored`
 
 pragma solidity >=0.8.0 <0.9.0;
 
-contract ERC721 {
-    uint8 _dummy = 0;
-    address _dummy_addr = 0x0000000000000000000000000000000000000000;
-    string _dummy_string = "";
-    string stub_error =
-        "this contract does not exists, code for collections is implemented at pallet side";
+// Common stubs holder
+contract Dummy {
+	uint8 dummy;
+	string stub_error = "this contract is implemented in native";
+}
 
-    event Transfer(
-        address indexed from,
-        address indexed to,
-        uint256 indexed tokenId
-    );
+// Inline
+contract ERC721Events {
+	event Transfer(address from, address to, uint256 tokenId);
+	event Approval(address owner, address approved, uint256 tokenId);
+	event ApprovalForAll(address owner, address operator, bool approved);
+}
 
-    event Approval(
-        address indexed owner,
-        address indexed approved,
-        uint256 indexed tokenId
-    );
+// Inline
+contract ERC721MintableEvents {
+	event MintingFinished();
+}
 
-    event ApprovalForAll(
-        address indexed owner,
-        address indexed operator,
-        bool approved
-    );
+// Inline
+contract InlineNameSymbol is Dummy {
+	function name() public view returns (string memory) {
+		require(false, stub_error);
+		dummy;
+		return "";
+	}
+	function symbol() public view returns (string memory) {
+		require(false, stub_error);
+		dummy;
+		return "";
+	}
+}
 
-	// 0x18160ddd
-    function totalSupply() external view returns (uint256) {
-        require(false, stub_error);
-        return 0;
-    }
+// Inline
+contract InlineTotalSupply is Dummy {
+	function totalSupply() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+}
 
-    function name() external view returns (string memory res_name) {
-        require(false, stub_error);
-        res_name = _dummy_string;
-    }
+contract ERC165 is Dummy {
+	function supportsInterface(uint32 interfaceId) public view returns (bool) {
+		require(false, stub_error);
+		interfaceId;
+		dummy;
+		return false;
+	}
+}
 
-    function symbol() external view returns (string memory res_symbol) {
-        require(false, stub_error);
-        res_symbol = _dummy_string;
-    }
+contract ERC721 is Dummy, ERC165, ERC721Events {
+	function balanceOf(address owner) public view returns (uint256) {
+		require(false, stub_error);
+		owner;
+		dummy;
+		return 0;
+	}
+	function ownerOf(uint256 tokenId) public view returns (address) {
+		require(false, stub_error);
+		tokenId;
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+	function safeTransferFromWithData(address from, address to, uint256 tokenId, bytes memory data) public {
+		require(false, stub_error);
+		from;
+		to;
+		tokenId;
+		data;
+		dummy = 0;
+	}
+	function safeTransferFrom(address from, address to, uint256 tokenId) public {
+		require(false, stub_error);
+		from;
+		to;
+		tokenId;
+		dummy = 0;
+	}
+	function transferFrom(address from, address to, uint256 tokenId) public {
+		require(false, stub_error);
+		from;
+		to;
+		tokenId;
+		dummy = 0;
+	}
+	function approve(address approved, uint256 tokenId) public {
+		require(false, stub_error);
+		approved;
+		tokenId;
+		dummy = 0;
+	}
+	function setApprovalForAll(address operator, bool approved) public {
+		require(false, stub_error);
+		operator;
+		approved;
+		dummy = 0;
+	}
+	function getApproved(uint256 tokenId) public view returns (address) {
+		require(false, stub_error);
+		tokenId;
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+	function isApprovedForAll(address owner, address operator) public view returns (address) {
+		require(false, stub_error);
+		owner;
+		operator;
+		dummy;
+		return 0x0000000000000000000000000000000000000000;
+	}
+}
 
-    function tokenURI(uint256 tokenId) external view returns (string memory) {
-        require(false, stub_error);
-        tokenId;
-        return _dummy_string;
-    }
+contract ERC721Burnable is Dummy {
+	function burn(uint256 tokenId) public {
+		require(false, stub_error);
+		tokenId;
+		dummy = 0;
+	}
+}
 
-    function tokenByIndex(uint256 index) external view returns (uint256) {
-        require(false, stub_error);
-        index;
+contract ERC721Enumerable is Dummy, InlineTotalSupply {
+	function tokenByIndex(uint256 index) public view returns (uint256) {
+		require(false, stub_error);
+		index;
+		dummy;
 		return 0;
-    }
-
-    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256) {
-        require(false, stub_error);
+	}
+	function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {
+		require(false, stub_error);
 		owner;
 		index;
+		dummy;
 		return 0;
-    }
+	}
+}
 
-    // 0x70a08231
-    function balanceOf(address owner) external view returns (uint256) {
-        require(false, stub_error);
-        owner;
-        return 0;
-    }
+contract ERC721Metadata is Dummy, InlineNameSymbol {
+	function tokenURI(uint256 tokenId) public view returns (string memory) {
+		require(false, stub_error);
+		tokenId;
+		dummy;
+		return "";
+	}
+}
 
-    // 0x6352211e
-    function ownerOf(uint256 tokenId) external view returns (address) {
-        require(false, stub_error);
-        tokenId;
-        return _dummy_addr;
-    }
+contract ERC721Mintable is Dummy, ERC721MintableEvents {
+	function mintingFinished() public view returns (bool) {
+		require(false, stub_error);
+		dummy;
+		return false;
+	}
+	function mint(address to, uint256 tokenId) public returns (bool) {
+		require(false, stub_error);
+		to;
+		tokenId;
+		dummy = 0;
+		return false;
+	}
+	function mintWithTokenURI(address to, uint256 tokenId, string memory tokenUri) public returns (bool) {
+		require(false, stub_error);
+		to;
+		tokenId;
+		tokenUri;
+		dummy = 0;
+		return false;
+	}
+	function finishMinting() public returns (bool) {
+		require(false, stub_error);
+		dummy = 0;
+		return false;
+	}
+}
 
-    // 0xb88d4fde
-    function safeTransferFrom(
-        address from,
-        address to,
-        uint256 tokenId,
-        bytes calldata data
-    ) external payable {
-        require(false, stub_error);
-        from;
-        to;
-        tokenId;
-        data;
-    }
-
-    // 0x42842e0e
-    function safeTransferFrom(
-        address from,
-        address to,
-        uint256 tokenId
-    ) external payable {
-        require(false, stub_error);
-        from;
-        to;
-        tokenId;
-    }
-
-    // 0x23b872dd
-    function transferFrom(
-        address from,
-        address to,
-        uint256 tokenId
-    ) external payable {
-        require(false, stub_error);
-        from;
-        to;
-        tokenId;
-    }
+contract ERC721UniqueExtensions is Dummy {
+	function transfer(address to, uint256 tokenId) public {
+		require(false, stub_error);
+		to;
+		tokenId;
+		dummy = 0;
+	}
+	function nextTokenId() public view returns (uint256) {
+		require(false, stub_error);
+		dummy;
+		return 0;
+	}
+}
 
-    // 0x095ea7b3
-    function approve(address approved, uint256 tokenId) external payable {
-        require(false, stub_error);
-        approved;
-        tokenId;
-    }
-
-    // 0xa22cb465
-    function setApprovalForAll(address operator, bool approved) external {
-        require(false, stub_error);
-        operator;
-        approved;
-        _dummy = 0;
-    }
-
-    // 0x081812fc
-    function getApproved(uint256 tokenId) external view returns (address) {
-        require(false, stub_error);
-        tokenId;
-        return _dummy_addr;
-    }
-
-    // 0xe985e9c5
-    function isApprovedForAll(address owner, address operator)
-        external
-        view
-        returns (bool)
-    {
-        require(false, stub_error);
-        owner;
-        operator;
-        return false;
-    }
-
-    // 0x01ffc9a7
-    function supportsInterface(bytes4 interfaceID) public pure returns (bool) {
-        return
-            // ERC721
-            interfaceID == 0x80ac58cd ||
-            // ERC721Metadata
-            interfaceID == 0x5b5e139f ||
-            // ERC721Enumerable
-            interfaceID == 0x780e9d63 ||
-            // ERC165
-            interfaceID == 0x01ffc9a7;
-    }
-}
+contract UniqueNFT is Dummy, ERC165, ERC721, ERC721Metadata, ERC721Enumerable, ERC721UniqueExtensions, ERC721Mintable, ERC721Burnable {
+}
\ No newline at end of file
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
before · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_event, decl_module, decl_storage, decl_error,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,24		Randomness, IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32};3334use frame_system::{self as system, ensure_signed};35use sp_core::H160;36use sp_std::vec;37use sp_runtime::{DispatchError, sp_std::prelude::Vec};38use core::ops::{Deref, DerefMut};39use nft_data_structs::{40	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,41	CUSTOM_DATA_LIMIT, COLLECTION_NUMBER_LIMIT, ACCOUNT_TOKEN_OWNERSHIP_LIMIT,42	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,43	OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,44	CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,45	FungibleItemType, ReFungibleItemType,46};4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;55mod eth;56mod sponsorship;57pub use sponsorship::NftSponsorshipHandler;58pub use eth::sponsoring::NftEthSponsorshipHandler;5960pub use eth::NftErcSupport;61pub use eth::account::*;62use eth::erc::{ERC20Events, ERC721Events};6364#[cfg(feature = "runtime-benchmarks")]65mod benchmarking;6667pub trait WeightInfo {68	fn create_collection() -> Weight;69	fn destroy_collection() -> Weight;70	fn add_to_white_list() -> Weight;71	fn remove_from_white_list() -> Weight;72	fn set_public_access_mode() -> Weight;73	fn set_mint_permission() -> Weight;74	fn change_collection_owner() -> Weight;75	fn add_collection_admin() -> Weight;76	fn remove_collection_admin() -> Weight;77	fn set_collection_sponsor() -> Weight;78	fn confirm_sponsorship() -> Weight;79	fn remove_collection_sponsor() -> Weight;80	fn create_item(s: usize) -> Weight;81	fn burn_item() -> Weight;82	fn transfer() -> Weight;83	fn approve() -> Weight;84	fn transfer_from() -> Weight;85	fn set_offchain_schema() -> Weight;86	fn set_const_on_chain_schema() -> Weight;87	fn set_variable_on_chain_schema() -> Weight;88	fn set_variable_meta_data() -> Weight;89	fn enable_contract_sponsoring() -> Weight;90	fn set_schema_version() -> Weight;91	fn set_contract_sponsoring_rate_limit() -> Weight;92	fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;93	fn toggle_contract_white_list() -> Weight;94	fn add_to_contract_white_list() -> Weight;95	fn remove_from_contract_white_list() -> Weight;96	fn set_collection_limits() -> Weight;97}9899decl_error! {100	/// Error for non-fungible-token module.101	pub enum Error for Module<T: Config> {102		/// Total collections bound exceeded.103		TotalCollectionsLimitExceeded,104		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.105		CollectionDecimalPointLimitExceeded,106		/// Collection name can not be longer than 63 char.107		CollectionNameLimitExceeded,108		/// Collection description can not be longer than 255 char.109		CollectionDescriptionLimitExceeded,110		/// Token prefix can not be longer than 15 char.111		CollectionTokenPrefixLimitExceeded,112		/// This collection does not exist.113		CollectionNotFound,114		/// Item not exists.115		TokenNotFound,116		/// Admin not found117		AdminNotFound,118		/// Arithmetic calculation overflow.119		NumOverflow,120		/// Account already has admin role.121		AlreadyAdmin,122		/// You do not own this collection.123		NoPermission,124		/// This address is not set as sponsor, use setCollectionSponsor first.125		ConfirmUnsetSponsorFail,126		/// Collection is not in mint mode.127		PublicMintingNotAllowed,128		/// Sender parameter and item owner must be equal.129		MustBeTokenOwner,130		/// Item balance not enough.131		TokenValueTooLow,132		/// Size of item is too large.133		NftSizeLimitExceeded,134		/// No approve found135		ApproveNotFound,136		/// Requested value more than approved.137		TokenValueNotEnough,138		/// Only approved addresses can call this method.139		ApproveRequired,140		/// Address is not in white list.141		AddresNotInWhiteList,142		/// Number of collection admins bound exceeded.143		CollectionAdminsLimitExceeded,144		/// Owned tokens by a single address bound exceeded.145		AddressOwnershipLimitExceeded,146		/// Length of items properties must be greater than 0.147		EmptyArgument,148		/// const_data exceeded data limit.149		TokenConstDataLimitExceeded,150		/// variable_data exceeded data limit.151		TokenVariableDataLimitExceeded,152		/// Not NFT item data used to mint in NFT collection.153		NotNftDataUsedToMintNftCollectionToken,154		/// Not Fungible item data used to mint in Fungible collection.155		NotFungibleDataUsedToMintFungibleCollectionToken,156		/// Not Re Fungible item data used to mint in Re Fungible collection.157		NotReFungibleDataUsedToMintReFungibleCollectionToken,158		/// Unexpected collection type.159		UnexpectedCollectionType,160		/// Can't store metadata in fungible tokens.161		CantStoreMetadataInFungibleTokens,162		/// Collection token limit exceeded163		CollectionTokenLimitExceeded,164		/// Account token limit exceeded per collection165		AccountTokenLimitExceeded,166		/// Collection limit bounds per collection exceeded167		CollectionLimitBoundsExceeded,168		/// Tried to enable permissions which are only permitted to be disabled169		OwnerPermissionsCantBeReverted,170		/// Schema data size limit bound exceeded171		SchemaDataLimitExceeded,172		/// Maximum refungibility exceeded173		WrongRefungiblePieces,174		/// createRefungible should be called with one owner175		BadCreateRefungibleCall,176		/// Gas limit exceeded177		OutOfGas,178		/// Collection settings not allowing items transferring179		TransferNotAllowed,180	}181}182183#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]184pub struct CollectionHandle<T: Config> {185	pub id: CollectionId,186	collection: Collection<T>,187	recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,188}189impl<T: Config> CollectionHandle<T> {190	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {191		<CollectionById<T>>::get(id).map(|collection| Self {192			id,193			collection,194			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(195				eth::collection_id_to_address(id),196				gas_limit,197			),198		})199	}200	pub fn get(id: CollectionId) -> Option<Self> {201		Self::get_with_gas_limit(id, u64::MAX)202	}203	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {204		self.recorder.log_sub(log)205	}206	#[allow(dead_code)]207	fn consume_gas(&self, gas: u64) -> DispatchResult {208		self.recorder.consume_gas_sub(gas)209	}210	fn consume_sload(&self) -> DispatchResult {211		self.recorder.consume_sload_sub()212	}213	fn consume_sstore(&self) -> DispatchResult {214		self.recorder.consume_sstore_sub()215	}216	pub fn submit_logs(self) -> DispatchResult {217		self.recorder.submit_logs()218	}219	pub fn save(self) -> DispatchResult {220		self.recorder.submit_logs()?;221		<CollectionById<T>>::insert(self.id, self.collection);222		Ok(())223	}224}225impl<T: Config> Deref for CollectionHandle<T> {226	type Target = Collection<T>;227228	fn deref(&self) -> &Self::Target {229		&self.collection230	}231}232233impl<T: Config> DerefMut for CollectionHandle<T> {234	fn deref_mut(&mut self) -> &mut Self::Target {235		&mut self.collection236	}237}238239pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {240	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;241242	/// Weight information for extrinsics in this pallet.243	type WeightInfo: WeightInfo;244245	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;246	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;247248	type CrossAccountId: CrossAccountId<Self::AccountId>;249	type Currency: Currency<Self::AccountId>;250	type CollectionCreationPrice: Get<251		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,252	>;253	type TreasuryAccountId: Get<Self::AccountId>;254}255256// # Used definitions257//258// ## User control levels259//260// chain-controlled - key is uncontrolled by user261//                    i.e autoincrementing index262//                    can use non-cryptographic hash263// real - key is controlled by user264//        but it is hard to generate enough colliding values, i.e owner of signed txs265//        can use non-cryptographic hash266// controlled - key is completly controlled by users267//              i.e maps with mutable keys268//              should use cryptographic hash269//270// ## User control level downgrade reasons271//272// ?1 - chain-controlled -> controlled273//      collections/tokens can be destroyed, resulting in massive holes274// ?2 - chain-controlled -> controlled275//      same as ?1, but can be only added, resulting in easier exploitation276// ?3 - real -> controlled277//      no confirmation required, so addresses can be easily generated278decl_storage! {279	trait Store for Module<T: Config> as Nft {280281		//#region Private members282		/// Id of next collection283		CreatedCollectionCount: u32;284		/// Used for migrations285		ChainVersion: u64;286		/// Id of last collection token287		/// Collection id (controlled?1)288		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;289		//#endregion290291		//#region Bound counters292		/// Amount of collections destroyed, used for total amount tracking with293		/// CreatedCollectionCount294		DestroyedCollectionCount: u32;295		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)296		/// Account id (real)297		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;298		//#endregion299300		//#region Basic collections301		/// Collection info302		/// Collection id (controlled?1)303		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;304		/// List of collection admins305		/// Collection id (controlled?2)306		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;307		/// Whitelisted collection users308		/// Collection id (controlled?2), user id (controlled?3)309		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;310		//#endregion311312		/// How many of collection items user have313		/// Collection id (controlled?2), account id (real)314		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;315316		/// Amount of items which spender can transfer out of owners account (via transferFrom)317		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))318		/// TODO: Off chain worker should remove from this map when token gets removed319		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;320321		//#region Item collections322		/// Collection id (controlled?2), token id (controlled?1)323		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;324		/// Collection id (controlled?2), owner (controlled?2)325		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;326		/// Collection id (controlled?2), token id (controlled?1)327		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;328		//#endregion329330		//#region Index list331		/// Collection id (controlled?2), tokens owner (controlled?2)332		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;333		//#endregion334335		//#region Tokens transfer rate limit baskets336		/// (Collection id (controlled?2), who created (real))337		/// TODO: Off chain worker should remove from this map when collection gets removed338		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;339		/// Collection id (controlled?2), token id (controlled?2)340		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;341		/// Collection id (controlled?2), owning user (real)342		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;343		/// Collection id (controlled?2), token id (controlled?2)344		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;345		//#endregion346347		/// Variable metadata sponsoring348		/// Collection id (controlled?2), token id (controlled?2)349		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;350	}351	add_extra_genesis {352		build(|config: &GenesisConfig<T>| {353			// Modification of storage354			for (_num, _c) in &config.collection_id {355				<Module<T>>::init_collection(_c);356			}357358			for (_num, _c, _i) in &config.nft_item_id {359				<Module<T>>::init_nft_token(*_c, _i);360			}361362			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {363				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);364			}365366			for (_num, _c, _i) in &config.refungible_item_id {367				<Module<T>>::init_refungible_token(*_c, _i);368			}369		})370	}371}372373decl_event!(374	pub enum Event<T>375	where376		AccountId = <T as frame_system::Config>::AccountId,377		CrossAccountId = <T as Config>::CrossAccountId,378	{379		/// New collection was created380		///381		/// # Arguments382		///383		/// * collection_id: Globally unique identifier of newly created collection.384		///385		/// * mode: [CollectionMode] converted into u8.386		///387		/// * account_id: Collection owner.388		CollectionCreated(CollectionId, u8, AccountId),389390		/// New item was created.391		///392		/// # Arguments393		///394		/// * collection_id: Id of the collection where item was created.395		///396		/// * item_id: Id of an item. Unique within the collection.397		///398		/// * recipient: Owner of newly created item399		ItemCreated(CollectionId, TokenId, CrossAccountId),400401		/// Collection item was burned.402		///403		/// # Arguments404		///405		/// collection_id.406		///407		/// item_id: Identifier of burned NFT.408		ItemDestroyed(CollectionId, TokenId),409410		/// Item was transferred411		///412		/// * collection_id: Id of collection to which item is belong413		///414		/// * item_id: Id of an item415		///416		/// * sender: Original owner of item417		///418		/// * recipient: New owner of item419		///420		/// * amount: Always 1 for NFT421		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),422423		/// * collection_id424		///425		/// * item_id426		///427		/// * sender428		///429		/// * spender430		///431		/// * amount432		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),433	}434);435436decl_module! {437	pub struct Module<T: Config> for enum Call438	where439		origin: T::Origin440	{441		fn deposit_event() = default;442		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;443		type Error = Error<T>;444445		fn on_initialize(_now: T::BlockNumber) -> Weight {446			0447		}448449		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.450		///451		/// # Permissions452		///453		/// * Anyone.454		///455		/// # Arguments456		///457		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.458		///459		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.460		///461		/// * token_prefix: UTF-8 string with token prefix.462		///463		/// * mode: [CollectionMode] collection type and type dependent data.464		// returns collection ID465		#[weight = <T as Config>::WeightInfo::create_collection()]466		#[transactional]467		pub fn create_collection(origin,468								 collection_name: Vec<u16>,469								 collection_description: Vec<u16>,470								 token_prefix: Vec<u8>,471								 mode: CollectionMode) -> DispatchResult {472473			// Anyone can create a collection474			let who = ensure_signed(origin)?;475476			// Take a (non-refundable) deposit of collection creation477			let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();478			imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(479				&T::TreasuryAccountId::get(),480				T::CollectionCreationPrice::get(),481			));482			<T as Config>::Currency::settle(483				&who,484				imbalance,485				WithdrawReasons::TRANSFER,486				ExistenceRequirement::KeepAlive,487			).map_err(|_| Error::<T>::NoPermission)?;488489			let decimal_points = match mode {490				CollectionMode::Fungible(points) => points,491				_ => 0492			};493494			let created_count = CreatedCollectionCount::get();495			let destroyed_count = DestroyedCollectionCount::get();496497			// bound Total number of collections498			ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::<T>::TotalCollectionsLimitExceeded);499500			// check params501			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);502			ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);503			ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);504			ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);505506			// Generate next collection ID507			let next_id = created_count508				.checked_add(1)509				.ok_or(Error::<T>::NumOverflow)?;510511			CreatedCollectionCount::put(next_id);512513			let limits = CollectionLimits {514				sponsored_data_size: CUSTOM_DATA_LIMIT,515				..Default::default()516			};517518			// Create new collection519			let new_collection = Collection {520				owner: who.clone(),521				name: collection_name,522				mode: mode.clone(),523				mint_mode: false,524				access: AccessMode::Normal,525				description: collection_description,526				decimal_points,527				token_prefix,528				offchain_schema: Vec::new(),529				schema_version: SchemaVersion::ImageURL,530				sponsorship: SponsorshipState::Disabled,531				variable_on_chain_schema: Vec::new(),532				const_on_chain_schema: Vec::new(),533				limits,534				transfers_enabled: true,535			};536537			// Add new collection to map538			<CollectionById<T>>::insert(next_id, new_collection);539540			// call event541			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));542543			Ok(())544		}545546		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.547		///548		/// # Permissions549		///550		/// * Collection Owner.551		///552		/// # Arguments553		///554		/// * collection_id: collection to destroy.555		#[weight = <T as Config>::WeightInfo::destroy_collection()]556		#[transactional]557		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {558559			let sender = ensure_signed(origin)?;560			let collection = Self::get_collection(collection_id)?;561			Self::check_owner_permissions(&collection, &sender)?;562			if !collection.limits.owner_can_destroy {563				fail!(Error::<T>::NoPermission);564			}565566			<AddressTokens<T>>::remove_prefix(collection_id, None);567			<Allowances<T>>::remove_prefix(collection_id, None);568			<Balance<T>>::remove_prefix(collection_id, None);569			<ItemListIndex>::remove(collection_id);570			<AdminList<T>>::remove(collection_id);571			<CollectionById<T>>::remove(collection_id);572			<WhiteList<T>>::remove_prefix(collection_id, None);573574			<NftItemList<T>>::remove_prefix(collection_id, None);575			<FungibleItemList<T>>::remove_prefix(collection_id, None);576			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);577578			<NftTransferBasket<T>>::remove_prefix(collection_id, None);579			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);580			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);581582			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);583584			DestroyedCollectionCount::put(DestroyedCollectionCount::get()585				.checked_add(1)586				.ok_or(Error::<T>::NumOverflow)?);587588			Ok(())589		}590591		/// Add an address to white list.592		///593		/// # Permissions594		///595		/// * Collection Owner596		/// * Collection Admin597		///598		/// # Arguments599		///600		/// * collection_id.601		///602		/// * address.603		#[weight = <T as Config>::WeightInfo::add_to_white_list()]604		#[transactional]605		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{606607			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);608			let collection = Self::get_collection(collection_id)?;609610			Self::toggle_white_list_internal(611				&sender,612				&collection,613				&address,614				true,615			)?;616617			Ok(())618		}619620		/// Remove an address from white list.621		///622		/// # Permissions623		///624		/// * Collection Owner625		/// * Collection Admin626		///627		/// # Arguments628		///629		/// * collection_id.630		///631		/// * address.632		#[weight = <T as Config>::WeightInfo::remove_from_white_list()]633		#[transactional]634		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{635636			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);637			let collection = Self::get_collection(collection_id)?;638639			Self::toggle_white_list_internal(640				&sender,641				&collection,642				&address,643				false,644			)?;645646			Ok(())647		}648649		/// Toggle between normal and white list access for the methods with access for `Anyone`.650		///651		/// # Permissions652		///653		/// * Collection Owner.654		///655		/// # Arguments656		///657		/// * collection_id.658		///659		/// * mode: [AccessMode]660		#[weight = <T as Config>::WeightInfo::set_public_access_mode()]661		#[transactional]662		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult663		{664			let sender = ensure_signed(origin)?;665666			let mut target_collection = Self::get_collection(collection_id)?;667			Self::check_owner_permissions(&target_collection, &sender)?;668			target_collection.access = mode;669			target_collection.save()670		}671672		/// Allows Anyone to create tokens if:673		/// * White List is enabled, and674		/// * Address is added to white list, and675		/// * This method was called with True parameter676		///677		/// # Permissions678		/// * Collection Owner679		///680		/// # Arguments681		///682		/// * collection_id.683		///684		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.685		#[weight = <T as Config>::WeightInfo::set_mint_permission()]686		#[transactional]687		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult688		{689			let sender = ensure_signed(origin)?;690691			let mut target_collection = Self::get_collection(collection_id)?;692			Self::check_owner_permissions(&target_collection, &sender)?;693			target_collection.mint_mode = mint_permission;694			target_collection.save()695		}696697		/// Change the owner of the collection.698		///699		/// # Permissions700		///701		/// * Collection Owner.702		///703		/// # Arguments704		///705		/// * collection_id.706		///707		/// * new_owner.708		#[weight = <T as Config>::WeightInfo::change_collection_owner()]709		#[transactional]710		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {711712			let sender = ensure_signed(origin)?;713			let mut target_collection = Self::get_collection(collection_id)?;714			Self::check_owner_permissions(&target_collection, &sender)?;715			target_collection.owner = new_owner;716			target_collection.save()717		}718719		/// Adds an admin of the Collection.720		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.721		///722		/// # Permissions723		///724		/// * Collection Owner.725		/// * Collection Admin.726		///727		/// # Arguments728		///729		/// * collection_id: ID of the Collection to add admin for.730		///731		/// * new_admin_id: Address of new admin to add.732		#[weight = <T as Config>::WeightInfo::add_collection_admin()]733		#[transactional]734		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {735			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);736			let collection = Self::get_collection(collection_id)?;737			Self::check_owner_or_admin_permissions(&collection, &sender)?;738			let mut admin_arr = <AdminList<T>>::get(collection_id);739740			match admin_arr.binary_search(&new_admin_id) {741				Ok(_) => {},742				Err(idx) => {743					ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::<T>::CollectionAdminsLimitExceeded);744					admin_arr.insert(idx, new_admin_id);745					<AdminList<T>>::insert(collection_id, admin_arr);746				}747			}748			Ok(())749		}750751		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.752		///753		/// # Permissions754		///755		/// * Collection Owner.756		/// * Collection Admin.757		///758		/// # Arguments759		///760		/// * collection_id: ID of the Collection to remove admin for.761		///762		/// * account_id: Address of admin to remove.763		#[weight = <T as Config>::WeightInfo::remove_collection_admin()]764		#[transactional]765		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {766			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);767			let collection = Self::get_collection(collection_id)?;768			Self::check_owner_or_admin_permissions(&collection, &sender)?;769			let mut admin_arr = <AdminList<T>>::get(collection_id);770771			if let Ok(idx) = admin_arr.binary_search(&account_id) {772				admin_arr.remove(idx);773				<AdminList<T>>::insert(collection_id, admin_arr);774			}775			Ok(())776		}777778		/// # Permissions779		///780		/// * Collection Owner781		///782		/// # Arguments783		///784		/// * collection_id.785		///786		/// * new_sponsor.787		#[weight = <T as Config>::WeightInfo::set_collection_sponsor()]788		#[transactional]789		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {790			let sender = ensure_signed(origin)?;791			let mut target_collection = Self::get_collection(collection_id)?;792			Self::check_owner_permissions(&target_collection, &sender)?;793794			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);795			target_collection.save()796		}797798		/// # Permissions799		///800		/// * Sponsor.801		///802		/// # Arguments803		///804		/// * collection_id.805		#[weight = <T as Config>::WeightInfo::confirm_sponsorship()]806		#[transactional]807		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {808			let sender = ensure_signed(origin)?;809810			let mut target_collection = Self::get_collection(collection_id)?;811			ensure!(812				target_collection.sponsorship.pending_sponsor() == Some(&sender),813				Error::<T>::ConfirmUnsetSponsorFail814			);815816			target_collection.sponsorship = SponsorshipState::Confirmed(sender);817			target_collection.save()818		}819820		/// Switch back to pay-per-own-transaction model.821		///822		/// # Permissions823		///824		/// * Collection owner.825		///826		/// # Arguments827		///828		/// * collection_id.829		#[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]830		#[transactional]831		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {832			let sender = ensure_signed(origin)?;833834			let mut target_collection = Self::get_collection(collection_id)?;835			Self::check_owner_permissions(&target_collection, &sender)?;836837			target_collection.sponsorship = SponsorshipState::Disabled;838			target_collection.save()839		}840841		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.842		///843		/// # Permissions844		///845		/// * Collection Owner.846		/// * Collection Admin.847		/// * Anyone if848		///     * White List is enabled, and849		///     * Address is added to white list, and850		///     * MintPermission is enabled (see SetMintPermission method)851		///852		/// # Arguments853		///854		/// * collection_id: ID of the collection.855		///856		/// * owner: Address, initial owner of the NFT.857		///858		/// * data: Token data to store on chain.859		// #[weight =860		// (130_000_000 as Weight)861		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))862		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))863		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]864865		#[weight = <T as Config>::WeightInfo::create_item(data.data_size())]866		#[transactional]867		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {868			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);869			let collection = Self::get_collection(collection_id)?;870871			Self::create_item_internal(&sender, &collection, &owner, data)?;872873			collection.submit_logs()874		}875876		/// This method creates multiple items in a collection created with CreateCollection method.877		///878		/// # Permissions879		///880		/// * Collection Owner.881		/// * Collection Admin.882		/// * Anyone if883		///     * White List is enabled, and884		///     * Address is added to white list, and885		///     * MintPermission is enabled (see SetMintPermission method)886		///887		/// # Arguments888		///889		/// * collection_id: ID of the collection.890		///891		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].892		///893		/// * owner: Address, initial owner of the NFT.894		#[weight = <T as Config>::WeightInfo::create_item(items_data.iter()895							   .map(|data| { data.data_size() })896							   .sum())]897		#[transactional]898		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {899900			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);901			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);902			let collection = Self::get_collection(collection_id)?;903904			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;905906			collection.submit_logs()907		}908909		// TODO! transaction weight910911		/// Set transfers_enabled value for particular collection912		///913		/// # Permissions914		///915		/// * Collection Owner.916		///917		/// # Arguments918		///919		/// * collection_id: ID of the collection.920		///921		/// * value: New flag value.922		#[weight = <T as Config>::WeightInfo::burn_item()]923		#[transactional]924		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {925926			let sender = ensure_signed(origin)?;927			let mut target_collection = Self::get_collection(collection_id)?;928929			Self::check_owner_permissions(&target_collection, &sender)?;930931			target_collection.transfers_enabled = value;932			target_collection.save()933		}934935		/// Destroys a concrete instance of NFT.936		///937		/// # Permissions938		///939		/// * Collection Owner.940		/// * Collection Admin.941		/// * Current NFT Owner.942		///943		/// # Arguments944		///945		/// * collection_id: ID of the collection.946		///947		/// * item_id: ID of NFT to burn.948		#[weight = <T as Config>::WeightInfo::burn_item()]949		#[transactional]950		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {951952			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);953			let target_collection = Self::get_collection(collection_id)?;954955			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;956957			target_collection.submit_logs()958		}959960		/// Change ownership of the token.961		///962		/// # Permissions963		///964		/// * Collection Owner965		/// * Collection Admin966		/// * Current NFT owner967		///968		/// # Arguments969		///970		/// * recipient: Address of token recipient.971		///972		/// * collection_id.973		///974		/// * item_id: ID of the item975		///     * Non-Fungible Mode: Required.976		///     * Fungible Mode: Ignored.977		///     * Re-Fungible Mode: Required.978		///979		/// * value: Amount to transfer.980		///     * Non-Fungible Mode: Ignored981		///     * Fungible Mode: Must specify transferred amount982		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)983		#[weight = <T as Config>::WeightInfo::transfer()]984		#[transactional]985		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {986			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);987			let collection = Self::get_collection(collection_id)?;988989			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;990991			collection.submit_logs()992		}993994		/// Set, change, or remove approved address to transfer the ownership of the NFT.995		///996		/// # Permissions997		///998		/// * Collection Owner999		/// * Collection Admin1000		/// * Current NFT owner1001		///1002		/// # Arguments1003		///1004		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1005		///1006		/// * collection_id.1007		///1008		/// * item_id: ID of the item.1009		#[weight = <T as Config>::WeightInfo::approve()]1010		#[transactional]1011		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1012			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1013			let collection = Self::get_collection(collection_id)?;10141015			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10161017			collection.submit_logs()1018		}10191020		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1021		///1022		/// # Permissions1023		/// * Collection Owner1024		/// * Collection Admin1025		/// * Current NFT owner1026		/// * Address approved by current NFT owner1027		///1028		/// # Arguments1029		///1030		/// * from: Address that owns token.1031		///1032		/// * recipient: Address of token recipient.1033		///1034		/// * collection_id.1035		///1036		/// * item_id: ID of the item.1037		///1038		/// * value: Amount to transfer.1039		#[weight = <T as Config>::WeightInfo::transfer_from()]1040		#[transactional]1041		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1042			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1043			let collection = Self::get_collection(collection_id)?;10441045			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10461047			collection.submit_logs()1048		}1049		// #[weight = 0]1050		//     // let no_perm_mes = "You do not have permissions to modify this collection";1051		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1052		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1053		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10541055		//     // // on_nft_received  call10561057		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;10581059		//     Ok(())1060		// }10611062		/// Set off-chain data schema.1063		///1064		/// # Permissions1065		///1066		/// * Collection Owner1067		/// * Collection Admin1068		///1069		/// # Arguments1070		///1071		/// * collection_id.1072		///1073		/// * schema: String representing the offchain data schema.1074		#[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1075		#[transactional]1076		pub fn set_variable_meta_data (1077			origin,1078			collection_id: CollectionId,1079			item_id: TokenId,1080			data: Vec<u8>1081		) -> DispatchResult {1082			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10831084			let collection = Self::get_collection(collection_id)?;10851086			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10871088			Ok(())1089		}10901091		/// Set schema standard1092		/// ImageURL1093		/// Unique1094		///1095		/// # Permissions1096		///1097		/// * Collection Owner1098		/// * Collection Admin1099		///1100		/// # Arguments1101		///1102		/// * collection_id.1103		///1104		/// * schema: SchemaVersion: enum1105		#[weight = <T as Config>::WeightInfo::set_schema_version()]1106		#[transactional]1107		pub fn set_schema_version(1108			origin,1109			collection_id: CollectionId,1110			version: SchemaVersion1111		) -> DispatchResult {1112			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1113			let mut target_collection = Self::get_collection(collection_id)?;1114			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1115			target_collection.schema_version = version;1116			target_collection.save()1117		}11181119		/// Set off-chain data schema.1120		///1121		/// # Permissions1122		///1123		/// * Collection Owner1124		/// * Collection Admin1125		///1126		/// # Arguments1127		///1128		/// * collection_id.1129		///1130		/// * schema: String representing the offchain data schema.1131		#[weight = <T as Config>::WeightInfo::set_offchain_schema()]1132		#[transactional]1133		pub fn set_offchain_schema(1134			origin,1135			collection_id: CollectionId,1136			schema: Vec<u8>1137		) -> DispatchResult {1138			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1139			let mut target_collection = Self::get_collection(collection_id)?;1140			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11411142			// check schema limit1143			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");11441145			target_collection.offchain_schema = schema;1146			target_collection.save()1147		}11481149		/// Set const on-chain data schema.1150		///1151		/// # Permissions1152		///1153		/// * Collection Owner1154		/// * Collection Admin1155		///1156		/// # Arguments1157		///1158		/// * collection_id.1159		///1160		/// * schema: String representing the const on-chain data schema.1161		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1162		#[transactional]1163		pub fn set_const_on_chain_schema (1164			origin,1165			collection_id: CollectionId,1166			schema: Vec<u8>1167		) -> DispatchResult {1168			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1169			let mut target_collection = Self::get_collection(collection_id)?;1170			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11711172			// check schema limit1173			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");11741175			target_collection.const_on_chain_schema = schema;1176			target_collection.save()1177		}11781179		/// Set variable on-chain data schema.1180		///1181		/// # Permissions1182		///1183		/// * Collection Owner1184		/// * Collection Admin1185		///1186		/// # Arguments1187		///1188		/// * collection_id.1189		///1190		/// * schema: String representing the variable on-chain data schema.1191		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1192		#[transactional]1193		pub fn set_variable_on_chain_schema (1194			origin,1195			collection_id: CollectionId,1196			schema: Vec<u8>1197		) -> DispatchResult {1198			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1199			let mut target_collection = Self::get_collection(collection_id)?;1200			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12011202			// check schema limit1203			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");12041205			target_collection.variable_on_chain_schema = schema;1206			target_collection.save()1207		}12081209		#[weight = <T as Config>::WeightInfo::set_collection_limits()]1210		#[transactional]1211		pub fn set_collection_limits(1212			origin,1213			collection_id: u32,1214			new_limits: CollectionLimits<T::BlockNumber>,1215		) -> DispatchResult {1216			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1217			let mut target_collection = Self::get_collection(collection_id)?;1218			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1219			let old_limits = &target_collection.limits;12201221			// collection bounds1222			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1223				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1224				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,1225				Error::<T>::CollectionLimitBoundsExceeded);12261227			// token_limit   check  prev1228			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1229			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12301231			ensure!(1232				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1233				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1234				Error::<T>::OwnerPermissionsCantBeReverted,1235			);12361237			target_collection.limits = new_limits;12381239			target_collection.save()1240		}1241	}1242}12431244impl<T: Config> Module<T> {1245	pub fn create_item_internal(1246		sender: &T::CrossAccountId,1247		collection: &CollectionHandle<T>,1248		owner: &T::CrossAccountId,1249		data: CreateItemData,1250	) -> DispatchResult {1251		Self::can_create_items_in_collection(collection, sender, owner, 1)?;1252		Self::validate_create_item_args(collection, &data)?;1253		Self::create_item_no_validation(collection, owner, data)?;12541255		Ok(())1256	}12571258	pub fn transfer_internal(1259		sender: &T::CrossAccountId,1260		recipient: &T::CrossAccountId,1261		target_collection: &CollectionHandle<T>,1262		item_id: TokenId,1263		value: u128,1264	) -> DispatchResult {1265		// Limits check1266		Self::is_correct_transfer(target_collection, recipient)?;12671268		// Transfer permissions check1269		ensure!(1270			Self::is_item_owner(sender, target_collection, item_id)?1271				|| Self::is_owner_or_admin_permissions(target_collection, sender)?,1272			Error::<T>::NoPermission1273		);12741275		if target_collection.access == AccessMode::WhiteList {1276			Self::check_white_list(target_collection, sender)?;1277			Self::check_white_list(target_collection, recipient)?;1278		}12791280		match target_collection.mode {1281			CollectionMode::NFT => Self::transfer_nft(1282				target_collection,1283				item_id,1284				sender.clone(),1285				recipient.clone(),1286			)?,1287			CollectionMode::Fungible(_) => {1288				Self::transfer_fungible(target_collection, value, sender, recipient)?1289			}1290			CollectionMode::ReFungible => Self::transfer_refungible(1291				target_collection,1292				item_id,1293				value,1294				sender.clone(),1295				recipient.clone(),1296			)?,1297			_ => (),1298		};12991300		Self::deposit_event(RawEvent::Transfer(1301			target_collection.id,1302			item_id,1303			sender.clone(),1304			recipient.clone(),1305			value,1306		));13071308		Ok(())1309	}13101311	pub fn approve_internal(1312		sender: &T::CrossAccountId,1313		spender: &T::CrossAccountId,1314		collection: &CollectionHandle<T>,1315		item_id: TokenId,1316		amount: u128,1317	) -> DispatchResult {1318		Self::token_exists(collection, item_id)?;13191320		// Transfer permissions check1321		let bypasses_limits = collection.limits.owner_can_transfer1322			&& Self::is_owner_or_admin_permissions(collection, sender)?;13231324		let allowance_limit = if bypasses_limits {1325			None1326		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id)? {1327			Some(amount)1328		} else {1329			fail!(Error::<T>::NoPermission);1330		};13311332		if collection.access == AccessMode::WhiteList {1333			Self::check_white_list(collection, sender)?;1334			Self::check_white_list(collection, spender)?;1335		}13361337		collection.consume_sload()?;1338		let allowance: u128 = amount1339			.checked_add(<Allowances<T>>::get(1340				collection.id,1341				(item_id, sender.as_sub(), spender.as_sub()),1342			))1343			.ok_or(Error::<T>::NumOverflow)?;1344		if let Some(limit) = allowance_limit {1345			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1346		}1347		collection.consume_sstore()?;1348		<Allowances<T>>::insert(1349			collection.id,1350			(item_id, sender.as_sub(), spender.as_sub()),1351			allowance,1352		);13531354		if matches!(collection.mode, CollectionMode::NFT) {1355			// TODO: NFT: only one owner may exist for token in ERC7211356			collection.log(ERC721Events::Approval {1357				owner: *sender.as_eth(),1358				approved: *spender.as_eth(),1359				token_id: item_id.into(),1360			})?;1361		}13621363		if matches!(collection.mode, CollectionMode::Fungible(_)) {1364			// TODO: NFT: only one owner may exist for token in ERC201365			collection.log(ERC20Events::Approval {1366				owner: *sender.as_eth(),1367				spender: *spender.as_eth(),1368				value: allowance.into(),1369			})?;1370		}13711372		Self::deposit_event(RawEvent::Approved(1373			collection.id,1374			item_id,1375			sender.clone(),1376			spender.clone(),1377			allowance,1378		));1379		Ok(())1380	}13811382	pub fn transfer_from_internal(1383		sender: &T::CrossAccountId,1384		from: &T::CrossAccountId,1385		recipient: &T::CrossAccountId,1386		collection: &CollectionHandle<T>,1387		item_id: TokenId,1388		amount: u128,1389	) -> DispatchResult {1390		// Check approval1391		collection.consume_sload()?;1392		let approval: u128 =1393			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));13941395		// Limits check1396		Self::is_correct_transfer(collection, recipient)?;13971398		// Transfer permissions check1399		ensure!(1400			approval >= amount1401				|| (collection.limits.owner_can_transfer1402					&& Self::is_owner_or_admin_permissions(collection, sender)?),1403			Error::<T>::NoPermission1404		);14051406		if collection.access == AccessMode::WhiteList {1407			Self::check_white_list(collection, sender)?;1408			Self::check_white_list(collection, recipient)?;1409		}14101411		// Reduce approval by transferred amount or remove if remaining approval drops to 01412		let allowance = approval.saturating_sub(amount);1413		collection.consume_sstore()?;1414		if allowance > 0 {1415			<Allowances<T>>::insert(1416				collection.id,1417				(item_id, from.as_sub(), sender.as_sub()),1418				allowance,1419			);1420		} else {1421			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1422		}14231424		match collection.mode {1425			CollectionMode::NFT => {1426				Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1427			}1428			CollectionMode::Fungible(_) => {1429				Self::transfer_fungible(collection, amount, from, recipient)?1430			}1431			CollectionMode::ReFungible => Self::transfer_refungible(1432				collection,1433				item_id,1434				amount,1435				from.clone(),1436				recipient.clone(),1437			)?,1438			_ => (),1439		};14401441		if matches!(collection.mode, CollectionMode::Fungible(_)) {1442			collection.log(ERC20Events::Approval {1443				owner: *from.as_eth(),1444				spender: *sender.as_eth(),1445				value: allowance.into(),1446			})?;1447		}14481449		Ok(())1450	}14511452	pub fn set_variable_meta_data_internal(1453		sender: &T::CrossAccountId,1454		collection: &CollectionHandle<T>,1455		item_id: TokenId,1456		data: Vec<u8>,1457	) -> DispatchResult {1458		Self::token_exists(collection, item_id)?;14591460		ensure!(1461			CUSTOM_DATA_LIMIT >= data.len() as u32,1462			Error::<T>::TokenVariableDataLimitExceeded1463		);14641465		// Modify permissions check1466		ensure!(1467			Self::is_item_owner(sender, collection, item_id)?1468				|| Self::is_owner_or_admin_permissions(collection, sender)?,1469			Error::<T>::NoPermission1470		);14711472		match collection.mode {1473			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1474			CollectionMode::ReFungible => {1475				Self::set_re_fungible_variable_data(collection, item_id, data)?1476			}1477			CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1478			_ => fail!(Error::<T>::UnexpectedCollectionType),1479		};14801481		Ok(())1482	}14831484	pub fn create_multiple_items_internal(1485		sender: &T::CrossAccountId,1486		collection: &CollectionHandle<T>,1487		owner: &T::CrossAccountId,1488		items_data: Vec<CreateItemData>,1489	) -> DispatchResult {1490		Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;14911492		for data in &items_data {1493			Self::validate_create_item_args(collection, data)?;1494		}1495		for data in &items_data {1496			Self::create_item_no_validation(collection, owner, data.clone())?;1497		}14981499		Ok(())1500	}15011502	pub fn burn_item_internal(1503		sender: &T::CrossAccountId,1504		collection: &CollectionHandle<T>,1505		item_id: TokenId,1506		value: u128,1507	) -> DispatchResult {1508		ensure!(1509			Self::is_item_owner(sender, collection, item_id)?1510				|| (collection.limits.owner_can_transfer1511					&& Self::is_owner_or_admin_permissions(collection, sender)?),1512			Error::<T>::NoPermission1513		);15141515		if collection.access == AccessMode::WhiteList {1516			Self::check_white_list(collection, sender)?;1517		}15181519		match collection.mode {1520			CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1521			CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1522			CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1523			_ => (),1524		};15251526		Ok(())1527	}15281529	pub fn toggle_white_list_internal(1530		sender: &T::CrossAccountId,1531		collection: &CollectionHandle<T>,1532		address: &T::CrossAccountId,1533		whitelisted: bool,1534	) -> DispatchResult {1535		Self::check_owner_or_admin_permissions(collection, sender)?;15361537		if whitelisted {1538			<WhiteList<T>>::insert(collection.id, address.as_sub(), true);1539		} else {1540			<WhiteList<T>>::remove(collection.id, address.as_sub());1541		}15421543		Ok(())1544	}15451546	fn is_correct_transfer(1547		collection: &CollectionHandle<T>,1548		recipient: &T::CrossAccountId,1549	) -> DispatchResult {1550		let collection_id = collection.id;15511552		// check token limit and account token limit1553		collection.consume_sload()?;1554		let account_items: u32 =1555			<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1556		ensure!(1557			collection.limits.account_token_ownership_limit > account_items,1558			Error::<T>::AccountTokenLimitExceeded1559		);15601561		// preliminary transfer check1562		ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);15631564		Ok(())1565	}15661567	fn can_create_items_in_collection(1568		collection: &CollectionHandle<T>,1569		sender: &T::CrossAccountId,1570		owner: &T::CrossAccountId,1571		amount: u32,1572	) -> DispatchResult {1573		let collection_id = collection.id;15741575		// check token limit and account token limit1576		let total_items: u32 = ItemListIndex::get(collection_id)1577			.checked_add(amount)1578			.ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1579		let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1580			as u32)1581			.checked_add(amount)1582			.ok_or(Error::<T>::AccountTokenLimitExceeded)?;1583		ensure!(1584			collection.limits.token_limit >= total_items,1585			Error::<T>::CollectionTokenLimitExceeded1586		);1587		ensure!(1588			collection.limits.account_token_ownership_limit >= account_items,1589			Error::<T>::AccountTokenLimitExceeded1590		);15911592		if !Self::is_owner_or_admin_permissions(collection, sender)? {1593			ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1594			Self::check_white_list(collection, owner)?;1595			Self::check_white_list(collection, sender)?;1596		}15971598		Ok(())1599	}16001601	fn validate_create_item_args(1602		target_collection: &CollectionHandle<T>,1603		data: &CreateItemData,1604	) -> DispatchResult {1605		match target_collection.mode {1606			CollectionMode::NFT => {1607				if !matches!(data, CreateItemData::NFT(_)) {1608					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1609				}1610			}1611			CollectionMode::Fungible(_) => {1612				if !matches!(data, CreateItemData::Fungible(_)) {1613					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1614				}1615			}1616			CollectionMode::ReFungible => {1617				if let CreateItemData::ReFungible(data) = data {1618					// Check refungibility limits1619					ensure!(1620						data.pieces <= MAX_REFUNGIBLE_PIECES,1621						Error::<T>::WrongRefungiblePieces1622					);1623					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1624				} else {1625					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1626				}1627			}1628			_ => {1629				fail!(Error::<T>::UnexpectedCollectionType);1630			}1631		};16321633		Ok(())1634	}16351636	fn create_item_no_validation(1637		collection: &CollectionHandle<T>,1638		owner: &T::CrossAccountId,1639		data: CreateItemData,1640	) -> DispatchResult {1641		match data {1642			CreateItemData::NFT(data) => {1643				let item = NftItemType {1644					owner: owner.clone(),1645					const_data: data.const_data.into_inner(),1646					variable_data: data.variable_data.into_inner(),1647				};16481649				Self::add_nft_item(collection, item)?;1650			}1651			CreateItemData::Fungible(data) => {1652				Self::add_fungible_item(collection, owner, data.value)?;1653			}1654			CreateItemData::ReFungible(data) => {1655				let owner_list = vec![Ownership {1656					owner: owner.clone(),1657					fraction: data.pieces,1658				}];16591660				let item = ReFungibleItemType {1661					owner: owner_list,1662					const_data: data.const_data.into_inner(),1663					variable_data: data.variable_data.into_inner(),1664				};16651666				Self::add_refungible_item(collection, item)?;1667			}1668		};16691670		Ok(())1671	}16721673	fn add_fungible_item(1674		collection: &CollectionHandle<T>,1675		owner: &T::CrossAccountId,1676		value: u128,1677	) -> DispatchResult {1678		let collection_id = collection.id;16791680		// Does new owner already have an account?1681		collection.consume_sload()?;1682		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;16831684		// Mint1685		let item = FungibleItemType {1686			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1687		};1688		collection.consume_sstore()?;1689		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);16901691		// Update balance1692		collection.consume_sload()?;1693		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1694			.checked_add(value)1695			.ok_or(Error::<T>::NumOverflow)?;1696		collection.consume_sstore()?;1697		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);16981699		collection.log(ERC20Events::Transfer {1700			from: H160::default(),1701			to: *owner.as_eth(),1702			value: value.into(),1703		})?;1704		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1705		Ok(())1706	}17071708	fn add_refungible_item(1709		collection: &CollectionHandle<T>,1710		item: ReFungibleItemType<T::CrossAccountId>,1711	) -> DispatchResult {1712		let collection_id = collection.id;17131714		let current_index = <ItemListIndex>::get(collection_id)1715			.checked_add(1)1716			.ok_or(Error::<T>::NumOverflow)?;1717		let itemcopy = item.clone();17181719		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1720		let item_owner = item.owner.first().expect("only one owner is defined");17211722		let value = item_owner.fraction;1723		let owner = item_owner.owner.clone();17241725		Self::add_token_index(collection, current_index, &owner)?;17261727		<ItemListIndex>::insert(collection_id, current_index);1728		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17291730		// Update balance1731		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1732			.checked_add(value)1733			.ok_or(Error::<T>::NumOverflow)?;1734		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17351736		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1737		Ok(())1738	}17391740	fn add_nft_item(1741		collection: &CollectionHandle<T>,1742		item: NftItemType<T::CrossAccountId>,1743	) -> DispatchResult {1744		let collection_id = collection.id;17451746		let current_index = <ItemListIndex>::get(collection_id)1747			.checked_add(1)1748			.ok_or(Error::<T>::NumOverflow)?;17491750		let item_owner = item.owner.clone();1751		Self::add_token_index(collection, current_index, &item.owner)?;17521753		<ItemListIndex>::insert(collection_id, current_index);1754		<NftItemList<T>>::insert(collection_id, current_index, item);17551756		// Update balance1757		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1758			.checked_add(1)1759			.ok_or(Error::<T>::NumOverflow)?;1760		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);17611762		collection.log(ERC721Events::Transfer {1763			from: H160::default(),1764			to: *item_owner.as_eth(),1765			token_id: current_index.into(),1766		})?;1767		Self::deposit_event(RawEvent::ItemCreated(1768			collection_id,1769			current_index,1770			item_owner,1771		));1772		Ok(())1773	}17741775	fn burn_refungible_item(1776		collection: &CollectionHandle<T>,1777		item_id: TokenId,1778		owner: &T::CrossAccountId,1779	) -> DispatchResult {1780		let collection_id = collection.id;17811782		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1783			.ok_or(Error::<T>::TokenNotFound)?;1784		let rft_balance = token1785			.owner1786			.iter()1787			.find(|&i| i.owner == *owner)1788			.ok_or(Error::<T>::TokenNotFound)?;1789		Self::remove_token_index(collection, item_id, owner)?;17901791		// update balance1792		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1793			.checked_sub(rft_balance.fraction)1794			.ok_or(Error::<T>::NumOverflow)?;1795		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);17961797		// Re-create owners list with sender removed1798		let index = token1799			.owner1800			.iter()1801			.position(|i| i.owner == *owner)1802			.expect("owned item is exists");1803		token.owner.remove(index);1804		let owner_count = token.owner.len();18051806		// Burn the token completely if this was the last (only) owner1807		if owner_count == 0 {1808			<ReFungibleItemList<T>>::remove(collection_id, item_id);1809			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1810		} else {1811			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1812		}18131814		Ok(())1815	}18161817	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1818		let collection_id = collection.id;18191820		let item =1821			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1822		Self::remove_token_index(collection, item_id, &item.owner)?;18231824		// update balance1825		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1826			.checked_sub(1)1827			.ok_or(Error::<T>::NumOverflow)?;1828		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1829		<NftItemList<T>>::remove(collection_id, item_id);1830		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);18311832		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1833		Ok(())1834	}18351836	fn burn_fungible_item(1837		owner: &T::CrossAccountId,1838		collection: &CollectionHandle<T>,1839		value: u128,1840	) -> DispatchResult {1841		let collection_id = collection.id;18421843		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1844		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18451846		// update balance1847		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1848			.checked_sub(value)1849			.ok_or(Error::<T>::NumOverflow)?;1850		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18511852		if balance.value - value > 0 {1853			balance.value -= value;1854			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1855		} else {1856			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1857		}18581859		collection.log(ERC20Events::Transfer {1860			from: *owner.as_eth(),1861			to: H160::default(),1862			value: value.into(),1863		})?;1864		Ok(())1865	}18661867	pub fn get_collection(1868		collection_id: CollectionId,1869	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1870		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1871	}18721873	fn check_owner_permissions(1874		target_collection: &CollectionHandle<T>,1875		subject: &T::AccountId,1876	) -> DispatchResult {1877		ensure!(1878			*subject == target_collection.owner,1879			Error::<T>::NoPermission1880		);18811882		Ok(())1883	}18841885	fn is_owner_or_admin_permissions(1886		collection: &CollectionHandle<T>,1887		subject: &T::CrossAccountId,1888	) -> Result<bool, DispatchError> {1889		collection.consume_sload()?;1890		Ok(*subject.as_sub() == collection.owner1891			|| <AdminList<T>>::get(collection.id).contains(subject))1892	}18931894	fn check_owner_or_admin_permissions(1895		collection: &CollectionHandle<T>,1896		subject: &T::CrossAccountId,1897	) -> DispatchResult {1898		ensure!(1899			Self::is_owner_or_admin_permissions(collection, subject)?,1900			Error::<T>::NoPermission1901		);19021903		Ok(())1904	}19051906	fn owned_amount(1907		subject: &T::CrossAccountId,1908		collection: &CollectionHandle<T>,1909		item_id: TokenId,1910	) -> Result<Option<u128>, DispatchError> {1911		collection.consume_sload()?;1912		Ok(Self::owned_amount_unchecked(subject, collection, item_id))1913	}19141915	fn owned_amount_unchecked(1916		subject: &T::CrossAccountId,1917		target_collection: &CollectionHandle<T>,1918		item_id: TokenId,1919	) -> Option<u128> {1920		let collection_id = target_collection.id;19211922		match target_collection.mode {1923			CollectionMode::NFT => {1924				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1925			}1926			CollectionMode::Fungible(_) => {1927				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1928			}1929			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1930				.owner1931				.iter()1932				.find(|i| i.owner == *subject)1933				.map(|i| i.fraction),1934			CollectionMode::Invalid => None,1935		}1936	}19371938	fn is_item_owner(1939		subject: &T::CrossAccountId,1940		target_collection: &CollectionHandle<T>,1941		item_id: TokenId,1942	) -> Result<bool, DispatchError> {1943		Ok(match target_collection.mode {1944			CollectionMode::Fungible(_) => true,1945			_ => Self::owned_amount(subject, target_collection, item_id)?.is_some(),1946		})1947	}19481949	fn check_white_list(1950		collection: &CollectionHandle<T>,1951		address: &T::CrossAccountId,1952	) -> DispatchResult {1953		collection.consume_sload()?;1954		ensure!(1955			<WhiteList<T>>::contains_key(collection.id, address.as_sub()),1956			Error::<T>::AddresNotInWhiteList,1957		);1958		Ok(())1959	}19601961	/// Check if token exists. In case of Fungible, check if there is an entry for1962	/// the owner in fungible balances double map1963	fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1964		let collection_id = target_collection.id;1965		let exists = match target_collection.mode {1966			CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1967			CollectionMode::Fungible(_) => true,1968			CollectionMode::ReFungible => {1969				<ReFungibleItemList<T>>::contains_key(collection_id, item_id)1970			}1971			_ => false,1972		};19731974		ensure!(exists, Error::<T>::TokenNotFound);1975		Ok(())1976	}19771978	fn transfer_fungible(1979		collection: &CollectionHandle<T>,1980		value: u128,1981		owner: &T::CrossAccountId,1982		recipient: &T::CrossAccountId,1983	) -> DispatchResult {1984		let collection_id = collection.id;19851986		collection.consume_sload()?;1987		collection.consume_sload()?;1988		let mut recipient_balance = <FungibleItemList<T>>::get(collection_id, recipient.as_sub());1989		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());19901991		recipient_balance.value = recipient_balance1992			.value1993			.checked_add(value)1994			.ok_or(Error::<T>::NumOverflow)?;1995		balance.value = balance1996			.value1997			.checked_sub(value)1998			.ok_or(Error::<T>::TokenValueTooLow)?;19992000		// update balanceOf2001		collection.consume_sstore()?;2002		collection.consume_sstore()?;2003		if balance.value != 0 {2004			<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value);2005		} else {2006			<Balance<T>>::remove(collection_id, owner.as_sub());2007		}2008		<Balance<T>>::insert(collection_id, recipient.as_sub(), recipient_balance.value);20092010		// Reduce or remove sender2011		collection.consume_sstore()?;2012		collection.consume_sstore()?;2013		if balance.value != 0 {2014			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2015		} else {2016			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());2017		}2018		<FungibleItemList<T>>::insert(collection_id, recipient.as_sub(), recipient_balance);20192020		collection.log(ERC20Events::Transfer {2021			from: *owner.as_eth(),2022			to: *recipient.as_eth(),2023			value: value.into(),2024		})?;2025		Self::deposit_event(RawEvent::Transfer(2026			collection.id,2027			1,2028			owner.clone(),2029			recipient.clone(),2030			value,2031		));20322033		Ok(())2034	}20352036	fn transfer_refungible(2037		collection: &CollectionHandle<T>,2038		item_id: TokenId,2039		value: u128,2040		owner: T::CrossAccountId,2041		new_owner: T::CrossAccountId,2042	) -> DispatchResult {2043		let collection_id = collection.id;2044		collection.consume_sload()?;2045		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2046			.ok_or(Error::<T>::TokenNotFound)?;20472048		let item = full_item2049			.owner2050			.iter()2051			.find(|i| i.owner == owner)2052			.ok_or(Error::<T>::TokenNotFound)?;2053		let amount = item.fraction;20542055		ensure!(amount >= value, Error::<T>::TokenValueTooLow);20562057		collection.consume_sload()?;2058		// update balance2059		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2060			.checked_sub(value)2061			.ok_or(Error::<T>::NumOverflow)?;2062		collection.consume_sstore()?;2063		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20642065		collection.consume_sload()?;2066		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2067			.checked_add(value)2068			.ok_or(Error::<T>::NumOverflow)?;2069		collection.consume_sstore()?;2070		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20712072		let old_owner = item.owner.clone();2073		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20742075		let mut new_full_item = full_item.clone();2076		// transfer2077		if amount == value && !new_owner_has_account {2078			// change owner2079			// new owner do not have account2080			new_full_item2081				.owner2082				.iter_mut()2083				.find(|i| i.owner == owner)2084				.expect("old owner does present in refungible")2085				.owner = new_owner.clone();2086			collection.consume_sstore()?;2087			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20882089			// update index collection2090			Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;2091		} else {2092			new_full_item2093				.owner2094				.iter_mut()2095				.find(|i| i.owner == owner)2096				.expect("old owner does present in refungible")2097				.fraction -= value;20982099			// separate amount2100			if new_owner_has_account {2101				// new owner has account2102				new_full_item2103					.owner2104					.iter_mut()2105					.find(|i| i.owner == new_owner)2106					.expect("new owner has account")2107					.fraction += value;2108			} else {2109				// new owner do not have account2110				new_full_item.owner.push(Ownership {2111					owner: new_owner.clone(),2112					fraction: value,2113				});2114				Self::add_token_index(collection, item_id, &new_owner)?;2115			}21162117			collection.consume_sstore()?;2118			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2119		}21202121		Self::deposit_event(RawEvent::Transfer(2122			collection.id,2123			item_id,2124			owner,2125			new_owner,2126			amount,2127		));21282129		Ok(())2130	}21312132	fn transfer_nft(2133		collection: &CollectionHandle<T>,2134		item_id: TokenId,2135		sender: T::CrossAccountId,2136		new_owner: T::CrossAccountId,2137	) -> DispatchResult {2138		let collection_id = collection.id;2139		collection.consume_sload()?;2140		let mut item =2141			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21422143		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);21442145		collection.consume_sload()?;2146		// update balance2147		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2148			.checked_sub(1)2149			.ok_or(Error::<T>::NumOverflow)?;2150		collection.consume_sstore()?;2151		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21522153		collection.consume_sload()?;2154		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2155			.checked_add(1)2156			.ok_or(Error::<T>::NumOverflow)?;2157		collection.consume_sstore()?;2158		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21592160		// change owner2161		let old_owner = item.owner.clone();2162		item.owner = new_owner.clone();2163		collection.consume_sstore()?;2164		<NftItemList<T>>::insert(collection_id, item_id, item);21652166		// update index collection2167		Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;21682169		collection.log(ERC721Events::Transfer {2170			from: *sender.as_eth(),2171			to: *new_owner.as_eth(),2172			token_id: item_id.into(),2173		})?;2174		Self::deposit_event(RawEvent::Transfer(2175			collection.id,2176			item_id,2177			sender,2178			new_owner,2179			1,2180		));21812182		Ok(())2183	}21842185	fn set_re_fungible_variable_data(2186		collection: &CollectionHandle<T>,2187		item_id: TokenId,2188		data: Vec<u8>,2189	) -> DispatchResult {2190		let collection_id = collection.id;2191		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2192			.ok_or(Error::<T>::TokenNotFound)?;21932194		item.variable_data = data;21952196		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);21972198		Ok(())2199	}22002201	fn set_nft_variable_data(2202		collection: &CollectionHandle<T>,2203		item_id: TokenId,2204		data: Vec<u8>,2205	) -> DispatchResult {2206		let collection_id = collection.id;2207		let mut item =2208			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;22092210		item.variable_data = data;22112212		<NftItemList<T>>::insert(collection_id, item_id, item);22132214		Ok(())2215	}22162217	#[allow(dead_code)]2218	fn init_collection(item: &Collection<T>) {2219		// check params2220		assert!(2221			item.decimal_points <= MAX_DECIMAL_POINTS,2222			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2223		);2224		assert!(2225			item.name.len() <= 64,2226			"Collection name can not be longer than 63 char"2227		);2228		assert!(2229			item.name.len() <= 256,2230			"Collection description can not be longer than 255 char"2231		);2232		assert!(2233			item.token_prefix.len() <= 16,2234			"Token prefix can not be longer than 15 char"2235		);22362237		// Generate next collection ID2238		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();22392240		CreatedCollectionCount::put(next_id);2241	}22422243	#[allow(dead_code)]2244	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2245		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22462247		Self::add_token_index(2248			&CollectionHandle::get(collection_id).unwrap(),2249			current_index,2250			&item.owner,2251		)2252		.unwrap();22532254		<ItemListIndex>::insert(collection_id, current_index);22552256		// Update balance2257		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2258			.checked_add(1)2259			.unwrap();2260		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2261	}22622263	#[allow(dead_code)]2264	fn init_fungible_token(2265		collection_id: CollectionId,2266		owner: &T::CrossAccountId,2267		item: &FungibleItemType,2268	) {2269		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22702271		Self::add_token_index(2272			&CollectionHandle::get(collection_id).unwrap(),2273			current_index,2274			owner,2275		)2276		.unwrap();22772278		<ItemListIndex>::insert(collection_id, current_index);22792280		// Update balance2281		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2282			.checked_add(item.value)2283			.unwrap();2284		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2285	}22862287	#[allow(dead_code)]2288	fn init_refungible_token(2289		collection_id: CollectionId,2290		item: &ReFungibleItemType<T::CrossAccountId>,2291	) {2292		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22932294		let value = item.owner.first().unwrap().fraction;2295		let owner = item.owner.first().unwrap().owner.clone();22962297		Self::add_token_index(2298			&CollectionHandle::get(collection_id).unwrap(),2299			current_index,2300			&owner,2301		)2302		.unwrap();23032304		<ItemListIndex>::insert(collection_id, current_index);23052306		// Update balance2307		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2308			.checked_add(value)2309			.unwrap();2310		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2311	}23122313	fn add_token_index(2314		collection: &CollectionHandle<T>,2315		item_index: TokenId,2316		owner: &T::CrossAccountId,2317	) -> DispatchResult {2318		// add to account limit2319		collection.consume_sload()?;2320		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2321			// bound Owned tokens by a single address2322			collection.consume_sload()?;2323			let count = <AccountItemCount<T>>::get(owner.as_sub());2324			ensure!(2325				count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,2326				Error::<T>::AddressOwnershipLimitExceeded2327			);23282329			collection.consume_sstore()?;2330			<AccountItemCount<T>>::insert(2331				owner.as_sub(),2332				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2333			);2334		} else {2335			collection.consume_sstore()?;2336			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2337		}23382339		collection.consume_sload()?;2340		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2341		if list_exists {2342			collection.consume_sload()?;2343			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2344			let item_contains = list.contains(&item_index.clone());23452346			if !item_contains {2347				list.push(item_index);2348			}23492350			collection.consume_sstore()?;2351			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2352		} else {2353			let itm = vec![item_index];2354			collection.consume_sstore()?;2355			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), itm);2356		}23572358		Ok(())2359	}23602361	fn remove_token_index(2362		collection: &CollectionHandle<T>,2363		item_index: TokenId,2364		owner: &T::CrossAccountId,2365	) -> DispatchResult {2366		// update counter2367		collection.consume_sload()?;2368		collection.consume_sstore()?;2369		<AccountItemCount<T>>::insert(2370			owner.as_sub(),2371			<AccountItemCount<T>>::get(owner.as_sub())2372				.checked_sub(1)2373				.ok_or(Error::<T>::NumOverflow)?,2374		);23752376		collection.consume_sload()?;2377		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2378		if list_exists {2379			collection.consume_sload()?;2380			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2381			let item_contains = list.contains(&item_index.clone());23822383			if item_contains {2384				list.retain(|&item| item != item_index);2385				collection.consume_sstore()?;2386				<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2387			}2388		}23892390		Ok(())2391	}23922393	fn move_token_index(2394		collection: &CollectionHandle<T>,2395		item_index: TokenId,2396		old_owner: &T::CrossAccountId,2397		new_owner: &T::CrossAccountId,2398	) -> DispatchResult {2399		Self::remove_token_index(collection, item_index, old_owner)?;2400		Self::add_token_index(collection, item_index, new_owner)?;24012402		Ok(())2403	}2404}24052406sp_api::decl_runtime_apis! {2407	pub trait NftApi {2408		/// Used for ethereum integration2409		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2410	}2411}
after · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]7#![cfg_attr(not(feature = "std"), no_std)]8#![allow(9	clippy::too_many_arguments,10	clippy::unnecessary_mut_passed,11	clippy::unused_unit12)]1314extern crate alloc;1516pub use serde::{Serialize, Deserialize};1718pub use frame_support::{19	construct_runtime, decl_event, decl_module, decl_storage, decl_error,20	dispatch::DispatchResult,21	ensure, fail, parameter_types,22	traits::{23		Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,24		Randomness, IsSubType, WithdrawReasons,25	},26	weights::{27		constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},28		DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,29		WeightToFeePolynomial, DispatchClass,30	},31	StorageValue, transactional,32};3334use frame_system::{self as system, ensure_signed};35use sp_core::H160;36use sp_std::vec;37use sp_runtime::{DispatchError, sp_std::prelude::Vec};38use core::ops::{Deref, DerefMut};39use nft_data_structs::{40	MAX_DECIMAL_POINTS, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_REFUNGIBLE_PIECES,41	CUSTOM_DATA_LIMIT, COLLECTION_NUMBER_LIMIT, ACCOUNT_TOKEN_OWNERSHIP_LIMIT,42	VARIABLE_ON_CHAIN_SCHEMA_LIMIT, CONST_ON_CHAIN_SCHEMA_LIMIT, COLLECTION_ADMINS_LIMIT,43	OFFCHAIN_SCHEMA_LIMIT, AccessMode, Collection, CreateItemData, CollectionLimits, CollectionId,44	CollectionMode, TokenId, SchemaVersion, SponsorshipState, Ownership, NftItemType,45	FungibleItemType, ReFungibleItemType,46};4748#[cfg(test)]49mod mock;5051#[cfg(test)]52mod tests;5354mod default_weights;55mod eth;56mod sponsorship;57pub use sponsorship::NftSponsorshipHandler;58pub use eth::sponsoring::NftEthSponsorshipHandler;5960pub use eth::NftErcSupport;61pub use eth::account::*;62use eth::erc::{ERC20Events, ERC721Events};6364#[cfg(feature = "runtime-benchmarks")]65mod benchmarking;6667pub trait WeightInfo {68	fn create_collection() -> Weight;69	fn destroy_collection() -> Weight;70	fn add_to_white_list() -> Weight;71	fn remove_from_white_list() -> Weight;72	fn set_public_access_mode() -> Weight;73	fn set_mint_permission() -> Weight;74	fn change_collection_owner() -> Weight;75	fn add_collection_admin() -> Weight;76	fn remove_collection_admin() -> Weight;77	fn set_collection_sponsor() -> Weight;78	fn confirm_sponsorship() -> Weight;79	fn remove_collection_sponsor() -> Weight;80	fn create_item(s: usize) -> Weight;81	fn burn_item() -> Weight;82	fn transfer() -> Weight;83	fn approve() -> Weight;84	fn transfer_from() -> Weight;85	fn set_offchain_schema() -> Weight;86	fn set_const_on_chain_schema() -> Weight;87	fn set_variable_on_chain_schema() -> Weight;88	fn set_variable_meta_data() -> Weight;89	fn enable_contract_sponsoring() -> Weight;90	fn set_schema_version() -> Weight;91	fn set_contract_sponsoring_rate_limit() -> Weight;92	fn set_variable_meta_data_sponsoring_rate_limit() -> Weight;93	fn toggle_contract_white_list() -> Weight;94	fn add_to_contract_white_list() -> Weight;95	fn remove_from_contract_white_list() -> Weight;96	fn set_collection_limits() -> Weight;97}9899decl_error! {100	/// Error for non-fungible-token module.101	pub enum Error for Module<T: Config> {102		/// Total collections bound exceeded.103		TotalCollectionsLimitExceeded,104		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.105		CollectionDecimalPointLimitExceeded,106		/// Collection name can not be longer than 63 char.107		CollectionNameLimitExceeded,108		/// Collection description can not be longer than 255 char.109		CollectionDescriptionLimitExceeded,110		/// Token prefix can not be longer than 15 char.111		CollectionTokenPrefixLimitExceeded,112		/// This collection does not exist.113		CollectionNotFound,114		/// Item not exists.115		TokenNotFound,116		/// Admin not found117		AdminNotFound,118		/// Arithmetic calculation overflow.119		NumOverflow,120		/// Account already has admin role.121		AlreadyAdmin,122		/// You do not own this collection.123		NoPermission,124		/// This address is not set as sponsor, use setCollectionSponsor first.125		ConfirmUnsetSponsorFail,126		/// Collection is not in mint mode.127		PublicMintingNotAllowed,128		/// Sender parameter and item owner must be equal.129		MustBeTokenOwner,130		/// Item balance not enough.131		TokenValueTooLow,132		/// Size of item is too large.133		NftSizeLimitExceeded,134		/// No approve found135		ApproveNotFound,136		/// Requested value more than approved.137		TokenValueNotEnough,138		/// Only approved addresses can call this method.139		ApproveRequired,140		/// Address is not in white list.141		AddresNotInWhiteList,142		/// Number of collection admins bound exceeded.143		CollectionAdminsLimitExceeded,144		/// Owned tokens by a single address bound exceeded.145		AddressOwnershipLimitExceeded,146		/// Length of items properties must be greater than 0.147		EmptyArgument,148		/// const_data exceeded data limit.149		TokenConstDataLimitExceeded,150		/// variable_data exceeded data limit.151		TokenVariableDataLimitExceeded,152		/// Not NFT item data used to mint in NFT collection.153		NotNftDataUsedToMintNftCollectionToken,154		/// Not Fungible item data used to mint in Fungible collection.155		NotFungibleDataUsedToMintFungibleCollectionToken,156		/// Not Re Fungible item data used to mint in Re Fungible collection.157		NotReFungibleDataUsedToMintReFungibleCollectionToken,158		/// Unexpected collection type.159		UnexpectedCollectionType,160		/// Can't store metadata in fungible tokens.161		CantStoreMetadataInFungibleTokens,162		/// Collection token limit exceeded163		CollectionTokenLimitExceeded,164		/// Account token limit exceeded per collection165		AccountTokenLimitExceeded,166		/// Collection limit bounds per collection exceeded167		CollectionLimitBoundsExceeded,168		/// Tried to enable permissions which are only permitted to be disabled169		OwnerPermissionsCantBeReverted,170		/// Schema data size limit bound exceeded171		SchemaDataLimitExceeded,172		/// Maximum refungibility exceeded173		WrongRefungiblePieces,174		/// createRefungible should be called with one owner175		BadCreateRefungibleCall,176		/// Gas limit exceeded177		OutOfGas,178		/// Collection settings not allowing items transferring179		TransferNotAllowed,180		/// Can't transfer tokens to ethereum zero address181		AddressIsZero,182	}183}184185#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]186pub struct CollectionHandle<T: Config> {187	pub id: CollectionId,188	collection: Collection<T>,189	recorder: pallet_evm_coder_substrate::SubstrateRecorder<T>,190}191impl<T: Config> CollectionHandle<T> {192	pub fn get_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {193		<CollectionById<T>>::get(id).map(|collection| Self {194			id,195			collection,196			recorder: pallet_evm_coder_substrate::SubstrateRecorder::new(197				eth::collection_id_to_address(id),198				gas_limit,199			),200		})201	}202	pub fn get(id: CollectionId) -> Option<Self> {203		Self::get_with_gas_limit(id, u64::MAX)204	}205	pub fn log(&self, log: impl evm_coder::ToLog) -> DispatchResult {206		self.recorder.log_sub(log)207	}208	#[allow(dead_code)]209	fn consume_gas(&self, gas: u64) -> DispatchResult {210		self.recorder.consume_gas_sub(gas)211	}212	fn consume_sload(&self) -> DispatchResult {213		self.recorder.consume_sload_sub()214	}215	fn consume_sstore(&self) -> DispatchResult {216		self.recorder.consume_sstore_sub()217	}218	pub fn submit_logs(self) -> DispatchResult {219		self.recorder.submit_logs()220	}221	pub fn save(self) -> DispatchResult {222		self.recorder.submit_logs()?;223		<CollectionById<T>>::insert(self.id, self.collection);224		Ok(())225	}226}227impl<T: Config> Deref for CollectionHandle<T> {228	type Target = Collection<T>;229230	fn deref(&self) -> &Self::Target {231		&self.collection232	}233}234235impl<T: Config> DerefMut for CollectionHandle<T> {236	fn deref_mut(&mut self) -> &mut Self::Target {237		&mut self.collection238	}239}240241pub trait Config: system::Config + pallet_evm_coder_substrate::Config + Sized {242	type Event: From<Event<Self>> + Into<<Self as system::Config>::Event>;243244	/// Weight information for extrinsics in this pallet.245	type WeightInfo: WeightInfo;246247	type EvmAddressMapping: pallet_evm::AddressMapping<Self::AccountId>;248	type EvmBackwardsAddressMapping: EvmBackwardsAddressMapping<Self::AccountId>;249250	type CrossAccountId: CrossAccountId<Self::AccountId>;251	type Currency: Currency<Self::AccountId>;252	type CollectionCreationPrice: Get<253		<<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,254	>;255	type TreasuryAccountId: Get<Self::AccountId>;256}257258// # Used definitions259//260// ## User control levels261//262// chain-controlled - key is uncontrolled by user263//                    i.e autoincrementing index264//                    can use non-cryptographic hash265// real - key is controlled by user266//        but it is hard to generate enough colliding values, i.e owner of signed txs267//        can use non-cryptographic hash268// controlled - key is completly controlled by users269//              i.e maps with mutable keys270//              should use cryptographic hash271//272// ## User control level downgrade reasons273//274// ?1 - chain-controlled -> controlled275//      collections/tokens can be destroyed, resulting in massive holes276// ?2 - chain-controlled -> controlled277//      same as ?1, but can be only added, resulting in easier exploitation278// ?3 - real -> controlled279//      no confirmation required, so addresses can be easily generated280decl_storage! {281	trait Store for Module<T: Config> as Nft {282283		//#region Private members284		/// Id of next collection285		CreatedCollectionCount: u32;286		/// Used for migrations287		ChainVersion: u64;288		/// Id of last collection token289		/// Collection id (controlled?1)290		ItemListIndex: map hasher(blake2_128_concat) CollectionId => TokenId;291		//#endregion292293		//#region Bound counters294		/// Amount of collections destroyed, used for total amount tracking with295		/// CreatedCollectionCount296		DestroyedCollectionCount: u32;297		/// Total amount of account owned tokens (NFTs + RFTs + unique fungibles)298		/// Account id (real)299		pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;300		//#endregion301302		//#region Basic collections303		/// Collection info304		/// Collection id (controlled?1)305		pub CollectionById get(fn collection_id) config(): map hasher(blake2_128_concat) CollectionId => Option<Collection<T>> = None;306		/// List of collection admins307		/// Collection id (controlled?2)308		pub AdminList get(fn admin_list_collection): map hasher(blake2_128_concat) CollectionId => Vec<T::CrossAccountId>;309		/// Whitelisted collection users310		/// Collection id (controlled?2), user id (controlled?3)311		pub WhiteList get(fn white_list): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => bool;312		//#endregion313314		/// How many of collection items user have315		/// Collection id (controlled?2), account id (real)316		pub Balance get(fn balance_count): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => u128;317318		/// Amount of items which spender can transfer out of owners account (via transferFrom)319		/// Collection id (controlled?2), (token id (controlled ?2) + owner account id (real) + spender account id (controlled?3))320		/// TODO: Off chain worker should remove from this map when token gets removed321		pub Allowances get(fn approved): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) (TokenId, T::AccountId, T::AccountId) => u128;322323		//#region Item collections324		/// Collection id (controlled?2), token id (controlled?1)325		pub NftItemList get(fn nft_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<NftItemType<T::CrossAccountId>>;326		/// Collection id (controlled?2), owner (controlled?2)327		pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => FungibleItemType;328		/// Collection id (controlled?2), token id (controlled?1)329		pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<ReFungibleItemType<T::CrossAccountId>>;330		//#endregion331332		//#region Index list333		/// Collection id (controlled?2), tokens owner (controlled?2)334		pub AddressTokens get(fn address_tokens): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) T::AccountId => Vec<TokenId>;335		//#endregion336337		//#region Tokens transfer rate limit baskets338		/// (Collection id (controlled?2), who created (real))339		/// TODO: Off chain worker should remove from this map when collection gets removed340		pub CreateItemBasket get(fn create_item_basket): map hasher(blake2_128_concat) (CollectionId, T::AccountId) => T::BlockNumber;341		/// Collection id (controlled?2), token id (controlled?2)342		pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;343		/// Collection id (controlled?2), owning user (real)344		pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;345		/// Collection id (controlled?2), token id (controlled?2)346		pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => T::BlockNumber;347		//#endregion348349		/// Variable metadata sponsoring350		/// Collection id (controlled?2), token id (controlled?2)351		pub VariableMetaDataBasket get(fn variable_meta_data_basket): double_map hasher(blake2_128_concat) CollectionId, hasher(blake2_128_concat) TokenId => Option<T::BlockNumber> = None;352	}353	add_extra_genesis {354		build(|config: &GenesisConfig<T>| {355			// Modification of storage356			for (_num, _c) in &config.collection_id {357				<Module<T>>::init_collection(_c);358			}359360			for (_num, _c, _i) in &config.nft_item_id {361				<Module<T>>::init_nft_token(*_c, _i);362			}363364			for (collection_id, account_id, fungible_item) in &config.fungible_item_id {365				<Module<T>>::init_fungible_token(*collection_id, &T::CrossAccountId::from_sub(account_id.clone()), fungible_item);366			}367368			for (_num, _c, _i) in &config.refungible_item_id {369				<Module<T>>::init_refungible_token(*_c, _i);370			}371		})372	}373}374375decl_event!(376	pub enum Event<T>377	where378		AccountId = <T as frame_system::Config>::AccountId,379		CrossAccountId = <T as Config>::CrossAccountId,380	{381		/// New collection was created382		///383		/// # Arguments384		///385		/// * collection_id: Globally unique identifier of newly created collection.386		///387		/// * mode: [CollectionMode] converted into u8.388		///389		/// * account_id: Collection owner.390		CollectionCreated(CollectionId, u8, AccountId),391392		/// New item was created.393		///394		/// # Arguments395		///396		/// * collection_id: Id of the collection where item was created.397		///398		/// * item_id: Id of an item. Unique within the collection.399		///400		/// * recipient: Owner of newly created item401		ItemCreated(CollectionId, TokenId, CrossAccountId),402403		/// Collection item was burned.404		///405		/// # Arguments406		///407		/// collection_id.408		///409		/// item_id: Identifier of burned NFT.410		ItemDestroyed(CollectionId, TokenId),411412		/// Item was transferred413		///414		/// * collection_id: Id of collection to which item is belong415		///416		/// * item_id: Id of an item417		///418		/// * sender: Original owner of item419		///420		/// * recipient: New owner of item421		///422		/// * amount: Always 1 for NFT423		Transfer(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),424425		/// * collection_id426		///427		/// * item_id428		///429		/// * sender430		///431		/// * spender432		///433		/// * amount434		Approved(CollectionId, TokenId, CrossAccountId, CrossAccountId, u128),435	}436);437438decl_module! {439	pub struct Module<T: Config> for enum Call440	where441		origin: T::Origin442	{443		fn deposit_event() = default;444		const CollectionAdminsLimit: u64 = COLLECTION_ADMINS_LIMIT;445		type Error = Error<T>;446447		fn on_initialize(_now: T::BlockNumber) -> Weight {448			0449		}450451		/// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.452		///453		/// # Permissions454		///455		/// * Anyone.456		///457		/// # Arguments458		///459		/// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.460		///461		/// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.462		///463		/// * token_prefix: UTF-8 string with token prefix.464		///465		/// * mode: [CollectionMode] collection type and type dependent data.466		// returns collection ID467		#[weight = <T as Config>::WeightInfo::create_collection()]468		#[transactional]469		pub fn create_collection(origin,470								 collection_name: Vec<u16>,471								 collection_description: Vec<u16>,472								 token_prefix: Vec<u8>,473								 mode: CollectionMode) -> DispatchResult {474475			// Anyone can create a collection476			let who = ensure_signed(origin)?;477478			// Take a (non-refundable) deposit of collection creation479			let mut imbalance = <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();480			imbalance.subsume(<<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(481				&T::TreasuryAccountId::get(),482				T::CollectionCreationPrice::get(),483			));484			<T as Config>::Currency::settle(485				&who,486				imbalance,487				WithdrawReasons::TRANSFER,488				ExistenceRequirement::KeepAlive,489			).map_err(|_| Error::<T>::NoPermission)?;490491			let decimal_points = match mode {492				CollectionMode::Fungible(points) => points,493				_ => 0494			};495496			let created_count = CreatedCollectionCount::get();497			let destroyed_count = DestroyedCollectionCount::get();498499			// bound Total number of collections500			ensure!(created_count - destroyed_count < COLLECTION_NUMBER_LIMIT, Error::<T>::TotalCollectionsLimitExceeded);501502			// check params503			ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);504			ensure!(collection_name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);505			ensure!(collection_description.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);506			ensure!(token_prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);507508			// Generate next collection ID509			let next_id = created_count510				.checked_add(1)511				.ok_or(Error::<T>::NumOverflow)?;512513			CreatedCollectionCount::put(next_id);514515			let limits = CollectionLimits {516				sponsored_data_size: CUSTOM_DATA_LIMIT,517				..Default::default()518			};519520			// Create new collection521			let new_collection = Collection {522				owner: who.clone(),523				name: collection_name,524				mode: mode.clone(),525				mint_mode: false,526				access: AccessMode::Normal,527				description: collection_description,528				decimal_points,529				token_prefix,530				offchain_schema: Vec::new(),531				schema_version: SchemaVersion::ImageURL,532				sponsorship: SponsorshipState::Disabled,533				variable_on_chain_schema: Vec::new(),534				const_on_chain_schema: Vec::new(),535				limits,536				transfers_enabled: true,537			};538539			// Add new collection to map540			<CollectionById<T>>::insert(next_id, new_collection);541542			// call event543			Self::deposit_event(RawEvent::CollectionCreated(next_id, mode.id(), who));544545			Ok(())546		}547548		/// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.549		///550		/// # Permissions551		///552		/// * Collection Owner.553		///554		/// # Arguments555		///556		/// * collection_id: collection to destroy.557		#[weight = <T as Config>::WeightInfo::destroy_collection()]558		#[transactional]559		pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {560561			let sender = ensure_signed(origin)?;562			let collection = Self::get_collection(collection_id)?;563			Self::check_owner_permissions(&collection, &sender)?;564			if !collection.limits.owner_can_destroy {565				fail!(Error::<T>::NoPermission);566			}567568			<AddressTokens<T>>::remove_prefix(collection_id, None);569			<Allowances<T>>::remove_prefix(collection_id, None);570			<Balance<T>>::remove_prefix(collection_id, None);571			<ItemListIndex>::remove(collection_id);572			<AdminList<T>>::remove(collection_id);573			<CollectionById<T>>::remove(collection_id);574			<WhiteList<T>>::remove_prefix(collection_id, None);575576			<NftItemList<T>>::remove_prefix(collection_id, None);577			<FungibleItemList<T>>::remove_prefix(collection_id, None);578			<ReFungibleItemList<T>>::remove_prefix(collection_id, None);579580			<NftTransferBasket<T>>::remove_prefix(collection_id, None);581			<FungibleTransferBasket<T>>::remove_prefix(collection_id, None);582			<ReFungibleTransferBasket<T>>::remove_prefix(collection_id, None);583584			<VariableMetaDataBasket<T>>::remove_prefix(collection_id, None);585586			DestroyedCollectionCount::put(DestroyedCollectionCount::get()587				.checked_add(1)588				.ok_or(Error::<T>::NumOverflow)?);589590			Ok(())591		}592593		/// Add an address to white list.594		///595		/// # Permissions596		///597		/// * Collection Owner598		/// * Collection Admin599		///600		/// # Arguments601		///602		/// * collection_id.603		///604		/// * address.605		#[weight = <T as Config>::WeightInfo::add_to_white_list()]606		#[transactional]607		pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{608609			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);610			let collection = Self::get_collection(collection_id)?;611612			Self::toggle_white_list_internal(613				&sender,614				&collection,615				&address,616				true,617			)?;618619			Ok(())620		}621622		/// Remove an address from white list.623		///624		/// # Permissions625		///626		/// * Collection Owner627		/// * Collection Admin628		///629		/// # Arguments630		///631		/// * collection_id.632		///633		/// * address.634		#[weight = <T as Config>::WeightInfo::remove_from_white_list()]635		#[transactional]636		pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::CrossAccountId) -> DispatchResult{637638			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);639			let collection = Self::get_collection(collection_id)?;640641			Self::toggle_white_list_internal(642				&sender,643				&collection,644				&address,645				false,646			)?;647648			Ok(())649		}650651		/// Toggle between normal and white list access for the methods with access for `Anyone`.652		///653		/// # Permissions654		///655		/// * Collection Owner.656		///657		/// # Arguments658		///659		/// * collection_id.660		///661		/// * mode: [AccessMode]662		#[weight = <T as Config>::WeightInfo::set_public_access_mode()]663		#[transactional]664		pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult665		{666			let sender = ensure_signed(origin)?;667668			let mut target_collection = Self::get_collection(collection_id)?;669			Self::check_owner_permissions(&target_collection, &sender)?;670			target_collection.access = mode;671			target_collection.save()672		}673674		/// Allows Anyone to create tokens if:675		/// * White List is enabled, and676		/// * Address is added to white list, and677		/// * This method was called with True parameter678		///679		/// # Permissions680		/// * Collection Owner681		///682		/// # Arguments683		///684		/// * collection_id.685		///686		/// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.687		#[weight = <T as Config>::WeightInfo::set_mint_permission()]688		#[transactional]689		pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult690		{691			let sender = ensure_signed(origin)?;692693			let mut target_collection = Self::get_collection(collection_id)?;694			Self::check_owner_permissions(&target_collection, &sender)?;695			target_collection.mint_mode = mint_permission;696			target_collection.save()697		}698699		/// Change the owner of the collection.700		///701		/// # Permissions702		///703		/// * Collection Owner.704		///705		/// # Arguments706		///707		/// * collection_id.708		///709		/// * new_owner.710		#[weight = <T as Config>::WeightInfo::change_collection_owner()]711		#[transactional]712		pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {713714			let sender = ensure_signed(origin)?;715			let mut target_collection = Self::get_collection(collection_id)?;716			Self::check_owner_permissions(&target_collection, &sender)?;717			target_collection.owner = new_owner;718			target_collection.save()719		}720721		/// Adds an admin of the Collection.722		/// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership.723		///724		/// # Permissions725		///726		/// * Collection Owner.727		/// * Collection Admin.728		///729		/// # Arguments730		///731		/// * collection_id: ID of the Collection to add admin for.732		///733		/// * new_admin_id: Address of new admin to add.734		#[weight = <T as Config>::WeightInfo::add_collection_admin()]735		#[transactional]736		pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::CrossAccountId) -> DispatchResult {737			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);738			let collection = Self::get_collection(collection_id)?;739			Self::check_owner_or_admin_permissions(&collection, &sender)?;740			let mut admin_arr = <AdminList<T>>::get(collection_id);741742			match admin_arr.binary_search(&new_admin_id) {743				Ok(_) => {},744				Err(idx) => {745					ensure!(admin_arr.len() < COLLECTION_ADMINS_LIMIT as usize, Error::<T>::CollectionAdminsLimitExceeded);746					admin_arr.insert(idx, new_admin_id);747					<AdminList<T>>::insert(collection_id, admin_arr);748				}749			}750			Ok(())751		}752753		/// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.754		///755		/// # Permissions756		///757		/// * Collection Owner.758		/// * Collection Admin.759		///760		/// # Arguments761		///762		/// * collection_id: ID of the Collection to remove admin for.763		///764		/// * account_id: Address of admin to remove.765		#[weight = <T as Config>::WeightInfo::remove_collection_admin()]766		#[transactional]767		pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::CrossAccountId) -> DispatchResult {768			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);769			let collection = Self::get_collection(collection_id)?;770			Self::check_owner_or_admin_permissions(&collection, &sender)?;771			let mut admin_arr = <AdminList<T>>::get(collection_id);772773			if let Ok(idx) = admin_arr.binary_search(&account_id) {774				admin_arr.remove(idx);775				<AdminList<T>>::insert(collection_id, admin_arr);776			}777			Ok(())778		}779780		/// # Permissions781		///782		/// * Collection Owner783		///784		/// # Arguments785		///786		/// * collection_id.787		///788		/// * new_sponsor.789		#[weight = <T as Config>::WeightInfo::set_collection_sponsor()]790		#[transactional]791		pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {792			let sender = ensure_signed(origin)?;793			let mut target_collection = Self::get_collection(collection_id)?;794			Self::check_owner_permissions(&target_collection, &sender)?;795796			target_collection.sponsorship = SponsorshipState::Unconfirmed(new_sponsor);797			target_collection.save()798		}799800		/// # Permissions801		///802		/// * Sponsor.803		///804		/// # Arguments805		///806		/// * collection_id.807		#[weight = <T as Config>::WeightInfo::confirm_sponsorship()]808		#[transactional]809		pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {810			let sender = ensure_signed(origin)?;811812			let mut target_collection = Self::get_collection(collection_id)?;813			ensure!(814				target_collection.sponsorship.pending_sponsor() == Some(&sender),815				Error::<T>::ConfirmUnsetSponsorFail816			);817818			target_collection.sponsorship = SponsorshipState::Confirmed(sender);819			target_collection.save()820		}821822		/// Switch back to pay-per-own-transaction model.823		///824		/// # Permissions825		///826		/// * Collection owner.827		///828		/// # Arguments829		///830		/// * collection_id.831		#[weight = <T as Config>::WeightInfo::remove_collection_sponsor()]832		#[transactional]833		pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {834			let sender = ensure_signed(origin)?;835836			let mut target_collection = Self::get_collection(collection_id)?;837			Self::check_owner_permissions(&target_collection, &sender)?;838839			target_collection.sponsorship = SponsorshipState::Disabled;840			target_collection.save()841		}842843		/// This method creates a concrete instance of NFT Collection created with CreateCollection method.844		///845		/// # Permissions846		///847		/// * Collection Owner.848		/// * Collection Admin.849		/// * Anyone if850		///     * White List is enabled, and851		///     * Address is added to white list, and852		///     * MintPermission is enabled (see SetMintPermission method)853		///854		/// # Arguments855		///856		/// * collection_id: ID of the collection.857		///858		/// * owner: Address, initial owner of the NFT.859		///860		/// * data: Token data to store on chain.861		// #[weight =862		// (130_000_000 as Weight)863		// .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))864		// .saturating_add(RocksDbWeight::get().reads(10 as Weight))865		// .saturating_add(RocksDbWeight::get().writes(8 as Weight))]866867		#[weight = <T as Config>::WeightInfo::create_item(data.data_size())]868		#[transactional]869		pub fn create_item(origin, collection_id: CollectionId, owner: T::CrossAccountId, data: CreateItemData) -> DispatchResult {870			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);871			let collection = Self::get_collection(collection_id)?;872873			Self::create_item_internal(&sender, &collection, &owner, data)?;874875			collection.submit_logs()876		}877878		/// This method creates multiple items in a collection created with CreateCollection method.879		///880		/// # Permissions881		///882		/// * Collection Owner.883		/// * Collection Admin.884		/// * Anyone if885		///     * White List is enabled, and886		///     * Address is added to white list, and887		///     * MintPermission is enabled (see SetMintPermission method)888		///889		/// # Arguments890		///891		/// * collection_id: ID of the collection.892		///893		/// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].894		///895		/// * owner: Address, initial owner of the NFT.896		#[weight = <T as Config>::WeightInfo::create_item(items_data.iter()897							   .map(|data| { data.data_size() })898							   .sum())]899		#[transactional]900		pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::CrossAccountId, items_data: Vec<CreateItemData>) -> DispatchResult {901902			ensure!(!items_data.is_empty(), Error::<T>::EmptyArgument);903			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);904			let collection = Self::get_collection(collection_id)?;905906			Self::create_multiple_items_internal(&sender, &collection, &owner, items_data)?;907908			collection.submit_logs()909		}910911		// TODO! transaction weight912913		/// Set transfers_enabled value for particular collection914		///915		/// # Permissions916		///917		/// * Collection Owner.918		///919		/// # Arguments920		///921		/// * collection_id: ID of the collection.922		///923		/// * value: New flag value.924		#[weight = <T as Config>::WeightInfo::burn_item()]925		#[transactional]926		pub fn set_transfers_enabled_flag(origin, collection_id: CollectionId, value: bool) -> DispatchResult {927928			let sender = ensure_signed(origin)?;929			let mut target_collection = Self::get_collection(collection_id)?;930931			Self::check_owner_permissions(&target_collection, &sender)?;932933			target_collection.transfers_enabled = value;934			target_collection.save()935		}936937		/// Destroys a concrete instance of NFT.938		///939		/// # Permissions940		///941		/// * Collection Owner.942		/// * Collection Admin.943		/// * Current NFT Owner.944		///945		/// # Arguments946		///947		/// * collection_id: ID of the collection.948		///949		/// * item_id: ID of NFT to burn.950		#[weight = <T as Config>::WeightInfo::burn_item()]951		#[transactional]952		pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {953954			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);955			let target_collection = Self::get_collection(collection_id)?;956957			Self::burn_item_internal(&sender, &target_collection, item_id, value)?;958959			target_collection.submit_logs()960		}961962		/// Change ownership of the token.963		///964		/// # Permissions965		///966		/// * Collection Owner967		/// * Collection Admin968		/// * Current NFT owner969		///970		/// # Arguments971		///972		/// * recipient: Address of token recipient.973		///974		/// * collection_id.975		///976		/// * item_id: ID of the item977		///     * Non-Fungible Mode: Required.978		///     * Fungible Mode: Ignored.979		///     * Re-Fungible Mode: Required.980		///981		/// * value: Amount to transfer.982		///     * Non-Fungible Mode: Ignored983		///     * Fungible Mode: Must specify transferred amount984		///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)985		#[weight = <T as Config>::WeightInfo::transfer()]986		#[transactional]987		pub fn transfer(origin, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {988			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);989			let collection = Self::get_collection(collection_id)?;990991			Self::transfer_internal(&sender, &recipient, &collection, item_id, value)?;992993			collection.submit_logs()994		}995996		/// Set, change, or remove approved address to transfer the ownership of the NFT.997		///998		/// # Permissions999		///1000		/// * Collection Owner1001		/// * Collection Admin1002		/// * Current NFT owner1003		///1004		/// # Arguments1005		///1006		/// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1007		///1008		/// * collection_id.1009		///1010		/// * item_id: ID of the item.1011		#[weight = <T as Config>::WeightInfo::approve()]1012		#[transactional]1013		pub fn approve(origin, spender: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {1014			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1015			let collection = Self::get_collection(collection_id)?;10161017			Self::approve_internal(&sender, &spender, &collection, item_id, amount)?;10181019			collection.submit_logs()1020		}10211022		/// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1023		///1024		/// # Permissions1025		/// * Collection Owner1026		/// * Collection Admin1027		/// * Current NFT owner1028		/// * Address approved by current NFT owner1029		///1030		/// # Arguments1031		///1032		/// * from: Address that owns token.1033		///1034		/// * recipient: Address of token recipient.1035		///1036		/// * collection_id.1037		///1038		/// * item_id: ID of the item.1039		///1040		/// * value: Amount to transfer.1041		#[weight = <T as Config>::WeightInfo::transfer_from()]1042		#[transactional]1043		pub fn transfer_from(origin, from: T::CrossAccountId, recipient: T::CrossAccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {1044			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1045			let collection = Self::get_collection(collection_id)?;10461047			Self::transfer_from_internal(&sender, &from, &recipient, &collection, item_id, value)?;10481049			collection.submit_logs()1050		}1051		// #[weight = 0]1052		//     // let no_perm_mes = "You do not have permissions to modify this collection";1053		//     // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1054		//     // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1055		//     // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);10561057		//     // // on_nft_received  call10581059		//     // Self::transfer(origin, collection_id, item_id, new_owner)?;10601061		//     Ok(())1062		// }10631064		/// Set off-chain data schema.1065		///1066		/// # Permissions1067		///1068		/// * Collection Owner1069		/// * Collection Admin1070		///1071		/// # Arguments1072		///1073		/// * collection_id.1074		///1075		/// * schema: String representing the offchain data schema.1076		#[weight = <T as Config>::WeightInfo::set_variable_meta_data()]1077		#[transactional]1078		pub fn set_variable_meta_data (1079			origin,1080			collection_id: CollectionId,1081			item_id: TokenId,1082			data: Vec<u8>1083		) -> DispatchResult {1084			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);10851086			let collection = Self::get_collection(collection_id)?;10871088			Self::set_variable_meta_data_internal(&sender, &collection, item_id, data)?;10891090			Ok(())1091		}10921093		/// Set schema standard1094		/// ImageURL1095		/// Unique1096		///1097		/// # Permissions1098		///1099		/// * Collection Owner1100		/// * Collection Admin1101		///1102		/// # Arguments1103		///1104		/// * collection_id.1105		///1106		/// * schema: SchemaVersion: enum1107		#[weight = <T as Config>::WeightInfo::set_schema_version()]1108		#[transactional]1109		pub fn set_schema_version(1110			origin,1111			collection_id: CollectionId,1112			version: SchemaVersion1113		) -> DispatchResult {1114			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1115			let mut target_collection = Self::get_collection(collection_id)?;1116			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;1117			target_collection.schema_version = version;1118			target_collection.save()1119		}11201121		/// Set off-chain data schema.1122		///1123		/// # Permissions1124		///1125		/// * Collection Owner1126		/// * Collection Admin1127		///1128		/// # Arguments1129		///1130		/// * collection_id.1131		///1132		/// * schema: String representing the offchain data schema.1133		#[weight = <T as Config>::WeightInfo::set_offchain_schema()]1134		#[transactional]1135		pub fn set_offchain_schema(1136			origin,1137			collection_id: CollectionId,1138			schema: Vec<u8>1139		) -> DispatchResult {1140			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1141			let mut target_collection = Self::get_collection(collection_id)?;1142			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11431144			// check schema limit1145			ensure!(schema.len() as u32 <= OFFCHAIN_SCHEMA_LIMIT, "");11461147			target_collection.offchain_schema = schema;1148			target_collection.save()1149		}11501151		/// Set const on-chain data schema.1152		///1153		/// # Permissions1154		///1155		/// * Collection Owner1156		/// * Collection Admin1157		///1158		/// # Arguments1159		///1160		/// * collection_id.1161		///1162		/// * schema: String representing the const on-chain data schema.1163		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1164		#[transactional]1165		pub fn set_const_on_chain_schema (1166			origin,1167			collection_id: CollectionId,1168			schema: Vec<u8>1169		) -> DispatchResult {1170			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1171			let mut target_collection = Self::get_collection(collection_id)?;1172			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;11731174			// check schema limit1175			ensure!(schema.len() as u32 <= CONST_ON_CHAIN_SCHEMA_LIMIT, "");11761177			target_collection.const_on_chain_schema = schema;1178			target_collection.save()1179		}11801181		/// Set variable on-chain data schema.1182		///1183		/// # Permissions1184		///1185		/// * Collection Owner1186		/// * Collection Admin1187		///1188		/// # Arguments1189		///1190		/// * collection_id.1191		///1192		/// * schema: String representing the variable on-chain data schema.1193		#[weight = <T as Config>::WeightInfo::set_const_on_chain_schema()]1194		#[transactional]1195		pub fn set_variable_on_chain_schema (1196			origin,1197			collection_id: CollectionId,1198			schema: Vec<u8>1199		) -> DispatchResult {1200			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1201			let mut target_collection = Self::get_collection(collection_id)?;1202			Self::check_owner_or_admin_permissions(&target_collection, &sender)?;12031204			// check schema limit1205			ensure!(schema.len() as u32 <= VARIABLE_ON_CHAIN_SCHEMA_LIMIT, "");12061207			target_collection.variable_on_chain_schema = schema;1208			target_collection.save()1209		}12101211		#[weight = <T as Config>::WeightInfo::set_collection_limits()]1212		#[transactional]1213		pub fn set_collection_limits(1214			origin,1215			collection_id: u32,1216			new_limits: CollectionLimits<T::BlockNumber>,1217		) -> DispatchResult {1218			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);1219			let mut target_collection = Self::get_collection(collection_id)?;1220			Self::check_owner_permissions(&target_collection, sender.as_sub())?;1221			let old_limits = &target_collection.limits;12221223			// collection bounds1224			ensure!(new_limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1225				new_limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP &&1226				new_limits.sponsored_data_size <= CUSTOM_DATA_LIMIT,1227				Error::<T>::CollectionLimitBoundsExceeded);12281229			// token_limit   check  prev1230			ensure!(old_limits.token_limit >= new_limits.token_limit, Error::<T>::CollectionTokenLimitExceeded);1231			ensure!(new_limits.token_limit > 0, Error::<T>::CollectionTokenLimitExceeded);12321233			ensure!(1234				(old_limits.owner_can_transfer || !new_limits.owner_can_transfer) &&1235				(old_limits.owner_can_destroy || !new_limits.owner_can_destroy),1236				Error::<T>::OwnerPermissionsCantBeReverted,1237			);12381239			target_collection.limits = new_limits;12401241			target_collection.save()1242		}1243	}1244}12451246impl<T: Config> Module<T> {1247	pub fn create_item_internal(1248		sender: &T::CrossAccountId,1249		collection: &CollectionHandle<T>,1250		owner: &T::CrossAccountId,1251		data: CreateItemData,1252	) -> DispatchResult {1253		ensure!(1254			owner != &T::CrossAccountId::from_eth(H160([0; 20])),1255			Error::<T>::AddressIsZero1256		);12571258		Self::can_create_items_in_collection(collection, sender, owner, 1)?;1259		Self::validate_create_item_args(collection, &data)?;1260		Self::create_item_no_validation(collection, owner, data)?;12611262		Ok(())1263	}12641265	pub fn transfer_internal(1266		sender: &T::CrossAccountId,1267		recipient: &T::CrossAccountId,1268		target_collection: &CollectionHandle<T>,1269		item_id: TokenId,1270		value: u128,1271	) -> DispatchResult {1272		ensure!(1273			recipient != &T::CrossAccountId::from_eth(H160([0; 20])),1274			Error::<T>::AddressIsZero1275		);12761277		// Limits check1278		Self::is_correct_transfer(target_collection, recipient)?;12791280		// Transfer permissions check1281		ensure!(1282			Self::is_item_owner(sender, target_collection, item_id)?1283				|| Self::is_owner_or_admin_permissions(target_collection, sender)?,1284			Error::<T>::NoPermission1285		);12861287		if target_collection.access == AccessMode::WhiteList {1288			Self::check_white_list(target_collection, sender)?;1289			Self::check_white_list(target_collection, recipient)?;1290		}12911292		match target_collection.mode {1293			CollectionMode::NFT => Self::transfer_nft(1294				target_collection,1295				item_id,1296				sender.clone(),1297				recipient.clone(),1298			)?,1299			CollectionMode::Fungible(_) => {1300				Self::transfer_fungible(target_collection, value, sender, recipient)?1301			}1302			CollectionMode::ReFungible => Self::transfer_refungible(1303				target_collection,1304				item_id,1305				value,1306				sender.clone(),1307				recipient.clone(),1308			)?,1309			_ => (),1310		};13111312		Self::deposit_event(RawEvent::Transfer(1313			target_collection.id,1314			item_id,1315			sender.clone(),1316			recipient.clone(),1317			value,1318		));13191320		Ok(())1321	}13221323	pub fn approve_internal(1324		sender: &T::CrossAccountId,1325		spender: &T::CrossAccountId,1326		collection: &CollectionHandle<T>,1327		item_id: TokenId,1328		amount: u128,1329	) -> DispatchResult {1330		Self::token_exists(collection, item_id)?;13311332		// Transfer permissions check1333		let bypasses_limits = collection.limits.owner_can_transfer1334			&& Self::is_owner_or_admin_permissions(collection, sender)?;13351336		let allowance_limit = if bypasses_limits {1337			None1338		} else if let Some(amount) = Self::owned_amount(sender, collection, item_id)? {1339			Some(amount)1340		} else {1341			fail!(Error::<T>::NoPermission);1342		};13431344		if collection.access == AccessMode::WhiteList {1345			Self::check_white_list(collection, sender)?;1346			Self::check_white_list(collection, spender)?;1347		}13481349		collection.consume_sload()?;1350		let allowance: u128 = amount1351			.checked_add(<Allowances<T>>::get(1352				collection.id,1353				(item_id, sender.as_sub(), spender.as_sub()),1354			))1355			.ok_or(Error::<T>::NumOverflow)?;1356		if let Some(limit) = allowance_limit {1357			ensure!(limit >= allowance, Error::<T>::TokenValueTooLow);1358		}1359		collection.consume_sstore()?;1360		<Allowances<T>>::insert(1361			collection.id,1362			(item_id, sender.as_sub(), spender.as_sub()),1363			allowance,1364		);13651366		if matches!(collection.mode, CollectionMode::NFT) {1367			// TODO: NFT: only one owner may exist for token in ERC7211368			collection.log(ERC721Events::Approval {1369				owner: *sender.as_eth(),1370				approved: *spender.as_eth(),1371				token_id: item_id.into(),1372			})?;1373		}13741375		if matches!(collection.mode, CollectionMode::Fungible(_)) {1376			// TODO: NFT: only one owner may exist for token in ERC201377			collection.log(ERC20Events::Approval {1378				owner: *sender.as_eth(),1379				spender: *spender.as_eth(),1380				value: allowance.into(),1381			})?;1382		}13831384		Self::deposit_event(RawEvent::Approved(1385			collection.id,1386			item_id,1387			sender.clone(),1388			spender.clone(),1389			allowance,1390		));1391		Ok(())1392	}13931394	pub fn transfer_from_internal(1395		sender: &T::CrossAccountId,1396		from: &T::CrossAccountId,1397		recipient: &T::CrossAccountId,1398		collection: &CollectionHandle<T>,1399		item_id: TokenId,1400		amount: u128,1401	) -> DispatchResult {1402		// Check approval1403		collection.consume_sload()?;1404		let approval: u128 =1405			<Allowances<T>>::get(collection.id, (item_id, from.as_sub(), sender.as_sub()));14061407		// Limits check1408		Self::is_correct_transfer(collection, recipient)?;14091410		// Transfer permissions check1411		ensure!(1412			approval >= amount1413				|| (collection.limits.owner_can_transfer1414					&& Self::is_owner_or_admin_permissions(collection, sender)?),1415			Error::<T>::NoPermission1416		);14171418		if collection.access == AccessMode::WhiteList {1419			Self::check_white_list(collection, sender)?;1420			Self::check_white_list(collection, recipient)?;1421		}14221423		// Reduce approval by transferred amount or remove if remaining approval drops to 01424		let allowance = approval.saturating_sub(amount);1425		collection.consume_sstore()?;1426		if allowance > 0 {1427			<Allowances<T>>::insert(1428				collection.id,1429				(item_id, from.as_sub(), sender.as_sub()),1430				allowance,1431			);1432		} else {1433			<Allowances<T>>::remove(collection.id, (item_id, from.as_sub(), sender.as_sub()));1434		}14351436		match collection.mode {1437			CollectionMode::NFT => {1438				Self::transfer_nft(collection, item_id, from.clone(), recipient.clone())?1439			}1440			CollectionMode::Fungible(_) => {1441				Self::transfer_fungible(collection, amount, from, recipient)?1442			}1443			CollectionMode::ReFungible => Self::transfer_refungible(1444				collection,1445				item_id,1446				amount,1447				from.clone(),1448				recipient.clone(),1449			)?,1450			_ => (),1451		};14521453		if matches!(collection.mode, CollectionMode::Fungible(_)) {1454			collection.log(ERC20Events::Approval {1455				owner: *from.as_eth(),1456				spender: *sender.as_eth(),1457				value: allowance.into(),1458			})?;1459		}14601461		Ok(())1462	}14631464	pub fn set_variable_meta_data_internal(1465		sender: &T::CrossAccountId,1466		collection: &CollectionHandle<T>,1467		item_id: TokenId,1468		data: Vec<u8>,1469	) -> DispatchResult {1470		Self::token_exists(collection, item_id)?;14711472		ensure!(1473			CUSTOM_DATA_LIMIT >= data.len() as u32,1474			Error::<T>::TokenVariableDataLimitExceeded1475		);14761477		// Modify permissions check1478		ensure!(1479			Self::is_item_owner(sender, collection, item_id)?1480				|| Self::is_owner_or_admin_permissions(collection, sender)?,1481			Error::<T>::NoPermission1482		);14831484		match collection.mode {1485			CollectionMode::NFT => Self::set_nft_variable_data(collection, item_id, data)?,1486			CollectionMode::ReFungible => {1487				Self::set_re_fungible_variable_data(collection, item_id, data)?1488			}1489			CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1490			_ => fail!(Error::<T>::UnexpectedCollectionType),1491		};14921493		Ok(())1494	}14951496	pub fn create_multiple_items_internal(1497		sender: &T::CrossAccountId,1498		collection: &CollectionHandle<T>,1499		owner: &T::CrossAccountId,1500		items_data: Vec<CreateItemData>,1501	) -> DispatchResult {1502		Self::can_create_items_in_collection(collection, sender, owner, items_data.len() as u32)?;15031504		for data in &items_data {1505			Self::validate_create_item_args(collection, data)?;1506		}1507		for data in &items_data {1508			Self::create_item_no_validation(collection, owner, data.clone())?;1509		}15101511		Ok(())1512	}15131514	pub fn burn_item_internal(1515		sender: &T::CrossAccountId,1516		collection: &CollectionHandle<T>,1517		item_id: TokenId,1518		value: u128,1519	) -> DispatchResult {1520		ensure!(1521			Self::is_item_owner(sender, collection, item_id)?1522				|| (collection.limits.owner_can_transfer1523					&& Self::is_owner_or_admin_permissions(collection, sender)?),1524			Error::<T>::NoPermission1525		);15261527		if collection.access == AccessMode::WhiteList {1528			Self::check_white_list(collection, sender)?;1529		}15301531		match collection.mode {1532			CollectionMode::NFT => Self::burn_nft_item(collection, item_id)?,1533			CollectionMode::Fungible(_) => Self::burn_fungible_item(sender, collection, value)?,1534			CollectionMode::ReFungible => Self::burn_refungible_item(collection, item_id, sender)?,1535			_ => (),1536		};15371538		Ok(())1539	}15401541	pub fn toggle_white_list_internal(1542		sender: &T::CrossAccountId,1543		collection: &CollectionHandle<T>,1544		address: &T::CrossAccountId,1545		whitelisted: bool,1546	) -> DispatchResult {1547		Self::check_owner_or_admin_permissions(collection, sender)?;15481549		if whitelisted {1550			<WhiteList<T>>::insert(collection.id, address.as_sub(), true);1551		} else {1552			<WhiteList<T>>::remove(collection.id, address.as_sub());1553		}15541555		Ok(())1556	}15571558	fn is_correct_transfer(1559		collection: &CollectionHandle<T>,1560		recipient: &T::CrossAccountId,1561	) -> DispatchResult {1562		let collection_id = collection.id;15631564		// check token limit and account token limit1565		collection.consume_sload()?;1566		let account_items: u32 =1567			<AddressTokens<T>>::get(collection_id, recipient.as_sub()).len() as u32;1568		ensure!(1569			collection.limits.account_token_ownership_limit > account_items,1570			Error::<T>::AccountTokenLimitExceeded1571		);15721573		// preliminary transfer check1574		ensure!(collection.transfers_enabled, Error::<T>::TransferNotAllowed);15751576		Ok(())1577	}15781579	fn can_create_items_in_collection(1580		collection: &CollectionHandle<T>,1581		sender: &T::CrossAccountId,1582		owner: &T::CrossAccountId,1583		amount: u32,1584	) -> DispatchResult {1585		let collection_id = collection.id;15861587		// check token limit and account token limit1588		let total_items: u32 = ItemListIndex::get(collection_id)1589			.checked_add(amount)1590			.ok_or(Error::<T>::CollectionTokenLimitExceeded)?;1591		let account_items: u32 = (<AddressTokens<T>>::get(collection_id, owner.as_sub()).len()1592			as u32)1593			.checked_add(amount)1594			.ok_or(Error::<T>::AccountTokenLimitExceeded)?;1595		ensure!(1596			collection.limits.token_limit >= total_items,1597			Error::<T>::CollectionTokenLimitExceeded1598		);1599		ensure!(1600			collection.limits.account_token_ownership_limit >= account_items,1601			Error::<T>::AccountTokenLimitExceeded1602		);16031604		if !Self::is_owner_or_admin_permissions(collection, sender)? {1605			ensure!(collection.mint_mode, Error::<T>::PublicMintingNotAllowed);1606			Self::check_white_list(collection, owner)?;1607			Self::check_white_list(collection, sender)?;1608		}16091610		Ok(())1611	}16121613	fn validate_create_item_args(1614		target_collection: &CollectionHandle<T>,1615		data: &CreateItemData,1616	) -> DispatchResult {1617		match target_collection.mode {1618			CollectionMode::NFT => {1619				if !matches!(data, CreateItemData::NFT(_)) {1620					fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1621				}1622			}1623			CollectionMode::Fungible(_) => {1624				if !matches!(data, CreateItemData::Fungible(_)) {1625					fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1626				}1627			}1628			CollectionMode::ReFungible => {1629				if let CreateItemData::ReFungible(data) = data {1630					// Check refungibility limits1631					ensure!(1632						data.pieces <= MAX_REFUNGIBLE_PIECES,1633						Error::<T>::WrongRefungiblePieces1634					);1635					ensure!(data.pieces > 0, Error::<T>::WrongRefungiblePieces);1636				} else {1637					fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1638				}1639			}1640			_ => {1641				fail!(Error::<T>::UnexpectedCollectionType);1642			}1643		};16441645		Ok(())1646	}16471648	fn create_item_no_validation(1649		collection: &CollectionHandle<T>,1650		owner: &T::CrossAccountId,1651		data: CreateItemData,1652	) -> DispatchResult {1653		match data {1654			CreateItemData::NFT(data) => {1655				let item = NftItemType {1656					owner: owner.clone(),1657					const_data: data.const_data.into_inner(),1658					variable_data: data.variable_data.into_inner(),1659				};16601661				Self::add_nft_item(collection, item)?;1662			}1663			CreateItemData::Fungible(data) => {1664				Self::add_fungible_item(collection, owner, data.value)?;1665			}1666			CreateItemData::ReFungible(data) => {1667				let owner_list = vec![Ownership {1668					owner: owner.clone(),1669					fraction: data.pieces,1670				}];16711672				let item = ReFungibleItemType {1673					owner: owner_list,1674					const_data: data.const_data.into_inner(),1675					variable_data: data.variable_data.into_inner(),1676				};16771678				Self::add_refungible_item(collection, item)?;1679			}1680		};16811682		Ok(())1683	}16841685	fn add_fungible_item(1686		collection: &CollectionHandle<T>,1687		owner: &T::CrossAccountId,1688		value: u128,1689	) -> DispatchResult {1690		let collection_id = collection.id;16911692		// Does new owner already have an account?1693		collection.consume_sload()?;1694		let balance: u128 = <FungibleItemList<T>>::get(collection_id, owner.as_sub()).value;16951696		// Mint1697		let item = FungibleItemType {1698			value: balance.checked_add(value).ok_or(Error::<T>::NumOverflow)?,1699		};1700		collection.consume_sstore()?;1701		<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), item);17021703		// Update balance1704		collection.consume_sload()?;1705		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1706			.checked_add(value)1707			.ok_or(Error::<T>::NumOverflow)?;1708		collection.consume_sstore()?;1709		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17101711		collection.log(ERC20Events::Transfer {1712			from: H160::default(),1713			to: *owner.as_eth(),1714			value: value.into(),1715		})?;1716		Self::deposit_event(RawEvent::ItemCreated(collection_id, 0, owner.clone()));1717		Ok(())1718	}17191720	fn add_refungible_item(1721		collection: &CollectionHandle<T>,1722		item: ReFungibleItemType<T::CrossAccountId>,1723	) -> DispatchResult {1724		let collection_id = collection.id;17251726		let current_index = <ItemListIndex>::get(collection_id)1727			.checked_add(1)1728			.ok_or(Error::<T>::NumOverflow)?;1729		let itemcopy = item.clone();17301731		ensure!(item.owner.len() == 1, Error::<T>::BadCreateRefungibleCall,);1732		let item_owner = item.owner.first().expect("only one owner is defined");17331734		let value = item_owner.fraction;1735		let owner = item_owner.owner.clone();17361737		Self::add_token_index(collection, current_index, &owner)?;17381739		<ItemListIndex>::insert(collection_id, current_index);1740		<ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);17411742		// Update balance1743		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1744			.checked_add(value)1745			.ok_or(Error::<T>::NumOverflow)?;1746		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);17471748		Self::deposit_event(RawEvent::ItemCreated(collection_id, current_index, owner));1749		Ok(())1750	}17511752	fn add_nft_item(1753		collection: &CollectionHandle<T>,1754		item: NftItemType<T::CrossAccountId>,1755	) -> DispatchResult {1756		let collection_id = collection.id;17571758		let current_index = <ItemListIndex>::get(collection_id)1759			.checked_add(1)1760			.ok_or(Error::<T>::NumOverflow)?;17611762		let item_owner = item.owner.clone();1763		Self::add_token_index(collection, current_index, &item.owner)?;17641765		<ItemListIndex>::insert(collection_id, current_index);1766		<NftItemList<T>>::insert(collection_id, current_index, item);17671768		// Update balance1769		let new_balance = <Balance<T>>::get(collection_id, item_owner.as_sub())1770			.checked_add(1)1771			.ok_or(Error::<T>::NumOverflow)?;1772		<Balance<T>>::insert(collection_id, item_owner.as_sub(), new_balance);17731774		collection.log(ERC721Events::Transfer {1775			from: H160::default(),1776			to: *item_owner.as_eth(),1777			token_id: current_index.into(),1778		})?;1779		Self::deposit_event(RawEvent::ItemCreated(1780			collection_id,1781			current_index,1782			item_owner,1783		));1784		Ok(())1785	}17861787	fn burn_refungible_item(1788		collection: &CollectionHandle<T>,1789		item_id: TokenId,1790		owner: &T::CrossAccountId,1791	) -> DispatchResult {1792		let collection_id = collection.id;17931794		let mut token = <ReFungibleItemList<T>>::get(collection_id, item_id)1795			.ok_or(Error::<T>::TokenNotFound)?;1796		let rft_balance = token1797			.owner1798			.iter()1799			.find(|&i| i.owner == *owner)1800			.ok_or(Error::<T>::TokenNotFound)?;1801		Self::remove_token_index(collection, item_id, owner)?;18021803		// update balance1804		let new_balance = <Balance<T>>::get(collection_id, rft_balance.owner.as_sub())1805			.checked_sub(rft_balance.fraction)1806			.ok_or(Error::<T>::NumOverflow)?;1807		<Balance<T>>::insert(collection_id, rft_balance.owner.as_sub(), new_balance);18081809		// Re-create owners list with sender removed1810		let index = token1811			.owner1812			.iter()1813			.position(|i| i.owner == *owner)1814			.expect("owned item is exists");1815		token.owner.remove(index);1816		let owner_count = token.owner.len();18171818		// Burn the token completely if this was the last (only) owner1819		if owner_count == 0 {1820			<ReFungibleItemList<T>>::remove(collection_id, item_id);1821			<VariableMetaDataBasket<T>>::remove(collection_id, item_id);1822		} else {1823			<ReFungibleItemList<T>>::insert(collection_id, item_id, token);1824		}18251826		Ok(())1827	}18281829	fn burn_nft_item(collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1830		let collection_id = collection.id;18311832		let item =1833			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;1834		Self::remove_token_index(collection, item_id, &item.owner)?;18351836		// update balance1837		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())1838			.checked_sub(1)1839			.ok_or(Error::<T>::NumOverflow)?;1840		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);1841		<NftItemList<T>>::remove(collection_id, item_id);1842		<VariableMetaDataBasket<T>>::remove(collection_id, item_id);18431844		collection.log(ERC721Events::Transfer {1845			from: *item.owner.as_eth(),1846			to: H160::default(),1847			token_id: item_id.into(),1848		})?;1849		Self::deposit_event(RawEvent::ItemDestroyed(collection.id, item_id));1850		Ok(())1851	}18521853	fn burn_fungible_item(1854		owner: &T::CrossAccountId,1855		collection: &CollectionHandle<T>,1856		value: u128,1857	) -> DispatchResult {1858		let collection_id = collection.id;18591860		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());1861		ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);18621863		// update balance1864		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())1865			.checked_sub(value)1866			.ok_or(Error::<T>::NumOverflow)?;1867		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);18681869		if balance.value - value > 0 {1870			balance.value -= value;1871			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);1872		} else {1873			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());1874		}18751876		collection.log(ERC20Events::Transfer {1877			from: *owner.as_eth(),1878			to: H160::default(),1879			value: value.into(),1880		})?;1881		Ok(())1882	}18831884	pub fn get_collection(1885		collection_id: CollectionId,1886	) -> Result<CollectionHandle<T>, sp_runtime::DispatchError> {1887		Ok(<CollectionHandle<T>>::get(collection_id).ok_or(Error::<T>::CollectionNotFound)?)1888	}18891890	fn check_owner_permissions(1891		target_collection: &CollectionHandle<T>,1892		subject: &T::AccountId,1893	) -> DispatchResult {1894		ensure!(1895			*subject == target_collection.owner,1896			Error::<T>::NoPermission1897		);18981899		Ok(())1900	}19011902	fn is_owner_or_admin_permissions(1903		collection: &CollectionHandle<T>,1904		subject: &T::CrossAccountId,1905	) -> Result<bool, DispatchError> {1906		collection.consume_sload()?;1907		Ok(*subject.as_sub() == collection.owner1908			|| <AdminList<T>>::get(collection.id).contains(subject))1909	}19101911	fn check_owner_or_admin_permissions(1912		collection: &CollectionHandle<T>,1913		subject: &T::CrossAccountId,1914	) -> DispatchResult {1915		ensure!(1916			Self::is_owner_or_admin_permissions(collection, subject)?,1917			Error::<T>::NoPermission1918		);19191920		Ok(())1921	}19221923	fn owned_amount(1924		subject: &T::CrossAccountId,1925		collection: &CollectionHandle<T>,1926		item_id: TokenId,1927	) -> Result<Option<u128>, DispatchError> {1928		collection.consume_sload()?;1929		Ok(Self::owned_amount_unchecked(subject, collection, item_id))1930	}19311932	fn owned_amount_unchecked(1933		subject: &T::CrossAccountId,1934		target_collection: &CollectionHandle<T>,1935		item_id: TokenId,1936	) -> Option<u128> {1937		let collection_id = target_collection.id;19381939		match target_collection.mode {1940			CollectionMode::NFT => {1941				(<NftItemList<T>>::get(collection_id, item_id)?.owner == *subject).then(|| 1)1942			}1943			CollectionMode::Fungible(_) => {1944				Some(<FungibleItemList<T>>::get(collection_id, &subject.as_sub()).value)1945			}1946			CollectionMode::ReFungible => <ReFungibleItemList<T>>::get(collection_id, item_id)?1947				.owner1948				.iter()1949				.find(|i| i.owner == *subject)1950				.map(|i| i.fraction),1951			CollectionMode::Invalid => None,1952		}1953	}19541955	fn is_item_owner(1956		subject: &T::CrossAccountId,1957		target_collection: &CollectionHandle<T>,1958		item_id: TokenId,1959	) -> Result<bool, DispatchError> {1960		Ok(match target_collection.mode {1961			CollectionMode::Fungible(_) => true,1962			_ => Self::owned_amount(subject, target_collection, item_id)?.is_some(),1963		})1964	}19651966	fn check_white_list(1967		collection: &CollectionHandle<T>,1968		address: &T::CrossAccountId,1969	) -> DispatchResult {1970		collection.consume_sload()?;1971		ensure!(1972			<WhiteList<T>>::contains_key(collection.id, address.as_sub()),1973			Error::<T>::AddresNotInWhiteList,1974		);1975		Ok(())1976	}19771978	/// Check if token exists. In case of Fungible, check if there is an entry for1979	/// the owner in fungible balances double map1980	fn token_exists(target_collection: &CollectionHandle<T>, item_id: TokenId) -> DispatchResult {1981		let collection_id = target_collection.id;1982		let exists = match target_collection.mode {1983			CollectionMode::NFT => <NftItemList<T>>::contains_key(collection_id, item_id),1984			CollectionMode::Fungible(_) => true,1985			CollectionMode::ReFungible => {1986				<ReFungibleItemList<T>>::contains_key(collection_id, item_id)1987			}1988			_ => false,1989		};19901991		ensure!(exists, Error::<T>::TokenNotFound);1992		Ok(())1993	}19941995	fn transfer_fungible(1996		collection: &CollectionHandle<T>,1997		value: u128,1998		owner: &T::CrossAccountId,1999		recipient: &T::CrossAccountId,2000	) -> DispatchResult {2001		let collection_id = collection.id;20022003		collection.consume_sload()?;2004		collection.consume_sload()?;2005		let mut recipient_balance = <FungibleItemList<T>>::get(collection_id, recipient.as_sub());2006		let mut balance = <FungibleItemList<T>>::get(collection_id, owner.as_sub());20072008		recipient_balance.value = recipient_balance2009			.value2010			.checked_add(value)2011			.ok_or(Error::<T>::NumOverflow)?;2012		balance.value = balance2013			.value2014			.checked_sub(value)2015			.ok_or(Error::<T>::TokenValueTooLow)?;20162017		// update balanceOf2018		collection.consume_sstore()?;2019		collection.consume_sstore()?;2020		if balance.value != 0 {2021			<Balance<T>>::insert(collection_id, owner.as_sub(), balance.value);2022		} else {2023			<Balance<T>>::remove(collection_id, owner.as_sub());2024		}2025		<Balance<T>>::insert(collection_id, recipient.as_sub(), recipient_balance.value);20262027		// Reduce or remove sender2028		collection.consume_sstore()?;2029		collection.consume_sstore()?;2030		if balance.value != 0 {2031			<FungibleItemList<T>>::insert(collection_id, owner.as_sub(), balance);2032		} else {2033			<FungibleItemList<T>>::remove(collection_id, owner.as_sub());2034		}2035		<FungibleItemList<T>>::insert(collection_id, recipient.as_sub(), recipient_balance);20362037		collection.log(ERC20Events::Transfer {2038			from: *owner.as_eth(),2039			to: *recipient.as_eth(),2040			value: value.into(),2041		})?;2042		Self::deposit_event(RawEvent::Transfer(2043			collection.id,2044			1,2045			owner.clone(),2046			recipient.clone(),2047			value,2048		));20492050		Ok(())2051	}20522053	fn transfer_refungible(2054		collection: &CollectionHandle<T>,2055		item_id: TokenId,2056		value: u128,2057		owner: T::CrossAccountId,2058		new_owner: T::CrossAccountId,2059	) -> DispatchResult {2060		let collection_id = collection.id;2061		collection.consume_sload()?;2062		let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id)2063			.ok_or(Error::<T>::TokenNotFound)?;20642065		let item = full_item2066			.owner2067			.iter()2068			.find(|i| i.owner == owner)2069			.ok_or(Error::<T>::TokenNotFound)?;2070		let amount = item.fraction;20712072		ensure!(amount >= value, Error::<T>::TokenValueTooLow);20732074		collection.consume_sload()?;2075		// update balance2076		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2077			.checked_sub(value)2078			.ok_or(Error::<T>::NumOverflow)?;2079		collection.consume_sstore()?;2080		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);20812082		collection.consume_sload()?;2083		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2084			.checked_add(value)2085			.ok_or(Error::<T>::NumOverflow)?;2086		collection.consume_sstore()?;2087		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);20882089		let old_owner = item.owner.clone();2090		let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20912092		let mut new_full_item = full_item.clone();2093		// transfer2094		if amount == value && !new_owner_has_account {2095			// change owner2096			// new owner do not have account2097			new_full_item2098				.owner2099				.iter_mut()2100				.find(|i| i.owner == owner)2101				.expect("old owner does present in refungible")2102				.owner = new_owner.clone();2103			collection.consume_sstore()?;2104			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);21052106			// update index collection2107			Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;2108		} else {2109			new_full_item2110				.owner2111				.iter_mut()2112				.find(|i| i.owner == owner)2113				.expect("old owner does present in refungible")2114				.fraction -= value;21152116			// separate amount2117			if new_owner_has_account {2118				// new owner has account2119				new_full_item2120					.owner2121					.iter_mut()2122					.find(|i| i.owner == new_owner)2123					.expect("new owner has account")2124					.fraction += value;2125			} else {2126				// new owner do not have account2127				new_full_item.owner.push(Ownership {2128					owner: new_owner.clone(),2129					fraction: value,2130				});2131				Self::add_token_index(collection, item_id, &new_owner)?;2132			}21332134			collection.consume_sstore()?;2135			<ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2136		}21372138		Self::deposit_event(RawEvent::Transfer(2139			collection.id,2140			item_id,2141			owner,2142			new_owner,2143			amount,2144		));21452146		Ok(())2147	}21482149	fn transfer_nft(2150		collection: &CollectionHandle<T>,2151		item_id: TokenId,2152		sender: T::CrossAccountId,2153		new_owner: T::CrossAccountId,2154	) -> DispatchResult {2155		let collection_id = collection.id;2156		collection.consume_sload()?;2157		let mut item =2158			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;21592160		ensure!(sender == item.owner, Error::<T>::MustBeTokenOwner);21612162		collection.consume_sload()?;2163		// update balance2164		let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.as_sub())2165			.checked_sub(1)2166			.ok_or(Error::<T>::NumOverflow)?;2167		collection.consume_sstore()?;2168		<Balance<T>>::insert(collection_id, item.owner.as_sub(), balance_old_owner);21692170		collection.consume_sload()?;2171		let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.as_sub())2172			.checked_add(1)2173			.ok_or(Error::<T>::NumOverflow)?;2174		collection.consume_sstore()?;2175		<Balance<T>>::insert(collection_id, new_owner.as_sub(), balance_new_owner);21762177		// change owner2178		let old_owner = item.owner.clone();2179		item.owner = new_owner.clone();2180		collection.consume_sstore()?;2181		<NftItemList<T>>::insert(collection_id, item_id, item);21822183		// update index collection2184		Self::move_token_index(collection, item_id, &old_owner, &new_owner)?;21852186		collection.log(ERC721Events::Transfer {2187			from: *sender.as_eth(),2188			to: *new_owner.as_eth(),2189			token_id: item_id.into(),2190		})?;2191		Self::deposit_event(RawEvent::Transfer(2192			collection.id,2193			item_id,2194			sender,2195			new_owner,2196			1,2197		));21982199		Ok(())2200	}22012202	fn set_re_fungible_variable_data(2203		collection: &CollectionHandle<T>,2204		item_id: TokenId,2205		data: Vec<u8>,2206	) -> DispatchResult {2207		let collection_id = collection.id;2208		let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id)2209			.ok_or(Error::<T>::TokenNotFound)?;22102211		item.variable_data = data;22122213		<ReFungibleItemList<T>>::insert(collection_id, item_id, item);22142215		Ok(())2216	}22172218	fn set_nft_variable_data(2219		collection: &CollectionHandle<T>,2220		item_id: TokenId,2221		data: Vec<u8>,2222	) -> DispatchResult {2223		let collection_id = collection.id;2224		let mut item =2225			<NftItemList<T>>::get(collection_id, item_id).ok_or(Error::<T>::TokenNotFound)?;22262227		item.variable_data = data;22282229		<NftItemList<T>>::insert(collection_id, item_id, item);22302231		Ok(())2232	}22332234	#[allow(dead_code)]2235	fn init_collection(item: &Collection<T>) {2236		// check params2237		assert!(2238			item.decimal_points <= MAX_DECIMAL_POINTS,2239			"decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2240		);2241		assert!(2242			item.name.len() <= 64,2243			"Collection name can not be longer than 63 char"2244		);2245		assert!(2246			item.name.len() <= 256,2247			"Collection description can not be longer than 255 char"2248		);2249		assert!(2250			item.token_prefix.len() <= 16,2251			"Token prefix can not be longer than 15 char"2252		);22532254		// Generate next collection ID2255		let next_id = CreatedCollectionCount::get().checked_add(1).unwrap();22562257		CreatedCollectionCount::put(next_id);2258	}22592260	#[allow(dead_code)]2261	fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::CrossAccountId>) {2262		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22632264		Self::add_token_index(2265			&CollectionHandle::get(collection_id).unwrap(),2266			current_index,2267			&item.owner,2268		)2269		.unwrap();22702271		<ItemListIndex>::insert(collection_id, current_index);22722273		// Update balance2274		let new_balance = <Balance<T>>::get(collection_id, item.owner.as_sub())2275			.checked_add(1)2276			.unwrap();2277		<Balance<T>>::insert(collection_id, item.owner.as_sub(), new_balance);2278	}22792280	#[allow(dead_code)]2281	fn init_fungible_token(2282		collection_id: CollectionId,2283		owner: &T::CrossAccountId,2284		item: &FungibleItemType,2285	) {2286		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();22872288		Self::add_token_index(2289			&CollectionHandle::get(collection_id).unwrap(),2290			current_index,2291			owner,2292		)2293		.unwrap();22942295		<ItemListIndex>::insert(collection_id, current_index);22962297		// Update balance2298		let new_balance = <Balance<T>>::get(collection_id, owner.as_sub())2299			.checked_add(item.value)2300			.unwrap();2301		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2302	}23032304	#[allow(dead_code)]2305	fn init_refungible_token(2306		collection_id: CollectionId,2307		item: &ReFungibleItemType<T::CrossAccountId>,2308	) {2309		let current_index = <ItemListIndex>::get(collection_id).checked_add(1).unwrap();23102311		let value = item.owner.first().unwrap().fraction;2312		let owner = item.owner.first().unwrap().owner.clone();23132314		Self::add_token_index(2315			&CollectionHandle::get(collection_id).unwrap(),2316			current_index,2317			&owner,2318		)2319		.unwrap();23202321		<ItemListIndex>::insert(collection_id, current_index);23222323		// Update balance2324		let new_balance = <Balance<T>>::get(collection_id, &owner.as_sub())2325			.checked_add(value)2326			.unwrap();2327		<Balance<T>>::insert(collection_id, owner.as_sub(), new_balance);2328	}23292330	fn add_token_index(2331		collection: &CollectionHandle<T>,2332		item_index: TokenId,2333		owner: &T::CrossAccountId,2334	) -> DispatchResult {2335		// add to account limit2336		collection.consume_sload()?;2337		if <AccountItemCount<T>>::contains_key(owner.as_sub()) {2338			// bound Owned tokens by a single address2339			collection.consume_sload()?;2340			let count = <AccountItemCount<T>>::get(owner.as_sub());2341			ensure!(2342				count < ACCOUNT_TOKEN_OWNERSHIP_LIMIT,2343				Error::<T>::AddressOwnershipLimitExceeded2344			);23452346			collection.consume_sstore()?;2347			<AccountItemCount<T>>::insert(2348				owner.as_sub(),2349				count.checked_add(1).ok_or(Error::<T>::NumOverflow)?,2350			);2351		} else {2352			collection.consume_sstore()?;2353			<AccountItemCount<T>>::insert(owner.as_sub(), 1);2354		}23552356		collection.consume_sload()?;2357		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2358		if list_exists {2359			collection.consume_sload()?;2360			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2361			let item_contains = list.contains(&item_index.clone());23622363			if !item_contains {2364				list.push(item_index);2365			}23662367			collection.consume_sstore()?;2368			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2369		} else {2370			let itm = vec![item_index];2371			collection.consume_sstore()?;2372			<AddressTokens<T>>::insert(collection.id, owner.as_sub(), itm);2373		}23742375		Ok(())2376	}23772378	fn remove_token_index(2379		collection: &CollectionHandle<T>,2380		item_index: TokenId,2381		owner: &T::CrossAccountId,2382	) -> DispatchResult {2383		// update counter2384		collection.consume_sload()?;2385		collection.consume_sstore()?;2386		<AccountItemCount<T>>::insert(2387			owner.as_sub(),2388			<AccountItemCount<T>>::get(owner.as_sub())2389				.checked_sub(1)2390				.ok_or(Error::<T>::NumOverflow)?,2391		);23922393		collection.consume_sload()?;2394		let list_exists = <AddressTokens<T>>::contains_key(collection.id, owner.as_sub());2395		if list_exists {2396			collection.consume_sload()?;2397			let mut list = <AddressTokens<T>>::get(collection.id, owner.as_sub());2398			let item_contains = list.contains(&item_index.clone());23992400			if item_contains {2401				list.retain(|&item| item != item_index);2402				collection.consume_sstore()?;2403				<AddressTokens<T>>::insert(collection.id, owner.as_sub(), list);2404			}2405		}24062407		Ok(())2408	}24092410	fn move_token_index(2411		collection: &CollectionHandle<T>,2412		item_index: TokenId,2413		old_owner: &T::CrossAccountId,2414		new_owner: &T::CrossAccountId,2415	) -> DispatchResult {2416		Self::remove_token_index(collection, item_index, old_owner)?;2417		Self::add_token_index(collection, item_index, new_owner)?;24182419		Ok(())2420	}2421}24222423sp_api::decl_runtime_apis! {2424	pub trait NftApi {2425		/// Used for ethereum integration2426		fn eth_contract_code(account: H160) -> Option<Vec<u8>>;2427	}2428}
modifiedtests/src/eth/nonFungible.test.tsdiffbeforeafterboth
--- a/tests/src/eth/nonFungible.test.ts
+++ b/tests/src/eth/nonFungible.test.ts
@@ -4,11 +4,13 @@
 //
 
 import privateKey from '../substrate/privateKey';
-import { approveExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE } from '../util/helpers';
+import { approveExpectSuccess, burnItemExpectSuccess, createCollectionExpectSuccess, createItemExpectSuccess, transferExpectSuccess, transferFromExpectSuccess, UNIQUE } from '../util/helpers';
 import { collectionIdToAddress, createEthAccount, createEthAccountWithBalance, GAS_ARGS, itWeb3, normalizeEvents, recordEthFee, recordEvents, subToEth, transferBalanceToEth } from './util/helpers';
+import { evmToAddress } from '@polkadot/util-crypto';
 import nonFungibleAbi from './nonFungibleAbi.json';
 import { expect } from 'chai';
 import waitNewBlocks from '../substrate/wait-new-blocks';
+import { submitTransactionAsync } from '../substrate/substrate-api';
 
 describe('NFT: Information getting', () => {
   itWeb3('totalSupply', async ({ api, web3 }) => {
@@ -64,6 +66,78 @@
 });
 
 describe('NFT: Plain calls', () => {
+  itWeb3('Can perform mint()', 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.mintWithTokenURI(
+        receiver,
+        nextTokenId,
+        'Test URI',
+      ).send({from: caller});
+      const events = normalizeEvents(result.events);
+
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: '0x0000000000000000000000000000000000000000',
+            to: receiver,
+            tokenId: nextTokenId,
+          },
+        },
+      ]);
+
+      await waitNewBlocks(api, 1);
+      expect(await contract.methods.tokenURI(nextTokenId).call()).to.be.equal('Test URI');
+    }
+  });
+
+  itWeb3('Can perform burn()', async ({ web3, api }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: {type: 'NFT'},
+    });
+    const alice = privateKey('//Alice');
+
+    const owner = await createEthAccountWithBalance(api, web3);
+
+    const tokenId = 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});
+
+    {
+      const result = await contract.methods.burn(tokenId).send({ from: owner });
+      const events = normalizeEvents(result.events);
+      
+      expect(events).to.be.deep.equal([
+        {
+          address,
+          event: 'Transfer',
+          args: {
+            from: owner,
+            to: '0x0000000000000000000000000000000000000000',
+            tokenId: tokenId.toString(),
+          },
+        },
+      ]);
+    }
+  });
+
   itWeb3('Can perform approve()', async ({ web3, api }) => {
     const collection = await createCollectionExpectSuccess({
       mode: { type: 'NFT' },
@@ -251,6 +325,60 @@
 });
 
 describe('NFT: Substrate calls', () => {
+  itWeb3('Events emitted for mint()', async ({ web3 }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+
+    const address = collectionIdToAddress(collection);
+    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);
+
+    let tokenId: number;
+    const events = await recordEvents(contract, async () => {
+      tokenId = await createItemExpectSuccess(alice, collection, 'NFT');
+    });
+
+    expect(events).to.be.deep.equal([
+      {
+        address,
+        event: 'Transfer',
+        args: {
+          from: '0x0000000000000000000000000000000000000000',
+          to: subToEth(alice.address),
+          tokenId: tokenId!.toString(),
+        },
+      },
+    ]);
+  });
+
+  itWeb3('Events emitted for burn()', async ({ web3 }) => {
+    const collection = await createCollectionExpectSuccess({
+      mode: { type: 'NFT' },
+    });
+    const alice = privateKey('//Alice');
+
+    const address = collectionIdToAddress(collection);
+    const contract = new web3.eth.Contract(nonFungibleAbi as any, address);
+
+    const tokenId = await createItemExpectSuccess(alice, collection, 'NFT');
+    const events = await recordEvents(contract, async () => {
+      await burnItemExpectSuccess(alice, collection, tokenId);
+    });
+
+    expect(events).to.be.deep.equal([
+      {
+        address,
+        event: 'Transfer',
+        args: {
+          from: subToEth(alice.address),
+          to: '0x0000000000000000000000000000000000000000',
+          tokenId: tokenId.toString(),
+        },
+      },
+    ]);
+  });
+
   itWeb3('Events emitted for approve()', async ({ web3 }) => {
     const collection = await createCollectionExpectSuccess({
       mode: { type: 'NFT' },
@@ -342,4 +470,4 @@
       },
     ]);
   });
-});
\ No newline at end of file
+});
modifiedtests/src/eth/nonFungibleAbi.jsondiffbeforeafterboth
--- a/tests/src/eth/nonFungibleAbi.json
+++ b/tests/src/eth/nonFungibleAbi.json
@@ -50,6 +50,12 @@
         "type": "event"
     },
     {
+        "anonymous": true,
+        "inputs": [],
+        "name": "MintingFinished",
+        "type": "event"
+    },
+    {
         "anonymous": false,
         "inputs": [
             {
@@ -89,7 +95,7 @@
         ],
         "name": "approve",
         "outputs": [],
-        "stateMutability": "payable",
+        "stateMutability": "nonpayable",
         "type": "function"
     },
     {
@@ -119,6 +125,32 @@
                 "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": [
             {
@@ -146,11 +178,77 @@
         "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"
     },
@@ -160,7 +258,7 @@
         "outputs": [
             {
                 "internalType": "string",
-                "name": "res_name",
+                "name": "",
                 "type": "string"
             }
         ],
@@ -168,6 +266,19 @@
         "type": "function"
     },
     {
+        "inputs": [],
+        "name": "nextTokenId",
+        "outputs": [
+            {
+                "internalType": "uint256",
+                "name": "",
+                "type": "uint256"
+            }
+        ],
+        "stateMutability": "view",
+        "type": "function"
+    },
+    {
         "inputs": [
             {
                 "internalType": "uint256",
@@ -206,7 +317,7 @@
         ],
         "name": "safeTransferFrom",
         "outputs": [],
-        "stateMutability": "payable",
+        "stateMutability": "nonpayable",
         "type": "function"
     },
     {
@@ -232,9 +343,9 @@
                 "type": "bytes"
             }
         ],
-        "name": "safeTransferFrom",
+        "name": "safeTransferFromWithData",
         "outputs": [],
-        "stateMutability": "payable",
+        "stateMutability": "nonpayable",
         "type": "function"
     },
     {
@@ -258,9 +369,9 @@
     {
         "inputs": [
             {
-                "internalType": "bytes4",
-                "name": "interfaceID",
-                "type": "bytes4"
+                "internalType": "uint32",
+                "name": "interfaceId",
+                "type": "uint32"
             }
         ],
         "name": "supportsInterface",
@@ -271,7 +382,7 @@
                 "type": "bool"
             }
         ],
-        "stateMutability": "pure",
+        "stateMutability": "view",
         "type": "function"
     },
     {
@@ -280,7 +391,7 @@
         "outputs": [
             {
                 "internalType": "string",
-                "name": "res_symbol",
+                "name": "",
                 "type": "string"
             }
         ],
@@ -366,11 +477,6 @@
         "inputs": [
             {
                 "internalType": "address",
-                "name": "from",
-                "type": "address"
-            },
-            {
-                "internalType": "address",
                 "name": "to",
                 "type": "address"
             },
@@ -380,15 +486,20 @@
                 "type": "uint256"
             }
         ],
-        "name": "transferFrom",
+        "name": "transfer",
         "outputs": [],
-        "stateMutability": "payable",
+        "stateMutability": "nonpayable",
         "type": "function"
     },
     {
         "inputs": [
             {
                 "internalType": "address",
+                "name": "from",
+                "type": "address"
+            },
+            {
+                "internalType": "address",
                 "name": "to",
                 "type": "address"
             },
@@ -398,9 +509,9 @@
                 "type": "uint256"
             }
         ],
-        "name": "transfer",
+        "name": "transferFrom",
         "outputs": [],
-        "stateMutability": "payable",
+        "stateMutability": "nonpayable",
         "type": "function"
     }
 ]
\ No newline at end of file