difftreelog
fix clippy warnings
in: master
36 files changed
node/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()
}
node/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);
node/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 =
node/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,
)
pallets/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
pallets/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),
}
}
}
pallets/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
pallets/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,
}
pallets/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:?}")),
}
}
pallets/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(())
}
pallets/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(())
}
}
pallets/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 {
pallets/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)
}
}
}
pallets/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(())
},
)
pallets/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")?,
))
}
}
pallets/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);
pallets/nonfungible/src/lib.rsdiffbeforeafterboth1// 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}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}pallets/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);
pallets/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);
}
pallets/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());
}
pallets/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 {
pallets/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}."))
}
pallets/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`.
primitives/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)
}
primitives/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;
runtime/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);
}
runtime/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,
runtime/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();
runtime/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
}
}
runtime/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(
runtime/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),
runtime/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)
}
}
runtime/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);
}
}
runtime/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))
runtime/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(),
)
test-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())
}
}