git.delta.rocks / unique-network / refs/commits / 6d7ceb40a19f

difftreelog

refactor split nonfungible into its own pallet

Yaroslav Bolyukin2021-10-12parent: #bfbca76.patch.diff
in: master

8 files changed

addedpallets/nonfungible/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/pallets/nonfungible/Cargo.toml
@@ -0,0 +1,37 @@
+[package]
+name = "pallet-nonfungible"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies.codec]
+default-features = false
+features = ['derive']
+package = 'parity-scale-codec'
+version = '2.0.0'
+
+[dependencies]
+frame-support = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' }
+frame-system = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' }
+sp-runtime = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' }
+sp-std = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' }
+sp-core = { default-features = false, version = '4.0.0-dev', git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.10' }
+pallet-common = { default-features = false, path = '../common' }
+nft-data-structs = { default-features = false, path = '../../primitives/nft' }
+evm-coder = { default-features = false, path = '../../crates/evm-coder' }
+pallet-evm-coder-substrate = { default-features = false, path = '../evm-coder-substrate' }
+ethereum = { default-features = false, version = "0.9.0" }
+
+[features]
+default = ["std"]
+std = [
+    "frame-support/std",
+    "frame-system/std",
+    "sp-runtime/std",
+    "sp-std/std",
+    "nft-data-structs/std",
+    "pallet-common/std",
+    "evm-coder/std",
+    "ethereum/std",
+    "pallet-evm-coder-substrate/std",
+]
+runtime-benchmarks = []
addedpallets/nonfungible/src/benchmarking.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/nonfungible/src/benchmarking.rs
@@ -0,0 +1 @@
+#![cfg(feature = "runtime-benchmarking")]
addedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/nonfungible/src/common.rs
@@ -0,0 +1,228 @@
+use core::marker::PhantomData;
+
+use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
+use nft_data_structs::TokenId;
+use pallet_common::{
+	CommonCollectionOperations, CommonWeightInfo, account::CrossAccountId, with_weight,
+};
+use sp_runtime::DispatchError;
+use sp_std::vec::Vec;
+
+use crate::{
+	AccountBalance, Allowance, Config, CreateItemData, DataKind, Error, NonfungibleHandle, Owned,
+	Owner, Pallet, SelfWeightOf, TokenData, weights::WeightInfo,
+};
+
+pub struct CommonWeights<T: Config>(PhantomData<T>);
+impl<T: Config> CommonWeightInfo for CommonWeights<T> {
+	fn create_item() -> Weight {
+		<SelfWeightOf<T>>::create_item()
+	}
+
+	fn create_multiple_items(amount: u32) -> Weight {
+		<SelfWeightOf<T>>::create_multiple_items(amount)
+	}
+
+	fn burn_item() -> Weight {
+		<SelfWeightOf<T>>::burn_item()
+	}
+
+	fn transfer() -> Weight {
+		<SelfWeightOf<T>>::transfer()
+	}
+
+	fn approve() -> Weight {
+		<SelfWeightOf<T>>::approve()
+	}
+
+	fn transfer_from() -> Weight {
+		<SelfWeightOf<T>>::transfer_from()
+	}
+
+	fn set_variable_metadata(_bytes: u32) -> Weight {
+		<SelfWeightOf<T>>::set_variable_metadata()
+	}
+}
+
+fn map_create_data<T: Config>(
+	data: nft_data_structs::CreateItemData,
+	to: &T::CrossAccountId,
+) -> Result<CreateItemData<T>, DispatchError> {
+	match data {
+		nft_data_structs::CreateItemData::NFT(data) => Ok(CreateItemData {
+			const_data: data.const_data,
+			variable_data: data.variable_data,
+			owner: to.clone(),
+		}),
+		_ => fail!(<Error<T>>::NotNonfungibleDataUsedToMintFungibleCollectionToken),
+	}
+}
+
+impl<T: Config> CommonCollectionOperations<T> for NonfungibleHandle<T> {
+	fn create_item(
+		&self,
+		sender: T::CrossAccountId,
+		to: T::CrossAccountId,
+		data: nft_data_structs::CreateItemData,
+	) -> DispatchResultWithPostInfo {
+		with_weight(
+			<Pallet<T>>::create_item(self, &sender, map_create_data(data, &to)?),
+			<SelfWeightOf<T>>::create_item(),
+		)
+	}
+
+	fn create_multiple_items(
+		&self,
+		sender: T::CrossAccountId,
+		to: T::CrossAccountId,
+		data: Vec<nft_data_structs::CreateItemData>,
+	) -> DispatchResultWithPostInfo {
+		let data = data
+			.into_iter()
+			.map(|d| map_create_data::<T>(d, &to))
+			.collect::<Result<Vec<_>, DispatchError>>()?;
+
+		let amount = data.len();
+		with_weight(
+			<Pallet<T>>::create_multiple_items(self, &sender, data),
+			<SelfWeightOf<T>>::create_multiple_items(amount as u32),
+		)
+	}
+
+	fn burn_item(
+		&self,
+		sender: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResultWithPostInfo {
+		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
+		if amount == 1 {
+			with_weight(
+				<Pallet<T>>::burn(&self, &sender, token),
+				<SelfWeightOf<T>>::burn_item(),
+			)
+		} else {
+			Ok(().into())
+		}
+	}
+
+	fn transfer(
+		&self,
+		from: T::CrossAccountId,
+		to: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResultWithPostInfo {
+		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
+		if amount == 1 {
+			with_weight(
+				<Pallet<T>>::transfer(&self, &from, &to, token),
+				<SelfWeightOf<T>>::transfer(),
+			)
+		} else {
+			Ok(().into())
+		}
+	}
+
+	fn approve(
+		&self,
+		sender: T::CrossAccountId,
+		spender: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResultWithPostInfo {
+		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
+
+		with_weight(
+			if amount == 1 {
+				<Pallet<T>>::set_allowance(&self, &sender, token, Some(&spender))
+			} else {
+				<Pallet<T>>::set_allowance(&self, &sender, token, None)
+			},
+			<SelfWeightOf<T>>::approve(),
+		)
+	}
+
+	fn transfer_from(
+		&self,
+		sender: T::CrossAccountId,
+		from: T::CrossAccountId,
+		to: T::CrossAccountId,
+		token: TokenId,
+		amount: u128,
+	) -> DispatchResultWithPostInfo {
+		ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
+
+		if amount == 1 {
+			with_weight(
+				<Pallet<T>>::transfer_from(&self, &sender, &from, &to, token),
+				<SelfWeightOf<T>>::transfer_from(),
+			)
+		} else {
+			Ok(().into())
+		}
+	}
+
+	fn set_variable_metadata(
+		&self,
+		sender: T::CrossAccountId,
+		token: TokenId,
+		data: Vec<u8>,
+	) -> DispatchResultWithPostInfo {
+		with_weight(
+			<Pallet<T>>::set_variable_metadata(&self, &sender, token, data),
+			<SelfWeightOf<T>>::set_variable_metadata(),
+		)
+	}
+
+	fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId> {
+		<Owned<T>>::iter_prefix((self.id, account.as_sub()))
+			.map(|(id, _)| id)
+			.collect()
+	}
+
+	fn token_exists(&self, token: TokenId) -> bool {
+		<Pallet<T>>::token_exists(self, token)
+	}
+
+	fn token_owner(&self, token: TokenId) -> T::CrossAccountId {
+		<Owner<T>>::get((self.id, token))
+	}
+	fn const_metadata(&self, token: TokenId) -> Vec<u8> {
+		<TokenData<T>>::get((self.id, token, DataKind::Constant))
+	}
+	fn variable_metadata(&self, token: TokenId) -> Vec<u8> {
+		<TokenData<T>>::get((self.id, token, DataKind::Variable))
+	}
+
+	fn collection_tokens(&self) -> u32 {
+		<Pallet<T>>::total_supply(self)
+	}
+
+	fn account_balance(&self, account: T::CrossAccountId) -> u32 {
+		<AccountBalance<T>>::get((self.id, account.as_sub()))
+	}
+
+	fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128 {
+		if <Owner<T>>::get((self.id, token)) == account {
+			1
+		} else {
+			0
+		}
+	}
+
+	fn allowance(
+		&self,
+		sender: T::CrossAccountId,
+		spender: T::CrossAccountId,
+		token: TokenId,
+	) -> u128 {
+		if <Owner<T>>::get((self.id, token)) != sender {
+			0
+		} else if <Allowance<T>>::get((self.id, token)) == Some(spender) {
+			1
+		} else {
+			0
+		}
+	}
+}
addedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/nonfungible/src/erc.rs
@@ -0,0 +1,396 @@
+use core::{
+	char::{REPLACEMENT_CHARACTER, decode_utf16},
+	convert::TryInto,
+};
+use evm_coder::{ToLog, execution::*, generate_stubgen, solidity, solidity_interface, types::*};
+use frame_support::BoundedVec;
+use nft_data_structs::TokenId;
+use pallet_evm_coder_substrate::dispatch_to_evm;
+use sp_core::{H160, U256};
+use sp_std::{vec::Vec, vec};
+use pallet_common::{account::CrossAccountId, erc::CommonEvmHandler};
+use pallet_evm_coder_substrate::call_internal;
+use pallet_common::erc::PrecompileOutput;
+
+use crate::{
+	AccountBalance, Config, CreateItemData, DataKind, NonfungibleHandle, Owner, Pallet, TokenData,
+	TokensMinted,
+};
+
+#[derive(ToLog)]
+pub enum ERC721Events {
+	Transfer {
+		#[indexed]
+		from: address,
+		#[indexed]
+		to: address,
+		#[indexed]
+		token_id: uint256,
+	},
+	Approval {
+		#[indexed]
+		owner: address,
+		#[indexed]
+		approved: address,
+		#[indexed]
+		token_id: uint256,
+	},
+	#[allow(dead_code)]
+	ApprovalForAll {
+		#[indexed]
+		owner: address,
+		#[indexed]
+		operator: address,
+		approved: bool,
+	},
+}
+
+#[derive(ToLog)]
+pub enum ERC721MintableEvents {
+	#[allow(dead_code)]
+	MintingFinished {},
+}
+
+#[solidity_interface(name = "ERC721Metadata")]
+impl<T: Config> NonfungibleHandle<T> {
+	fn name(&self) -> Result<string> {
+		Ok(decode_utf16(self.name.iter().copied())
+			.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
+			.collect::<string>())
+	}
+	fn symbol(&self) -> Result<string> {
+		Ok(string::from_utf8_lossy(&self.token_prefix).into())
+	}
+
+	#[solidity(rename_selector = "tokenURI")]
+	fn token_uri(&self, token_id: uint256) -> Result<string> {
+		let token_id: u32 = token_id.try_into().map_err(|_| "token id overflow")?;
+		Ok(string::from_utf8_lossy(&<TokenData<T>>::get((
+			self.id,
+			token_id,
+			DataKind::Constant,
+		)))
+		.into())
+	}
+}
+
+#[solidity_interface(name = "ERC721Enumerable")]
+impl<T: Config> NonfungibleHandle<T> {
+	fn token_by_index(&self, index: uint256) -> Result<uint256> {
+		Ok(index)
+	}
+
+	fn token_of_owner_by_index(&self, _owner: address, _index: uint256) -> Result<uint256> {
+		// TODO: Not implemetable
+		Err("not implemented".into())
+	}
+
+	fn total_supply(&self) -> Result<uint256> {
+		Ok(<Pallet<T>>::total_supply(self).into())
+	}
+}
+
+#[solidity_interface(name = "ERC721", events(ERC721Events))]
+impl<T: Config> NonfungibleHandle<T> {
+	fn balance_of(&self, owner: address) -> Result<uint256> {
+		let owner = T::CrossAccountId::from_eth(owner);
+		let balance = <AccountBalance<T>>::get((self.id, owner.as_sub()));
+		Ok(balance.into())
+	}
+	fn owner_of(&self, token_id: uint256) -> Result<address> {
+		let token: TokenId = token_id.try_into()?;
+		Ok(*<Owner<T>>::get((self.id, token)).as_eth())
+	}
+	fn safe_transfer_from_with_data(
+		&mut self,
+		_from: address,
+		_to: address,
+		_token_id: uint256,
+		_data: bytes,
+		_value: value,
+	) -> Result<void> {
+		// TODO: Not implemetable
+		Err("not implemented".into())
+	}
+	fn safe_transfer_from(
+		&mut self,
+		_from: address,
+		_to: address,
+		_token_id: uint256,
+		_value: value,
+	) -> Result<void> {
+		// TODO: Not implemetable
+		Err("not implemented".into())
+	}
+
+	fn transfer_from(
+		&mut self,
+		caller: caller,
+		from: address,
+		to: address,
+		token_id: uint256,
+		_value: value,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let from = T::CrossAccountId::from_eth(from);
+		let to = T::CrossAccountId::from_eth(to);
+		let token = token_id.try_into()?;
+
+		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
+	fn approve(
+		&mut self,
+		caller: caller,
+		approved: address,
+		token_id: uint256,
+		_value: value,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let approved = T::CrossAccountId::from_eth(approved);
+		let token = token_id.try_into()?;
+
+		<Pallet<T>>::set_allowance(self, &caller, token, Some(&approved))
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
+	fn set_approval_for_all(
+		&mut self,
+		_caller: caller,
+		_operator: address,
+		_approved: bool,
+	) -> Result<void> {
+		// TODO: Not implemetable
+		Err("not implemented".into())
+	}
+
+	fn get_approved(&self, _token_id: uint256) -> Result<address> {
+		// TODO: Not implemetable
+		Err("not implemented".into())
+	}
+
+	fn is_approved_for_all(&self, _owner: address, _operator: address) -> Result<address> {
+		// TODO: Not implemetable
+		Err("not implemented".into())
+	}
+}
+
+#[solidity_interface(name = "ERC721Burnable")]
+impl<T: Config> NonfungibleHandle<T> {
+	fn burn(&mut self, caller: caller, token_id: uint256) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let token = token_id.try_into()?;
+
+		<Pallet<T>>::burn(self, &caller, token).map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+}
+
+#[solidity_interface(name = "ERC721Mintable", events(ERC721MintableEvents))]
+impl<T: Config> NonfungibleHandle<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()?;
+		if <TokensMinted<T>>::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?
+			!= token_id
+		{
+			return Err("item id should be next".into());
+		}
+
+		<Pallet<T>>::create_item(
+			self,
+			&caller,
+			CreateItemData {
+				const_data: BoundedVec::default(),
+				variable_data: BoundedVec::default(),
+				owner: to,
+			},
+		)
+		.map_err(dispatch_to_evm::<T>)?;
+
+		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 <TokensMinted<T>>::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?
+			!= token_id
+		{
+			return Err("item id should be next".into());
+		}
+
+		<Pallet<T>>::create_item(
+			self,
+			&caller,
+			CreateItemData {
+				const_data: Vec::<u8>::from(token_uri)
+					.try_into()
+					.map_err(|_| "token uri is too long")?,
+				variable_data: BoundedVec::default(),
+				owner: to,
+			},
+		)
+		.map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+
+	fn finish_minting(&mut self, _caller: caller) -> Result<bool> {
+		Err("not implementable".into())
+	}
+}
+
+#[solidity_interface(name = "ERC721UniqueExtensions")]
+impl<T: Config> NonfungibleHandle<T> {
+	#[solidity(rename_selector = "transfer")]
+	fn transfer_nft(
+		&mut self,
+		caller: caller,
+		to: address,
+		token_id: uint256,
+		_value: value,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let token = token_id.try_into()?;
+
+		<Pallet<T>>::transfer(self, &caller, &to, token).map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
+	fn next_token_id(&self) -> Result<uint256> {
+		Ok(<TokensMinted<T>>::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?
+			.into())
+	}
+
+	fn set_variable_metadata(
+		&mut self,
+		caller: caller,
+		token_id: uint256,
+		data: bytes,
+	) -> Result<void> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let token = token_id.try_into()?;
+
+		<Pallet<T>>::set_variable_metadata(self, &caller, token, data)
+			.map_err(dispatch_to_evm::<T>)?;
+		Ok(())
+	}
+
+	fn get_variable_metadata(&self, token_id: uint256) -> Result<bytes> {
+		let token: TokenId = token_id.try_into()?;
+
+		Ok(<TokenData<T>>::get((self.id, token, DataKind::Variable)))
+	}
+
+	fn mint_bulk(&mut self, caller: caller, to: address, token_ids: Vec<uint256>) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let mut expected_index = <TokensMinted<T>>::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?;
+
+		let total_tokens = token_ids.len();
+		for id in token_ids.into_iter() {
+			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;
+			if id != expected_index {
+				return Err("item id should be next".into());
+			}
+			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;
+		}
+		let data = (0..total_tokens)
+			.map(|_| CreateItemData {
+				const_data: BoundedVec::default(),
+				variable_data: BoundedVec::default(),
+				owner: to.clone(),
+			})
+			.collect();
+
+		<Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+
+	#[solidity(rename_selector = "mintBulkWithTokenURI")]
+	fn mint_bulk_with_token_uri(
+		&mut self,
+		caller: caller,
+		to: address,
+		tokens: Vec<(uint256, string)>,
+	) -> Result<bool> {
+		let caller = T::CrossAccountId::from_eth(caller);
+		let to = T::CrossAccountId::from_eth(to);
+		let mut expected_index = <TokensMinted<T>>::get(self.id)
+			.checked_add(1)
+			.ok_or("item id overflow")?;
+
+		let mut data = Vec::with_capacity(tokens.len());
+		for (id, token_uri) in tokens {
+			let id: u32 = id.try_into().map_err(|_| "token id overflow")?;
+			if id != expected_index {
+				panic!("item id should be next ({}) but got {}", expected_index, id);
+			}
+			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;
+
+			data.push(CreateItemData {
+				const_data: Vec::<u8>::from(token_uri)
+					.try_into()
+					.map_err(|_| "token uri is too long")?,
+				variable_data: vec![].try_into().unwrap(),
+				owner: to.clone(),
+			});
+		}
+
+		<Pallet<T>>::create_multiple_items(self, &caller, data).map_err(dispatch_to_evm::<T>)?;
+		Ok(true)
+	}
+}
+
+#[solidity_interface(
+	name = "UniqueNFT",
+	is(
+		ERC721,
+		ERC721Metadata,
+		ERC721Enumerable,
+		ERC721UniqueExtensions,
+		ERC721Mintable,
+		ERC721Burnable,
+	)
+)]
+impl<T: Config> NonfungibleHandle<T> {}
+
+// Not a tests, but code generators
+generate_stubgen!(gen_impl, UniqueNFTCall, true);
+generate_stubgen!(gen_iface, UniqueNFTCall, false);
+
+pub const CODE: &[u8] = include_bytes!("./stubs/UniqueNFT.raw");
+
+impl<T: Config> CommonEvmHandler for NonfungibleHandle<T> {
+	const CODE: &'static [u8] = include_bytes!("./stubs/UniqueNFT.raw");
+
+	fn call(mut self, source: &H160, input: &[u8], value: U256) -> Option<PrecompileOutput> {
+		let result = call_internal::<UniqueNFTCall, _>(*source, &mut self, value, input);
+		self.0.recorder.evm_to_precompile_output(result)
+	}
+}
addedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/nonfungible/src/lib.rs
@@ -0,0 +1,537 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use erc::ERC721Events;
+use frame_support::{BoundedVec, ensure};
+use nft_data_structs::{
+	AccessMode, CUSTOM_DATA_LIMIT, Collection, CollectionId, CustomDataLimit, TokenId,
+};
+use pallet_common::{
+	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, account::CrossAccountId,
+};
+use sp_core::H160;
+use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
+use sp_std::{vec::Vec, vec};
+use core::ops::Deref;
+use sp_std::collections::btree_map::BTreeMap;
+
+pub use pallet::*;
+pub mod benchmarking;
+pub mod common;
+pub mod erc;
+pub mod weights;
+
+pub struct CreateItemData<T: Config> {
+	pub const_data: BoundedVec<u8, CustomDataLimit>,
+	pub variable_data: BoundedVec<u8, CustomDataLimit>,
+	pub owner: T::CrossAccountId,
+}
+pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;
+
+#[frame_support::pallet]
+pub mod pallet {
+	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key};
+	use sp_std::vec::Vec;
+	use nft_data_structs::{CollectionId, TokenId};
+	use super::weights::WeightInfo;
+
+	#[pallet::error]
+	pub enum Error<T> {
+		/// Not Nonfungible item data used to mint in Nonfungible collection.
+		NotNonfungibleDataUsedToMintFungibleCollectionToken,
+		/// Used amount > 1 with NFT
+		NonfungibleItemsHaveNoAmount,
+	}
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config + pallet_common::Config {
+		type WeightInfo: WeightInfo;
+	}
+
+	#[pallet::pallet]
+	#[pallet::generate_store(pub(super) trait Store)]
+	pub struct Pallet<T>(_);
+
+	#[pallet::storage]
+	pub(super) type TokensMinted<T: Config> =
+		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
+	#[pallet::storage]
+	pub(super) type TokensBurnt<T: Config> =
+		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;
+
+	#[derive(Encode, Decode)]
+	pub enum DataKind {
+		Constant,
+		Variable,
+	}
+
+	#[pallet::storage]
+	pub(super) type TokenData<T: Config> = StorageNMap<
+		Key = (
+			Key<Twox64Concat, CollectionId>,
+			Key<Twox64Concat, TokenId>,
+			Key<Identity, DataKind>,
+		),
+		Value = Vec<u8>,
+		QueryKind = ValueQuery,
+	>;
+
+	#[pallet::storage]
+	pub(super) type Owner<T: Config> = StorageNMap<
+		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
+		Value = T::CrossAccountId,
+		QueryKind = ValueQuery,
+	>;
+	/// Used to enumerate tokens owned by account
+	#[pallet::storage]
+	pub(super) type Owned<T: Config> = StorageNMap<
+		Key = (
+			Key<Twox64Concat, CollectionId>,
+			Key<Blake2_128Concat, T::AccountId>,
+			Key<Twox64Concat, TokenId>,
+		),
+		Value = bool,
+		QueryKind = ValueQuery,
+	>;
+
+	#[pallet::storage]
+	pub(super) type AccountBalance<T: Config> = StorageNMap<
+		Key = (
+			Key<Twox64Concat, CollectionId>,
+			Key<Blake2_128Concat, T::AccountId>,
+		),
+		Value = u32,
+		QueryKind = ValueQuery,
+	>;
+
+	#[pallet::storage]
+	pub(super) type Allowance<T: Config> = StorageNMap<
+		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),
+		Value = T::CrossAccountId,
+		QueryKind = OptionQuery,
+	>;
+}
+
+pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);
+impl<T: Config> NonfungibleHandle<T> {
+	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {
+		Self(inner)
+	}
+	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {
+		self.0
+	}
+}
+impl<T: Config> Deref for NonfungibleHandle<T> {
+	type Target = pallet_common::CollectionHandle<T>;
+
+	fn deref(&self) -> &Self::Target {
+		&self.0
+	}
+}
+
+impl<T: Config> Pallet<T> {
+	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {
+		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)
+	}
+	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {
+		<Owner<T>>::contains_key((collection.id, token))
+	}
+	pub fn ensure_owner(
+		collection: &NonfungibleHandle<T>,
+		token: TokenId,
+		sender: &T::CrossAccountId,
+	) -> DispatchResult {
+		ensure!(
+			&<Owner<T>>::get((collection.id, token)) == sender,
+			<CommonError<T>>::NoPermission
+		);
+		Ok(())
+	}
+	pub fn item_owner(
+		collection: &NonfungibleHandle<T>,
+		token: TokenId,
+	) -> Result<T::CrossAccountId, DispatchError> {
+		let owner = <Owner<T>>::get((collection.id, token));
+		ensure!(
+			owner != T::CrossAccountId::default(),
+			<CommonError<T>>::TokenNotFound
+		);
+		Ok(owner)
+	}
+}
+
+// unchecked calls skips any permission checks
+impl<T: Config> Pallet<T> {
+	pub fn init_collection(data: Collection<T>) -> Result<CollectionId, DispatchError> {
+		PalletCommon::init_collection(data)
+	}
+	pub fn destroy_collection(
+		collection: NonfungibleHandle<T>,
+		sender: &T::CrossAccountId,
+	) -> DispatchResult {
+		let id = collection.id;
+
+		// =========
+
+		PalletCommon::destroy_collection(collection.0, sender)?;
+
+		<Owner<T>>::remove_prefix((id,), None);
+		<Owned<T>>::remove_prefix((id,), None);
+		<TokensMinted<T>>::remove(id);
+		<TokensBurnt<T>>::remove(id);
+		<TokenData<T>>::remove_prefix((id,), None);
+		<Allowance<T>>::remove_prefix((id,), None);
+		<AccountBalance<T>>::remove_prefix((id,), None);
+		Ok(())
+	}
+
+	pub fn burn(
+		collection: &NonfungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		token: TokenId,
+	) -> DispatchResult {
+		let token_owner = <Pallet<T>>::item_owner(collection, token)?;
+		ensure!(
+			&token_owner == sender
+				|| (collection.limits.owner_can_transfer
+					&& collection.is_owner_or_admin(sender)?),
+			<CommonError<T>>::NoPermission
+		);
+
+		if collection.access == AccessMode::WhiteList {
+			collection.check_whitelist(sender)?;
+		}
+
+		let burnt = <TokensBurnt<T>>::get(collection.id)
+			.checked_add(1)
+			.ok_or(ArithmeticError::Overflow)?;
+
+		// =========
+
+		<Owner<T>>::remove((collection.id, token));
+		<Owned<T>>::remove((collection.id, token_owner.as_sub(), token));
+		<TokensBurnt<T>>::insert(collection.id, burnt);
+		<TokenData<T>>::remove_prefix((collection.id, token), None);
+		<Allowance<T>>::remove((collection.id, token));
+
+		collection.log_infallible(ERC721Events::Transfer {
+			from: *token_owner.as_eth(),
+			to: H160::default(),
+			token_id: token.into(),
+		});
+		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(
+			collection.id,
+			token,
+			token_owner,
+			1,
+		));
+		return Ok(());
+	}
+
+	pub fn transfer(
+		collection: &NonfungibleHandle<T>,
+		from: &T::CrossAccountId,
+		to: &T::CrossAccountId,
+		token: TokenId,
+	) -> DispatchResult {
+		ensure!(
+			collection.transfers_enabled,
+			<CommonError<T>>::TransferNotAllowed
+		);
+
+		let token_owner = <Pallet<T>>::item_owner(collection, token)?;
+		ensure!(
+			&token_owner == from
+				|| (collection.limits.owner_can_transfer && collection.is_owner_or_admin(from)?),
+			<CommonError<T>>::NoPermission
+		);
+
+		if collection.access == AccessMode::WhiteList {
+			collection.check_whitelist(from)?;
+			collection.check_whitelist(to)?;
+		}
+		<PalletCommon<T>>::ensure_correct_receiver(to)?;
+
+		let balance_from = <AccountBalance<T>>::get((collection.id, from.as_sub()))
+			.checked_sub(1)
+			.ok_or(<CommonError<T>>::TokenValueTooLow)?;
+		let balance_to = if from != to {
+			let balance_to = <AccountBalance<T>>::get((collection.id, to.as_sub()))
+				.checked_add(1)
+				.ok_or(ArithmeticError::Overflow)?;
+
+			ensure!(
+				balance_to < collection.limits.account_token_ownership_limit(),
+				<CommonError<T>>::AccountTokenLimitExceeded,
+			);
+
+			Some(balance_to)
+		} else {
+			None
+		};
+
+		collection.consume_sstores(4)?;
+		collection.consume_log(3, 0)?;
+
+		// =========
+
+		if let Some(balance_to) = balance_to {
+			// from != to
+			if balance_from == 0 {
+				<AccountBalance<T>>::remove((collection.id, from.as_sub()));
+			} else {
+				<AccountBalance<T>>::insert((collection.id, from.as_sub()), balance_from);
+			}
+			<AccountBalance<T>>::insert((collection.id, to.as_sub()), balance_to);
+			<Owned<T>>::remove((collection.id, from.as_sub(), token));
+			<Owned<T>>::insert((collection.id, to.as_sub(), token), true);
+		}
+		Self::set_allowance_unchecked(collection, from, token, None);
+		<Owner<T>>::insert((collection.id, token), &to);
+
+		collection.log_infallible(ERC721Events::Transfer {
+			from: *from.as_eth(),
+			to: *to.as_eth(),
+			token_id: token.into(),
+		});
+		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(
+			collection.id,
+			token,
+			from.clone(),
+			to.clone(),
+			1,
+		));
+		Ok(())
+	}
+
+	pub fn create_multiple_items(
+		collection: &NonfungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		data: Vec<CreateItemData<T>>,
+	) -> DispatchResult {
+		let unrestricted_minting = collection.is_owner_or_admin(sender)?;
+		if !unrestricted_minting {
+			ensure!(
+				collection.mint_mode,
+				<CommonError<T>>::PublicMintingNotAllowed
+			);
+			collection.check_whitelist(sender)?;
+
+			for item in data.iter() {
+				collection.check_whitelist(&item.owner)?;
+			}
+		}
+
+		for data in data.iter() {
+			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;
+			if !data.const_data.is_empty() {
+				collection.consume_sstore()?;
+			}
+			if !data.variable_data.is_empty() {
+				collection.consume_sstore()?;
+			}
+			collection.consume_sstore()?;
+			collection.consume_log(3, 0)?;
+		}
+
+		let first_token = <TokensMinted<T>>::get(collection.id);
+		let tokens_minted = first_token
+			.checked_add(data.len() as u32)
+			.ok_or(ArithmeticError::Overflow)?;
+		ensure!(
+			tokens_minted < collection.limits.token_limit,
+			<CommonError<T>>::CollectionTokenLimitExceeded
+		);
+		collection.consume_sstore()?;
+
+		let mut balances = BTreeMap::new();
+		for data in &data {
+			let balance = balances
+				.entry(data.owner.as_sub())
+				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, data.owner.as_sub())));
+			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;
+
+			ensure!(
+				*balance <= collection.limits.account_token_ownership_limit(),
+				<CommonError<T>>::AccountTokenLimitExceeded,
+			);
+		}
+		collection.consume_sstores(balances.len())?;
+
+		// =========
+
+		<TokensMinted<T>>::insert(collection.id, tokens_minted);
+		for (account, balance) in balances {
+			<AccountBalance<T>>::insert((collection.id, account), balance);
+		}
+		for (i, data) in data.into_iter().enumerate() {
+			let token = first_token + i as u32;
+
+			if !data.const_data.is_empty() {
+				<TokenData<T>>::insert((collection.id, token, DataKind::Constant), data.const_data);
+			}
+			if !data.variable_data.is_empty() {
+				<TokenData<T>>::insert(
+					(collection.id, token, DataKind::Variable),
+					data.variable_data,
+				);
+			}
+			<Owner<T>>::insert((collection.id, token), &data.owner);
+			<Owned<T>>::insert((collection.id, data.owner.as_sub(), token), true);
+
+			collection.log_infallible(ERC721Events::Transfer {
+				from: H160::default(),
+				to: *data.owner.as_eth(),
+				token_id: token.into(),
+			});
+		}
+		Ok(())
+	}
+
+	pub fn set_allowance_unchecked(
+		collection: &NonfungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		token: TokenId,
+		spender: Option<&T::CrossAccountId>,
+	) {
+		if let Some(spender) = spender {
+			let old_spender = <Allowance<T>>::get((collection.id, token));
+			<Allowance<T>>::insert((collection.id, token), spender);
+			// In ERC721 there is only one possible approved user of token, so we set
+			// approved user to spender
+			collection.log_infallible(ERC721Events::Approval {
+				owner: *sender.as_eth(),
+				approved: *spender.as_eth(),
+				token_id: token.into(),
+			});
+			// In Unique chain, any token can have any amount of approved users, so we need to
+			// set allowance of old owner to 0, and allowance of new owner to 1
+			if old_spender.as_ref() != Some(spender) {
+				if let Some(old_owner) = old_spender {
+					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(
+						collection.id,
+						token,
+						sender.clone(),
+						old_owner.clone(),
+						0,
+					));
+				}
+				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(
+					collection.id,
+					token,
+					sender.clone(),
+					spender.clone(),
+					1,
+				));
+			}
+		} else {
+			let old_spender = <Allowance<T>>::take((collection.id, token));
+			// In ERC721 there is only one possible approved user of token, so we set
+			// approved user to zero address
+			collection.log_infallible(ERC721Events::Approval {
+				owner: *sender.as_eth(),
+				approved: H160::default(),
+				token_id: token.into(),
+			});
+			// In Unique chain, any token can have any amount of approved users, so we need to
+			// set allowance of old owner to 0
+			if let Some(old_spender) = old_spender {
+				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(
+					collection.id,
+					token,
+					sender.clone(),
+					old_spender.clone(),
+					0,
+				));
+			}
+		}
+	}
+
+	pub fn set_allowance(
+		collection: &NonfungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		token: TokenId,
+		spender: Option<&T::CrossAccountId>,
+	) -> DispatchResult {
+		if collection.access == AccessMode::WhiteList {
+			collection.check_whitelist(&sender)?;
+			if let Some(spender) = spender {
+				collection.check_whitelist(&spender)?;
+			}
+		}
+
+		if let Some(spender) = spender {
+			<PalletCommon<T>>::ensure_correct_receiver(spender)?;
+		}
+		let token_owner = Self::item_owner(collection, token)?;
+		if &token_owner != sender {
+			ensure!(
+				collection.ignores_owned_amount(sender)?,
+				<CommonError<T>>::CantApproveMoreThanOwned
+			);
+		}
+
+		// =========
+
+		Self::set_allowance_unchecked(collection, sender, token, spender);
+		Ok(())
+	}
+
+	pub fn transfer_from(
+		collection: &NonfungibleHandle<T>,
+		spender: &T::CrossAccountId,
+		from: &T::CrossAccountId,
+		to: &T::CrossAccountId,
+		token: TokenId,
+	) -> DispatchResult {
+		if spender == from {
+			return Self::transfer(collection, from, to, token);
+		}
+		if collection.access == AccessMode::WhiteList {
+			// `from`, `to` checked in [`transfer`]
+			collection.check_whitelist(spender)?;
+		}
+
+		if <Allowance<T>>::get((collection.id, token)).as_ref() != Some(spender) {
+			ensure!(
+				collection.ignores_allowance(spender)?,
+				<CommonError<T>>::TokenValueNotEnough
+			);
+		}
+
+		// =========
+
+		Self::transfer(collection, &from, to, token)?;
+		// Allowance is reset in [`transfer`]
+		Ok(())
+	}
+
+	pub fn set_variable_metadata(
+		collection: &NonfungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		token: TokenId,
+		data: Vec<u8>,
+	) -> DispatchResult {
+		ensure!(
+			data.len() as u32 <= CUSTOM_DATA_LIMIT,
+			<CommonError<T>>::TokenVariableDataLimitExceeded
+		);
+		let item_owner = Self::item_owner(collection, token)?;
+		collection.check_can_update_meta(sender, &item_owner)?;
+
+		collection.consume_sstore()?;
+
+		// =========
+
+		<TokenData<T>>::insert((collection.id, token, DataKind::Variable), data);
+		Ok(())
+	}
+
+	/// Delegated to `create_multiple_items`
+	pub fn create_item(
+		collection: &NonfungibleHandle<T>,
+		sender: &T::CrossAccountId,
+		data: CreateItemData<T>,
+	) -> DispatchResult {
+		Self::create_multiple_items(collection, sender, vec![data])
+	}
+}
addedpallets/nonfungible/src/stubs/UniqueNFT.rawdiffbeforeafterboth

binary blob — no preview

addedpallets/nonfungible/src/stubs/UniqueNFT.soldiffbeforeafterboth
after · pallets/nonfungible/src/stubs/UniqueNFT.sol
1// SPDX-License-Identifier: OTHER2// This code is automatically generated34pragma solidity >=0.8.0 <0.9.0;56// Anonymous struct7struct Tuple0 {8	uint256 field_0;9	string field_1;10}1112// Common stubs holder13contract Dummy {14	uint8 dummy;15	string stub_error = "this contract is implemented in native";16}1718// Inline19contract ERC721Events {20	event Transfer(21		address indexed from,22		address indexed to,23		uint256 indexed tokenId24	);25	event Approval(26		address indexed owner,27		address indexed approved,28		uint256 indexed tokenId29	);30	event ApprovalForAll(31		address indexed owner,32		address indexed operator,33		bool approved34	);35}3637// Inline38contract ERC721MintableEvents {39	event MintingFinished();40}4142// Inline43contract InlineNameSymbol is Dummy {44	// Selector: name() 06fdde0345	function name() public view returns (string memory) {46		require(false, stub_error);47		dummy;48		return "";49	}5051	// Selector: symbol() 95d89b4152	function symbol() public view returns (string memory) {53		require(false, stub_error);54		dummy;55		return "";56	}57}5859// Inline60contract InlineTotalSupply is Dummy {61	// Selector: totalSupply() 18160ddd62	function totalSupply() public view returns (uint256) {63		require(false, stub_error);64		dummy;65		return 0;66	}67}6869contract ERC165 is Dummy {70	// Selector: supportsInterface(bytes4) 01ffc9a771	function supportsInterface(uint32 interfaceId) public view returns (bool) {72		require(false, stub_error);73		interfaceId;74		dummy;75		return false;76	}77}7879contract ERC721 is Dummy, ERC165, ERC721Events {80	// Selector: balanceOf(address) 70a0823181	function balanceOf(address owner) public view returns (uint256) {82		require(false, stub_error);83		owner;84		dummy;85		return 0;86	}8788	// Selector: ownerOf(uint256) 6352211e89	function ownerOf(uint256 tokenId) public view returns (address) {90		require(false, stub_error);91		tokenId;92		dummy;93		return 0x0000000000000000000000000000000000000000;94	}9596	// Selector: safeTransferFromWithData(address,address,uint256,bytes) 60a1167297	function safeTransferFromWithData(98		address from,99		address to,100		uint256 tokenId,101		bytes memory data102	) public {103		require(false, stub_error);104		from;105		to;106		tokenId;107		data;108		dummy = 0;109	}110111	// Selector: safeTransferFrom(address,address,uint256) 42842e0e112	function safeTransferFrom(113		address from,114		address to,115		uint256 tokenId116	) public {117		require(false, stub_error);118		from;119		to;120		tokenId;121		dummy = 0;122	}123124	// Selector: transferFrom(address,address,uint256) 23b872dd125	function transferFrom(126		address from,127		address to,128		uint256 tokenId129	) public {130		require(false, stub_error);131		from;132		to;133		tokenId;134		dummy = 0;135	}136137	// Selector: approve(address,uint256) 095ea7b3138	function approve(address approved, uint256 tokenId) public {139		require(false, stub_error);140		approved;141		tokenId;142		dummy = 0;143	}144145	// Selector: setApprovalForAll(address,bool) a22cb465146	function setApprovalForAll(address operator, bool approved) public {147		require(false, stub_error);148		operator;149		approved;150		dummy = 0;151	}152153	// Selector: getApproved(uint256) 081812fc154	function getApproved(uint256 tokenId) public view returns (address) {155		require(false, stub_error);156		tokenId;157		dummy;158		return 0x0000000000000000000000000000000000000000;159	}160161	// Selector: isApprovedForAll(address,address) e985e9c5162	function isApprovedForAll(address owner, address operator)163		public164		view165		returns (address)166	{167		require(false, stub_error);168		owner;169		operator;170		dummy;171		return 0x0000000000000000000000000000000000000000;172	}173}174175contract ERC721Burnable is Dummy {176	// Selector: burn(uint256) 42966c68177	function burn(uint256 tokenId) public {178		require(false, stub_error);179		tokenId;180		dummy = 0;181	}182}183184contract ERC721Enumerable is Dummy, InlineTotalSupply {185	// Selector: tokenByIndex(uint256) 4f6ccce7186	function tokenByIndex(uint256 index) public view returns (uint256) {187		require(false, stub_error);188		index;189		dummy;190		return 0;191	}192193	// Selector: tokenOfOwnerByIndex(address,uint256) 2f745c59194	function tokenOfOwnerByIndex(address owner, uint256 index)195		public196		view197		returns (uint256)198	{199		require(false, stub_error);200		owner;201		index;202		dummy;203		return 0;204	}205}206207contract ERC721Metadata is Dummy, InlineNameSymbol {208	// Selector: tokenURI(uint256) c87b56dd209	function tokenURI(uint256 tokenId) public view returns (string memory) {210		require(false, stub_error);211		tokenId;212		dummy;213		return "";214	}215}216217contract ERC721Mintable is Dummy, ERC721MintableEvents {218	// Selector: mintingFinished() 05d2035b219	function mintingFinished() public view returns (bool) {220		require(false, stub_error);221		dummy;222		return false;223	}224225	// Selector: mint(address,uint256) 40c10f19226	function mint(address to, uint256 tokenId) public returns (bool) {227		require(false, stub_error);228		to;229		tokenId;230		dummy = 0;231		return false;232	}233234	// Selector: mintWithTokenURI(address,uint256,string) 50bb4e7f235	function mintWithTokenURI(236		address to,237		uint256 tokenId,238		string memory tokenUri239	) public returns (bool) {240		require(false, stub_error);241		to;242		tokenId;243		tokenUri;244		dummy = 0;245		return false;246	}247248	// Selector: finishMinting() 7d64bcb4249	function finishMinting() public returns (bool) {250		require(false, stub_error);251		dummy = 0;252		return false;253	}254}255256contract ERC721UniqueExtensions is Dummy {257	// Selector: transfer(address,uint256) a9059cbb258	function transfer(address to, uint256 tokenId) public {259		require(false, stub_error);260		to;261		tokenId;262		dummy = 0;263	}264265	// Selector: nextTokenId() 75794a3c266	function nextTokenId() public view returns (uint256) {267		require(false, stub_error);268		dummy;269		return 0;270	}271272	// Selector: setVariableMetadata(uint256,bytes) d4eac26d273	function setVariableMetadata(uint256 tokenId, bytes memory data) public {274		require(false, stub_error);275		tokenId;276		data;277		dummy = 0;278	}279280	// Selector: getVariableMetadata(uint256) e6c5ce6f281	function getVariableMetadata(uint256 tokenId)282		public283		view284		returns (bytes memory)285	{286		require(false, stub_error);287		tokenId;288		dummy;289		return hex"";290	}291292	// Selector: mintBulk(address,uint256[]) 44a9945e293	function mintBulk(address to, uint256[] memory tokenIds)294		public295		returns (bool)296	{297		require(false, stub_error);298		to;299		tokenIds;300		dummy = 0;301		return false;302	}303304	// Selector: mintBulkWithTokenURI(address,(uint256,string)[]) 36543006305	function mintBulkWithTokenURI(address to, Tuple0[] memory tokens)306		public307		returns (bool)308	{309		require(false, stub_error);310		to;311		tokens;312		dummy = 0;313		return false;314	}315}316317contract UniqueNFT is318	Dummy,319	ERC165,320	ERC721,321	ERC721Metadata,322	ERC721Enumerable,323	ERC721UniqueExtensions,324	ERC721Mintable,325	ERC721Burnable326{}
addedpallets/nonfungible/src/weights.rsdiffbeforeafterboth
--- /dev/null
+++ b/pallets/nonfungible/src/weights.rs
@@ -0,0 +1,37 @@
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+
+use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
+use sp_std::marker::PhantomData;
+
+pub trait WeightInfo {
+	fn create_item() -> Weight;
+	fn create_multiple_items(b: u32) -> Weight;
+	fn burn_item() -> Weight;
+	fn transfer() -> Weight;
+	fn approve() -> Weight;
+	fn transfer_from() -> Weight;
+	fn set_variable_metadata() -> Weight;
+}
+
+pub struct SubstrateWeight<T>(PhantomData<T>);
+impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
+    fn create_item() -> Weight {0}
+	fn create_multiple_items(_b: u32) -> Weight {0}
+	fn burn_item() -> Weight {0}
+	fn transfer() -> Weight {0}
+	fn approve() -> Weight {0}
+	fn transfer_from() -> Weight {0}
+	fn set_variable_metadata() -> Weight {0}
+}
+
+impl WeightInfo for () {
+    fn create_item() -> Weight {0}
+	fn create_multiple_items(_b: u32) -> Weight {0}
+	fn burn_item() -> Weight {0}
+	fn transfer() -> Weight {0}
+	fn approve() -> Weight {0}
+	fn transfer_from() -> Weight {0}
+	fn set_variable_metadata() -> Weight {0}
+}