git.delta.rocks / unique-network / refs/commits / 940b0a339ab0

difftreelog

fix clippy warnings

Grigoriy Simonov2023-06-05parent: #e7cba9a.patch.diff
in: master

36 files changed

modifiednode/cli/src/chain_spec.rsdiffbeforeafterboth
--- a/node/cli/src/chain_spec.rs
+++ b/node/cli/src/chain_spec.rs
@@ -110,7 +110,7 @@
 
 /// Helper function to generate a crypto pair from seed
 pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
-	TPublic::Pair::from_string(&format!("//{}", seed), None)
+	TPublic::Pair::from_string(&format!("//{seed}"), None)
 		.expect("static values are valid; qed")
 		.public()
 }
modifiednode/cli/src/command.rsdiffbeforeafterboth
--- a/node/cli/src/command.rs
+++ b/node/cli/src/command.rs
@@ -83,7 +83,7 @@
 		"" | "local" => Box::new(chain_spec::local_testnet_config()),
 		path => {
 			let path = std::path::PathBuf::from(path);
-			let chain_spec = Box::new(chain_spec::OpalChainSpec::from_json_file(path.clone())?)
+			let chain_spec = Box::new(chain_spec::OpalChainSpec::from_json_file(path)?)
 				as Box<dyn sc_service::ChainSpec>;
 
 			match chain_spec.runtime_id() {
@@ -352,7 +352,7 @@
 					&polkadot_cli,
 					config.tokio_handle.clone(),
 				)
-				.map_err(|err| format!("Relay chain argument error: {}", err))?;
+				.map_err(|err| format!("Relay chain argument error: {err}"))?;
 
 				cmd.run(config, polkadot_config)
 			})
@@ -464,7 +464,7 @@
 			runner.run_node_until_exit(|config| async move {
 				let hwbench = if !cli.no_hardware_benchmarks {
 					config.database.path().map(|database_path| {
-						let _ = std::fs::create_dir_all(&database_path);
+						let _ = std::fs::create_dir_all(database_path);
 						sc_sysinfo::gather_hwbench(Some(database_path))
 					})
 				} else {
@@ -513,7 +513,7 @@
 				let state_version =
 					RelayChainCli::native_runtime_version(&config.chain_spec).state_version();
 				let block: Block = generate_genesis_block(&*config.chain_spec, state_version)
-					.map_err(|e| format!("{:?}", e))?;
+					.map_err(|e| format!("{e:?}"))?;
 				let genesis_state = format!("0x{:?}", HexDisplay::from(&block.header().encode()));
 				let genesis_hash = format!("0x{:?}", HexDisplay::from(&block.header().hash().0));
 
@@ -522,7 +522,7 @@
 					&polkadot_cli,
 					config.tokio_handle.clone(),
 				)
-				.map_err(|err| format!("Relay chain argument error: {}", err))?;
+				.map_err(|err| format!("Relay chain argument error: {err}"))?;
 
 				info!("Parachain id: {:?}", para_id);
 				info!("Parachain Account: {}", parachain_account);
modifiednode/cli/src/service.rsdiffbeforeafterboth
--- a/node/cli/src/service.rs
+++ b/node/cli/src/service.rs
@@ -698,7 +698,7 @@
 {
 	let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?;
 
-	let block_import = ParachainBlockImport::new(client.clone(), backend.clone());
+	let block_import = ParachainBlockImport::new(client.clone(), backend);
 
 	cumulus_client_consensus_aura::import_queue::<
 		sp_consensus_aura::sr25519::AuthorityPair,
@@ -709,7 +709,7 @@
 		_,
 	>(cumulus_client_consensus_aura::ImportQueueParams {
 		block_import,
-		client: client.clone(),
+		client,
 		create_inherent_data_providers: move |_, _| async move {
 			let time = sp_timestamp::InherentDataProvider::from_system_time();
 
@@ -787,7 +787,7 @@
 				telemetry.clone(),
 			);
 
-			let block_import = ParachainBlockImport::new(client.clone(), backend.clone());
+			let block_import = ParachainBlockImport::new(client.clone(), backend);
 
 			Ok(AuraConsensus::build::<
 				sp_consensus_aura::sr25519::AuthorityPair,
@@ -864,7 +864,7 @@
 	ExecutorDispatch: NativeExecutionDispatch + 'static,
 {
 	Ok(sc_consensus_manual_seal::import_queue(
-		Box::new(client.clone()),
+		Box::new(client),
 		&task_manager.spawn_essential_handle(),
 		config.prometheus_registry(),
 	))
@@ -956,7 +956,7 @@
 
 	let collator = config.role.is_authority();
 
-	let select_chain = maybe_select_chain.clone();
+	let select_chain = maybe_select_chain;
 
 	if collator {
 		let block_import =
modifiednode/rpc/src/lib.rsdiffbeforeafterboth
--- a/node/rpc/src/lib.rs
+++ b/node/rpc/src/lib.rs
@@ -289,7 +289,7 @@
 	io.merge(
 		Net::new(
 			client.clone(),
-			network.clone(),
+			network,
 			// Whether to format the `peer_count` response as Hex (default) or not.
 			true,
 		)
modifiedpallets/app-promotion/src/lib.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/lib.rs
+++ b/pallets/app-promotion/src/lib.rs
@@ -296,7 +296,7 @@
 
 			if !block_pending.is_empty() {
 				block_pending.into_iter().for_each(|(staker, amount)| {
-					Self::get_frozen_balance(&staker).map(|b| {
+					if let Some(b) = Self::get_frozen_balance(&staker) {
 						let new_state = b.checked_sub(&amount).unwrap_or_default();
 
 						// In this case, setting a new state for the frozen funds cannot fail
@@ -305,7 +305,7 @@
 						// that we cannot (in the current implementation) unfreeze more funds
 						// than were originally frozen by the pallet. Either way, `on_initialize()` cannot fail.
 						Self::set_freeze_unchecked(&staker, new_state);
-					});
+					};
 				});
 			}
 
@@ -598,8 +598,8 @@
 			// this value is set for the stakers to whom the recalculation will be performed
 			let next_recalc_block = current_recalc_block + config.recalculation_interval;
 
-			let mut storage_iterator = Self::get_next_calculated_key()
-				.map_or(Staked::<T>::iter(), |key| Staked::<T>::iter_from(key));
+			let storage_iterator =
+				Self::get_next_calculated_key().map_or(Staked::<T>::iter(), Staked::<T>::iter_from);
 
 			PreviousCalculatedRecord::<T>::set(None);
 
@@ -658,10 +658,8 @@
 				// stakers_number - keeps the remaining number of iterations (staker addresses to handle)
 				// next_recalc_block_for_stake - is taken from the state and stores the starting relay block from which reward should be paid out
 				// income_acc - stores the reward amount to pay to the staker address (accumulates over all address stake records)
-				while let Some((
-					(current_id, staked_block),
-					(amount, next_recalc_block_for_stake),
-				)) = storage_iterator.next()
+				for ((current_id, staked_block), (amount, next_recalc_block_for_stake)) in
+					storage_iterator
 				{
 					// last_id is not equal current_id when we switch to handling a new staker address
 					// or just start handling the very first address. In the latter case last_id will be None and
@@ -859,11 +857,11 @@
 				if acc_amount < balance_per_block {
 					let res = (block, balance_per_block - acc_amount);
 					acc_amount = <BalanceOf<T>>::default();
-					return Some(res);
+					Some(res)
 				} else {
 					acc_amount -= balance_per_block;
 					will_deleted_stakes_count += 1;
-					return Some((block, <BalanceOf<T>>::default()));
+					Some((block, <BalanceOf<T>>::default()))
 				}
 			})
 			.collect::<Vec<_>>();
@@ -926,7 +924,7 @@
 		if amount.is_zero() {
 			<<T as Config>::Currency as MutateFreeze<T::AccountId>>::thaw(
 				&T::FreezeIdentifier::get(),
-				&staker,
+				staker,
 			)
 		} else {
 			<<T as Config>::Currency as MutateFreeze<T::AccountId>>::set_freeze(
@@ -1026,10 +1024,10 @@
 	) {
 		let income = Self::calculate_income(base, iters);
 
-		base.checked_add(&income).map(|res| {
+		if let Some(res) = base.checked_add(&income) {
 			<Staked<T>>::insert((staker, staked_block), (res, next_recalc_block));
 			*income_acc += income;
-		});
+		};
 	}
 
 	fn calculate_income<I>(base: I, iters: u32) -> I
modifiedpallets/app-promotion/src/types.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/types.rs
+++ b/pallets/app-promotion/src/types.rs
@@ -149,16 +149,16 @@
 		Self {
 			recalculation_interval: config
 				.recalculation_interval
-				.unwrap_or_else(|| T::RecalculationInterval::get()),
+				.unwrap_or_else(T::RecalculationInterval::get),
 			pending_interval: config
 				.pending_interval
-				.unwrap_or_else(|| T::PendingInterval::get()),
+				.unwrap_or_else(T::PendingInterval::get),
 			interval_income: config
 				.interval_income
-				.unwrap_or_else(|| T::IntervalIncome::get()),
+				.unwrap_or_else(T::IntervalIncome::get),
 			max_stakers_per_calculation: config
 				.max_stakers_per_calculation
-				.unwrap_or_else(|| MAX_NUMBER_PAYOUTS),
+				.unwrap_or(MAX_NUMBER_PAYOUTS),
 		}
 	}
 }
modifiedpallets/balances-adapter/src/lib.rsdiffbeforeafterboth
--- a/pallets/balances-adapter/src/lib.rs
+++ b/pallets/balances-adapter/src/lib.rs
@@ -31,6 +31,12 @@
 	}
 }
 
+impl<T: Config> Default for NativeFungibleHandle<T> {
+	fn default() -> Self {
+		Self::new()
+	}
+}
+
 impl<T: Config> WithRecorder<T> for NativeFungibleHandle<T> {
 	fn recorder(&self) -> &pallet_evm_coder_substrate::SubstrateRecorder<T> {
 		&self.0
modifiedpallets/common/src/eth.rsdiffbeforeafterboth
--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -136,7 +136,7 @@
 
 	fn try_from(from: up_data_structs::Property) -> Result<Self, Self::Error> {
 		let key = evm_coder::types::String::from_utf8(from.key.into())
-			.map_err(|e| Self::Error::Revert(format!("utf8 conversion error: {}", e)))?;
+			.map_err(|e| Self::Error::Revert(format!("utf8 conversion error: {e}")))?;
 		let value = evm_coder::types::Bytes(from.value.to_vec());
 		Ok(Property { key, value })
 	}
@@ -201,10 +201,7 @@
 	pub fn new(field: CollectionLimitField, value: Option<u32>) -> Self {
 		Self {
 			field,
-			value: match value {
-				Some(value) => Some(value.into()),
-				None => None,
-			},
+			value: value.map(|value| value.into()),
 		}
 	}
 	/// Whether the field contains a value.
@@ -222,8 +219,7 @@
 			.ok_or::<Self::Error>("can't convert `None` value to boolean".into())?;
 		let value = Some(value.try_into().map_err(|error| {
 			Self::Error::Revert(format!(
-				"can't convert value to u32 \"{}\" because: \"{error}\"",
-				value
+				"can't convert value to u32 \"{value}\" because: \"{error}\""
 			))
 		})?);
 
@@ -249,10 +245,8 @@
 				limits.sponsored_data_size = value;
 			}
 			CollectionLimitField::SponsoredDataRateLimit => {
-				limits.sponsored_data_rate_limit = match value {
-					Some(value) => Some(up_data_structs::SponsoringRateLimit::Blocks(value)),
-					None => None,
-				};
+				limits.sponsored_data_rate_limit =
+					value.map(up_data_structs::SponsoringRateLimit::Blocks);
 			}
 			CollectionLimitField::TokenLimit => {
 				limits.token_limit = value;
@@ -454,9 +448,9 @@
 	}
 }
 
-impl Into<up_data_structs::AccessMode> for AccessMode {
-	fn into(self) -> up_data_structs::AccessMode {
-		match self {
+impl From<AccessMode> for up_data_structs::AccessMode {
+	fn from(value: AccessMode) -> Self {
+		match value {
 			AccessMode::Normal => up_data_structs::AccessMode::Normal,
 			AccessMode::AllowList => up_data_structs::AccessMode::AllowList,
 		}
modifiedpallets/evm-coder-substrate/src/lib.rsdiffbeforeafterboth
--- a/pallets/evm-coder-substrate/src/lib.rs
+++ b/pallets/evm-coder-substrate/src/lib.rs
@@ -260,9 +260,9 @@
 			message: Some(msg), ..
 		}) => ExError::Revert(msg.into()),
 		DispatchError::Module(ModuleError { index, error, .. }) => {
-			ExError::Revert(format!("error {:?} in pallet {}", error, index))
+			ExError::Revert(format!("error {error:?} in pallet {index}"))
 		}
-		e => ExError::Revert(format!("substrate error: {:?}", e)),
+		e => ExError::Revert(format!("substrate error: {e:?}")),
 	}
 }
 
modifiedpallets/evm-contract-helpers/src/eth.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/eth.rs
+++ b/pallets/evm-contract-helpers/src/eth.rs
@@ -184,10 +184,9 @@
 	/// @param contractAddress The contract for which a sponsor is requested.
 	/// @return Tuble with sponsor address and his substrate mirror. If there is no confirmed sponsor error "Contract has no sponsor" throw.
 	fn sponsor(&self, contract_address: Address) -> Result<Option<eth::CrossAddress>> {
-		Ok(match Pallet::<T>::get_sponsor(contract_address) {
-			Some(ref value) => Some(eth::CrossAddress::from_sub_cross_account::<T>(value)),
-			None => None,
-		})
+		Ok(Pallet::<T>::get_sponsor(contract_address)
+			.as_ref()
+			.map(eth::CrossAddress::from_sub_cross_account::<T>))
 	}
 
 	/// Check tat contract has confirmed sponsor.
@@ -275,7 +274,7 @@
 		self.recorder().consume_sstore()?;
 
 		<Pallet<T>>::ensure_owner(contract_address, caller).map_err(dispatch_to_evm::<T>)?;
-		<Pallet<T>>::set_sponsoring_fee_limit(contract_address, fee_limit.into())
+		<Pallet<T>>::set_sponsoring_fee_limit(contract_address, fee_limit)
 			.map_err(dispatch_to_evm::<T>)?;
 		Ok(())
 	}
modifiedpallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -376,7 +376,7 @@
 			<SponsoringMode<T>>::get(contract)
 				.or_else(|| {
 					#[allow(deprecated)]
-					<SelfSponsoring<T>>::get(contract).then(|| SponsoringModeT::Allowlisted)
+					<SelfSponsoring<T>>::get(contract).then_some(SponsoringModeT::Allowlisted)
 				})
 				.unwrap_or_default()
 		}
@@ -410,7 +410,7 @@
 
 		/// Is user added to allowlist, or he is owner of specified contract
 		pub fn allowed(contract: H160, user: H160) -> bool {
-			<Allowlist<T>>::get(&contract, &user) || <Owner<T>>::get(&contract) == user
+			<Allowlist<T>>::get(contract, user) || <Owner<T>>::get(contract) == user
 		}
 
 		/// Toggle contract allowlist access
@@ -425,7 +425,7 @@
 
 		/// Throw error if user is not allowed to reconfigure target contract
 		pub fn ensure_owner(contract: H160, user: H160) -> DispatchResult {
-			ensure!(<Owner<T>>::get(&contract) == user, Error::<T>::NoPermission);
+			ensure!(<Owner<T>>::get(contract) == user, Error::<T>::NoPermission);
 			Ok(())
 		}
 	}
modifiedpallets/evm-migration/src/lib.rsdiffbeforeafterboth
--- a/pallets/evm-migration/src/lib.rs
+++ b/pallets/evm-migration/src/lib.rs
@@ -78,7 +78,7 @@
 		pub fn begin(origin: OriginFor<T>, address: H160) -> DispatchResult {
 			ensure_root(origin)?;
 			ensure!(
-				<PalletEvm<T>>::is_account_empty(&address) && !<MigrationPending<T>>::get(&address),
+				<PalletEvm<T>>::is_account_empty(&address) && !<MigrationPending<T>>::get(address),
 				<Error<T>>::AccountNotEmpty,
 			);
 
@@ -97,12 +97,12 @@
 		) -> DispatchResult {
 			ensure_root(origin)?;
 			ensure!(
-				<MigrationPending<T>>::get(&address),
+				<MigrationPending<T>>::get(address),
 				<Error<T>>::AccountIsNotMigrating,
 			);
 
 			for (k, v) in data {
-				<pallet_evm::AccountStorages<T>>::insert(&address, k, v);
+				<pallet_evm::AccountStorages<T>>::insert(address, k, v);
 			}
 			Ok(())
 		}
@@ -115,11 +115,11 @@
 		pub fn finish(origin: OriginFor<T>, address: H160, code: Vec<u8>) -> DispatchResult {
 			ensure_root(origin)?;
 			ensure!(
-				<MigrationPending<T>>::get(&address),
+				<MigrationPending<T>>::get(address),
 				<Error<T>>::AccountIsNotMigrating,
 			);
 
-			<pallet_evm::AccountCodes<T>>::insert(&address, code);
+			<pallet_evm::AccountCodes<T>>::insert(address, code);
 			<MigrationPending<T>>::remove(address);
 			Ok(())
 		}
@@ -166,7 +166,7 @@
 	pub struct OnMethodCall<T>(PhantomData<T>);
 	impl<T: Config> pallet_evm::OnMethodCall<T> for OnMethodCall<T> {
 		fn is_reserved(contract: &H160) -> bool {
-			<MigrationPending<T>>::get(&contract)
+			<MigrationPending<T>>::get(contract)
 		}
 
 		fn is_used(_contract: &H160) -> bool {
modifiedpallets/foreign-assets/src/impl_fungibles.rsdiffbeforeafterboth
--- a/pallets/foreign-assets/src/impl_fungibles.rs
+++ b/pallets/foreign-assets/src/impl_fungibles.rs
@@ -333,7 +333,7 @@
 					&Value::new(0),
 				)?;
 
-				Ok(amount.into())
+				Ok(amount)
 			}
 		}
 	}
modifiedpallets/foreign-assets/src/lib.rsdiffbeforeafterboth
--- a/pallets/foreign-assets/src/lib.rs
+++ b/pallets/foreign-assets/src/lib.rs
@@ -161,7 +161,7 @@
 
 	fn get_currency_id(multi_location: MultiLocation) -> Option<CurrencyId> {
 		log::trace!(target: "fassets::get_currency_id", "call");
-		Pallet::<T>::location_to_currency_ids(multi_location).map(|id| AssetIds::ForeignAssetId(id))
+		Pallet::<T>::location_to_currency_ids(multi_location).map(AssetIds::ForeignAssetId)
 	}
 }
 
@@ -378,7 +378,7 @@
 				foreign_asset_id,
 				|maybe_location| -> DispatchResult {
 					ensure!(maybe_location.is_none(), Error::<T>::MultiLocationExisted);
-					*maybe_location = Some(location.clone());
+					*maybe_location = Some(*location);
 
 					AssetMetadatas::<T>::try_mutate(
 						AssetIds::ForeignAssetId(foreign_asset_id),
@@ -422,7 +422,7 @@
 
 						// modify location
 						if location != old_multi_locations {
-							LocationToCurrencyIds::<T>::remove(old_multi_locations.clone());
+							LocationToCurrencyIds::<T>::remove(*old_multi_locations);
 							LocationToCurrencyIds::<T>::try_mutate(
 								location,
 								|maybe_currency_ids| -> DispatchResult {
@@ -437,7 +437,7 @@
 							)?;
 						}
 						*maybe_asset_metadatas = Some(metadata.clone());
-						*old_multi_locations = location.clone();
+						*old_multi_locations = *location;
 						Ok(())
 					},
 				)
modifiedpallets/identity/src/types.rsdiffbeforeafterboth
--- a/pallets/identity/src/types.rs
+++ b/pallets/identity/src/types.rs
@@ -104,7 +104,7 @@
 			Data::Raw(ref x) => {
 				let l = x.len().min(32);
 				let mut r = vec![l as u8 + 1; l + 1];
-				r[1..].copy_from_slice(&x[..l as usize]);
+				r[1..].copy_from_slice(&x[..l]);
 				r
 			}
 			Data::BlakeTwo256(ref h) => once(34u8).chain(h.iter().cloned()).collect(),
@@ -287,7 +287,7 @@
 	fn decode<I: codec::Input>(input: &mut I) -> sp_std::result::Result<Self, codec::Error> {
 		let field = u64::decode(input)?;
 		Ok(Self(
-			<BitFlags<IdentityField>>::from_bits(field as u64).map_err(|_| "invalid value")?,
+			<BitFlags<IdentityField>>::from_bits(field).map_err(|_| "invalid value")?,
 		))
 	}
 }
modifiedpallets/nonfungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -20,6 +20,8 @@
 //! Method implementations are mostly doing parameter conversion and calling Nonfungible Pallet methods.
 
 extern crate alloc;
+
+use alloc::string::ToString;
 use core::{
 	char::{REPLACEMENT_CHARACTER, decode_utf16},
 	convert::TryInto,
@@ -356,8 +358,7 @@
 				.transpose()
 				.map_err(|e| {
 					Error::Revert(alloc::format!(
-						"Can not convert value \"baseURI\" to string with error \"{}\"",
-						e
+						"Can not convert value \"baseURI\" to string with error \"{e}\""
 					))
 				})?;
 
@@ -675,7 +676,7 @@
 					.try_into()
 					.map_err(|_| "token uri is too long")?,
 			})
-			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;
+			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {e:?}")))?;
 
 		<Pallet<T>>::create_item(
 			self,
@@ -717,7 +718,7 @@
 		.map(Clone::clone)
 		.ok_or_else(|| {
 			let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();
-			Error::Revert(alloc::format!("No permission for key {}", key))
+			Error::Revert(alloc::format!("No permission for key {key}"))
 		})?;
 	Ok(a)
 }
@@ -752,14 +753,14 @@
 	/// @param tokenId Id for the token.
 	#[solidity(hide)]
 	fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {
-		Self::owner_of_cross(&self, token_id)
+		Self::owner_of_cross(self, token_id)
 	}
 
 	/// Returns the owner (in cross format) of the token.
 	///
 	/// @param tokenId Id for the token.
 	fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {
-		Self::token_owner(&self, token_id.try_into()?)
+		Self::token_owner(self, token_id.try_into()?)
 			.map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))
 			.map_err(|_| Error::Revert("token not found".into()))
 	}
@@ -789,7 +790,7 @@
 			.collect::<Result<Vec<_>>>()?;
 
 		<Self as CommonCollectionOperations<T>>::token_properties(
-			&self,
+			self,
 			token_id.try_into()?,
 			if keys.is_empty() { None } else { Some(keys) },
 		)
@@ -1021,7 +1022,7 @@
 						.try_into()
 						.map_err(|_| "token uri is too long")?,
 				})
-				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;
+				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {e:?}")))?;
 
 			data.push(CreateItemData::<T> {
 				properties,
@@ -1056,7 +1057,7 @@
 			.map(eth::Property::try_into)
 			.collect::<Result<Vec<_>>>()?
 			.try_into()
-			.map_err(|_| Error::Revert(alloc::format!("too many properties")))?;
+			.map_err(|_| Error::Revert("too many properties".to_string()))?;
 
 		let caller = T::CrossAccountId::from_eth(caller);
 
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
before · pallets/nonfungible/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet18//!19//! The Nonfungible pallet provides functionality for handling nonfungible collections and tokens.20//!21//! - [`Config`]22//! - [`NonfungibleHandle`]23//! - [`Pallet`]24//! - [`CommonWeights`](common::CommonWeights)25//!26//! ## Overview27//!28//! The Nonfungible pallet provides functions for:29//!30//! - NFT collection creation and removal31//! - Minting and burning of NFT tokens32//! - Retrieving account balances33//! - Transfering NFT tokens34//! - Setting and checking allowance for NFT tokens35//! - Setting properties and permissions for NFT collections and tokens36//! - Nesting and unnesting tokens37//!38//! ### Terminology39//!40//! - **NFT token:** Non fungible token.41//!42//! - **NFT Collection:** A collection of NFT tokens. All NFT tokens are part of a collection.43//!   Each collection can define it's own properties, properties for it's tokens and set of permissions.44//!45//! - **Balance:** Number of NFT tokens owned by an account46//!47//! - **Allowance:** NFT tokens owned by one account that another account is allowed to make operations on48//!49//! - **Burning:** The process of “deleting” a token from a collection and from50//!   an account balance of the owner.51//!52//! - **Nesting:** Setting up parent-child relationship between tokens. Nested tokens are inhereting53//!   owner from their parent. There could be multiple levels of nesting. Token couldn't be nested in54//!   it's child token i.e. parent-child relationship graph shouldn't have cycles.55//!56//! - **Properties:** Key-Values pairs. Token properties are attached to a token. Collection properties are57//!   attached to a collection. Set of permissions could be defined for each property.58//!59//! ### Implementations60//!61//! The Nonfungible pallet provides implementations for the following traits. If these traits provide62//! the functionality that you need, then you can avoid coupling with the Nonfungible pallet.63//!64//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight65//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing66//!   with collections67//!68//! ## Interface69//!70//! ### Dispatchable Functions71//!72//! - `init_collection` - Create NFT collection. NFT collection can be configured to allow or deny access for73//!   some accounts.74//! - `destroy_collection` - Destroy exising NFT collection. There should be no tokens in the collection.75//! - `burn` - Burn NFT token owned by account.76//! - `transfer` - Transfer NFT token. Transfers should be enabled for NFT collection.77//!   Nests the NFT token if it is sent to another token.78//! - `create_item` - Mint NFT token in collection. Sender should have permission to mint tokens.79//! - `set_allowance` - Set allowance for another account.80//! - `set_token_property` - Set token property value.81//! - `delete_token_property` - Remove property from the token.82//! - `set_collection_properties` - Set collection properties.83//! - `delete_collection_properties` - Remove properties from the collection.84//! - `set_property_permission` - Set collection property permission.85//! - `set_token_property_permissions` - Set token property permissions.86//!87//! ## Assumptions88//!89//! * To perform operations on tokens sender should be in collection's allow list if collection access mode is `AllowList`.9091#![cfg_attr(not(feature = "std"), no_std)]9293use erc::ERC721Events;94use evm_coder::ToLog;95use frame_support::{96	BoundedVec, ensure, fail, transactional,97	storage::with_transaction,98	pallet_prelude::DispatchResultWithPostInfo,99	pallet_prelude::Weight,100	dispatch::{PostDispatchInfo, Pays},101};102use up_data_structs::{103	AccessMode, CollectionId, CollectionFlags, CustomDataLimit, TokenId, CreateCollectionData,104	CreateNftExData, mapping::TokenAddressMapping, budget::Budget, Property, PropertyKey,105	PropertyValue, PropertyKeyPermission, PropertyScope, TrySetProperty, TokenChild,106	AuxPropertyValue, PropertiesPermissionMap, TokenProperties as TokenPropertiesT,107};108use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};109use pallet_common::{110	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,111	eth::collection_id_to_address, SelfWeightOf as PalletCommonWeightOf,112	weights::WeightInfo as CommonWeightInfo, helpers::add_weight_to_post_info,113};114use pallet_structure::{Pallet as PalletStructure, Error as StructureError};115use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};116use sp_core::{Get, H160};117use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};118use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};119use core::ops::Deref;120use codec::{Encode, Decode, MaxEncodedLen};121use scale_info::TypeInfo;122123pub use pallet::*;124use weights::WeightInfo;125#[cfg(feature = "runtime-benchmarks")]126pub mod benchmarking;127pub mod common;128pub mod erc;129pub mod weights;130131pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::Config>::CrossAccountId>;132pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;133134/// Token data, stored independently from other data used to describe it135/// for the convenience of database access. Notably contains the owner account address.136#[struct_versioning::versioned(version = 2, upper)]137#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]138pub struct ItemData<CrossAccountId> {139	#[version(..2)]140	pub const_data: BoundedVec<u8, CustomDataLimit>,141142	#[version(..2)]143	pub variable_data: BoundedVec<u8, CustomDataLimit>,144145	pub owner: CrossAccountId,146}147148#[frame_support::pallet]149pub mod pallet {150	use super::*;151	use frame_support::{152		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,153	};154	use up_data_structs::{CollectionId, TokenId};155	use super::weights::WeightInfo;156157	#[pallet::error]158	pub enum Error<T> {159		/// Not Nonfungible item data used to mint in Nonfungible collection.160		NotNonfungibleDataUsedToMintFungibleCollectionToken,161		/// Used amount > 1 with NFT162		NonfungibleItemsHaveNoAmount,163		/// Unable to burn NFT with children164		CantBurnNftWithChildren,165	}166167	#[pallet::config]168	pub trait Config:169		frame_system::Config170		+ pallet_common::Config171		+ pallet_structure::Config172		+ pallet_evm::Config173	{174		type WeightInfo: WeightInfo;175	}176177	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);178179	#[pallet::pallet]180	#[pallet::storage_version(STORAGE_VERSION)]181	pub struct Pallet<T>(_);182183	/// Total amount of minted tokens in a collection.184	#[pallet::storage]185	pub type TokensMinted<T: Config> =186		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;187188	/// Amount of burnt tokens in a collection.189	#[pallet::storage]190	pub type TokensBurnt<T: Config> =191		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;192193	/// Token data, used to partially describe a token.194	#[pallet::storage]195	pub type TokenData<T: Config> = StorageNMap<196		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),197		Value = ItemData<T::CrossAccountId>,198		QueryKind = OptionQuery,199	>;200201	/// Map of key-value pairs, describing the metadata of a token.202	#[pallet::storage]203	#[pallet::getter(fn token_properties)]204	pub type TokenProperties<T: Config> = StorageNMap<205		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),206		Value = TokenPropertiesT,207		QueryKind = ValueQuery,208	>;209210	/// Custom data of a token that is serialized to bytes,211	/// primarily reserved for on-chain operations,212	/// normally obscured from the external users.213	///214	/// Auxiliary properties are slightly different from215	/// usual [`TokenProperties`] due to an unlimited number216	/// and separately stored and written-to key-value pairs.217	///218	/// Currently unused.219	#[pallet::storage]220	#[pallet::getter(fn token_aux_property)]221	pub type TokenAuxProperties<T: Config> = StorageNMap<222		Key = (223			Key<Twox64Concat, CollectionId>,224			Key<Twox64Concat, TokenId>,225			Key<Twox64Concat, PropertyScope>,226			Key<Twox64Concat, PropertyKey>,227		),228		Value = AuxPropertyValue,229		QueryKind = OptionQuery,230	>;231232	/// Used to enumerate tokens owned by account.233	#[pallet::storage]234	pub type Owned<T: Config> = StorageNMap<235		Key = (236			Key<Twox64Concat, CollectionId>,237			Key<Blake2_128Concat, T::CrossAccountId>,238			Key<Twox64Concat, TokenId>,239		),240		Value = bool,241		QueryKind = ValueQuery,242	>;243244	/// Used to enumerate token's children.245	#[pallet::storage]246	#[pallet::getter(fn token_children)]247	pub type TokenChildren<T: Config> = StorageNMap<248		Key = (249			Key<Twox64Concat, CollectionId>,250			Key<Twox64Concat, TokenId>,251			Key<Twox64Concat, (CollectionId, TokenId)>,252		),253		Value = bool,254		QueryKind = ValueQuery,255	>;256257	/// Amount of tokens owned by an account in a collection.258	#[pallet::storage]259	pub type AccountBalance<T: Config> = StorageNMap<260		Key = (261			Key<Twox64Concat, CollectionId>,262			Key<Blake2_128Concat, T::CrossAccountId>,263		),264		Value = u32,265		QueryKind = ValueQuery,266	>;267268	/// Allowance set by a token owner for another user to perform one of certain transactions on a token.269	#[pallet::storage]270	pub type Allowance<T: Config> = StorageNMap<271		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),272		Value = T::CrossAccountId,273		QueryKind = OptionQuery,274	>;275276	/// Operator set by a wallet owner that could perform certain transactions on all tokens in the wallet.277	#[pallet::storage]278	pub type CollectionAllowance<T: Config> = StorageNMap<279		Key = (280			Key<Twox64Concat, CollectionId>,281			Key<Blake2_128Concat, T::CrossAccountId>,282			Key<Blake2_128Concat, T::CrossAccountId>,283		),284		Value = bool,285		QueryKind = ValueQuery,286	>;287288	#[pallet::genesis_config]289	pub struct GenesisConfig<T>(PhantomData<T>);290291	#[cfg(feature = "std")]292	impl<T: Config> Default for GenesisConfig<T> {293		fn default() -> Self {294			Self(Default::default())295		}296	}297298	#[pallet::genesis_build]299	impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {300		fn build(&self) {301			StorageVersion::new(1).put::<Pallet<T>>();302		}303	}304}305306pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);307impl<T: Config> NonfungibleHandle<T> {308	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {309		Self(inner)310	}311	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {312		self.0313	}314	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {315		&mut self.0316	}317}318319impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {320	fn recorder(&self) -> &SubstrateRecorder<T> {321		self.0.recorder()322	}323	fn into_recorder(self) -> SubstrateRecorder<T> {324		self.0.into_recorder()325	}326}327impl<T: Config> Deref for NonfungibleHandle<T> {328	type Target = pallet_common::CollectionHandle<T>;329330	fn deref(&self) -> &Self::Target {331		&self.0332	}333}334335impl<T: Config> Pallet<T> {336	/// Get number of NFT tokens in collection.337	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {338		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)339	}340341	/// Check that NFT token exists.342	///343	/// - `token`: Token ID.344	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {345		<TokenData<T>>::contains_key((collection.id, token))346	}347348	/// Set the token property with the scope.349	///350	/// - `property`: Contains key-value pair.351	pub fn set_scoped_token_property(352		collection_id: CollectionId,353		token_id: TokenId,354		scope: PropertyScope,355		property: Property,356	) -> DispatchResult {357		TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {358			properties.try_scoped_set(scope, property.key, property.value)359		})360		.map_err(<CommonError<T>>::from)?;361362		Ok(())363	}364365	/// Batch operation to set multiple properties with the same scope.366	pub fn set_scoped_token_properties(367		collection_id: CollectionId,368		token_id: TokenId,369		scope: PropertyScope,370		properties: impl Iterator<Item = Property>,371	) -> DispatchResult {372		TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {373			stored_properties.try_scoped_set_from_iter(scope, properties)374		})375		.map_err(<CommonError<T>>::from)?;376377		Ok(())378	}379380	/// Add or edit auxiliary data for the property.381	///382	/// - `f`: function that adds or edits auxiliary data.383	pub fn try_mutate_token_aux_property<R, E>(384		collection_id: CollectionId,385		token_id: TokenId,386		scope: PropertyScope,387		key: PropertyKey,388		f: impl FnOnce(&mut Option<AuxPropertyValue>) -> Result<R, E>,389	) -> Result<R, E> {390		<TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)391	}392393	/// Remove auxiliary data for the property.394	pub fn remove_token_aux_property(395		collection_id: CollectionId,396		token_id: TokenId,397		scope: PropertyScope,398		key: PropertyKey,399	) {400		<TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));401	}402403	/// Get all auxiliary data in a given scope.404	///405	/// Returns iterator over Property Key - Data pairs.406	pub fn iterate_token_aux_properties(407		collection_id: CollectionId,408		token_id: TokenId,409		scope: PropertyScope,410	) -> impl Iterator<Item = (PropertyKey, AuxPropertyValue)> {411		<TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))412	}413414	/// Get ID of the last minted token415	pub fn current_token_id(collection_id: CollectionId) -> TokenId {416		TokenId(<TokensMinted<T>>::get(collection_id))417	}418}419420// unchecked calls skips any permission checks421impl<T: Config> Pallet<T> {422	/// Create NFT collection423	///424	/// `init_collection` will take non-refundable deposit for collection creation.425	///426	/// - `data`: Contains settings for collection limits and permissions.427	pub fn init_collection(428		owner: T::CrossAccountId,429		payer: T::CrossAccountId,430		data: CreateCollectionData<T::AccountId>,431		flags: CollectionFlags,432	) -> Result<CollectionId, DispatchError> {433		<PalletCommon<T>>::init_collection(owner, payer, data, flags)434	}435436	/// Destroy NFT collection437	///438	/// `destroy_collection` will throw error if collection contains any tokens.439	/// Only owner can destroy collection.440	pub fn destroy_collection(441		collection: NonfungibleHandle<T>,442		sender: &T::CrossAccountId,443	) -> DispatchResult {444		let id = collection.id;445446		if Self::collection_has_tokens(id) {447			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());448		}449450		// =========451452		PalletCommon::destroy_collection(collection.0, sender)?;453454		let _ = <TokenData<T>>::clear_prefix((id,), u32::MAX, None);455		let _ = <TokenChildren<T>>::clear_prefix((id,), u32::MAX, None);456		let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);457		<TokensMinted<T>>::remove(id);458		<TokensBurnt<T>>::remove(id);459		let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);460		let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);461		let _ = <CollectionAllowance<T>>::clear_prefix((id,), u32::MAX, None);462		Ok(())463	}464465	/// Burn NFT token466	///467	/// `burn` removes `token` from the `collection`, from it's owner and from the parent token468	/// if the token is nested.469	/// Only the owner can `burn` the token. The `token` shouldn't have any nested tokens.470	/// Also removes all corresponding properties and auxiliary properties.471	///472	/// - `token`: Token that should be burned473	/// - `collection`: Collection that contains the token474	pub fn burn(475		collection: &NonfungibleHandle<T>,476		sender: &T::CrossAccountId,477		token: TokenId,478	) -> DispatchResult {479		let token_data =480			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;481		ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);482483		if collection.permissions.access() == AccessMode::AllowList {484			collection.check_allowlist(sender)?;485		}486487		if Self::token_has_children(collection.id, token) {488			return Err(<Error<T>>::CantBurnNftWithChildren.into());489		}490491		let burnt = <TokensBurnt<T>>::get(collection.id)492			.checked_add(1)493			.ok_or(ArithmeticError::Overflow)?;494495		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))496			.checked_sub(1)497			.ok_or(ArithmeticError::Overflow)?;498499		// =========500501		if balance == 0 {502			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));503		} else {504			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);505		}506507		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);508509		<Owned<T>>::remove((collection.id, &token_data.owner, token));510		<TokensBurnt<T>>::insert(collection.id, burnt);511		<TokenData<T>>::remove((collection.id, token));512		<TokenProperties<T>>::remove((collection.id, token));513		let _ = <TokenAuxProperties<T>>::clear_prefix((collection.id, token), u32::MAX, None);514		let old_spender = <Allowance<T>>::take((collection.id, token));515516		if let Some(old_spender) = old_spender {517			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(518				collection.id,519				token,520				token_data.owner.clone(),521				old_spender,522				0,523			));524		}525526		<PalletEvm<T>>::deposit_log(527			ERC721Events::Transfer {528				from: *token_data.owner.as_eth(),529				to: H160::default(),530				token_id: token.into(),531			}532			.to_log(collection_id_to_address(collection.id)),533		);534		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(535			collection.id,536			token,537			token_data.owner,538			1,539		));540		Ok(())541	}542543	/// Same as [`burn`] but burns all the tokens that are nested in the token first544	///545	/// - `self_budget`: Limit for searching children in depth.546	/// - `breadth_budget`: Limit of breadth of searching children.547	///548	/// [`burn`]: struct.Pallet.html#method.burn549	#[transactional]550	pub fn burn_recursively(551		collection: &NonfungibleHandle<T>,552		sender: &T::CrossAccountId,553		token: TokenId,554		self_budget: &dyn Budget,555		breadth_budget: &dyn Budget,556	) -> DispatchResultWithPostInfo {557		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);558559		let current_token_account =560			T::CrossTokenAddressMapping::token_to_address(collection.id, token);561562		let mut weight = Weight::zero();563564		// This method is transactional, if user in fact doesn't have permissions to remove token -565		// tokens removed here will be restored after rejected transaction566		for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {567			ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);568			let PostDispatchInfo { actual_weight, .. } =569				<PalletStructure<T>>::burn_item_recursively(570					current_token_account.clone(),571					collection,572					token,573					self_budget,574					breadth_budget,575				)?;576			if let Some(actual_weight) = actual_weight {577				weight = weight.saturating_add(actual_weight);578			}579		}580581		Self::burn(collection, sender, token)?;582		DispatchResultWithPostInfo::Ok(PostDispatchInfo {583			actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),584			pays_fee: Pays::Yes,585		})586	}587588	/// A batch operation to add, edit or remove properties for a token.589	///590	/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.591	/// - `is_token_create`: Indicates that method is called during token initialization.592	///   Allows to bypass ownership check.593	///594	/// All affected properties should have `mutable` permission595	/// to be **deleted** or to be **set more than once**,596	/// and the sender should have permission to edit those properties.597	///598	/// This function fires an event for each property change.599	/// In case of an error, all the changes (including the events) will be reverted600	/// since the function is transactional.601	#[transactional]602	fn modify_token_properties(603		collection: &NonfungibleHandle<T>,604		sender: &T::CrossAccountId,605		token_id: TokenId,606		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,607		is_token_create: bool,608		nesting_budget: &dyn Budget,609	) -> DispatchResult {610		let is_token_owner = || {611			let is_owned = <PalletStructure<T>>::check_indirectly_owned(612				sender.clone(),613				collection.id,614				token_id,615				None,616				nesting_budget,617			)?;618619			Ok(is_owned)620		};621622		let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));623624		<PalletCommon<T>>::modify_token_properties(625			collection,626			sender,627			token_id,628			properties_updates,629			is_token_create,630			stored_properties,631			is_token_owner,632			|properties| <TokenProperties<T>>::set((collection.id, token_id), properties),633			erc::ERC721TokenEvent::TokenChanged {634				token_id: token_id.into(),635			}636			.to_log(T::ContractAddress::get()),637		)638	}639640	/// Batch operation to add or edit properties for the token641	///642	/// Same as [`modify_token_properties`] but doesn't allow to remove properties643	///644	/// [`modify_token_properties`]: struct.Pallet.html#method.modify_token_properties645	pub fn set_token_properties(646		collection: &NonfungibleHandle<T>,647		sender: &T::CrossAccountId,648		token_id: TokenId,649		properties: impl Iterator<Item = Property>,650		is_token_create: bool,651		nesting_budget: &dyn Budget,652	) -> DispatchResult {653		Self::modify_token_properties(654			collection,655			sender,656			token_id,657			properties.map(|p| (p.key, Some(p.value))),658			is_token_create,659			nesting_budget,660		)661	}662663	/// Add or edit single property for the token664	///665	/// Calls [`set_token_properties`] internally666	///667	/// [`set_token_properties`]: struct.Pallet.html#method.set_token_properties668	pub fn set_token_property(669		collection: &NonfungibleHandle<T>,670		sender: &T::CrossAccountId,671		token_id: TokenId,672		property: Property,673		nesting_budget: &dyn Budget,674	) -> DispatchResult {675		let is_token_create = false;676677		Self::set_token_properties(678			collection,679			sender,680			token_id,681			[property].into_iter(),682			is_token_create,683			nesting_budget,684		)685	}686687	/// Batch operation to remove properties from the token688	///689	/// Same as [`modify_token_properties`] but doesn't allow to add or edit properties690	///691	/// [`modify_token_properties`]: struct.Pallet.html#method.modify_token_properties692	pub fn delete_token_properties(693		collection: &NonfungibleHandle<T>,694		sender: &T::CrossAccountId,695		token_id: TokenId,696		property_keys: impl Iterator<Item = PropertyKey>,697		nesting_budget: &dyn Budget,698	) -> DispatchResult {699		let is_token_create = false;700701		Self::modify_token_properties(702			collection,703			sender,704			token_id,705			property_keys.into_iter().map(|key| (key, None)),706			is_token_create,707			nesting_budget,708		)709	}710711	/// Remove single property from the token712	///713	/// Calls [`delete_token_properties`] internally714	///715	/// [`delete_token_properties`]: struct.Pallet.html#method.delete_token_properties716	pub fn delete_token_property(717		collection: &NonfungibleHandle<T>,718		sender: &T::CrossAccountId,719		token_id: TokenId,720		property_key: PropertyKey,721		nesting_budget: &dyn Budget,722	) -> DispatchResult {723		Self::delete_token_properties(724			collection,725			sender,726			token_id,727			[property_key].into_iter(),728			nesting_budget,729		)730	}731732	/// Add or edit properties for the collection733	pub fn set_collection_properties(734		collection: &NonfungibleHandle<T>,735		sender: &T::CrossAccountId,736		properties: Vec<Property>,737	) -> DispatchResult {738		<PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())739	}740741	/// Remove properties from the collection742	pub fn delete_collection_properties(743		collection: &CollectionHandle<T>,744		sender: &T::CrossAccountId,745		property_keys: Vec<PropertyKey>,746	) -> DispatchResult {747		<PalletCommon<T>>::delete_collection_properties(748			collection,749			sender,750			property_keys.into_iter(),751		)752	}753754	/// Set property permissions for the token.755	///756	/// Sender should be the owner or admin of token's collection.757	pub fn set_token_property_permissions(758		collection: &CollectionHandle<T>,759		sender: &T::CrossAccountId,760		property_permissions: Vec<PropertyKeyPermission>,761	) -> DispatchResult {762		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)763	}764765	/// Set property permissions for the token with scope.766	///767	/// Sender should be the owner or admin of token's collection.768	pub fn set_scoped_token_property_permissions(769		collection: &CollectionHandle<T>,770		sender: &T::CrossAccountId,771		scope: PropertyScope,772		property_permissions: Vec<PropertyKeyPermission>,773	) -> DispatchResult {774		<PalletCommon<T>>::set_scoped_token_property_permissions(775			collection,776			sender,777			scope,778			property_permissions,779		)780	}781782	pub fn token_property_permission(collection_id: CollectionId) -> PropertiesPermissionMap {783		<PalletCommon<T>>::property_permissions(collection_id)784	}785786	pub fn check_token_immediate_ownership(787		collection: &NonfungibleHandle<T>,788		token: TokenId,789		possible_owner: &T::CrossAccountId,790	) -> DispatchResult {791		let token_data =792			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;793		ensure!(794			&token_data.owner == possible_owner,795			<CommonError<T>>::NoPermission796		);797		Ok(())798	}799800	/// Transfer NFT token from one account to another.801	///802	/// `from` account stops being the owner and `to` account becomes the owner of the token.803	/// If `to` is token than `to` becomes owner of the token and the token become nested.804	/// Unnests token from previous parent if it was nested before.805	/// Removes allowance for the token if there was any.806	/// Throws if transfers aren't allowed for collection or if receiver reached token ownership limit.807	///808	/// - `nesting_budget`: Limit for token nesting depth809	pub fn transfer(810		collection: &NonfungibleHandle<T>,811		from: &T::CrossAccountId,812		to: &T::CrossAccountId,813		token: TokenId,814		nesting_budget: &dyn Budget,815	) -> DispatchResultWithPostInfo {816		ensure!(817			collection.limits.transfers_enabled(),818			<CommonError<T>>::TransferNotAllowed819		);820821		let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();822		let token_data =823			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;824		ensure!(&token_data.owner == from, <CommonError<T>>::NoPermission);825826		if collection.permissions.access() == AccessMode::AllowList {827			collection.check_allowlist(from)?;828			collection.check_allowlist(to)?;829			actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;830		}831		<PalletCommon<T>>::ensure_correct_receiver(to)?;832833		let balance_from = <AccountBalance<T>>::get((collection.id, from))834			.checked_sub(1)835			.ok_or(<CommonError<T>>::TokenValueTooLow)?;836		let balance_to = if from != to {837			let balance_to = <AccountBalance<T>>::get((collection.id, to))838				.checked_add(1)839				.ok_or(ArithmeticError::Overflow)?;840841			ensure!(842				balance_to < collection.limits.account_token_ownership_limit(),843				<CommonError<T>>::AccountTokenLimitExceeded,844			);845846			Some(balance_to)847		} else {848			None849		};850851		<PalletStructure<T>>::nest_if_sent_to_token(852			from.clone(),853			to,854			collection.id,855			token,856			nesting_budget,857		)?;858859		// =========860861		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);862863		<TokenData<T>>::insert(864			(collection.id, token),865			ItemData {866				owner: to.clone(),867				..token_data868			},869		);870871		if let Some(balance_to) = balance_to {872			// from != to873			if balance_from == 0 {874				<AccountBalance<T>>::remove((collection.id, from));875			} else {876				<AccountBalance<T>>::insert((collection.id, from), balance_from);877			}878			<AccountBalance<T>>::insert((collection.id, to), balance_to);879			<Owned<T>>::remove((collection.id, from, token));880			<Owned<T>>::insert((collection.id, to, token), true);881		}882		Self::set_allowance_unchecked(collection, from, token, None, true);883884		<PalletEvm<T>>::deposit_log(885			ERC721Events::Transfer {886				from: *from.as_eth(),887				to: *to.as_eth(),888				token_id: token.into(),889			}890			.to_log(collection_id_to_address(collection.id)),891		);892		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(893			collection.id,894			token,895			from.clone(),896			to.clone(),897			1,898		));899900		Ok(PostDispatchInfo {901			actual_weight: Some(actual_weight),902			pays_fee: Pays::Yes,903		})904	}905906	/// Batch operation to mint multiple NFT tokens.907	///908	/// The sender should be the owner/admin of the collection or collection should be configured909	/// to allow public minting.910	/// Throws if amount of tokens reached it's limit for the collection or if caller reached911	/// token ownership limit.912	///913	/// - `data`: Contains list of token properties and users who will become the owners of the914	///   corresponging tokens.915	/// - `nesting_budget`: Limit for token nesting depth916	pub fn create_multiple_items(917		collection: &NonfungibleHandle<T>,918		sender: &T::CrossAccountId,919		data: Vec<CreateItemData<T>>,920		nesting_budget: &dyn Budget,921	) -> DispatchResult {922		if !collection.is_owner_or_admin(sender) {923			ensure!(924				collection.permissions.mint_mode(),925				<CommonError<T>>::PublicMintingNotAllowed926			);927			collection.check_allowlist(sender)?;928929			for item in data.iter() {930				collection.check_allowlist(&item.owner)?;931			}932		}933934		for data in data.iter() {935			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;936		}937938		let first_token = <TokensMinted<T>>::get(collection.id);939		let tokens_minted = first_token940			.checked_add(data.len() as u32)941			.ok_or(ArithmeticError::Overflow)?;942		ensure!(943			tokens_minted <= collection.limits.token_limit(),944			<CommonError<T>>::CollectionTokenLimitExceeded945		);946947		let mut balances = BTreeMap::new();948		for data in &data {949			let balance = balances950				.entry(&data.owner)951				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));952			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;953954			ensure!(955				*balance <= collection.limits.account_token_ownership_limit(),956				<CommonError<T>>::AccountTokenLimitExceeded,957			);958		}959960		for (i, data) in data.iter().enumerate() {961			let token = TokenId(first_token + i as u32 + 1);962963			<PalletStructure<T>>::check_nesting(964				sender.clone(),965				&data.owner,966				collection.id,967				token,968				nesting_budget,969			)?;970		}971972		// =========973974		with_transaction(|| {975			for (i, data) in data.iter().enumerate() {976				let token = first_token + i as u32 + 1;977978				<TokenData<T>>::insert(979					(collection.id, token),980					ItemData {981						// const_data: data.const_data.clone(),982						owner: data.owner.clone(),983					},984				);985986				<PalletStructure<T>>::nest_if_sent_to_token_unchecked(987					&data.owner,988					collection.id,989					TokenId(token),990				);991992				if let Err(e) = Self::set_token_properties(993					collection,994					sender,995					TokenId(token),996					data.properties.clone().into_iter(),997					true,998					nesting_budget,999				) {1000					return TransactionOutcome::Rollback(Err(e));1001				}1002			}1003			TransactionOutcome::Commit(Ok(()))1004		})?;10051006		<TokensMinted<T>>::insert(collection.id, tokens_minted);1007		for (account, balance) in balances {1008			<AccountBalance<T>>::insert((collection.id, account), balance);1009		}1010		for (i, data) in data.into_iter().enumerate() {1011			let token = first_token + i as u32 + 1;1012			<Owned<T>>::insert((collection.id, &data.owner, token), true);10131014			<PalletEvm<T>>::deposit_log(1015				ERC721Events::Transfer {1016					from: H160::default(),1017					to: *data.owner.as_eth(),1018					token_id: token.into(),1019				}1020				.to_log(collection_id_to_address(collection.id)),1021			);1022			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1023				collection.id,1024				TokenId(token),1025				data.owner.clone(),1026				1,1027			));1028		}1029		Ok(())1030	}10311032	pub fn set_allowance_unchecked(1033		collection: &NonfungibleHandle<T>,1034		sender: &T::CrossAccountId,1035		token: TokenId,1036		spender: Option<&T::CrossAccountId>,1037		assume_implicit_eth: bool,1038	) {1039		if let Some(spender) = spender {1040			let old_spender = <Allowance<T>>::get((collection.id, token));1041			<Allowance<T>>::insert((collection.id, token), spender);1042			// In ERC721 there is only one possible approved user of token, so we set1043			// approved user to spender1044			<PalletEvm<T>>::deposit_log(1045				ERC721Events::Approval {1046					owner: *sender.as_eth(),1047					approved: *spender.as_eth(),1048					token_id: token.into(),1049				}1050				.to_log(collection_id_to_address(collection.id)),1051			);1052			// In Unique chain, any token can have any amount of approved users, so we need to1053			// set allowance of old owner to 0, and allowance of new owner to 11054			if old_spender.as_ref() != Some(spender) {1055				if let Some(old_owner) = old_spender {1056					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(1057						collection.id,1058						token,1059						sender.clone(),1060						old_owner,1061						0,1062					));1063				}1064				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(1065					collection.id,1066					token,1067					sender.clone(),1068					spender.clone(),1069					1,1070				));1071			}1072		} else {1073			let old_spender = <Allowance<T>>::take((collection.id, token));1074			if !assume_implicit_eth {1075				// In ERC721 there is only one possible approved user of token, so we set1076				// approved user to zero address1077				<PalletEvm<T>>::deposit_log(1078					ERC721Events::Approval {1079						owner: *sender.as_eth(),1080						approved: H160::default(),1081						token_id: token.into(),1082					}1083					.to_log(collection_id_to_address(collection.id)),1084				);1085			}1086			// In Unique chain, any token can have any amount of approved users, so we need to1087			// set allowance of old owner to 01088			if let Some(old_spender) = old_spender {1089				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(1090					collection.id,1091					token,1092					sender.clone(),1093					old_spender,1094					0,1095				));1096			}1097		}1098	}10991100	pub fn get_allowance(1101		collection: &NonfungibleHandle<T>,1102		token_id: TokenId,1103	) -> Result<Option<T::CrossAccountId>, DispatchError> {1104		ensure!(1105			<TokenData<T>>::get((collection.id, token_id)).is_some(),1106			<CommonError<T>>::TokenNotFound1107		);1108		Ok(<Allowance<T>>::get((collection.id, token_id)))1109	}11101111	/// Set allowance for the spender to `transfer` or `burn` sender's token.1112	///1113	/// - `token`: Token the spender is allowed to `transfer` or `burn`.1114	pub fn set_allowance(1115		collection: &NonfungibleHandle<T>,1116		sender: &T::CrossAccountId,1117		token: TokenId,1118		spender: Option<&T::CrossAccountId>,1119	) -> DispatchResult {1120		if collection.permissions.access() == AccessMode::AllowList {1121			collection.check_allowlist(sender)?;1122			if let Some(spender) = spender {1123				collection.check_allowlist(spender)?;1124			}1125		}11261127		if let Some(spender) = spender {1128			<PalletCommon<T>>::ensure_correct_receiver(spender)?;1129		}11301131		let token_data =1132			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;1133		if &token_data.owner != sender {1134			ensure!(1135				collection.ignores_owned_amount(sender),1136				<CommonError<T>>::CantApproveMoreThanOwned1137			);1138		}11391140		// =========11411142		Self::set_allowance_unchecked(collection, sender, token, spender, false);1143		Ok(())1144	}11451146	/// Set allowance for the spender to `transfer` or `burn` sender's token from eth mirror.1147	///1148	/// - `from`: Address of sender's eth mirror.1149	/// - `to`: Adress of spender.1150	/// - `token`: Token the spender is allowed to `transfer` or `burn`.1151	pub fn set_allowance_from(1152		collection: &NonfungibleHandle<T>,1153		sender: &T::CrossAccountId,1154		from: &T::CrossAccountId,1155		token: TokenId,1156		to: Option<&T::CrossAccountId>,1157	) -> DispatchResult {1158		if collection.permissions.access() == AccessMode::AllowList {1159			collection.check_allowlist(sender)?;1160			collection.check_allowlist(from)?;1161			if let Some(to) = to {1162				collection.check_allowlist(to)?;1163			}1164		}11651166		if let Some(to) = to {1167			<PalletCommon<T>>::ensure_correct_receiver(to)?;1168		}11691170		ensure!(1171			sender.conv_eq(from),1172			<CommonError<T>>::AddressIsNotEthMirror1173		);11741175		let token_data =1176			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;1177		if token_data.owner != *from {1178			ensure!(1179				collection.limits.owner_can_transfer()1180					&& (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),1181				<CommonError<T>>::CantApproveMoreThanOwned1182			);1183		}11841185		// =========11861187		Self::set_allowance_unchecked(collection, from, token, to, false);1188		Ok(())1189	}11901191	/// Checks allowance for the spender to use the token.1192	fn check_allowed(1193		collection: &NonfungibleHandle<T>,1194		spender: &T::CrossAccountId,1195		from: &T::CrossAccountId,1196		token: TokenId,1197		nesting_budget: &dyn Budget,1198	) -> DispatchResult {1199		if spender.conv_eq(from) {1200			return Ok(());1201		}1202		if collection.permissions.access() == AccessMode::AllowList {1203			// `from`, `to` checked in [`transfer`]1204			collection.check_allowlist(spender)?;1205		}12061207		if collection.ignores_token_restrictions(spender) {1208			return Ok(());1209		}12101211		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1212			ensure!(1213				<PalletStructure<T>>::check_indirectly_owned(1214					spender.clone(),1215					source.0,1216					source.1,1217					None,1218					nesting_budget1219				)?,1220				<CommonError<T>>::ApprovedValueTooLow,1221			);1222			return Ok(());1223		}1224		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {1225			return Ok(());1226		}1227		if <CollectionAllowance<T>>::get((collection.id, from, spender)) {1228			return Ok(());1229		}12301231		Err(<CommonError<T>>::ApprovedValueTooLow.into())1232	}12331234	/// Transfer NFT token from one account to another.1235	///1236	/// Same as the [`transfer`] but spender doesn't needs to be the owner of the token.1237	/// The owner should set allowance for the spender to transfer token.1238	///1239	/// [`transfer`]: struct.Pallet.html#method.transfer1240	pub fn transfer_from(1241		collection: &NonfungibleHandle<T>,1242		spender: &T::CrossAccountId,1243		from: &T::CrossAccountId,1244		to: &T::CrossAccountId,1245		token: TokenId,1246		nesting_budget: &dyn Budget,1247	) -> DispatchResultWithPostInfo {1248		Self::check_allowed(collection, spender, from, token, nesting_budget)?;12491250		// =========12511252		// Allowance is reset in [`transfer`]1253		let mut result = Self::transfer(collection, from, to, token, nesting_budget);1254		add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());1255		result1256	}12571258	/// Burn NFT token for `from` account.1259	///1260	/// Same as the [`burn`] but spender doesn't need to be an owner of the token. The owner should1261	/// set allowance for the spender to burn token.1262	///1263	/// [`burn`]: struct.Pallet.html#method.burn1264	pub fn burn_from(1265		collection: &NonfungibleHandle<T>,1266		spender: &T::CrossAccountId,1267		from: &T::CrossAccountId,1268		token: TokenId,1269		nesting_budget: &dyn Budget,1270	) -> DispatchResult {1271		Self::check_allowed(collection, spender, from, token, nesting_budget)?;12721273		// =========12741275		Self::burn(collection, from, token)1276	}12771278	/// Check that `from` token could be nested in `under` token.1279	///1280	pub fn check_nesting(1281		handle: &NonfungibleHandle<T>,1282		sender: T::CrossAccountId,1283		from: (CollectionId, TokenId),1284		under: TokenId,1285		nesting_budget: &dyn Budget,1286	) -> DispatchResult {1287		let nesting = handle.permissions.nesting();12881289		#[cfg(not(feature = "runtime-benchmarks"))]1290		let permissive = false;1291		#[cfg(feature = "runtime-benchmarks")]1292		let permissive = nesting.permissive;12931294		if permissive {1295			ensure!(1296				<TokenData<T>>::contains_key((handle.id, under)),1297				<CommonError<T>>::TokenNotFound1298			);1299		} else if nesting.token_owner1300			&& <PalletStructure<T>>::check_indirectly_owned(1301				sender.clone(),1302				handle.id,1303				under,1304				Some(from),1305				nesting_budget,1306			)? {1307			// Pass, token existence and ouroboros checks are done in `check_indirectly_owned`1308		} else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {1309			// token existence and ouroboros checks are done in `get_checked_topmost_owner`1310			let _ = <PalletStructure<T>>::get_checked_topmost_owner(1311				handle.id,1312				under,1313				Some(from),1314				nesting_budget,1315			)?1316			.ok_or(<CommonError<T>>::TokenNotFound)?;1317		} else {1318			fail!(<CommonError<T>>::UserIsNotAllowedToNest);1319		}13201321		if let Some(whitelist) = &nesting.restricted {1322			ensure!(1323				whitelist.contains(&from.0),1324				<CommonError<T>>::SourceCollectionIsNotAllowedToNest1325			);1326		}1327		Ok(())1328	}13291330	fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1331		if to_nest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1332			<TokenChildren<T>>::insert((under.0, under.1, to_nest), true);1333		}1334	}13351336	fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1337		if to_unnest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1338			<TokenChildren<T>>::remove((under.0, under.1, to_unnest));1339		}1340	}13411342	fn collection_has_tokens(collection_id: CollectionId) -> bool {1343		<TokenData<T>>::iter_prefix((collection_id,))1344			.next()1345			.is_some()1346	}13471348	fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1349		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1350			.next()1351			.is_some()1352	}13531354	pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1355		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1356			.map(|((child_collection_id, child_id), _)| TokenChild {1357				collection: child_collection_id,1358				token: child_id,1359			})1360			.collect()1361	}13621363	/// Mint single NFT token.1364	///1365	/// Delegated to [`create_multiple_items`]1366	///1367	/// [`create_multiple_items`]: struct.Pallet.html#method.create_multiple_items1368	pub fn create_item(1369		collection: &NonfungibleHandle<T>,1370		sender: &T::CrossAccountId,1371		data: CreateItemData<T>,1372		nesting_budget: &dyn Budget,1373	) -> DispatchResult {1374		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1375	}13761377	/// Sets or unsets the approval of a given operator.1378	///1379	/// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1380	/// - `owner`: Token owner1381	/// - `operator`: Operator1382	/// - `approve`: Should operator status be granted or revoked?1383	pub fn set_allowance_for_all(1384		collection: &NonfungibleHandle<T>,1385		owner: &T::CrossAccountId,1386		operator: &T::CrossAccountId,1387		approve: bool,1388	) -> DispatchResult {1389		<PalletCommon<T>>::set_allowance_for_all(1390			collection,1391			owner,1392			operator,1393			approve,1394			|| <CollectionAllowance<T>>::insert((collection.id, owner, operator), approve),1395			ERC721Events::ApprovalForAll {1396				owner: *owner.as_eth(),1397				operator: *operator.as_eth(),1398				approved: approve,1399			}1400			.to_log(collection_id_to_address(collection.id)),1401		)1402	}14031404	/// Tells whether the given `owner` approves the `operator`.1405	pub fn allowance_for_all(1406		collection: &NonfungibleHandle<T>,1407		owner: &T::CrossAccountId,1408		operator: &T::CrossAccountId,1409	) -> bool {1410		<CollectionAllowance<T>>::get((collection.id, owner, operator))1411	}14121413	pub fn repair_item(collection: &NonfungibleHandle<T>, token: TokenId) -> DispatchResult {1414		<TokenProperties<T>>::mutate((collection.id, token), |properties| {1415			properties.recompute_consumed_space();1416		});14171418		Ok(())1419	}1420}
after · pallets/nonfungible/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Nonfungible Pallet18//!19//! The Nonfungible pallet provides functionality for handling nonfungible collections and tokens.20//!21//! - [`Config`]22//! - [`NonfungibleHandle`]23//! - [`Pallet`]24//! - [`CommonWeights`](common::CommonWeights)25//!26//! ## Overview27//!28//! The Nonfungible pallet provides functions for:29//!30//! - NFT collection creation and removal31//! - Minting and burning of NFT tokens32//! - Retrieving account balances33//! - Transfering NFT tokens34//! - Setting and checking allowance for NFT tokens35//! - Setting properties and permissions for NFT collections and tokens36//! - Nesting and unnesting tokens37//!38//! ### Terminology39//!40//! - **NFT token:** Non fungible token.41//!42//! - **NFT Collection:** A collection of NFT tokens. All NFT tokens are part of a collection.43//!   Each collection can define it's own properties, properties for it's tokens and set of permissions.44//!45//! - **Balance:** Number of NFT tokens owned by an account46//!47//! - **Allowance:** NFT tokens owned by one account that another account is allowed to make operations on48//!49//! - **Burning:** The process of “deleting” a token from a collection and from50//!   an account balance of the owner.51//!52//! - **Nesting:** Setting up parent-child relationship between tokens. Nested tokens are inhereting53//!   owner from their parent. There could be multiple levels of nesting. Token couldn't be nested in54//!   it's child token i.e. parent-child relationship graph shouldn't have cycles.55//!56//! - **Properties:** Key-Values pairs. Token properties are attached to a token. Collection properties are57//!   attached to a collection. Set of permissions could be defined for each property.58//!59//! ### Implementations60//!61//! The Nonfungible pallet provides implementations for the following traits. If these traits provide62//! the functionality that you need, then you can avoid coupling with the Nonfungible pallet.63//!64//! - [`CommonWeightInfo`](pallet_common::CommonWeightInfo): Functions for retrieval of transaction weight65//! - [`CommonCollectionOperations`](pallet_common::CommonCollectionOperations): Functions for dealing66//!   with collections67//!68//! ## Interface69//!70//! ### Dispatchable Functions71//!72//! - `init_collection` - Create NFT collection. NFT collection can be configured to allow or deny access for73//!   some accounts.74//! - `destroy_collection` - Destroy exising NFT collection. There should be no tokens in the collection.75//! - `burn` - Burn NFT token owned by account.76//! - `transfer` - Transfer NFT token. Transfers should be enabled for NFT collection.77//!   Nests the NFT token if it is sent to another token.78//! - `create_item` - Mint NFT token in collection. Sender should have permission to mint tokens.79//! - `set_allowance` - Set allowance for another account.80//! - `set_token_property` - Set token property value.81//! - `delete_token_property` - Remove property from the token.82//! - `set_collection_properties` - Set collection properties.83//! - `delete_collection_properties` - Remove properties from the collection.84//! - `set_property_permission` - Set collection property permission.85//! - `set_token_property_permissions` - Set token property permissions.86//!87//! ## Assumptions88//!89//! * To perform operations on tokens sender should be in collection's allow list if collection access mode is `AllowList`.9091#![cfg_attr(not(feature = "std"), no_std)]9293use erc::ERC721Events;94use evm_coder::ToLog;95use frame_support::{96	BoundedVec, ensure, fail, transactional,97	storage::with_transaction,98	pallet_prelude::DispatchResultWithPostInfo,99	pallet_prelude::Weight,100	dispatch::{PostDispatchInfo, Pays},101};102use up_data_structs::{103	AccessMode, CollectionId, CollectionFlags, CustomDataLimit, TokenId, CreateCollectionData,104	CreateNftExData, mapping::TokenAddressMapping, budget::Budget, Property, PropertyKey,105	PropertyValue, PropertyKeyPermission, PropertyScope, TrySetProperty, TokenChild,106	AuxPropertyValue, PropertiesPermissionMap, TokenProperties as TokenPropertiesT,107};108use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};109use pallet_common::{110	Error as CommonError, Pallet as PalletCommon, Event as CommonEvent, CollectionHandle,111	eth::collection_id_to_address, SelfWeightOf as PalletCommonWeightOf,112	weights::WeightInfo as CommonWeightInfo, helpers::add_weight_to_post_info,113};114use pallet_structure::{Pallet as PalletStructure, Error as StructureError};115use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};116use sp_core::{Get, H160};117use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, TransactionOutcome};118use sp_std::{vec::Vec, vec, collections::btree_map::BTreeMap};119use core::ops::Deref;120use codec::{Encode, Decode, MaxEncodedLen};121use scale_info::TypeInfo;122123pub use pallet::*;124use weights::WeightInfo;125#[cfg(feature = "runtime-benchmarks")]126pub mod benchmarking;127pub mod common;128pub mod erc;129pub mod weights;130131pub type CreateItemData<T> = CreateNftExData<<T as pallet_evm::Config>::CrossAccountId>;132pub(crate) type SelfWeightOf<T> = <T as Config>::WeightInfo;133134/// Token data, stored independently from other data used to describe it135/// for the convenience of database access. Notably contains the owner account address.136#[struct_versioning::versioned(version = 2, upper)]137#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]138pub struct ItemData<CrossAccountId> {139	#[version(..2)]140	pub const_data: BoundedVec<u8, CustomDataLimit>,141142	#[version(..2)]143	pub variable_data: BoundedVec<u8, CustomDataLimit>,144145	pub owner: CrossAccountId,146}147148#[frame_support::pallet]149pub mod pallet {150	use super::*;151	use frame_support::{152		Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, traits::StorageVersion,153	};154	use up_data_structs::{CollectionId, TokenId};155	use super::weights::WeightInfo;156157	#[pallet::error]158	pub enum Error<T> {159		/// Not Nonfungible item data used to mint in Nonfungible collection.160		NotNonfungibleDataUsedToMintFungibleCollectionToken,161		/// Used amount > 1 with NFT162		NonfungibleItemsHaveNoAmount,163		/// Unable to burn NFT with children164		CantBurnNftWithChildren,165	}166167	#[pallet::config]168	pub trait Config:169		frame_system::Config + pallet_common::Config + pallet_structure::Config + pallet_evm::Config170	{171		type WeightInfo: WeightInfo;172	}173174	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);175176	#[pallet::pallet]177	#[pallet::storage_version(STORAGE_VERSION)]178	pub struct Pallet<T>(_);179180	/// Total amount of minted tokens in a collection.181	#[pallet::storage]182	pub type TokensMinted<T: Config> =183		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;184185	/// Amount of burnt tokens in a collection.186	#[pallet::storage]187	pub type TokensBurnt<T: Config> =188		StorageMap<Hasher = Twox64Concat, Key = CollectionId, Value = u32, QueryKind = ValueQuery>;189190	/// Token data, used to partially describe a token.191	#[pallet::storage]192	pub type TokenData<T: Config> = StorageNMap<193		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),194		Value = ItemData<T::CrossAccountId>,195		QueryKind = OptionQuery,196	>;197198	/// Map of key-value pairs, describing the metadata of a token.199	#[pallet::storage]200	#[pallet::getter(fn token_properties)]201	pub type TokenProperties<T: Config> = StorageNMap<202		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),203		Value = TokenPropertiesT,204		QueryKind = ValueQuery,205	>;206207	/// Custom data of a token that is serialized to bytes,208	/// primarily reserved for on-chain operations,209	/// normally obscured from the external users.210	///211	/// Auxiliary properties are slightly different from212	/// usual [`TokenProperties`] due to an unlimited number213	/// and separately stored and written-to key-value pairs.214	///215	/// Currently unused.216	#[pallet::storage]217	#[pallet::getter(fn token_aux_property)]218	pub type TokenAuxProperties<T: Config> = StorageNMap<219		Key = (220			Key<Twox64Concat, CollectionId>,221			Key<Twox64Concat, TokenId>,222			Key<Twox64Concat, PropertyScope>,223			Key<Twox64Concat, PropertyKey>,224		),225		Value = AuxPropertyValue,226		QueryKind = OptionQuery,227	>;228229	/// Used to enumerate tokens owned by account.230	#[pallet::storage]231	pub type Owned<T: Config> = StorageNMap<232		Key = (233			Key<Twox64Concat, CollectionId>,234			Key<Blake2_128Concat, T::CrossAccountId>,235			Key<Twox64Concat, TokenId>,236		),237		Value = bool,238		QueryKind = ValueQuery,239	>;240241	/// Used to enumerate token's children.242	#[pallet::storage]243	#[pallet::getter(fn token_children)]244	pub type TokenChildren<T: Config> = StorageNMap<245		Key = (246			Key<Twox64Concat, CollectionId>,247			Key<Twox64Concat, TokenId>,248			Key<Twox64Concat, (CollectionId, TokenId)>,249		),250		Value = bool,251		QueryKind = ValueQuery,252	>;253254	/// Amount of tokens owned by an account in a collection.255	#[pallet::storage]256	pub type AccountBalance<T: Config> = StorageNMap<257		Key = (258			Key<Twox64Concat, CollectionId>,259			Key<Blake2_128Concat, T::CrossAccountId>,260		),261		Value = u32,262		QueryKind = ValueQuery,263	>;264265	/// Allowance set by a token owner for another user to perform one of certain transactions on a token.266	#[pallet::storage]267	pub type Allowance<T: Config> = StorageNMap<268		Key = (Key<Twox64Concat, CollectionId>, Key<Twox64Concat, TokenId>),269		Value = T::CrossAccountId,270		QueryKind = OptionQuery,271	>;272273	/// Operator set by a wallet owner that could perform certain transactions on all tokens in the wallet.274	#[pallet::storage]275	pub type CollectionAllowance<T: Config> = StorageNMap<276		Key = (277			Key<Twox64Concat, CollectionId>,278			Key<Blake2_128Concat, T::CrossAccountId>,279			Key<Blake2_128Concat, T::CrossAccountId>,280		),281		Value = bool,282		QueryKind = ValueQuery,283	>;284285	#[pallet::genesis_config]286	pub struct GenesisConfig<T>(PhantomData<T>);287288	#[cfg(feature = "std")]289	impl<T: Config> Default for GenesisConfig<T> {290		fn default() -> Self {291			Self(Default::default())292		}293	}294295	#[pallet::genesis_build]296	impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {297		fn build(&self) {298			StorageVersion::new(1).put::<Pallet<T>>();299		}300	}301}302303pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);304impl<T: Config> NonfungibleHandle<T> {305	pub fn cast(inner: pallet_common::CollectionHandle<T>) -> Self {306		Self(inner)307	}308	pub fn into_inner(self) -> pallet_common::CollectionHandle<T> {309		self.0310	}311	pub fn common_mut(&mut self) -> &mut pallet_common::CollectionHandle<T> {312		&mut self.0313	}314}315316impl<T: Config> WithRecorder<T> for NonfungibleHandle<T> {317	fn recorder(&self) -> &SubstrateRecorder<T> {318		self.0.recorder()319	}320	fn into_recorder(self) -> SubstrateRecorder<T> {321		self.0.into_recorder()322	}323}324impl<T: Config> Deref for NonfungibleHandle<T> {325	type Target = pallet_common::CollectionHandle<T>;326327	fn deref(&self) -> &Self::Target {328		&self.0329	}330}331332impl<T: Config> Pallet<T> {333	/// Get number of NFT tokens in collection.334	pub fn total_supply(collection: &NonfungibleHandle<T>) -> u32 {335		<TokensMinted<T>>::get(collection.id) - <TokensBurnt<T>>::get(collection.id)336	}337338	/// Check that NFT token exists.339	///340	/// - `token`: Token ID.341	pub fn token_exists(collection: &NonfungibleHandle<T>, token: TokenId) -> bool {342		<TokenData<T>>::contains_key((collection.id, token))343	}344345	/// Set the token property with the scope.346	///347	/// - `property`: Contains key-value pair.348	pub fn set_scoped_token_property(349		collection_id: CollectionId,350		token_id: TokenId,351		scope: PropertyScope,352		property: Property,353	) -> DispatchResult {354		TokenProperties::<T>::try_mutate((collection_id, token_id), |properties| {355			properties.try_scoped_set(scope, property.key, property.value)356		})357		.map_err(<CommonError<T>>::from)?;358359		Ok(())360	}361362	/// Batch operation to set multiple properties with the same scope.363	pub fn set_scoped_token_properties(364		collection_id: CollectionId,365		token_id: TokenId,366		scope: PropertyScope,367		properties: impl Iterator<Item = Property>,368	) -> DispatchResult {369		TokenProperties::<T>::try_mutate((collection_id, token_id), |stored_properties| {370			stored_properties.try_scoped_set_from_iter(scope, properties)371		})372		.map_err(<CommonError<T>>::from)?;373374		Ok(())375	}376377	/// Add or edit auxiliary data for the property.378	///379	/// - `f`: function that adds or edits auxiliary data.380	pub fn try_mutate_token_aux_property<R, E>(381		collection_id: CollectionId,382		token_id: TokenId,383		scope: PropertyScope,384		key: PropertyKey,385		f: impl FnOnce(&mut Option<AuxPropertyValue>) -> Result<R, E>,386	) -> Result<R, E> {387		<TokenAuxProperties<T>>::try_mutate((collection_id, token_id, scope, key), f)388	}389390	/// Remove auxiliary data for the property.391	pub fn remove_token_aux_property(392		collection_id: CollectionId,393		token_id: TokenId,394		scope: PropertyScope,395		key: PropertyKey,396	) {397		<TokenAuxProperties<T>>::remove((collection_id, token_id, scope, key));398	}399400	/// Get all auxiliary data in a given scope.401	///402	/// Returns iterator over Property Key - Data pairs.403	pub fn iterate_token_aux_properties(404		collection_id: CollectionId,405		token_id: TokenId,406		scope: PropertyScope,407	) -> impl Iterator<Item = (PropertyKey, AuxPropertyValue)> {408		<TokenAuxProperties<T>>::iter_prefix((collection_id, token_id, scope))409	}410411	/// Get ID of the last minted token412	pub fn current_token_id(collection_id: CollectionId) -> TokenId {413		TokenId(<TokensMinted<T>>::get(collection_id))414	}415}416417// unchecked calls skips any permission checks418impl<T: Config> Pallet<T> {419	/// Create NFT collection420	///421	/// `init_collection` will take non-refundable deposit for collection creation.422	///423	/// - `data`: Contains settings for collection limits and permissions.424	pub fn init_collection(425		owner: T::CrossAccountId,426		payer: T::CrossAccountId,427		data: CreateCollectionData<T::AccountId>,428		flags: CollectionFlags,429	) -> Result<CollectionId, DispatchError> {430		<PalletCommon<T>>::init_collection(owner, payer, data, flags)431	}432433	/// Destroy NFT collection434	///435	/// `destroy_collection` will throw error if collection contains any tokens.436	/// Only owner can destroy collection.437	pub fn destroy_collection(438		collection: NonfungibleHandle<T>,439		sender: &T::CrossAccountId,440	) -> DispatchResult {441		let id = collection.id;442443		if Self::collection_has_tokens(id) {444			return Err(<CommonError<T>>::CantDestroyNotEmptyCollection.into());445		}446447		// =========448449		PalletCommon::destroy_collection(collection.0, sender)?;450451		let _ = <TokenData<T>>::clear_prefix((id,), u32::MAX, None);452		let _ = <TokenChildren<T>>::clear_prefix((id,), u32::MAX, None);453		let _ = <Owned<T>>::clear_prefix((id,), u32::MAX, None);454		<TokensMinted<T>>::remove(id);455		<TokensBurnt<T>>::remove(id);456		let _ = <Allowance<T>>::clear_prefix((id,), u32::MAX, None);457		let _ = <AccountBalance<T>>::clear_prefix((id,), u32::MAX, None);458		let _ = <CollectionAllowance<T>>::clear_prefix((id,), u32::MAX, None);459		Ok(())460	}461462	/// Burn NFT token463	///464	/// `burn` removes `token` from the `collection`, from it's owner and from the parent token465	/// if the token is nested.466	/// Only the owner can `burn` the token. The `token` shouldn't have any nested tokens.467	/// Also removes all corresponding properties and auxiliary properties.468	///469	/// - `token`: Token that should be burned470	/// - `collection`: Collection that contains the token471	pub fn burn(472		collection: &NonfungibleHandle<T>,473		sender: &T::CrossAccountId,474		token: TokenId,475	) -> DispatchResult {476		let token_data =477			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;478		ensure!(&token_data.owner == sender, <CommonError<T>>::NoPermission);479480		if collection.permissions.access() == AccessMode::AllowList {481			collection.check_allowlist(sender)?;482		}483484		if Self::token_has_children(collection.id, token) {485			return Err(<Error<T>>::CantBurnNftWithChildren.into());486		}487488		let burnt = <TokensBurnt<T>>::get(collection.id)489			.checked_add(1)490			.ok_or(ArithmeticError::Overflow)?;491492		let balance = <AccountBalance<T>>::get((collection.id, token_data.owner.clone()))493			.checked_sub(1)494			.ok_or(ArithmeticError::Overflow)?;495496		// =========497498		if balance == 0 {499			<AccountBalance<T>>::remove((collection.id, token_data.owner.clone()));500		} else {501			<AccountBalance<T>>::insert((collection.id, token_data.owner.clone()), balance);502		}503504		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);505506		<Owned<T>>::remove((collection.id, &token_data.owner, token));507		<TokensBurnt<T>>::insert(collection.id, burnt);508		<TokenData<T>>::remove((collection.id, token));509		<TokenProperties<T>>::remove((collection.id, token));510		let _ = <TokenAuxProperties<T>>::clear_prefix((collection.id, token), u32::MAX, None);511		let old_spender = <Allowance<T>>::take((collection.id, token));512513		if let Some(old_spender) = old_spender {514			<PalletCommon<T>>::deposit_event(CommonEvent::Approved(515				collection.id,516				token,517				token_data.owner.clone(),518				old_spender,519				0,520			));521		}522523		<PalletEvm<T>>::deposit_log(524			ERC721Events::Transfer {525				from: *token_data.owner.as_eth(),526				to: H160::default(),527				token_id: token.into(),528			}529			.to_log(collection_id_to_address(collection.id)),530		);531		<PalletCommon<T>>::deposit_event(CommonEvent::ItemDestroyed(532			collection.id,533			token,534			token_data.owner,535			1,536		));537		Ok(())538	}539540	/// Same as [`burn`] but burns all the tokens that are nested in the token first541	///542	/// - `self_budget`: Limit for searching children in depth.543	/// - `breadth_budget`: Limit of breadth of searching children.544	///545	/// [`burn`]: struct.Pallet.html#method.burn546	#[transactional]547	pub fn burn_recursively(548		collection: &NonfungibleHandle<T>,549		sender: &T::CrossAccountId,550		token: TokenId,551		self_budget: &dyn Budget,552		breadth_budget: &dyn Budget,553	) -> DispatchResultWithPostInfo {554		ensure!(self_budget.consume(), <StructureError<T>>::DepthLimit,);555556		let current_token_account =557			T::CrossTokenAddressMapping::token_to_address(collection.id, token);558559		let mut weight = Weight::zero();560561		// This method is transactional, if user in fact doesn't have permissions to remove token -562		// tokens removed here will be restored after rejected transaction563		for ((collection, token), _) in <TokenChildren<T>>::iter_prefix((collection.id, token)) {564			ensure!(breadth_budget.consume(), <StructureError<T>>::BreadthLimit,);565			let PostDispatchInfo { actual_weight, .. } =566				<PalletStructure<T>>::burn_item_recursively(567					current_token_account.clone(),568					collection,569					token,570					self_budget,571					breadth_budget,572				)?;573			if let Some(actual_weight) = actual_weight {574				weight = weight.saturating_add(actual_weight);575			}576		}577578		Self::burn(collection, sender, token)?;579		DispatchResultWithPostInfo::Ok(PostDispatchInfo {580			actual_weight: Some(weight + <SelfWeightOf<T>>::burn_item()),581			pays_fee: Pays::Yes,582		})583	}584585	/// A batch operation to add, edit or remove properties for a token.586	///587	/// - `nesting_budget`: Limit for searching parents in-depth to check ownership.588	/// - `is_token_create`: Indicates that method is called during token initialization.589	///   Allows to bypass ownership check.590	///591	/// All affected properties should have `mutable` permission592	/// to be **deleted** or to be **set more than once**,593	/// and the sender should have permission to edit those properties.594	///595	/// This function fires an event for each property change.596	/// In case of an error, all the changes (including the events) will be reverted597	/// since the function is transactional.598	#[transactional]599	fn modify_token_properties(600		collection: &NonfungibleHandle<T>,601		sender: &T::CrossAccountId,602		token_id: TokenId,603		properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,604		is_token_create: bool,605		nesting_budget: &dyn Budget,606	) -> DispatchResult {607		let is_token_owner = || {608			let is_owned = <PalletStructure<T>>::check_indirectly_owned(609				sender.clone(),610				collection.id,611				token_id,612				None,613				nesting_budget,614			)?;615616			Ok(is_owned)617		};618619		let stored_properties = <TokenProperties<T>>::get((collection.id, token_id));620621		<PalletCommon<T>>::modify_token_properties(622			collection,623			sender,624			token_id,625			properties_updates,626			is_token_create,627			stored_properties,628			is_token_owner,629			|properties| <TokenProperties<T>>::set((collection.id, token_id), properties),630			erc::ERC721TokenEvent::TokenChanged {631				token_id: token_id.into(),632			}633			.to_log(T::ContractAddress::get()),634		)635	}636637	/// Batch operation to add or edit properties for the token638	///639	/// Same as [`modify_token_properties`] but doesn't allow to remove properties640	///641	/// [`modify_token_properties`]: struct.Pallet.html#method.modify_token_properties642	pub fn set_token_properties(643		collection: &NonfungibleHandle<T>,644		sender: &T::CrossAccountId,645		token_id: TokenId,646		properties: impl Iterator<Item = Property>,647		is_token_create: bool,648		nesting_budget: &dyn Budget,649	) -> DispatchResult {650		Self::modify_token_properties(651			collection,652			sender,653			token_id,654			properties.map(|p| (p.key, Some(p.value))),655			is_token_create,656			nesting_budget,657		)658	}659660	/// Add or edit single property for the token661	///662	/// Calls [`set_token_properties`] internally663	///664	/// [`set_token_properties`]: struct.Pallet.html#method.set_token_properties665	pub fn set_token_property(666		collection: &NonfungibleHandle<T>,667		sender: &T::CrossAccountId,668		token_id: TokenId,669		property: Property,670		nesting_budget: &dyn Budget,671	) -> DispatchResult {672		let is_token_create = false;673674		Self::set_token_properties(675			collection,676			sender,677			token_id,678			[property].into_iter(),679			is_token_create,680			nesting_budget,681		)682	}683684	/// Batch operation to remove properties from the token685	///686	/// Same as [`modify_token_properties`] but doesn't allow to add or edit properties687	///688	/// [`modify_token_properties`]: struct.Pallet.html#method.modify_token_properties689	pub fn delete_token_properties(690		collection: &NonfungibleHandle<T>,691		sender: &T::CrossAccountId,692		token_id: TokenId,693		property_keys: impl Iterator<Item = PropertyKey>,694		nesting_budget: &dyn Budget,695	) -> DispatchResult {696		let is_token_create = false;697698		Self::modify_token_properties(699			collection,700			sender,701			token_id,702			property_keys.into_iter().map(|key| (key, None)),703			is_token_create,704			nesting_budget,705		)706	}707708	/// Remove single property from the token709	///710	/// Calls [`delete_token_properties`] internally711	///712	/// [`delete_token_properties`]: struct.Pallet.html#method.delete_token_properties713	pub fn delete_token_property(714		collection: &NonfungibleHandle<T>,715		sender: &T::CrossAccountId,716		token_id: TokenId,717		property_key: PropertyKey,718		nesting_budget: &dyn Budget,719	) -> DispatchResult {720		Self::delete_token_properties(721			collection,722			sender,723			token_id,724			[property_key].into_iter(),725			nesting_budget,726		)727	}728729	/// Add or edit properties for the collection730	pub fn set_collection_properties(731		collection: &NonfungibleHandle<T>,732		sender: &T::CrossAccountId,733		properties: Vec<Property>,734	) -> DispatchResult {735		<PalletCommon<T>>::set_collection_properties(collection, sender, properties.into_iter())736	}737738	/// Remove properties from the collection739	pub fn delete_collection_properties(740		collection: &CollectionHandle<T>,741		sender: &T::CrossAccountId,742		property_keys: Vec<PropertyKey>,743	) -> DispatchResult {744		<PalletCommon<T>>::delete_collection_properties(745			collection,746			sender,747			property_keys.into_iter(),748		)749	}750751	/// Set property permissions for the token.752	///753	/// Sender should be the owner or admin of token's collection.754	pub fn set_token_property_permissions(755		collection: &CollectionHandle<T>,756		sender: &T::CrossAccountId,757		property_permissions: Vec<PropertyKeyPermission>,758	) -> DispatchResult {759		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)760	}761762	/// Set property permissions for the token with scope.763	///764	/// Sender should be the owner or admin of token's collection.765	pub fn set_scoped_token_property_permissions(766		collection: &CollectionHandle<T>,767		sender: &T::CrossAccountId,768		scope: PropertyScope,769		property_permissions: Vec<PropertyKeyPermission>,770	) -> DispatchResult {771		<PalletCommon<T>>::set_scoped_token_property_permissions(772			collection,773			sender,774			scope,775			property_permissions,776		)777	}778779	pub fn token_property_permission(collection_id: CollectionId) -> PropertiesPermissionMap {780		<PalletCommon<T>>::property_permissions(collection_id)781	}782783	pub fn check_token_immediate_ownership(784		collection: &NonfungibleHandle<T>,785		token: TokenId,786		possible_owner: &T::CrossAccountId,787	) -> DispatchResult {788		let token_data =789			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;790		ensure!(791			&token_data.owner == possible_owner,792			<CommonError<T>>::NoPermission793		);794		Ok(())795	}796797	/// Transfer NFT token from one account to another.798	///799	/// `from` account stops being the owner and `to` account becomes the owner of the token.800	/// If `to` is token than `to` becomes owner of the token and the token become nested.801	/// Unnests token from previous parent if it was nested before.802	/// Removes allowance for the token if there was any.803	/// Throws if transfers aren't allowed for collection or if receiver reached token ownership limit.804	///805	/// - `nesting_budget`: Limit for token nesting depth806	pub fn transfer(807		collection: &NonfungibleHandle<T>,808		from: &T::CrossAccountId,809		to: &T::CrossAccountId,810		token: TokenId,811		nesting_budget: &dyn Budget,812	) -> DispatchResultWithPostInfo {813		ensure!(814			collection.limits.transfers_enabled(),815			<CommonError<T>>::TransferNotAllowed816		);817818		let mut actual_weight = <SelfWeightOf<T>>::transfer_raw();819		let token_data =820			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;821		ensure!(&token_data.owner == from, <CommonError<T>>::NoPermission);822823		if collection.permissions.access() == AccessMode::AllowList {824			collection.check_allowlist(from)?;825			collection.check_allowlist(to)?;826			actual_weight += <PalletCommonWeightOf<T>>::check_accesslist() * 2;827		}828		<PalletCommon<T>>::ensure_correct_receiver(to)?;829830		let balance_from = <AccountBalance<T>>::get((collection.id, from))831			.checked_sub(1)832			.ok_or(<CommonError<T>>::TokenValueTooLow)?;833		let balance_to = if from != to {834			let balance_to = <AccountBalance<T>>::get((collection.id, to))835				.checked_add(1)836				.ok_or(ArithmeticError::Overflow)?;837838			ensure!(839				balance_to < collection.limits.account_token_ownership_limit(),840				<CommonError<T>>::AccountTokenLimitExceeded,841			);842843			Some(balance_to)844		} else {845			None846		};847848		<PalletStructure<T>>::nest_if_sent_to_token(849			from.clone(),850			to,851			collection.id,852			token,853			nesting_budget,854		)?;855856		// =========857858		<PalletStructure<T>>::unnest_if_nested(&token_data.owner, collection.id, token);859860		<TokenData<T>>::insert((collection.id, token), ItemData { owner: to.clone() });861862		if let Some(balance_to) = balance_to {863			// from != to864			if balance_from == 0 {865				<AccountBalance<T>>::remove((collection.id, from));866			} else {867				<AccountBalance<T>>::insert((collection.id, from), balance_from);868			}869			<AccountBalance<T>>::insert((collection.id, to), balance_to);870			<Owned<T>>::remove((collection.id, from, token));871			<Owned<T>>::insert((collection.id, to, token), true);872		}873		Self::set_allowance_unchecked(collection, from, token, None, true);874875		<PalletEvm<T>>::deposit_log(876			ERC721Events::Transfer {877				from: *from.as_eth(),878				to: *to.as_eth(),879				token_id: token.into(),880			}881			.to_log(collection_id_to_address(collection.id)),882		);883		<PalletCommon<T>>::deposit_event(CommonEvent::Transfer(884			collection.id,885			token,886			from.clone(),887			to.clone(),888			1,889		));890891		Ok(PostDispatchInfo {892			actual_weight: Some(actual_weight),893			pays_fee: Pays::Yes,894		})895	}896897	/// Batch operation to mint multiple NFT tokens.898	///899	/// The sender should be the owner/admin of the collection or collection should be configured900	/// to allow public minting.901	/// Throws if amount of tokens reached it's limit for the collection or if caller reached902	/// token ownership limit.903	///904	/// - `data`: Contains list of token properties and users who will become the owners of the905	///   corresponging tokens.906	/// - `nesting_budget`: Limit for token nesting depth907	pub fn create_multiple_items(908		collection: &NonfungibleHandle<T>,909		sender: &T::CrossAccountId,910		data: Vec<CreateItemData<T>>,911		nesting_budget: &dyn Budget,912	) -> DispatchResult {913		if !collection.is_owner_or_admin(sender) {914			ensure!(915				collection.permissions.mint_mode(),916				<CommonError<T>>::PublicMintingNotAllowed917			);918			collection.check_allowlist(sender)?;919920			for item in data.iter() {921				collection.check_allowlist(&item.owner)?;922			}923		}924925		for data in data.iter() {926			<PalletCommon<T>>::ensure_correct_receiver(&data.owner)?;927		}928929		let first_token = <TokensMinted<T>>::get(collection.id);930		let tokens_minted = first_token931			.checked_add(data.len() as u32)932			.ok_or(ArithmeticError::Overflow)?;933		ensure!(934			tokens_minted <= collection.limits.token_limit(),935			<CommonError<T>>::CollectionTokenLimitExceeded936		);937938		let mut balances = BTreeMap::new();939		for data in &data {940			let balance = balances941				.entry(&data.owner)942				.or_insert_with(|| <AccountBalance<T>>::get((collection.id, &data.owner)));943			*balance = balance.checked_add(1).ok_or(ArithmeticError::Overflow)?;944945			ensure!(946				*balance <= collection.limits.account_token_ownership_limit(),947				<CommonError<T>>::AccountTokenLimitExceeded,948			);949		}950951		for (i, data) in data.iter().enumerate() {952			let token = TokenId(first_token + i as u32 + 1);953954			<PalletStructure<T>>::check_nesting(955				sender.clone(),956				&data.owner,957				collection.id,958				token,959				nesting_budget,960			)?;961		}962963		// =========964965		with_transaction(|| {966			for (i, data) in data.iter().enumerate() {967				let token = first_token + i as u32 + 1;968969				<TokenData<T>>::insert(970					(collection.id, token),971					ItemData {972						// const_data: data.const_data.clone(),973						owner: data.owner.clone(),974					},975				);976977				<PalletStructure<T>>::nest_if_sent_to_token_unchecked(978					&data.owner,979					collection.id,980					TokenId(token),981				);982983				if let Err(e) = Self::set_token_properties(984					collection,985					sender,986					TokenId(token),987					data.properties.clone().into_iter(),988					true,989					nesting_budget,990				) {991					return TransactionOutcome::Rollback(Err(e));992				}993			}994			TransactionOutcome::Commit(Ok(()))995		})?;996997		<TokensMinted<T>>::insert(collection.id, tokens_minted);998		for (account, balance) in balances {999			<AccountBalance<T>>::insert((collection.id, account), balance);1000		}1001		for (i, data) in data.into_iter().enumerate() {1002			let token = first_token + i as u32 + 1;1003			<Owned<T>>::insert((collection.id, &data.owner, token), true);10041005			<PalletEvm<T>>::deposit_log(1006				ERC721Events::Transfer {1007					from: H160::default(),1008					to: *data.owner.as_eth(),1009					token_id: token.into(),1010				}1011				.to_log(collection_id_to_address(collection.id)),1012			);1013			<PalletCommon<T>>::deposit_event(CommonEvent::ItemCreated(1014				collection.id,1015				TokenId(token),1016				data.owner.clone(),1017				1,1018			));1019		}1020		Ok(())1021	}10221023	pub fn set_allowance_unchecked(1024		collection: &NonfungibleHandle<T>,1025		sender: &T::CrossAccountId,1026		token: TokenId,1027		spender: Option<&T::CrossAccountId>,1028		assume_implicit_eth: bool,1029	) {1030		if let Some(spender) = spender {1031			let old_spender = <Allowance<T>>::get((collection.id, token));1032			<Allowance<T>>::insert((collection.id, token), spender);1033			// In ERC721 there is only one possible approved user of token, so we set1034			// approved user to spender1035			<PalletEvm<T>>::deposit_log(1036				ERC721Events::Approval {1037					owner: *sender.as_eth(),1038					approved: *spender.as_eth(),1039					token_id: token.into(),1040				}1041				.to_log(collection_id_to_address(collection.id)),1042			);1043			// In Unique chain, any token can have any amount of approved users, so we need to1044			// set allowance of old owner to 0, and allowance of new owner to 11045			if old_spender.as_ref() != Some(spender) {1046				if let Some(old_owner) = old_spender {1047					<PalletCommon<T>>::deposit_event(CommonEvent::Approved(1048						collection.id,1049						token,1050						sender.clone(),1051						old_owner,1052						0,1053					));1054				}1055				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(1056					collection.id,1057					token,1058					sender.clone(),1059					spender.clone(),1060					1,1061				));1062			}1063		} else {1064			let old_spender = <Allowance<T>>::take((collection.id, token));1065			if !assume_implicit_eth {1066				// In ERC721 there is only one possible approved user of token, so we set1067				// approved user to zero address1068				<PalletEvm<T>>::deposit_log(1069					ERC721Events::Approval {1070						owner: *sender.as_eth(),1071						approved: H160::default(),1072						token_id: token.into(),1073					}1074					.to_log(collection_id_to_address(collection.id)),1075				);1076			}1077			// In Unique chain, any token can have any amount of approved users, so we need to1078			// set allowance of old owner to 01079			if let Some(old_spender) = old_spender {1080				<PalletCommon<T>>::deposit_event(CommonEvent::Approved(1081					collection.id,1082					token,1083					sender.clone(),1084					old_spender,1085					0,1086				));1087			}1088		}1089	}10901091	pub fn get_allowance(1092		collection: &NonfungibleHandle<T>,1093		token_id: TokenId,1094	) -> Result<Option<T::CrossAccountId>, DispatchError> {1095		ensure!(1096			<TokenData<T>>::get((collection.id, token_id)).is_some(),1097			<CommonError<T>>::TokenNotFound1098		);1099		Ok(<Allowance<T>>::get((collection.id, token_id)))1100	}11011102	/// Set allowance for the spender to `transfer` or `burn` sender's token.1103	///1104	/// - `token`: Token the spender is allowed to `transfer` or `burn`.1105	pub fn set_allowance(1106		collection: &NonfungibleHandle<T>,1107		sender: &T::CrossAccountId,1108		token: TokenId,1109		spender: Option<&T::CrossAccountId>,1110	) -> DispatchResult {1111		if collection.permissions.access() == AccessMode::AllowList {1112			collection.check_allowlist(sender)?;1113			if let Some(spender) = spender {1114				collection.check_allowlist(spender)?;1115			}1116		}11171118		if let Some(spender) = spender {1119			<PalletCommon<T>>::ensure_correct_receiver(spender)?;1120		}11211122		let token_data =1123			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;1124		if &token_data.owner != sender {1125			ensure!(1126				collection.ignores_owned_amount(sender),1127				<CommonError<T>>::CantApproveMoreThanOwned1128			);1129		}11301131		// =========11321133		Self::set_allowance_unchecked(collection, sender, token, spender, false);1134		Ok(())1135	}11361137	/// Set allowance for the spender to `transfer` or `burn` sender's token from eth mirror.1138	///1139	/// - `from`: Address of sender's eth mirror.1140	/// - `to`: Adress of spender.1141	/// - `token`: Token the spender is allowed to `transfer` or `burn`.1142	pub fn set_allowance_from(1143		collection: &NonfungibleHandle<T>,1144		sender: &T::CrossAccountId,1145		from: &T::CrossAccountId,1146		token: TokenId,1147		to: Option<&T::CrossAccountId>,1148	) -> DispatchResult {1149		if collection.permissions.access() == AccessMode::AllowList {1150			collection.check_allowlist(sender)?;1151			collection.check_allowlist(from)?;1152			if let Some(to) = to {1153				collection.check_allowlist(to)?;1154			}1155		}11561157		if let Some(to) = to {1158			<PalletCommon<T>>::ensure_correct_receiver(to)?;1159		}11601161		ensure!(1162			sender.conv_eq(from),1163			<CommonError<T>>::AddressIsNotEthMirror1164		);11651166		let token_data =1167			<TokenData<T>>::get((collection.id, token)).ok_or(<CommonError<T>>::TokenNotFound)?;1168		if token_data.owner != *from {1169			ensure!(1170				collection.limits.owner_can_transfer()1171					&& (collection.is_owner_or_admin(sender) || collection.is_owner_or_admin(from)),1172				<CommonError<T>>::CantApproveMoreThanOwned1173			);1174		}11751176		// =========11771178		Self::set_allowance_unchecked(collection, from, token, to, false);1179		Ok(())1180	}11811182	/// Checks allowance for the spender to use the token.1183	fn check_allowed(1184		collection: &NonfungibleHandle<T>,1185		spender: &T::CrossAccountId,1186		from: &T::CrossAccountId,1187		token: TokenId,1188		nesting_budget: &dyn Budget,1189	) -> DispatchResult {1190		if spender.conv_eq(from) {1191			return Ok(());1192		}1193		if collection.permissions.access() == AccessMode::AllowList {1194			// `from`, `to` checked in [`transfer`]1195			collection.check_allowlist(spender)?;1196		}11971198		if collection.ignores_token_restrictions(spender) {1199			return Ok(());1200		}12011202		if let Some(source) = T::CrossTokenAddressMapping::address_to_token(from) {1203			ensure!(1204				<PalletStructure<T>>::check_indirectly_owned(1205					spender.clone(),1206					source.0,1207					source.1,1208					None,1209					nesting_budget1210				)?,1211				<CommonError<T>>::ApprovedValueTooLow,1212			);1213			return Ok(());1214		}1215		if <Allowance<T>>::get((collection.id, token)).as_ref() == Some(spender) {1216			return Ok(());1217		}1218		if <CollectionAllowance<T>>::get((collection.id, from, spender)) {1219			return Ok(());1220		}12211222		Err(<CommonError<T>>::ApprovedValueTooLow.into())1223	}12241225	/// Transfer NFT token from one account to another.1226	///1227	/// Same as the [`transfer`] but spender doesn't needs to be the owner of the token.1228	/// The owner should set allowance for the spender to transfer token.1229	///1230	/// [`transfer`]: struct.Pallet.html#method.transfer1231	pub fn transfer_from(1232		collection: &NonfungibleHandle<T>,1233		spender: &T::CrossAccountId,1234		from: &T::CrossAccountId,1235		to: &T::CrossAccountId,1236		token: TokenId,1237		nesting_budget: &dyn Budget,1238	) -> DispatchResultWithPostInfo {1239		Self::check_allowed(collection, spender, from, token, nesting_budget)?;12401241		// =========12421243		// Allowance is reset in [`transfer`]1244		let mut result = Self::transfer(collection, from, to, token, nesting_budget);1245		add_weight_to_post_info(&mut result, <SelfWeightOf<T>>::check_allowed_raw());1246		result1247	}12481249	/// Burn NFT token for `from` account.1250	///1251	/// Same as the [`burn`] but spender doesn't need to be an owner of the token. The owner should1252	/// set allowance for the spender to burn token.1253	///1254	/// [`burn`]: struct.Pallet.html#method.burn1255	pub fn burn_from(1256		collection: &NonfungibleHandle<T>,1257		spender: &T::CrossAccountId,1258		from: &T::CrossAccountId,1259		token: TokenId,1260		nesting_budget: &dyn Budget,1261	) -> DispatchResult {1262		Self::check_allowed(collection, spender, from, token, nesting_budget)?;12631264		// =========12651266		Self::burn(collection, from, token)1267	}12681269	/// Check that `from` token could be nested in `under` token.1270	///1271	pub fn check_nesting(1272		handle: &NonfungibleHandle<T>,1273		sender: T::CrossAccountId,1274		from: (CollectionId, TokenId),1275		under: TokenId,1276		nesting_budget: &dyn Budget,1277	) -> DispatchResult {1278		let nesting = handle.permissions.nesting();12791280		#[cfg(not(feature = "runtime-benchmarks"))]1281		let permissive = false;1282		#[cfg(feature = "runtime-benchmarks")]1283		let permissive = nesting.permissive;12841285		if permissive {1286			ensure!(1287				<TokenData<T>>::contains_key((handle.id, under)),1288				<CommonError<T>>::TokenNotFound1289			);1290		} else if nesting.token_owner1291			&& <PalletStructure<T>>::check_indirectly_owned(1292				sender.clone(),1293				handle.id,1294				under,1295				Some(from),1296				nesting_budget,1297			)? {1298			// Pass, token existence and ouroboros checks are done in `check_indirectly_owned`1299		} else if nesting.collection_admin && handle.is_owner_or_admin(&sender) {1300			// token existence and ouroboros checks are done in `get_checked_topmost_owner`1301			let _ = <PalletStructure<T>>::get_checked_topmost_owner(1302				handle.id,1303				under,1304				Some(from),1305				nesting_budget,1306			)?1307			.ok_or(<CommonError<T>>::TokenNotFound)?;1308		} else {1309			fail!(<CommonError<T>>::UserIsNotAllowedToNest);1310		}13111312		if let Some(whitelist) = &nesting.restricted {1313			ensure!(1314				whitelist.contains(&from.0),1315				<CommonError<T>>::SourceCollectionIsNotAllowedToNest1316			);1317		}1318		Ok(())1319	}13201321	fn nest(under: (CollectionId, TokenId), to_nest: (CollectionId, TokenId)) {1322		if to_nest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1323			<TokenChildren<T>>::insert((under.0, under.1, to_nest), true);1324		}1325	}13261327	fn unnest(under: (CollectionId, TokenId), to_unnest: (CollectionId, TokenId)) {1328		if to_unnest.0 != pallet_common::NATIVE_FUNGIBLE_COLLECTION_ID {1329			<TokenChildren<T>>::remove((under.0, under.1, to_unnest));1330		}1331	}13321333	fn collection_has_tokens(collection_id: CollectionId) -> bool {1334		<TokenData<T>>::iter_prefix((collection_id,))1335			.next()1336			.is_some()1337	}13381339	fn token_has_children(collection_id: CollectionId, token_id: TokenId) -> bool {1340		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1341			.next()1342			.is_some()1343	}13441345	pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {1346		<TokenChildren<T>>::iter_prefix((collection_id, token_id))1347			.map(|((child_collection_id, child_id), _)| TokenChild {1348				collection: child_collection_id,1349				token: child_id,1350			})1351			.collect()1352	}13531354	/// Mint single NFT token.1355	///1356	/// Delegated to [`create_multiple_items`]1357	///1358	/// [`create_multiple_items`]: struct.Pallet.html#method.create_multiple_items1359	pub fn create_item(1360		collection: &NonfungibleHandle<T>,1361		sender: &T::CrossAccountId,1362		data: CreateItemData<T>,1363		nesting_budget: &dyn Budget,1364	) -> DispatchResult {1365		Self::create_multiple_items(collection, sender, vec![data], nesting_budget)1366	}13671368	/// Sets or unsets the approval of a given operator.1369	///1370	/// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1371	/// - `owner`: Token owner1372	/// - `operator`: Operator1373	/// - `approve`: Should operator status be granted or revoked?1374	pub fn set_allowance_for_all(1375		collection: &NonfungibleHandle<T>,1376		owner: &T::CrossAccountId,1377		operator: &T::CrossAccountId,1378		approve: bool,1379	) -> DispatchResult {1380		<PalletCommon<T>>::set_allowance_for_all(1381			collection,1382			owner,1383			operator,1384			approve,1385			|| <CollectionAllowance<T>>::insert((collection.id, owner, operator), approve),1386			ERC721Events::ApprovalForAll {1387				owner: *owner.as_eth(),1388				operator: *operator.as_eth(),1389				approved: approve,1390			}1391			.to_log(collection_id_to_address(collection.id)),1392		)1393	}13941395	/// Tells whether the given `owner` approves the `operator`.1396	pub fn allowance_for_all(1397		collection: &NonfungibleHandle<T>,1398		owner: &T::CrossAccountId,1399		operator: &T::CrossAccountId,1400	) -> bool {1401		<CollectionAllowance<T>>::get((collection.id, owner, operator))1402	}14031404	pub fn repair_item(collection: &NonfungibleHandle<T>, token: TokenId) -> DispatchResult {1405		<TokenProperties<T>>::mutate((collection.id, token), |properties| {1406			properties.recompute_consumed_space();1407		});14081409		Ok(())1410	}1411}
modifiedpallets/refungible/src/erc.rsdiffbeforeafterboth
--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -21,6 +21,7 @@
 
 extern crate alloc;
 
+use alloc::string::ToString;
 use core::{
 	char::{REPLACEMENT_CHARACTER, decode_utf16},
 	convert::TryInto,
@@ -353,8 +354,7 @@
 				.transpose()
 				.map_err(|e| {
 					Error::Revert(alloc::format!(
-						"Can not convert value \"baseURI\" to string with error \"{}\"",
-						e
+						"Can not convert value \"baseURI\" to string with error \"{e}\""
 					))
 				})?;
 
@@ -482,8 +482,8 @@
 			.recorder
 			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
-		let balance = balance(&self, token, &from)?;
-		ensure_single_owner(&self, token, balance)?;
+		let balance = balance(self, token, &from)?;
+		ensure_single_owner(self, token, balance)?;
 
 		<Pallet<T>>::transfer_from(self, &caller, &from, &to, token, balance, &budget)
 			.map_err(dispatch_to_evm::<T>)?;
@@ -575,8 +575,8 @@
 		let caller = T::CrossAccountId::from_eth(caller);
 		let token = token_id.try_into()?;
 
-		let balance = balance(&self, token, &caller)?;
-		ensure_single_owner(&self, token, balance)?;
+		let balance = balance(self, token, &caller)?;
+		ensure_single_owner(self, token, balance)?;
 
 		<Pallet<T>>::burn(self, &caller, token, balance).map_err(dispatch_to_evm::<T>)?;
 		Ok(())
@@ -622,7 +622,7 @@
 			return Err("item id should be next".into());
 		}
 
-		let users = [(to.clone(), 1)]
+		let users = [(to, 1)]
 			.into_iter()
 			.collect::<BTreeMap<_, _>>()
 			.try_into()
@@ -706,9 +706,9 @@
 					.try_into()
 					.map_err(|_| "token uri is too long")?,
 			})
-			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;
+			.map_err(|e| Error::Revert(alloc::format!("Can't add property: {e:?}")))?;
 
-		let users = [(to.clone(), 1)]
+		let users = [(to, 1)]
 			.into_iter()
 			.collect::<BTreeMap<_, _>>()
 			.try_into()
@@ -750,7 +750,7 @@
 		.map(Clone::clone)
 		.ok_or_else(|| {
 			let key = String::from_utf8(key.clone().into_inner()).unwrap_or_default();
-			Error::Revert(alloc::format!("No permission for key {}", key))
+			Error::Revert(alloc::format!("No permission for key {key}"))
 		})?;
 	Ok(a)
 }
@@ -785,14 +785,14 @@
 	/// @param tokenId Id for the token.
 	#[solidity(hide)]
 	fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {
-		Self::owner_of_cross(&self, token_id)
+		Self::owner_of_cross(self, token_id)
 	}
 
 	/// Returns the owner (in cross format) of the token.
 	///
 	/// @param tokenId Id for the token.
 	fn owner_of_cross(&self, token_id: U256) -> Result<eth::CrossAddress> {
-		Self::token_owner(&self, token_id.try_into()?)
+		Self::token_owner(self, token_id.try_into()?)
 			.map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))
 			.or_else(|err| match err {
 				TokenOwnerError::NotFound => Err(Error::Revert("token not found".into())),
@@ -827,7 +827,7 @@
 			.collect::<Result<Vec<_>>>()?;
 
 		<Self as CommonCollectionOperations<T>>::token_properties(
-			&self,
+			self,
 			token_id.try_into()?,
 			if keys.is_empty() { None } else { Some(keys) },
 		)
@@ -1004,7 +1004,7 @@
 			}
 			expected_index = expected_index.checked_add(1).ok_or("item id overflow")?;
 		}
-		let users = [(to.clone(), 1)]
+		let users = [(to, 1)]
 			.into_iter()
 			.collect::<BTreeMap<_, _>>()
 			.try_into()
@@ -1046,7 +1046,7 @@
 			.weight_calls_budget(<StructureWeight<T>>::find_parent());
 
 		let mut data = Vec::with_capacity(tokens.len());
-		let users: BoundedBTreeMap<_, _, _> = [(to.clone(), 1)]
+		let users: BoundedBTreeMap<_, _, _> = [(to, 1)]
 			.into_iter()
 			.collect::<BTreeMap<_, _>>()
 			.try_into()
@@ -1067,7 +1067,7 @@
 						.try_into()
 						.map_err(|_| "token uri is too long")?,
 				})
-				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {:?}", e)))?;
+				.map_err(|e| Error::Revert(alloc::format!("Can't add property: {e:?}")))?;
 
 			let create_item_data = CreateItemData::<T> {
 				users: users.clone(),
@@ -1103,7 +1103,7 @@
 			.map(eth::Property::try_into)
 			.collect::<Result<Vec<_>>>()?
 			.try_into()
-			.map_err(|_| Error::Revert(alloc::format!("too many properties")))?;
+			.map_err(|_| Error::Revert("too many properties".to_string()))?;
 
 		let caller = T::CrossAccountId::from_eth(caller);
 
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -1124,7 +1124,7 @@
 
 		if collection.ignores_token_restrictions(spender) {
 			return Ok(Self::compute_allowance_decrease(
-				collection, token, from, &spender, amount,
+				collection, token, from, spender, amount,
 			));
 		}
 
@@ -1143,7 +1143,7 @@
 			return Ok(None);
 		}
 
-		let allowance = Self::compute_allowance_decrease(collection, token, from, &spender, amount);
+		let allowance = Self::compute_allowance_decrease(collection, token, from, spender, amount);
 		if allowance.is_some() {
 			return Ok(allowance);
 		}
modifiedpallets/scheduler-v2/src/lib.rsdiffbeforeafterboth
--- a/pallets/scheduler-v2/src/lib.rs
+++ b/pallets/scheduler-v2/src/lib.rs
@@ -969,7 +969,7 @@
 		call: ScheduledCall<T>,
 	) -> Result<TaskAddress<T::BlockNumber>, DispatchError> {
 		// ensure id it is unique
-		if Lookup::<T>::contains_key(&id) {
+		if Lookup::<T>::contains_key(id) {
 			return Err(Error::<T>::FailedToSchedule.into());
 		}
 
modifiedpallets/structure/src/lib.rsdiffbeforeafterboth
--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -280,7 +280,7 @@
 	) -> DispatchResultWithPostInfo {
 		let dispatch = T::CollectionDispatch::dispatch(collection)?;
 		let dispatch = dispatch.as_dyn();
-		dispatch.burn_item_recursively(from.clone(), token, self_budget, breadth_budget)
+		dispatch.burn_item_recursively(from, token, self_budget, breadth_budget)
 	}
 
 	/// Check if `token` indirectly owned by `user`
@@ -396,7 +396,7 @@
 		account: &T::CrossAccountId,
 		action: impl FnOnce(&dyn CommonCollectionOperations<T>, TokenId) -> DispatchResult,
 	) -> DispatchResult {
-		if is_collection(&account.as_eth()) {
+		if is_collection(account.as_eth()) {
 			fail!(<Error<T>>::CantNestTokenUnderCollection);
 		}
 		let Some((collection, token)) = T::CrossTokenAddressMapping::address_to_token(account) else {
modifiedpallets/unique/src/eth/mod.rsdiffbeforeafterboth
--- a/pallets/unique/src/eth/mod.rs
+++ b/pallets/unique/src/eth/mod.rs
@@ -113,13 +113,9 @@
 	let collection_helpers_address =
 		T::CrossAccountId::from_eth(<T as pallet_common::Config>::ContractAddress::get());
 
-	let collection_id = T::CollectionDispatch::create(
-		caller.clone(),
-		collection_helpers_address,
-		data,
-		Default::default(),
-	)
-	.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
+	let collection_id =
+		T::CollectionDispatch::create(caller, collection_helpers_address, data, Default::default())
+			.map_err(pallet_evm_coder_substrate::dispatch_to_evm::<T>)?;
 	let address = pallet_common::eth::collection_id_to_address(collection_id);
 	Ok(address)
 }
@@ -132,8 +128,7 @@
 		.expect("Collection creation price should be convertible to u128");
 	if value != creation_price {
 		return Err(format!(
-			"Sent amount not equals to collection creation price ({0})",
-			creation_price
+			"Sent amount not equals to collection creation price ({creation_price})",
 		)
 		.into());
 	}
@@ -383,8 +378,7 @@
 		map_eth_to_id(&collection_address)
 			.map(|id| id.0)
 			.ok_or(Error::Revert(format!(
-				"failed to convert address {} into collectionId.",
-				collection_address
+				"failed to convert address {collection_address} into collectionId."
 			)))
 	}
 }
@@ -422,5 +416,5 @@
 generate_stubgen!(collection_helper_iface, CollectionHelpersCall<()>, false);
 
 fn error_field_too_long(feild: &str, bound: usize) -> Error {
-	Error::Revert(format!("{} is too long. Max length is {}.", feild, bound))
+	Error::Revert(format!("{feild} is too long. Max length is {bound}."))
 }
modifiedpallets/unique/src/lib.rsdiffbeforeafterboth
--- a/pallets/unique/src/lib.rs
+++ b/pallets/unique/src/lib.rs
@@ -507,7 +507,7 @@
 			let sender = T::CrossAccountId::from_sub(ensure_signed(origin)?);
 			let new_owner = T::CrossAccountId::from_sub(new_owner);
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
-			target_collection.change_owner(sender, new_owner.clone())
+			target_collection.change_owner(sender, new_owner)
 		}
 
 		/// Add an admin to a collection.
@@ -667,7 +667,7 @@
 		/// * `owner`: Address of the initial owner of the item.
 		/// * `data`: Token data describing the item to store on chain.
 		#[pallet::call_index(11)]
-		#[pallet::weight(T::CommonWeightInfo::create_item(&data))]
+		#[pallet::weight(T::CommonWeightInfo::create_item(data))]
 		pub fn create_item(
 			origin: OriginFor<T>,
 			collection_id: CollectionId,
@@ -701,7 +701,7 @@
 		/// * `owner`: Address of the initial owner of the tokens.
 		/// * `items_data`: Vector of data describing each item to be created.
 		#[pallet::call_index(12)]
-		#[pallet::weight(T::CommonWeightInfo::create_multiple_items(&items_data))]
+		#[pallet::weight(T::CommonWeightInfo::create_multiple_items(items_data))]
 		pub fn create_multiple_items(
 			origin: OriginFor<T>,
 			collection_id: CollectionId,
@@ -889,7 +889,7 @@
 		/// * `collection_id`: ID of the collection to which the tokens would belong.
 		/// * `data`: Explicit item creation data.
 		#[pallet::call_index(18)]
-		#[pallet::weight(T::CommonWeightInfo::create_multiple_items_ex(&data))]
+		#[pallet::weight(T::CommonWeightInfo::create_multiple_items_ex(data))]
 		pub fn create_multiple_items_ex(
 			origin: OriginFor<T>,
 			collection_id: CollectionId,
@@ -1313,7 +1313,7 @@
 			collection_id: CollectionId,
 		) -> DispatchResult {
 			let mut target_collection = <CollectionHandle<T>>::try_get(collection_id)?;
-			target_collection.force_set_sponsor(sponsor.clone())
+			target_collection.force_set_sponsor(sponsor)
 		}
 
 		/// Force remove `sponsor` for `collection`.
modifiedprimitives/data-structs/src/bounded.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/bounded.rs
+++ b/primitives/data-structs/src/bounded.rs
@@ -63,7 +63,7 @@
 	V: fmt::Debug,
 {
 	use core::fmt::Debug;
-	(&v as &Vec<V>).fmt(f)
+	(v as &Vec<V>).fmt(f)
 }
 
 #[cfg(feature = "serde1")]
@@ -114,7 +114,7 @@
 	V: fmt::Debug,
 {
 	use core::fmt::Debug;
-	(&v as &BTreeMap<K, V>).fmt(f)
+	(v as &BTreeMap<K, V>).fmt(f)
 }
 
 #[cfg(feature = "serde1")]
@@ -157,5 +157,5 @@
 	K: fmt::Debug + Ord,
 {
 	use core::fmt::Debug;
-	(&v as &BTreeSet<K>).fmt(f)
+	(v as &BTreeSet<K>).fmt(f)
 }
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -536,7 +536,7 @@
 	type Target = Vec<u8>;
 
 	fn deref(&self) -> &Self::Target {
-		return &self.0;
+		&self.0
 	}
 }
 
@@ -816,6 +816,11 @@
 		Self(Default::default())
 	}
 }
+impl Default for OwnerRestrictedSet {
+	fn default() -> Self {
+		Self::new()
+	}
+}
 impl core::ops::Deref for OwnerRestrictedSet {
 	type Target = OwnerRestrictedSetInner;
 	fn deref(&self) -> &Self::Target {
@@ -1098,9 +1103,9 @@
 	pub value: PropertyValue,
 }
 
-impl Into<(PropertyKey, PropertyValue)> for Property {
-	fn into(self) -> (PropertyKey, PropertyValue) {
-		(self.key, self.value)
+impl From<Property> for (PropertyKey, PropertyValue) {
+	fn from(value: Property) -> Self {
+		(value.key, value.value)
 	}
 }
 
@@ -1116,9 +1121,9 @@
 	pub permission: PropertyPermission,
 }
 
-impl Into<(PropertyKey, PropertyPermission)> for PropertyKeyPermission {
-	fn into(self) -> (PropertyKey, PropertyPermission) {
-		(self.key, self.permission)
+impl From<PropertyKeyPermission> for (PropertyKey, PropertyPermission) {
+	fn from(value: PropertyKeyPermission) -> Self {
+		(value.key, value.permission)
 	}
 }
 
@@ -1415,7 +1420,7 @@
 		value: Self::Value,
 	) -> Result<Option<Self::Value>, PropertiesError> {
 		let key_size = scoped_slice_size(scope, &key);
-		let value_size = slice_size(&value) as u32;
+		let value_size = slice_size(&value);
 
 		if self.consumed_space + value_size + key_size > S && !cfg!(feature = "runtime-benchmarks")
 		{
@@ -1425,7 +1430,7 @@
 		let old_value = self.map.try_scoped_set(scope, key, value)?;
 
 		if let Some(old_value) = old_value.as_ref() {
-			let old_value_size = slice_size(&old_value);
+			let old_value_size = slice_size(old_value);
 			self.consumed_space = self.consumed_space.saturating_sub(old_value_size) + value_size;
 		} else {
 			self.consumed_space += key_size + value_size;
modifiedruntime/common/config/xcm/foreignassets.rsdiffbeforeafterboth
--- a/runtime/common/config/xcm/foreignassets.rs
+++ b/runtime/common/config/xcm/foreignassets.rs
@@ -65,7 +65,7 @@
 			return ConvertAssetId::convert_ref(AssetIds::NativeAssetId(NativeCurrency::Here));
 		}
 
-		match XcmForeignAssetIdMapping::<Runtime>::get_currency_id(id.clone()) {
+		match XcmForeignAssetIdMapping::<Runtime>::get_currency_id(*id) {
 			Some(AssetIds::ForeignAssetId(foreign_asset_id)) => {
 				ConvertAssetId::convert_ref(AssetIds::ForeignAssetId(foreign_asset_id))
 			}
@@ -206,9 +206,7 @@
 			return Some(AssetIds::NativeAssetId(NativeCurrency::Parent));
 		}
 
-		if let Some(currency_id) =
-			XcmForeignAssetIdMapping::<Runtime>::get_currency_id(location.clone())
-		{
+		if let Some(currency_id) = XcmForeignAssetIdMapping::<Runtime>::get_currency_id(location) {
 			return Some(currency_id);
 		}
 
modifiedruntime/common/ethereum/precompiles/mod.rsdiffbeforeafterboth
--- a/runtime/common/ethereum/precompiles/mod.rs
+++ b/runtime/common/ethereum/precompiles/mod.rs
@@ -37,6 +37,16 @@
 		[hash(1), hash(20482)]
 	}
 }
+
+impl<R> Default for UniquePrecompiles<R>
+where
+	R: pallet_evm::Config,
+{
+	fn default() -> Self {
+		Self::new()
+	}
+}
+
 impl<R> PrecompileSet for UniquePrecompiles<R>
 where
 	R: pallet_evm::Config,
modifiedruntime/common/ethereum/precompiles/sr25519.rsdiffbeforeafterboth
--- a/runtime/common/ethereum/precompiles/sr25519.rs
+++ b/runtime/common/ethereum/precompiles/sr25519.rs
@@ -64,7 +64,7 @@
 
 		// Parse arguments
 		let public: sr25519::Public =
-			sr25519::Public::unchecked_from(input.read::<H256>(gasometer)?).into();
+			sr25519::Public::unchecked_from(input.read::<H256>(gasometer)?);
 		let signature_bytes: Vec<u8> = input.read::<Bytes>(gasometer)?.into();
 		let message: Vec<u8> = input.read::<Bytes>(gasometer)?.into();
 
modifiedruntime/common/ethereum/precompiles/utils/data.rsdiffbeforeafterboth
--- a/runtime/common/ethereum/precompiles/utils/data.rs
+++ b/runtime/common/ethereum/precompiles/utils/data.rs
@@ -60,7 +60,7 @@
 }
 
 impl Into<Vec<u8>> for Bytes {
-	fn into(self: Self) -> Vec<u8> {
+	fn into(self) -> Vec<u8> {
 		self.0
 	}
 }
modifiedruntime/common/ethereum/precompiles/utils/mod.rsdiffbeforeafterboth
--- a/runtime/common/ethereum/precompiles/utils/mod.rs
+++ b/runtime/common/ethereum/precompiles/utils/mod.rs
@@ -73,7 +73,6 @@
 		}
 	}
 
-	#[must_use]
 	/// Check that a function call is compatible with the context it is
 	/// called into.
 	pub fn check_function_modifier(
modifiedruntime/common/ethereum/sponsoring.rsdiffbeforeafterboth
--- a/runtime/common/ethereum/sponsoring.rs
+++ b/runtime/common/ethereum/sponsoring.rs
@@ -78,7 +78,7 @@
 							let token_id: TokenId = token_id.try_into().ok()?;
 							withdraw_set_token_property::<T>(
 								&collection,
-								&who,
+								who,
 								&token_id,
 								key.len() + value.len(),
 							)
@@ -88,7 +88,7 @@
 							ERC721UniqueExtensionsCall::Transfer { token_id, .. },
 						) => {
 							let token_id: TokenId = token_id.try_into().ok()?;
-							withdraw_transfer::<T>(&collection, &who, &token_id).map(|()| sponsor)
+							withdraw_transfer::<T>(&collection, who, &token_id).map(|()| sponsor)
 						}
 						UniqueNFTCall::ERC721UniqueMintable(
 							ERC721UniqueMintableCall::Mint { .. }
@@ -97,7 +97,7 @@
 							| ERC721UniqueMintableCall::MintWithTokenUriCheckId { .. },
 						) => withdraw_create_item::<T>(
 							&collection,
-							&who,
+							who,
 							&CreateItemData::NFT(CreateNftData::default()),
 						)
 						.map(|()| sponsor),
modifiedruntime/common/ethereum/sponsoring/refungible.rsdiffbeforeafterboth
--- a/runtime/common/ethereum/sponsoring/refungible.rs
+++ b/runtime/common/ethereum/sponsoring/refungible.rs
@@ -16,7 +16,6 @@
 
 //! Implements EVM sponsoring logic via TransactionValidityHack
 
-use core::convert::TryInto;
 use pallet_common::CollectionHandle;
 use pallet_evm::account::CrossAccountId;
 use pallet_fungible::Config as FungibleConfig;
@@ -95,7 +94,7 @@
 			..
 		} => {
 			let token_id = TokenId::try_from(token_id).ok()?;
-			withdraw_set_token_property::<T>(&collection, &who, &token_id, key.len() + value.len())
+			withdraw_set_token_property::<T>(&collection, who, &token_id, key.len() + value.len())
 		}
 	}
 }
@@ -242,7 +241,7 @@
 
 			MintCross { .. } => withdraw_create_item::<T>(
 				&collection,
-				&who,
+				who,
 				&CreateItemData::NFT(CreateNftData::default()),
 			),
 
@@ -250,7 +249,7 @@
 			| TransferFromCross { token_id, .. }
 			| Transfer { token_id, .. } => {
 				let token_id = TokenId::try_from(token_id).ok()?;
-				withdraw_transfer::<T>(&collection, &who, &token_id)
+				withdraw_transfer::<T>(&collection, who, &token_id)
 			}
 		}
 	}
@@ -275,7 +274,7 @@
 			| MintWithTokenUri { .. }
 			| MintWithTokenUriCheckId { .. } => withdraw_create_item::<T>(
 				&collection,
-				&who,
+				who,
 				&CreateItemData::NFT(CreateNftData::default()),
 			),
 		}
@@ -311,18 +310,15 @@
 
 			Transfer { .. } => {
 				let RefungibleTokenHandle(handle, token_id) = token;
-				let token_id = token_id.try_into().ok()?;
-				withdraw_transfer::<T>(&handle, &who, &token_id)
+				withdraw_transfer::<T>(&handle, who, &token_id)
 			}
 			TransferFrom { from, .. } => {
 				let RefungibleTokenHandle(handle, token_id) = token;
-				let token_id = token_id.try_into().ok()?;
 				let from = T::CrossAccountId::from_eth(from);
 				withdraw_transfer::<T>(&handle, &from, &token_id)
 			}
 			Approve { .. } => {
 				let RefungibleTokenHandle(handle, token_id) = token;
-				let token_id = token_id.try_into().ok()?;
 				withdraw_approve::<T>(&handle, who.as_sub(), &token_id)
 			}
 		}
@@ -351,13 +347,11 @@
 
 			TransferCross { .. } | TransferFromCross { .. } => {
 				let RefungibleTokenHandle(handle, token_id) = token;
-				let token_id = token_id.try_into().ok()?;
-				withdraw_transfer::<T>(&handle, &who, &token_id)
+				withdraw_transfer::<T>(&handle, who, &token_id)
 			}
 
 			ApproveCross { .. } => {
 				let RefungibleTokenHandle(handle, token_id) = token;
-				let token_id = token_id.try_into().ok()?;
 				withdraw_approve::<T>(&handle, who.as_sub(), &token_id)
 			}
 		}
modifiedruntime/common/mod.rsdiffbeforeafterboth
--- a/runtime/common/mod.rs
+++ b/runtime/common/mod.rs
@@ -204,10 +204,7 @@
 				&[],
 			);
 
-			let should_upgrade = match version {
-				None => true,
-				Some(_) => false,
-			};
+			let should_upgrade = version.is_none();
 
 			if should_upgrade {
 				log::info!(
@@ -220,7 +217,7 @@
 					.cloned()
 					.filter_map(|authority_id| {
 						weight.saturating_accrue(<Runtime as frame_system::Config>::DbWeight::get().reads_writes(1, 1));
-						let vec = authority_id.clone().to_raw_vec();
+						let vec = authority_id.to_raw_vec();
 						let slice = vec.as_slice();
 						let array: Option<[u8; 32]> = match slice.try_into() {
 							Ok(a) => Some(a),
@@ -248,20 +245,20 @@
 					.into_iter()
 					.map(|(acc, aura)| {
 						(
-							acc.clone(),                        // account id
-							acc,                                // validator id
-							SessionKeys { aura: aura.clone() }, // session keys
+							acc.clone(),          // account id
+							acc,                  // validator id
+							SessionKeys { aura }, // session keys
 						)
 					})
 					.collect::<Vec<_>>();
 
-				for (account, val, keys) in keys.iter().cloned() {
+				for (account, val, keys) in keys.iter() {
 					for id in <Runtime as pallet_session::Config>::Keys::key_ids() {
-						<pallet_session::KeyOwner<Runtime>>::insert((*id, keys.get_raw(*id)), &val)
+						<pallet_session::KeyOwner<Runtime>>::insert((*id, keys.get_raw(*id)), val)
 					}
-					<pallet_session::NextKeys<Runtime>>::insert(&val, &keys);
+					<pallet_session::NextKeys<Runtime>>::insert(val, keys);
 					// todo exercise caution, the following is taken from genesis
-					if frame_system::Pallet::<Runtime>::inc_consumers_without_limit(&account)
+					if frame_system::Pallet::<Runtime>::inc_consumers_without_limit(account)
 						.is_err()
 					{
 						log::warn!(
@@ -271,7 +268,7 @@
 						// genesis) so it's really not a big deal and we assume that the user wants to
 						// do this since it's the only way a non-endowed account can contain a session
 						// key.
-						frame_system::Pallet::<Runtime>::inc_providers(&account);
+						frame_system::Pallet::<Runtime>::inc_providers(account);
 					}
 				}
 
modifiedruntime/common/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -84,7 +84,7 @@
                 fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
                     let budget = up_data_structs::budget::Value::new(10);
 
-                    Ok(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?)
+                    <pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)
                 }
                 fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>, DispatchError> {
                     Ok(<pallet_nonfungible::Pallet<Runtime>>::token_children_ids(collection, token))
modifiedruntime/common/sponsoring.rsdiffbeforeafterboth
--- a/runtime/common/sponsoring.rs
+++ b/runtime/common/sponsoring.rs
@@ -240,7 +240,7 @@
 				withdraw_set_token_property(
 					&collection,
 					&T::CrossAccountId::from_sub(who.clone()),
-					&token_id,
+					token_id,
 					// No overflow may happen, as data larger than usize can't reach here
 					properties.iter().map(|p| p.key.len() + p.value.len()).sum(),
 				)
modifiedtest-pallets/utils/src/lib.rsdiffbeforeafterboth
--- a/test-pallets/utils/src/lib.rs
+++ b/test-pallets/utils/src/lib.rs
@@ -170,7 +170,7 @@
 	fn ensure_origin_and_enabled(origin: OriginFor<T>) -> DispatchResult {
 		ensure_signed(origin)?;
 		<Enabled<T>>::get()
-			.then(|| ())
+			.then_some(())
 			.ok_or(<Error<T>>::TestPalletDisabled.into())
 	}
 }