difftreelog
Revert "fix zero transfer"
in: master
This reverts commit 2880b7f76c032798bcf34e0b1b79ca02a267e201.
13 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5901,7 +5901,7 @@
[[package]]
name = "pallet-common"
-version = "0.1.13"
+version = "0.1.12"
dependencies = [
"ethereum",
"evm-coder",
@@ -6191,7 +6191,7 @@
[[package]]
name = "pallet-fungible"
-version = "0.1.8"
+version = "0.1.7"
dependencies = [
"ethereum",
"evm-coder",
@@ -6446,7 +6446,7 @@
[[package]]
name = "pallet-nonfungible"
-version = "0.1.10"
+version = "0.1.9"
dependencies = [
"ethereum",
"evm-coder",
@@ -6568,7 +6568,7 @@
[[package]]
name = "pallet-refungible"
-version = "0.2.9"
+version = "0.2.8"
dependencies = [
"derivative",
"ethereum",
pallets/common/CHANGELOG.mddiffbeforeafterboth--- a/pallets/common/CHANGELOG.md
+++ b/pallets/common/CHANGELOG.md
@@ -4,12 +4,6 @@
<!-- bureaucrate goes here -->
-## [0.1.13] - 2022-12-05
-
-### Added
-
-- The error `ZeroTransferNotAllowed` to handling transactions with the transfer of a zero amount of tokens.
-
## [0.1.12] - 2022-11-16
### Changed
pallets/common/Cargo.tomldiffbeforeafterboth--- a/pallets/common/Cargo.toml
+++ b/pallets/common/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-common"
-version = "0.1.13"
+version = "0.1.12"
license = "GPLv3"
edition = "2021"
pallets/common/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//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63 ensure,64 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65 dispatch::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 RpcCollectionFlags,75 CollectionId,76 CreateItemData,77 MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT,79 TokenId,80 TokenChild,81 CollectionStats,82 MAX_TOKEN_OWNERSHIP,83 CollectionMode,84 NFT_SPONSOR_TRANSFER_TIMEOUT,85 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87 MAX_SPONSOR_TIMEOUT,88 CUSTOM_DATA_LIMIT,89 CollectionLimits,90 CreateCollectionData,91 SponsorshipState,92 CreateItemExData,93 SponsoringRateLimit,94 budget::Budget,95 PhantomType,96 Property,97 Properties,98 PropertiesPermissionMap,99 PropertyKey,100 PropertyValue,101 PropertyPermission,102 PropertiesError,103 PropertyKeyPermission,104 TokenData,105 TrySetProperty,106 PropertyScope,107 // RMRK108 RmrkCollectionInfo,109 RmrkInstanceInfo,110 RmrkResourceInfo,111 RmrkPropertyInfo,112 RmrkBaseInfo,113 RmrkPartType,114 RmrkBoundedTheme,115 RmrkNftChild,116 CollectionPermissions,117};118119pub use pallet::*;120use sp_core::H160;121use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};122#[cfg(feature = "runtime-benchmarks")]123pub mod benchmarking;124pub mod dispatch;125pub mod erc;126pub mod eth;127pub mod weights;128129/// Weight info.130pub type SelfWeightOf<T> = <T as Config>::WeightInfo;131132/// Collection handle contains information about collection data and id.133/// Also provides functionality to count consumed gas.134///135/// CollectionHandle is used as a generic wrapper for collections of all types.136/// It allows to perform common operations and queries on any collection type,137/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].138#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]139pub struct CollectionHandle<T: Config> {140 /// Collection id141 pub id: CollectionId,142 collection: Collection<T::AccountId>,143 /// Substrate recorder for counting consumed gas144 pub recorder: SubstrateRecorder<T>,145}146147impl<T: Config> WithRecorder<T> for CollectionHandle<T> {148 fn recorder(&self) -> &SubstrateRecorder<T> {149 &self.recorder150 }151 fn into_recorder(self) -> SubstrateRecorder<T> {152 self.recorder153 }154}155156impl<T: Config> CollectionHandle<T> {157 /// Same as [CollectionHandle::new] but with an explicit gas limit.158 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {159 <CollectionById<T>>::get(id).map(|collection| Self {160 id,161 collection,162 recorder: SubstrateRecorder::new(gas_limit),163 })164 }165166 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].167 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {168 <CollectionById<T>>::get(id).map(|collection| Self {169 id,170 collection,171 recorder,172 })173 }174175 /// Retrives collection data from storage and creates collection handle with default parameters.176 /// If collection not found return `None`177 pub fn new(id: CollectionId) -> Option<Self> {178 Self::new_with_gas_limit(id, u64::MAX)179 }180181 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.182 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {183 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)184 }185186 /// Consume gas for reading.187 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {188 self.recorder189 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(190 <T as frame_system::Config>::DbWeight::get()191 .read192 .saturating_mul(reads),193 )))194 }195196 /// Consume gas for writing.197 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {198 self.recorder199 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(200 <T as frame_system::Config>::DbWeight::get()201 .write202 .saturating_mul(writes),203 )))204 }205206 /// Consume gas for reading and writing.207 pub fn consume_store_reads_and_writes(208 &self,209 reads: u64,210 writes: u64,211 ) -> evm_coder::execution::Result<()> {212 let weight = <T as frame_system::Config>::DbWeight::get();213 let reads = weight.read.saturating_mul(reads);214 let writes = weight.read.saturating_mul(writes);215 self.recorder216 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(217 reads.saturating_add(writes),218 )))219 }220221 /// Save collection to storage.222 pub fn save(&self) -> DispatchResult {223 <CollectionById<T>>::insert(self.id, &self.collection);224 Ok(())225 }226227 /// Set collection sponsor.228 ///229 /// Unique collections allows sponsoring for certain actions.230 /// This method allows you to set the sponsor of the collection.231 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].232 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {233 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);234 Ok(())235 }236237 /// Confirm sponsorship238 ///239 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.240 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].241 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {242 if self.collection.sponsorship.pending_sponsor() != Some(sender) {243 return Ok(false);244 }245246 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());247 Ok(true)248 }249250 /// Remove collection sponsor.251 pub fn remove_sponsor(&mut self) -> DispatchResult {252 self.collection.sponsorship = SponsorshipState::Disabled;253 Ok(())254 }255256 /// Checks that the collection was created with, and must be operated upon through **Unique API**.257 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.258 pub fn check_is_internal(&self) -> DispatchResult {259 if self.flags.external {260 return Err(<Error<T>>::CollectionIsExternal)?;261 }262263 Ok(())264 }265266 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.267 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.268 pub fn check_is_external(&self) -> DispatchResult {269 if !self.flags.external {270 return Err(<Error<T>>::CollectionIsInternal)?;271 }272273 Ok(())274 }275}276277impl<T: Config> Deref for CollectionHandle<T> {278 type Target = Collection<T::AccountId>;279280 fn deref(&self) -> &Self::Target {281 &self.collection282 }283}284285impl<T: Config> DerefMut for CollectionHandle<T> {286 fn deref_mut(&mut self) -> &mut Self::Target {287 &mut self.collection288 }289}290291impl<T: Config> CollectionHandle<T> {292 /// Checks if the `user` is the owner of the collection.293 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {294 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);295 Ok(())296 }297298 /// Returns **true** if the `user` is the owner or administrator of the collection.299 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {300 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))301 }302303 /// Checks if the `user` is the owner or administrator of the collection.304 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {305 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);306 Ok(())307 }308309 /// Return **true** if `user` was not allowed to have tokens, and he can ignore such restrictions.310 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {311 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)312 }313314 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.315 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {316 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)317 }318319 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.320 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {321 ensure!(322 <Allowlist<T>>::get((self.id, user)),323 <Error<T>>::AddressNotInAllowlist324 );325 Ok(())326 }327328 /// Changes collection owner to another account329 /// #### Store read/writes330 /// 1 writes331 fn set_owner_internal(332 &mut self,333 caller: T::CrossAccountId,334 new_owner: T::CrossAccountId,335 ) -> DispatchResult {336 self.check_is_owner(&caller)?;337 self.collection.owner = new_owner.as_sub().clone();338 self.save()339 }340}341342#[frame_support::pallet]343pub mod pallet {344 use super::*;345 use dispatch::CollectionDispatch;346 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};347 use frame_system::pallet_prelude::*;348 use frame_support::traits::Currency;349 use up_data_structs::{TokenId, mapping::TokenAddressMapping};350 use scale_info::TypeInfo;351 use weights::WeightInfo;352353 #[pallet::config]354 pub trait Config:355 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo356 {357 /// Weight information for functions of this pallet.358 type WeightInfo: WeightInfo;359360 /// Events compatible with [`frame_system::Config::Event`].361 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;362363 /// Handler of accounts and payment.364 type Currency: Currency<Self::AccountId>;365366 /// Set price to create a collection.367 #[pallet::constant]368 type CollectionCreationPrice: Get<369 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,370 >;371372 /// Dispatcher of operations on collections.373 type CollectionDispatch: CollectionDispatch<Self>;374375 /// Account which holds the chain's treasury.376 type TreasuryAccountId: Get<Self::AccountId>;377378 /// Address under which the CollectionHelper contract would be available.379 #[pallet::constant]380 type ContractAddress: Get<H160>;381382 /// Mapper for token addresses to Ethereum addresses.383 type EvmTokenAddressMapping: TokenAddressMapping<H160>;384385 /// Mapper for token addresses to [`CrossAccountId`].386 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;387 }388389 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);390391 #[pallet::pallet]392 #[pallet::storage_version(STORAGE_VERSION)]393 #[pallet::generate_store(pub(super) trait Store)]394 pub struct Pallet<T>(_);395396 #[pallet::extra_constants]397 impl<T: Config> Pallet<T> {398 /// Maximum admins per collection.399 pub fn collection_admins_limit() -> u32 {400 COLLECTION_ADMINS_LIMIT401 }402 }403404 #[pallet::event]405 #[pallet::generate_deposit(pub fn deposit_event)]406 pub enum Event<T: Config> {407 /// New collection was created408 CollectionCreated(409 /// Globally unique identifier of newly created collection.410 CollectionId,411 /// [`CollectionMode`] converted into _u8_.412 u8,413 /// Collection owner.414 T::AccountId,415 ),416417 /// New collection was destroyed418 CollectionDestroyed(419 /// Globally unique identifier of collection.420 CollectionId,421 ),422423 /// New item was created.424 ItemCreated(425 /// Id of the collection where item was created.426 CollectionId,427 /// Id of an item. Unique within the collection.428 TokenId,429 /// Owner of newly created item430 T::CrossAccountId,431 /// Always 1 for NFT432 u128,433 ),434435 /// Collection item was burned.436 ItemDestroyed(437 /// Id of the collection where item was destroyed.438 CollectionId,439 /// Identifier of burned NFT.440 TokenId,441 /// Which user has destroyed its tokens.442 T::CrossAccountId,443 /// Amount of token pieces destroed. Always 1 for NFT.444 u128,445 ),446447 /// Item was transferred448 Transfer(449 /// Id of collection to which item is belong.450 CollectionId,451 /// Id of an item.452 TokenId,453 /// Original owner of item.454 T::CrossAccountId,455 /// New owner of item.456 T::CrossAccountId,457 /// Amount of token pieces transfered. Always 1 for NFT.458 u128,459 ),460461 /// Amount pieces of token owned by `sender` was approved for `spender`.462 Approved(463 /// Id of collection to which item is belong.464 CollectionId,465 /// Id of an item.466 TokenId,467 /// Original owner of item.468 T::CrossAccountId,469 /// Id for which the approval was granted.470 T::CrossAccountId,471 /// Amount of token pieces transfered. Always 1 for NFT.472 u128,473 ),474475 /// The colletion property has been added or edited.476 CollectionPropertySet(477 /// Id of collection to which property has been set.478 CollectionId,479 /// The property that was set.480 PropertyKey,481 ),482483 /// The property has been deleted.484 CollectionPropertyDeleted(485 /// Id of collection to which property has been deleted.486 CollectionId,487 /// The property that was deleted.488 PropertyKey,489 ),490491 /// The token property has been added or edited.492 TokenPropertySet(493 /// Identifier of the collection whose token has the property set.494 CollectionId,495 /// The token for which the property was set.496 TokenId,497 /// The property that was set.498 PropertyKey,499 ),500501 /// The token property has been deleted.502 TokenPropertyDeleted(503 /// Identifier of the collection whose token has the property deleted.504 CollectionId,505 /// The token for which the property was deleted.506 TokenId,507 /// The property that was deleted.508 PropertyKey,509 ),510511 /// The token property permission of a collection has been set.512 PropertyPermissionSet(513 /// ID of collection to which property permission has been set.514 CollectionId,515 /// The property permission that was set.516 PropertyKey,517 ),518 }519520 #[pallet::error]521 pub enum Error<T> {522 /// This collection does not exist.523 CollectionNotFound,524 /// Sender parameter and item owner must be equal.525 MustBeTokenOwner,526 /// No permission to perform action527 NoPermission,528 /// Destroying only empty collections is allowed529 CantDestroyNotEmptyCollection,530 /// Collection is not in mint mode.531 PublicMintingNotAllowed,532 /// Address is not in allow list.533 AddressNotInAllowlist,534535 /// Collection name can not be longer than 63 char.536 CollectionNameLimitExceeded,537 /// Collection description can not be longer than 255 char.538 CollectionDescriptionLimitExceeded,539 /// Token prefix can not be longer than 15 char.540 CollectionTokenPrefixLimitExceeded,541 /// Total collections bound exceeded.542 TotalCollectionsLimitExceeded,543 /// Exceeded max admin count544 CollectionAdminCountExceeded,545 /// Collection limit bounds per collection exceeded546 CollectionLimitBoundsExceeded,547 /// Tried to enable permissions which are only permitted to be disabled548 OwnerPermissionsCantBeReverted,549 /// Collection settings not allowing items transferring550 TransferNotAllowed,551 /// Account token limit exceeded per collection552 AccountTokenLimitExceeded,553 /// Collection token limit exceeded554 CollectionTokenLimitExceeded,555 /// Metadata flag frozen556 MetadataFlagFrozen,557558 /// Item does not exist559 TokenNotFound,560 /// Item is balance not enough561 TokenValueTooLow,562 /// Requested value is more than the approved563 ApprovedValueTooLow,564 /// Tried to approve more than owned565 CantApproveMoreThanOwned,566567 /// Can't transfer tokens to ethereum zero address568 AddressIsZero,569570 /// The operation is not supported571 UnsupportedOperation,572573 /// Insufficient funds to perform an action574 NotSufficientFounds,575576 /// User does not satisfy the nesting rule577 UserIsNotAllowedToNest,578 /// Only tokens from specific collections may nest tokens under this one579 SourceCollectionIsNotAllowedToNest,580581 /// Tried to store more data than allowed in collection field582 CollectionFieldSizeExceeded,583584 /// Tried to store more property data than allowed585 NoSpaceForProperty,586587 /// Tried to store more property keys than allowed588 PropertyLimitReached,589590 /// Property key is too long591 PropertyKeyIsTooLong,592593 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed594 InvalidCharacterInPropertyKey,595596 /// Empty property keys are forbidden597 EmptyPropertyKey,598599 /// Tried to access an external collection with an internal API600 CollectionIsExternal,601602 /// Tried to access an internal collection with an external API603 CollectionIsInternal,604605 /// Transfer operation with zero amount606 ZeroTransferNotAllowed,607 }608609 /// Storage of the count of created collections. Essentially contains the last collection ID.610 #[pallet::storage]611 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;612613 /// Storage of the count of deleted collections.614 #[pallet::storage]615 pub type DestroyedCollectionCount<T> =616 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;617618 /// Storage of collection info.619 #[pallet::storage]620 pub type CollectionById<T> = StorageMap<621 Hasher = Blake2_128Concat,622 Key = CollectionId,623 Value = Collection<<T as frame_system::Config>::AccountId>,624 QueryKind = OptionQuery,625 >;626627 /// Storage of collection properties.628 #[pallet::storage]629 #[pallet::getter(fn collection_properties)]630 pub type CollectionProperties<T> = StorageMap<631 Hasher = Blake2_128Concat,632 Key = CollectionId,633 Value = Properties,634 QueryKind = ValueQuery,635 OnEmpty = up_data_structs::CollectionProperties,636 >;637638 /// Storage of token property permissions of a collection.639 #[pallet::storage]640 #[pallet::getter(fn property_permissions)]641 pub type CollectionPropertyPermissions<T> = StorageMap<642 Hasher = Blake2_128Concat,643 Key = CollectionId,644 Value = PropertiesPermissionMap,645 QueryKind = ValueQuery,646 >;647648 /// Storage of the amount of collection admins.649 #[pallet::storage]650 pub type AdminAmount<T> = StorageMap<651 Hasher = Blake2_128Concat,652 Key = CollectionId,653 Value = u32,654 QueryKind = ValueQuery,655 >;656657 /// List of collection admins.658 #[pallet::storage]659 pub type IsAdmin<T: Config> = StorageNMap<660 Key = (661 Key<Blake2_128Concat, CollectionId>,662 Key<Blake2_128Concat, T::CrossAccountId>,663 ),664 Value = bool,665 QueryKind = ValueQuery,666 >;667668 /// Allowlisted collection users.669 #[pallet::storage]670 pub type Allowlist<T: Config> = StorageNMap<671 Key = (672 Key<Blake2_128Concat, CollectionId>,673 Key<Blake2_128Concat, T::CrossAccountId>,674 ),675 Value = bool,676 QueryKind = ValueQuery,677 >;678679 /// Not used by code, exists only to provide some types to metadata.680 #[pallet::storage]681 pub type DummyStorageValue<T: Config> = StorageValue<682 Value = (683 CollectionStats,684 CollectionId,685 TokenId,686 TokenChild,687 PhantomType<(688 TokenData<T::CrossAccountId>,689 RpcCollection<T::AccountId>,690 // RMRK691 RmrkCollectionInfo<T::AccountId>,692 RmrkInstanceInfo<T::AccountId>,693 RmrkResourceInfo,694 RmrkPropertyInfo,695 RmrkBaseInfo<T::AccountId>,696 RmrkPartType,697 RmrkBoundedTheme,698 RmrkNftChild,699 )>,700 ),701 QueryKind = OptionQuery,702 >;703704 #[pallet::hooks]705 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {706 fn on_runtime_upgrade() -> Weight {707 StorageVersion::new(1).put::<Pallet<T>>();708709 Weight::zero()710 }711 }712}713714impl<T: Config> Pallet<T> {715 /// Enshure that receiver address is correct.716 ///717 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.718 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {719 ensure!(720 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,721 <Error<T>>::AddressIsZero722 );723 Ok(())724 }725726 /// Get a vector of collection admins.727 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {728 <IsAdmin<T>>::iter_prefix((collection,))729 .map(|(a, _)| a)730 .collect()731 }732733 /// Get a vector of users allowed to mint tokens.734 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {735 <Allowlist<T>>::iter_prefix((collection,))736 .map(|(a, _)| a)737 .collect()738 }739740 /// Is `user` allowed to mint token in `collection`.741 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {742 <Allowlist<T>>::get((collection, user))743 }744745 /// Get statistics of collections.746 pub fn collection_stats() -> CollectionStats {747 let created = <CreatedCollectionCount<T>>::get();748 let destroyed = <DestroyedCollectionCount<T>>::get();749 CollectionStats {750 created: created.0,751 destroyed: destroyed.0,752 alive: created.0 - destroyed.0,753 }754 }755756 /// Get the effective limits for the collection.757 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {758 let collection = <CollectionById<T>>::get(collection)?;759 let limits = collection.limits;760 let effective_limits = CollectionLimits {761 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),762 sponsored_data_size: Some(limits.sponsored_data_size()),763 sponsored_data_rate_limit: Some(764 limits765 .sponsored_data_rate_limit766 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),767 ),768 token_limit: Some(limits.token_limit()),769 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(770 match collection.mode {771 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,772 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,773 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,774 },775 )),776 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),777 owner_can_transfer: Some(limits.owner_can_transfer()),778 owner_can_destroy: Some(limits.owner_can_destroy()),779 transfers_enabled: Some(limits.transfers_enabled()),780 };781782 Some(effective_limits)783 }784785 /// Returns information about the `collection` adapted for rpc.786 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {787 let Collection {788 name,789 description,790 owner,791 mode,792 token_prefix,793 sponsorship,794 limits,795 permissions,796 flags,797 } = <CollectionById<T>>::get(collection)?;798799 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)800 .into_iter()801 .map(|(key, permission)| PropertyKeyPermission { key, permission })802 .collect();803804 let properties = <CollectionProperties<T>>::get(collection)805 .into_iter()806 .map(|(key, value)| Property { key, value })807 .collect();808809 let permissions = CollectionPermissions {810 access: Some(permissions.access()),811 mint_mode: Some(permissions.mint_mode()),812 nesting: Some(permissions.nesting().clone()),813 };814815 Some(RpcCollection {816 name: name.into_inner(),817 description: description.into_inner(),818 owner,819 mode,820 token_prefix: token_prefix.into_inner(),821 sponsorship,822 limits,823 permissions,824 token_property_permissions,825 properties,826 read_only: flags.external,827828 flags: RpcCollectionFlags {829 foreign: flags.foreign,830 erc721metadata: flags.erc721metadata,831 },832 })833 }834}835836macro_rules! limit_default {837 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{838 $(839 if let Some($new) = $new.$field {840 let $old = $old.$field($($arg)?);841 let _ = $new;842 let _ = $old;843 $check844 } else {845 $new.$field = $old.$field846 }847 )*848 }};849}850macro_rules! limit_default_clone {851 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{852 $(853 if let Some($new) = $new.$field.clone() {854 let $old = $old.$field($($arg)?);855 let _ = $new;856 let _ = $old;857 $check858 } else {859 $new.$field = $old.$field.clone()860 }861 )*862 }};863}864865impl<T: Config> Pallet<T> {866 /// Create new collection.867 ///868 /// * `owner` - The owner of the collection.869 /// * `data` - Description of the created collection.870 /// * `flags` - Extra flags to store.871 pub fn init_collection(872 owner: T::CrossAccountId,873 payer: T::CrossAccountId,874 data: CreateCollectionData<T::AccountId>,875 flags: CollectionFlags,876 ) -> Result<CollectionId, DispatchError> {877 {878 ensure!(879 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,880 Error::<T>::CollectionTokenPrefixLimitExceeded881 );882 }883884 let created_count = <CreatedCollectionCount<T>>::get()885 .0886 .checked_add(1)887 .ok_or(ArithmeticError::Overflow)?;888 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;889 let id = CollectionId(created_count);890891 // bound Total number of collections892 ensure!(893 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,894 <Error<T>>::TotalCollectionsLimitExceeded895 );896897 // =========898899 let collection = Collection {900 owner: owner.as_sub().clone(),901 name: data.name,902 mode: data.mode.clone(),903 description: data.description,904 token_prefix: data.token_prefix,905 sponsorship: data906 .pending_sponsor907 .map(SponsorshipState::Unconfirmed)908 .unwrap_or_default(),909 limits: data910 .limits911 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))912 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,913 permissions: data914 .permissions915 .map(|permissions| {916 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)917 })918 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,919 flags,920 };921922 let mut collection_properties = up_data_structs::CollectionProperties::get();923 collection_properties924 .try_set_from_iter(data.properties.into_iter())925 .map_err(<Error<T>>::from)?;926927 CollectionProperties::<T>::insert(id, collection_properties);928929 let mut token_props_permissions = PropertiesPermissionMap::new();930 token_props_permissions931 .try_set_from_iter(data.token_property_permissions.into_iter())932 .map_err(<Error<T>>::from)?;933934 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);935936 // Take a (non-refundable) deposit of collection creation937 {938 let mut imbalance =939 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();940 imbalance.subsume(941 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(942 &T::TreasuryAccountId::get(),943 T::CollectionCreationPrice::get(),944 ),945 );946 <T as Config>::Currency::settle(947 payer.as_sub(),948 imbalance,949 WithdrawReasons::TRANSFER,950 ExistenceRequirement::KeepAlive,951 )952 .map_err(|_| Error::<T>::NotSufficientFounds)?;953 }954955 <CreatedCollectionCount<T>>::put(created_count);956 <Pallet<T>>::deposit_event(Event::CollectionCreated(957 id,958 data.mode.id(),959 owner.as_sub().clone(),960 ));961 <PalletEvm<T>>::deposit_log(962 erc::CollectionHelpersEvents::CollectionCreated {963 owner: *owner.as_eth(),964 collection_id: eth::collection_id_to_address(id),965 }966 .to_log(T::ContractAddress::get()),967 );968 <CollectionById<T>>::insert(id, collection);969 Ok(id)970 }971972 /// Destroy collection.973 ///974 /// * `collection` - Collection handler.975 /// * `sender` - The owner or administrator of the collection.976 pub fn destroy_collection(977 collection: CollectionHandle<T>,978 sender: &T::CrossAccountId,979 ) -> DispatchResult {980 ensure!(981 collection.limits.owner_can_destroy(),982 <Error<T>>::NoPermission,983 );984 collection.check_is_owner(sender)?;985986 let destroyed_collections = <DestroyedCollectionCount<T>>::get()987 .0988 .checked_add(1)989 .ok_or(ArithmeticError::Overflow)?;990991 // =========992993 <DestroyedCollectionCount<T>>::put(destroyed_collections);994 <CollectionById<T>>::remove(collection.id);995 <AdminAmount<T>>::remove(collection.id);996 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);997 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);998 <CollectionProperties<T>>::remove(collection.id);9991000 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));10011002 <PalletEvm<T>>::deposit_log(1003 erc::CollectionHelpersEvents::CollectionDestroyed {1004 collection_id: eth::collection_id_to_address(collection.id),1005 }1006 .to_log(T::ContractAddress::get()),1007 );1008 Ok(())1009 }10101011 /// Set collection property.1012 ///1013 /// * `collection` - Collection handler.1014 /// * `sender` - The owner or administrator of the collection.1015 /// * `property` - The property to set.1016 pub fn set_collection_property(1017 collection: &CollectionHandle<T>,1018 sender: &T::CrossAccountId,1019 property: Property,1020 ) -> DispatchResult {1021 collection.check_is_owner_or_admin(sender)?;10221023 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1024 let property = property.clone();1025 properties.try_set(property.key, property.value)1026 })1027 .map_err(<Error<T>>::from)?;10281029 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10301031 Ok(())1032 }10331034 /// Set scouped collection property.1035 ///1036 /// * `collection_id` - ID of the collection for which the property is being set.1037 /// * `scope` - Property scope.1038 /// * `property` - The property to set.1039 pub fn set_scoped_collection_property(1040 collection_id: CollectionId,1041 scope: PropertyScope,1042 property: Property,1043 ) -> DispatchResult {1044 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1045 properties.try_scoped_set(scope, property.key, property.value)1046 })1047 .map_err(<Error<T>>::from)?;10481049 Ok(())1050 }10511052 /// Set scouped collection properties.1053 ///1054 /// * `collection_id` - ID of the collection for which the properties is being set.1055 /// * `scope` - Property scope.1056 /// * `properties` - The properties to set.1057 pub fn set_scoped_collection_properties(1058 collection_id: CollectionId,1059 scope: PropertyScope,1060 properties: impl Iterator<Item = Property>,1061 ) -> DispatchResult {1062 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1063 stored_properties.try_scoped_set_from_iter(scope, properties)1064 })1065 .map_err(<Error<T>>::from)?;10661067 Ok(())1068 }10691070 /// Set collection properties.1071 ///1072 /// * `collection` - Collection handler.1073 /// * `sender` - The owner or administrator of the collection.1074 /// * `properties` - The properties to set.1075 #[transactional]1076 pub fn set_collection_properties(1077 collection: &CollectionHandle<T>,1078 sender: &T::CrossAccountId,1079 properties: Vec<Property>,1080 ) -> DispatchResult {1081 for property in properties {1082 Self::set_collection_property(collection, sender, property)?;1083 }10841085 Ok(())1086 }10871088 /// Delete collection property.1089 ///1090 /// * `collection` - Collection handler.1091 /// * `sender` - The owner or administrator of the collection.1092 /// * `property` - The property to delete.1093 pub fn delete_collection_property(1094 collection: &CollectionHandle<T>,1095 sender: &T::CrossAccountId,1096 property_key: PropertyKey,1097 ) -> DispatchResult {1098 collection.check_is_owner_or_admin(sender)?;10991100 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1101 properties.remove(&property_key)1102 })1103 .map_err(<Error<T>>::from)?;11041105 Self::deposit_event(Event::CollectionPropertyDeleted(1106 collection.id,1107 property_key,1108 ));11091110 Ok(())1111 }11121113 /// Delete collection properties.1114 ///1115 /// * `collection` - Collection handler.1116 /// * `sender` - The owner or administrator of the collection.1117 /// * `properties` - The properties to delete.1118 #[transactional]1119 pub fn delete_collection_properties(1120 collection: &CollectionHandle<T>,1121 sender: &T::CrossAccountId,1122 property_keys: Vec<PropertyKey>,1123 ) -> DispatchResult {1124 for key in property_keys {1125 Self::delete_collection_property(collection, sender, key)?;1126 }11271128 Ok(())1129 }11301131 /// Set collection propetry permission without any checks.1132 ///1133 /// Used for migrations.1134 ///1135 /// * `collection` - Collection handler.1136 /// * `property_permissions` - Property permissions.1137 pub fn set_property_permission_unchecked(1138 collection: CollectionId,1139 property_permission: PropertyKeyPermission,1140 ) -> DispatchResult {1141 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1142 permissions.try_set(property_permission.key, property_permission.permission)1143 })1144 .map_err(<Error<T>>::from)?;1145 Ok(())1146 }11471148 /// Set collection property permission.1149 ///1150 /// * `collection` - Collection handler.1151 /// * `sender` - The owner or administrator of the collection.1152 /// * `property_permission` - Property permission.1153 pub fn set_property_permission(1154 collection: &CollectionHandle<T>,1155 sender: &T::CrossAccountId,1156 property_permission: PropertyKeyPermission,1157 ) -> DispatchResult {1158 Self::set_scoped_property_permission(1159 collection,1160 sender,1161 PropertyScope::None,1162 property_permission,1163 )1164 }11651166 /// Set collection property permission with scope.1167 ///1168 /// * `collection` - Collection handler.1169 /// * `sender` - The owner or administrator of the collection.1170 /// * `scope` - Property scope.1171 /// * `property_permission` - Property permission.1172 pub fn set_scoped_property_permission(1173 collection: &CollectionHandle<T>,1174 sender: &T::CrossAccountId,1175 scope: PropertyScope,1176 property_permission: PropertyKeyPermission,1177 ) -> DispatchResult {1178 collection.check_is_owner_or_admin(sender)?;11791180 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1181 let current_permission = all_permissions.get(&property_permission.key);1182 if matches![1183 current_permission,1184 Some(PropertyPermission { mutable: false, .. })1185 ] {1186 return Err(<Error<T>>::NoPermission.into());1187 }11881189 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1190 let property_permission = property_permission.clone();1191 permissions.try_scoped_set(1192 scope,1193 property_permission.key,1194 property_permission.permission,1195 )1196 })1197 .map_err(<Error<T>>::from)?;11981199 Self::deposit_event(Event::PropertyPermissionSet(1200 collection.id,1201 property_permission.key,1202 ));12031204 Ok(())1205 }12061207 /// Set token property permission.1208 ///1209 /// * `collection` - Collection handler.1210 /// * `sender` - The owner or administrator of the collection.1211 /// * `property_permissions` - Property permissions.1212 #[transactional]1213 pub fn set_token_property_permissions(1214 collection: &CollectionHandle<T>,1215 sender: &T::CrossAccountId,1216 property_permissions: Vec<PropertyKeyPermission>,1217 ) -> DispatchResult {1218 Self::set_scoped_token_property_permissions(1219 collection,1220 sender,1221 PropertyScope::None,1222 property_permissions,1223 )1224 }12251226 /// Set token property permission with scope.1227 ///1228 /// * `collection` - Collection handler.1229 /// * `sender` - The owner or administrator of the collection.1230 /// * `scope` - Property scope.1231 /// * `property_permissions` - Property permissions.1232 #[transactional]1233 pub fn set_scoped_token_property_permissions(1234 collection: &CollectionHandle<T>,1235 sender: &T::CrossAccountId,1236 scope: PropertyScope,1237 property_permissions: Vec<PropertyKeyPermission>,1238 ) -> DispatchResult {1239 for prop_pemission in property_permissions {1240 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1241 }12421243 Ok(())1244 }12451246 /// Get collection property.1247 pub fn get_collection_property(1248 collection_id: CollectionId,1249 key: &PropertyKey,1250 ) -> Option<PropertyValue> {1251 Self::collection_properties(collection_id).get(key).cloned()1252 }12531254 /// Convert byte vector to property key vector.1255 pub fn bytes_keys_to_property_keys(1256 keys: Vec<Vec<u8>>,1257 ) -> Result<Vec<PropertyKey>, DispatchError> {1258 keys.into_iter()1259 .map(|key| -> Result<PropertyKey, DispatchError> {1260 key.try_into()1261 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1262 })1263 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1264 }12651266 /// Get properties according to given keys.1267 pub fn filter_collection_properties(1268 collection_id: CollectionId,1269 keys: Option<Vec<PropertyKey>>,1270 ) -> Result<Vec<Property>, DispatchError> {1271 let properties = Self::collection_properties(collection_id);12721273 let properties = keys1274 .map(|keys| {1275 keys.into_iter()1276 .filter_map(|key| {1277 properties.get(&key).map(|value| Property {1278 key,1279 value: value.clone(),1280 })1281 })1282 .collect()1283 })1284 .unwrap_or_else(|| {1285 properties1286 .into_iter()1287 .map(|(key, value)| Property { key, value })1288 .collect()1289 });12901291 Ok(properties)1292 }12931294 /// Get property permissions according to given keys.1295 pub fn filter_property_permissions(1296 collection_id: CollectionId,1297 keys: Option<Vec<PropertyKey>>,1298 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1299 let permissions = Self::property_permissions(collection_id);13001301 let key_permissions = keys1302 .map(|keys| {1303 keys.into_iter()1304 .filter_map(|key| {1305 permissions1306 .get(&key)1307 .map(|permission| PropertyKeyPermission {1308 key,1309 permission: permission.clone(),1310 })1311 })1312 .collect()1313 })1314 .unwrap_or_else(|| {1315 permissions1316 .into_iter()1317 .map(|(key, permission)| PropertyKeyPermission { key, permission })1318 .collect()1319 });13201321 Ok(key_permissions)1322 }13231324 /// Toggle `user` participation in the `collection`'s allow list.1325 /// #### Store read/writes1326 /// 1 writes1327 pub fn toggle_allowlist(1328 collection: &CollectionHandle<T>,1329 sender: &T::CrossAccountId,1330 user: &T::CrossAccountId,1331 allowed: bool,1332 ) -> DispatchResult {1333 collection.check_is_owner_or_admin(sender)?;13341335 // =========13361337 if allowed {1338 <Allowlist<T>>::insert((collection.id, user), true);1339 } else {1340 <Allowlist<T>>::remove((collection.id, user));1341 }13421343 Ok(())1344 }13451346 /// Toggle `user` participation in the `collection`'s admin list.1347 /// #### Store read/writes1348 /// 2 writes1349 pub fn toggle_admin(1350 collection: &CollectionHandle<T>,1351 sender: &T::CrossAccountId,1352 user: &T::CrossAccountId,1353 admin: bool,1354 ) -> DispatchResult {1355 collection.check_is_owner(sender)?;13561357 let was_admin = <IsAdmin<T>>::get((collection.id, user));1358 if was_admin == admin {1359 return Ok(());1360 }1361 let amount = <AdminAmount<T>>::get(collection.id);13621363 if admin {1364 let amount = amount1365 .checked_add(1)1366 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1367 ensure!(1368 amount <= Self::collection_admins_limit(),1369 <Error<T>>::CollectionAdminCountExceeded,1370 );13711372 // =========13731374 <AdminAmount<T>>::insert(collection.id, amount);1375 <IsAdmin<T>>::insert((collection.id, user), true);1376 } else {1377 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1378 <IsAdmin<T>>::remove((collection.id, user));1379 }13801381 Ok(())1382 }13831384 /// Merge set fields from `new_limit` to `old_limit`.1385 pub fn clamp_limits(1386 mode: CollectionMode,1387 old_limit: &CollectionLimits,1388 mut new_limit: CollectionLimits,1389 ) -> Result<CollectionLimits, DispatchError> {1390 let limits = old_limit;1391 limit_default!(old_limit, new_limit,1392 account_token_ownership_limit => ensure!(1393 new_limit <= MAX_TOKEN_OWNERSHIP,1394 <Error<T>>::CollectionLimitBoundsExceeded,1395 ),1396 sponsored_data_size => ensure!(1397 new_limit <= CUSTOM_DATA_LIMIT,1398 <Error<T>>::CollectionLimitBoundsExceeded,1399 ),14001401 sponsored_data_rate_limit => {},1402 token_limit => ensure!(1403 old_limit >= new_limit && new_limit > 0,1404 <Error<T>>::CollectionTokenLimitExceeded1405 ),14061407 sponsor_transfer_timeout(match mode {1408 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1409 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1410 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1411 }) => ensure!(1412 new_limit <= MAX_SPONSOR_TIMEOUT,1413 <Error<T>>::CollectionLimitBoundsExceeded,1414 ),1415 sponsor_approve_timeout => {},1416 owner_can_transfer => ensure!(1417 !limits.owner_can_transfer_instaled() ||1418 old_limit || !new_limit,1419 <Error<T>>::OwnerPermissionsCantBeReverted,1420 ),1421 owner_can_destroy => ensure!(1422 old_limit || !new_limit,1423 <Error<T>>::OwnerPermissionsCantBeReverted,1424 ),1425 transfers_enabled => {},1426 );1427 Ok(new_limit)1428 }14291430 /// Merge set fields from `new_permission` to `old_permission`.1431 pub fn clamp_permissions(1432 _mode: CollectionMode,1433 old_permission: &CollectionPermissions,1434 mut new_permission: CollectionPermissions,1435 ) -> Result<CollectionPermissions, DispatchError> {1436 limit_default_clone!(old_permission, new_permission,1437 access => {},1438 mint_mode => {},1439 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1440 );1441 Ok(new_permission)1442 }1443}14441445/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1446#[macro_export]1447macro_rules! unsupported {1448 ($runtime:path) => {1449 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1450 };1451}14521453/// Return weights for various worst-case operations.1454pub trait CommonWeightInfo<CrossAccountId> {1455 /// Weight of item creation.1456 fn create_item() -> Weight;14571458 /// Weight of items creation.1459 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14601461 /// Weight of items creation.1462 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14631464 /// The weight of the burning item.1465 fn burn_item() -> Weight;14661467 /// Property setting weight.1468 ///1469 /// * `amount`- The number of properties to set.1470 fn set_collection_properties(amount: u32) -> Weight;14711472 /// Collection property deletion weight.1473 ///1474 /// * `amount`- The number of properties to set.1475 fn delete_collection_properties(amount: u32) -> Weight;14761477 /// Token property setting weight.1478 ///1479 /// * `amount`- The number of properties to set.1480 fn set_token_properties(amount: u32) -> Weight;14811482 /// Token property deletion weight.1483 ///1484 /// * `amount`- The number of properties to delete.1485 fn delete_token_properties(amount: u32) -> Weight;14861487 /// Token property permissions set weight.1488 ///1489 /// * `amount`- The number of property permissions to set.1490 fn set_token_property_permissions(amount: u32) -> Weight;14911492 /// Transfer price of the token or its parts.1493 fn transfer() -> Weight;14941495 /// The price of setting the permission of the operation from another user.1496 fn approve() -> Weight;14971498 /// Transfer price from another user.1499 fn transfer_from() -> Weight;15001501 /// The price of burning a token from another user.1502 fn burn_from() -> Weight;15031504 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1505 /// whole users's balance.1506 ///1507 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1508 fn burn_recursively_self_raw() -> Weight;15091510 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1511 ///1512 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1513 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15141515 /// The price of recursive burning a token.1516 ///1517 /// `max_selfs` - The maximum burning weight of the token itself.1518 /// `max_breadth` - The maximum number of nested tokens to burn.1519 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1520 Self::burn_recursively_self_raw()1521 .saturating_mul(max_selfs.max(1) as u64)1522 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1523 }15241525 /// The price of retrieving token owner1526 fn token_owner() -> Weight;1527}15281529/// Weight info extension trait for refungible pallet.1530pub trait RefungibleExtensionsWeightInfo {1531 /// Weight of token repartition.1532 fn repartition() -> Weight;1533}15341535/// Common collection operations.1536///1537/// It wraps methods in Fungible, Nonfungible and Refungible pallets1538/// and adds weight info.1539pub trait CommonCollectionOperations<T: Config> {1540 /// Create token.1541 ///1542 /// * `sender` - The user who mint the token and pays for the transaction.1543 /// * `to` - The user who will own the token.1544 /// * `data` - Token data.1545 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1546 fn create_item(1547 &self,1548 sender: T::CrossAccountId,1549 to: T::CrossAccountId,1550 data: CreateItemData,1551 nesting_budget: &dyn Budget,1552 ) -> DispatchResultWithPostInfo;15531554 /// Create multiple tokens.1555 ///1556 /// * `sender` - The user who mint the token and pays for the transaction.1557 /// * `to` - The user who will own the token.1558 /// * `data` - Token data.1559 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1560 fn create_multiple_items(1561 &self,1562 sender: T::CrossAccountId,1563 to: T::CrossAccountId,1564 data: Vec<CreateItemData>,1565 nesting_budget: &dyn Budget,1566 ) -> DispatchResultWithPostInfo;15671568 /// Create multiple tokens.1569 ///1570 /// * `sender` - The user who mint the token and pays for the transaction.1571 /// * `to` - The user who will own the token.1572 /// * `data` - Token data.1573 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1574 fn create_multiple_items_ex(1575 &self,1576 sender: T::CrossAccountId,1577 data: CreateItemExData<T::CrossAccountId>,1578 nesting_budget: &dyn Budget,1579 ) -> DispatchResultWithPostInfo;15801581 /// Burn token.1582 ///1583 /// * `sender` - The user who owns the token.1584 /// * `token` - Token id that will burned.1585 /// * `amount` - The number of parts of the token that will be burned.1586 fn burn_item(1587 &self,1588 sender: T::CrossAccountId,1589 token: TokenId,1590 amount: u128,1591 ) -> DispatchResultWithPostInfo;15921593 /// Burn token and all nested tokens recursievly.1594 ///1595 /// * `sender` - The user who owns the token.1596 /// * `token` - Token id that will burned.1597 /// * `self_budget` - The budget that can be spent on burning tokens.1598 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.1599 fn burn_item_recursively(1600 &self,1601 sender: T::CrossAccountId,1602 token: TokenId,1603 self_budget: &dyn Budget,1604 breadth_budget: &dyn Budget,1605 ) -> DispatchResultWithPostInfo;16061607 /// Set collection properties.1608 ///1609 /// * `sender` - Must be either the owner of the collection or its admin.1610 /// * `properties` - Properties to be set.1611 fn set_collection_properties(1612 &self,1613 sender: T::CrossAccountId,1614 properties: Vec<Property>,1615 ) -> DispatchResultWithPostInfo;16161617 /// Delete collection properties.1618 ///1619 /// * `sender` - Must be either the owner of the collection or its admin.1620 /// * `properties` - The properties to be removed.1621 fn delete_collection_properties(1622 &self,1623 sender: &T::CrossAccountId,1624 property_keys: Vec<PropertyKey>,1625 ) -> DispatchResultWithPostInfo;16261627 /// Set token properties.1628 ///1629 /// The appropriate [`PropertyPermission`] for the token property1630 /// must be set with [`Self::set_token_property_permissions`].1631 ///1632 /// * `sender` - Must be either the owner of the token or its admin.1633 /// * `token_id` - The token for which the properties are being set.1634 /// * `properties` - Properties to be set.1635 /// * `budget` - Budget for setting properties.1636 fn set_token_properties(1637 &self,1638 sender: T::CrossAccountId,1639 token_id: TokenId,1640 properties: Vec<Property>,1641 budget: &dyn Budget,1642 ) -> DispatchResultWithPostInfo;16431644 /// Remove token properties.1645 ///1646 /// The appropriate [`PropertyPermission`] for the token property1647 /// must be set with [`Self::set_token_property_permissions`].1648 ///1649 /// * `sender` - Must be either the owner of the token or its admin.1650 /// * `token_id` - The token for which the properties are being remove.1651 /// * `property_keys` - Keys to remove corresponding properties.1652 /// * `budget` - Budget for removing properties.1653 fn delete_token_properties(1654 &self,1655 sender: T::CrossAccountId,1656 token_id: TokenId,1657 property_keys: Vec<PropertyKey>,1658 budget: &dyn Budget,1659 ) -> DispatchResultWithPostInfo;16601661 /// Set token property permissions.1662 ///1663 /// * `sender` - Must be either the owner of the token or its admin.1664 /// * `token_id` - The token for which the properties are being set.1665 /// * `property_permissions` - Property permissions to be set.1666 /// * `budget` - Budget for setting properties.1667 fn set_token_property_permissions(1668 &self,1669 sender: &T::CrossAccountId,1670 property_permissions: Vec<PropertyKeyPermission>,1671 ) -> DispatchResultWithPostInfo;16721673 /// Transfer amount of token pieces.1674 ///1675 /// * `sender` - Donor user.1676 /// * `to` - Recepient user.1677 /// * `token` - The token of which parts are being sent.1678 /// * `amount` - The number of parts of the token that will be transferred.1679 /// * `budget` - The maximum budget that can be spent on the transfer.1680 fn transfer(1681 &self,1682 sender: T::CrossAccountId,1683 to: T::CrossAccountId,1684 token: TokenId,1685 amount: u128,1686 budget: &dyn Budget,1687 ) -> DispatchResultWithPostInfo;16881689 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].1690 ///1691 /// * `sender` - The user who grants access to the token.1692 /// * `spender` - The user to whom the rights are granted.1693 /// * `token` - The token to which access is granted.1694 /// * `amount` - The amount of pieces that another user can dispose of.1695 fn approve(1696 &self,1697 sender: T::CrossAccountId,1698 spender: T::CrossAccountId,1699 token: TokenId,1700 amount: u128,1701 ) -> DispatchResultWithPostInfo;17021703 /// Send parts of a token owned by another user.1704 ///1705 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1706 ///1707 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).1708 /// * `from` - The user who owns the token.1709 /// * `to` - Recepient user.1710 /// * `token` - The token of which parts are being sent.1711 /// * `amount` - The number of parts of the token that will be transferred.1712 /// * `budget` - The maximum budget that can be spent on the transfer.1713 fn transfer_from(1714 &self,1715 sender: T::CrossAccountId,1716 from: T::CrossAccountId,1717 to: T::CrossAccountId,1718 token: TokenId,1719 amount: u128,1720 budget: &dyn Budget,1721 ) -> DispatchResultWithPostInfo;17221723 /// Burn parts of a token owned by another user.1724 ///1725 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1726 ///1727 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).1728 /// * `from` - The user who owns the token.1729 /// * `token` - The token of which parts are being sent.1730 /// * `amount` - The number of parts of the token that will be transferred.1731 /// * `budget` - The maximum budget that can be spent on the burn.1732 fn burn_from(1733 &self,1734 sender: T::CrossAccountId,1735 from: T::CrossAccountId,1736 token: TokenId,1737 amount: u128,1738 budget: &dyn Budget,1739 ) -> DispatchResultWithPostInfo;17401741 /// Check permission to nest token.1742 ///1743 /// * `sender` - The user who initiated the check.1744 /// * `from` - The token that is checked for embedding.1745 /// * `under` - Token under which to check.1746 /// * `budget` - The maximum budget that can be spent on the check.1747 fn check_nesting(1748 &self,1749 sender: T::CrossAccountId,1750 from: (CollectionId, TokenId),1751 under: TokenId,1752 budget: &dyn Budget,1753 ) -> DispatchResult;17541755 /// Nest one token into another.1756 ///1757 /// * `under` - Token holder.1758 /// * `to_nest` - Nested token.1759 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17601761 /// Unnest token.1762 ///1763 /// * `under` - Token holder.1764 /// * `to_nest` - Token to unnest.1765 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17661767 /// Get all user tokens.1768 ///1769 /// * `account` - Account for which you need to get tokens.1770 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17711772 /// Get all the tokens in the collection.1773 fn collection_tokens(&self) -> Vec<TokenId>;17741775 /// Check if the token exists.1776 ///1777 /// * `token` - Id token to check.1778 fn token_exists(&self, token: TokenId) -> bool;17791780 /// Get the id of the last minted token.1781 fn last_token_id(&self) -> TokenId;17821783 /// Get the owner of the token.1784 ///1785 /// * `token` - The token for which you need to find out the owner.1786 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17871788 /// Returns 10 tokens owners in no particular order.1789 ///1790 /// * `token` - The token for which you need to find out the owners.1791 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17921793 /// Get the value of the token property by key.1794 ///1795 /// * `token` - Token with the property to get.1796 /// * `key` - Property name.1797 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17981799 /// Get a set of token properties by key vector.1800 ///1801 /// * `token` - Token with the property to get.1802 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),1803 /// then all properties are returned.1804 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18051806 /// Amount of unique collection tokens1807 fn total_supply(&self) -> u32;18081809 /// Amount of different tokens account has.1810 ///1811 /// * `account` - The account for which need to get the balance.1812 fn account_balance(&self, account: T::CrossAccountId) -> u32;18131814 /// Amount of specific token account have.1815 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18161817 /// Amount of token pieces1818 fn total_pieces(&self, token: TokenId) -> Option<u128>;18191820 /// Get the number of parts of the token that a trusted user can manage.1821 ///1822 /// * `sender` - Trusted user.1823 /// * `spender` - Owner of the token.1824 /// * `token` - The token for which to get the value.1825 fn allowance(1826 &self,1827 sender: T::CrossAccountId,1828 spender: T::CrossAccountId,1829 token: TokenId,1830 ) -> u128;18311832 /// Get extension for RFT collection.1833 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1834}18351836/// Extension for RFT collection.1837pub trait RefungibleExtensions<T>1838where1839 T: Config,1840{1841 /// Change the number of parts of the token.1842 ///1843 /// When the value changes down, this function is equivalent to burning parts of the token.1844 ///1845 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.1846 /// * `token` - The token for which you want to change the number of parts.1847 /// * `amount` - The new value of the parts of the token.1848 fn repartition(1849 &self,1850 sender: &T::CrossAccountId,1851 token: TokenId,1852 amount: u128,1853 ) -> DispatchResultWithPostInfo;1854}18551856/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].1857///1858/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.1859pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1860 let post_info = PostDispatchInfo {1861 actual_weight: Some(weight),1862 pays_fee: Pays::Yes,1863 };1864 match res {1865 Ok(()) => Ok(post_info),1866 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1867 }1868}18691870impl<T: Config> From<PropertiesError> for Error<T> {1871 fn from(error: PropertiesError) -> Self {1872 match error {1873 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1874 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1875 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1876 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1877 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1878 }1879 }1880}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//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63 ensure,64 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65 dispatch::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 RpcCollectionFlags,75 CollectionId,76 CreateItemData,77 MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT,79 TokenId,80 TokenChild,81 CollectionStats,82 MAX_TOKEN_OWNERSHIP,83 CollectionMode,84 NFT_SPONSOR_TRANSFER_TIMEOUT,85 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87 MAX_SPONSOR_TIMEOUT,88 CUSTOM_DATA_LIMIT,89 CollectionLimits,90 CreateCollectionData,91 SponsorshipState,92 CreateItemExData,93 SponsoringRateLimit,94 budget::Budget,95 PhantomType,96 Property,97 Properties,98 PropertiesPermissionMap,99 PropertyKey,100 PropertyValue,101 PropertyPermission,102 PropertiesError,103 PropertyKeyPermission,104 TokenData,105 TrySetProperty,106 PropertyScope,107 // RMRK108 RmrkCollectionInfo,109 RmrkInstanceInfo,110 RmrkResourceInfo,111 RmrkPropertyInfo,112 RmrkBaseInfo,113 RmrkPartType,114 RmrkBoundedTheme,115 RmrkNftChild,116 CollectionPermissions,117};118119pub use pallet::*;120use sp_core::H160;121use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};122#[cfg(feature = "runtime-benchmarks")]123pub mod benchmarking;124pub mod dispatch;125pub mod erc;126pub mod eth;127pub mod weights;128129/// Weight info.130pub type SelfWeightOf<T> = <T as Config>::WeightInfo;131132/// Collection handle contains information about collection data and id.133/// Also provides functionality to count consumed gas.134///135/// CollectionHandle is used as a generic wrapper for collections of all types.136/// It allows to perform common operations and queries on any collection type,137/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].138#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]139pub struct CollectionHandle<T: Config> {140 /// Collection id141 pub id: CollectionId,142 collection: Collection<T::AccountId>,143 /// Substrate recorder for counting consumed gas144 pub recorder: SubstrateRecorder<T>,145}146147impl<T: Config> WithRecorder<T> for CollectionHandle<T> {148 fn recorder(&self) -> &SubstrateRecorder<T> {149 &self.recorder150 }151 fn into_recorder(self) -> SubstrateRecorder<T> {152 self.recorder153 }154}155156impl<T: Config> CollectionHandle<T> {157 /// Same as [CollectionHandle::new] but with an explicit gas limit.158 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {159 <CollectionById<T>>::get(id).map(|collection| Self {160 id,161 collection,162 recorder: SubstrateRecorder::new(gas_limit),163 })164 }165166 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].167 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {168 <CollectionById<T>>::get(id).map(|collection| Self {169 id,170 collection,171 recorder,172 })173 }174175 /// Retrives collection data from storage and creates collection handle with default parameters.176 /// If collection not found return `None`177 pub fn new(id: CollectionId) -> Option<Self> {178 Self::new_with_gas_limit(id, u64::MAX)179 }180181 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.182 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {183 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)184 }185186 /// Consume gas for reading.187 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {188 self.recorder189 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(190 <T as frame_system::Config>::DbWeight::get()191 .read192 .saturating_mul(reads),193 )))194 }195196 /// Consume gas for writing.197 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {198 self.recorder199 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(200 <T as frame_system::Config>::DbWeight::get()201 .write202 .saturating_mul(writes),203 )))204 }205206 /// Consume gas for reading and writing.207 pub fn consume_store_reads_and_writes(208 &self,209 reads: u64,210 writes: u64,211 ) -> evm_coder::execution::Result<()> {212 let weight = <T as frame_system::Config>::DbWeight::get();213 let reads = weight.read.saturating_mul(reads);214 let writes = weight.read.saturating_mul(writes);215 self.recorder216 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(217 reads.saturating_add(writes),218 )))219 }220221 /// Save collection to storage.222 pub fn save(&self) -> DispatchResult {223 <CollectionById<T>>::insert(self.id, &self.collection);224 Ok(())225 }226227 /// Set collection sponsor.228 ///229 /// Unique collections allows sponsoring for certain actions.230 /// This method allows you to set the sponsor of the collection.231 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].232 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {233 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);234 Ok(())235 }236237 /// Confirm sponsorship238 ///239 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.240 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].241 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {242 if self.collection.sponsorship.pending_sponsor() != Some(sender) {243 return Ok(false);244 }245246 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());247 Ok(true)248 }249250 /// Remove collection sponsor.251 pub fn remove_sponsor(&mut self) -> DispatchResult {252 self.collection.sponsorship = SponsorshipState::Disabled;253 Ok(())254 }255256 /// Checks that the collection was created with, and must be operated upon through **Unique API**.257 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.258 pub fn check_is_internal(&self) -> DispatchResult {259 if self.flags.external {260 return Err(<Error<T>>::CollectionIsExternal)?;261 }262263 Ok(())264 }265266 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.267 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.268 pub fn check_is_external(&self) -> DispatchResult {269 if !self.flags.external {270 return Err(<Error<T>>::CollectionIsInternal)?;271 }272273 Ok(())274 }275}276277impl<T: Config> Deref for CollectionHandle<T> {278 type Target = Collection<T::AccountId>;279280 fn deref(&self) -> &Self::Target {281 &self.collection282 }283}284285impl<T: Config> DerefMut for CollectionHandle<T> {286 fn deref_mut(&mut self) -> &mut Self::Target {287 &mut self.collection288 }289}290291impl<T: Config> CollectionHandle<T> {292 /// Checks if the `user` is the owner of the collection.293 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {294 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);295 Ok(())296 }297298 /// Returns **true** if the `user` is the owner or administrator of the collection.299 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {300 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))301 }302303 /// Checks if the `user` is the owner or administrator of the collection.304 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {305 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);306 Ok(())307 }308309 /// Return **true** if `user` was not allowed to have tokens, and he can ignore such restrictions.310 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {311 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)312 }313314 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.315 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {316 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)317 }318319 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.320 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {321 ensure!(322 <Allowlist<T>>::get((self.id, user)),323 <Error<T>>::AddressNotInAllowlist324 );325 Ok(())326 }327328 /// Changes collection owner to another account329 /// #### Store read/writes330 /// 1 writes331 fn set_owner_internal(332 &mut self,333 caller: T::CrossAccountId,334 new_owner: T::CrossAccountId,335 ) -> DispatchResult {336 self.check_is_owner(&caller)?;337 self.collection.owner = new_owner.as_sub().clone();338 self.save()339 }340}341342#[frame_support::pallet]343pub mod pallet {344 use super::*;345 use dispatch::CollectionDispatch;346 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};347 use frame_system::pallet_prelude::*;348 use frame_support::traits::Currency;349 use up_data_structs::{TokenId, mapping::TokenAddressMapping};350 use scale_info::TypeInfo;351 use weights::WeightInfo;352353 #[pallet::config]354 pub trait Config:355 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo356 {357 /// Weight information for functions of this pallet.358 type WeightInfo: WeightInfo;359360 /// Events compatible with [`frame_system::Config::Event`].361 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;362363 /// Handler of accounts and payment.364 type Currency: Currency<Self::AccountId>;365366 /// Set price to create a collection.367 #[pallet::constant]368 type CollectionCreationPrice: Get<369 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,370 >;371372 /// Dispatcher of operations on collections.373 type CollectionDispatch: CollectionDispatch<Self>;374375 /// Account which holds the chain's treasury.376 type TreasuryAccountId: Get<Self::AccountId>;377378 /// Address under which the CollectionHelper contract would be available.379 #[pallet::constant]380 type ContractAddress: Get<H160>;381382 /// Mapper for token addresses to Ethereum addresses.383 type EvmTokenAddressMapping: TokenAddressMapping<H160>;384385 /// Mapper for token addresses to [`CrossAccountId`].386 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;387 }388389 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);390391 #[pallet::pallet]392 #[pallet::storage_version(STORAGE_VERSION)]393 #[pallet::generate_store(pub(super) trait Store)]394 pub struct Pallet<T>(_);395396 #[pallet::extra_constants]397 impl<T: Config> Pallet<T> {398 /// Maximum admins per collection.399 pub fn collection_admins_limit() -> u32 {400 COLLECTION_ADMINS_LIMIT401 }402 }403404 #[pallet::event]405 #[pallet::generate_deposit(pub fn deposit_event)]406 pub enum Event<T: Config> {407 /// New collection was created408 CollectionCreated(409 /// Globally unique identifier of newly created collection.410 CollectionId,411 /// [`CollectionMode`] converted into _u8_.412 u8,413 /// Collection owner.414 T::AccountId,415 ),416417 /// New collection was destroyed418 CollectionDestroyed(419 /// Globally unique identifier of collection.420 CollectionId,421 ),422423 /// New item was created.424 ItemCreated(425 /// Id of the collection where item was created.426 CollectionId,427 /// Id of an item. Unique within the collection.428 TokenId,429 /// Owner of newly created item430 T::CrossAccountId,431 /// Always 1 for NFT432 u128,433 ),434435 /// Collection item was burned.436 ItemDestroyed(437 /// Id of the collection where item was destroyed.438 CollectionId,439 /// Identifier of burned NFT.440 TokenId,441 /// Which user has destroyed its tokens.442 T::CrossAccountId,443 /// Amount of token pieces destroed. Always 1 for NFT.444 u128,445 ),446447 /// Item was transferred448 Transfer(449 /// Id of collection to which item is belong.450 CollectionId,451 /// Id of an item.452 TokenId,453 /// Original owner of item.454 T::CrossAccountId,455 /// New owner of item.456 T::CrossAccountId,457 /// Amount of token pieces transfered. Always 1 for NFT.458 u128,459 ),460461 /// Amount pieces of token owned by `sender` was approved for `spender`.462 Approved(463 /// Id of collection to which item is belong.464 CollectionId,465 /// Id of an item.466 TokenId,467 /// Original owner of item.468 T::CrossAccountId,469 /// Id for which the approval was granted.470 T::CrossAccountId,471 /// Amount of token pieces transfered. Always 1 for NFT.472 u128,473 ),474475 /// The colletion property has been added or edited.476 CollectionPropertySet(477 /// Id of collection to which property has been set.478 CollectionId,479 /// The property that was set.480 PropertyKey,481 ),482483 /// The property has been deleted.484 CollectionPropertyDeleted(485 /// Id of collection to which property has been deleted.486 CollectionId,487 /// The property that was deleted.488 PropertyKey,489 ),490491 /// The token property has been added or edited.492 TokenPropertySet(493 /// Identifier of the collection whose token has the property set.494 CollectionId,495 /// The token for which the property was set.496 TokenId,497 /// The property that was set.498 PropertyKey,499 ),500501 /// The token property has been deleted.502 TokenPropertyDeleted(503 /// Identifier of the collection whose token has the property deleted.504 CollectionId,505 /// The token for which the property was deleted.506 TokenId,507 /// The property that was deleted.508 PropertyKey,509 ),510511 /// The token property permission of a collection has been set.512 PropertyPermissionSet(513 /// ID of collection to which property permission has been set.514 CollectionId,515 /// The property permission that was set.516 PropertyKey,517 ),518 }519520 #[pallet::error]521 pub enum Error<T> {522 /// This collection does not exist.523 CollectionNotFound,524 /// Sender parameter and item owner must be equal.525 MustBeTokenOwner,526 /// No permission to perform action527 NoPermission,528 /// Destroying only empty collections is allowed529 CantDestroyNotEmptyCollection,530 /// Collection is not in mint mode.531 PublicMintingNotAllowed,532 /// Address is not in allow list.533 AddressNotInAllowlist,534535 /// Collection name can not be longer than 63 char.536 CollectionNameLimitExceeded,537 /// Collection description can not be longer than 255 char.538 CollectionDescriptionLimitExceeded,539 /// Token prefix can not be longer than 15 char.540 CollectionTokenPrefixLimitExceeded,541 /// Total collections bound exceeded.542 TotalCollectionsLimitExceeded,543 /// Exceeded max admin count544 CollectionAdminCountExceeded,545 /// Collection limit bounds per collection exceeded546 CollectionLimitBoundsExceeded,547 /// Tried to enable permissions which are only permitted to be disabled548 OwnerPermissionsCantBeReverted,549 /// Collection settings not allowing items transferring550 TransferNotAllowed,551 /// Account token limit exceeded per collection552 AccountTokenLimitExceeded,553 /// Collection token limit exceeded554 CollectionTokenLimitExceeded,555 /// Metadata flag frozen556 MetadataFlagFrozen,557558 /// Item does not exist559 TokenNotFound,560 /// Item is balance not enough561 TokenValueTooLow,562 /// Requested value is more than the approved563 ApprovedValueTooLow,564 /// Tried to approve more than owned565 CantApproveMoreThanOwned,566567 /// Can't transfer tokens to ethereum zero address568 AddressIsZero,569570 /// The operation is not supported571 UnsupportedOperation,572573 /// Insufficient funds to perform an action574 NotSufficientFounds,575576 /// User does not satisfy the nesting rule577 UserIsNotAllowedToNest,578 /// Only tokens from specific collections may nest tokens under this one579 SourceCollectionIsNotAllowedToNest,580581 /// Tried to store more data than allowed in collection field582 CollectionFieldSizeExceeded,583584 /// Tried to store more property data than allowed585 NoSpaceForProperty,586587 /// Tried to store more property keys than allowed588 PropertyLimitReached,589590 /// Property key is too long591 PropertyKeyIsTooLong,592593 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed594 InvalidCharacterInPropertyKey,595596 /// Empty property keys are forbidden597 EmptyPropertyKey,598599 /// Tried to access an external collection with an internal API600 CollectionIsExternal,601602 /// Tried to access an internal collection with an external API603 CollectionIsInternal,604 }605606 /// Storage of the count of created collections. Essentially contains the last collection ID.607 #[pallet::storage]608 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;609610 /// Storage of the count of deleted collections.611 #[pallet::storage]612 pub type DestroyedCollectionCount<T> =613 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;614615 /// Storage of collection info.616 #[pallet::storage]617 pub type CollectionById<T> = StorageMap<618 Hasher = Blake2_128Concat,619 Key = CollectionId,620 Value = Collection<<T as frame_system::Config>::AccountId>,621 QueryKind = OptionQuery,622 >;623624 /// Storage of collection properties.625 #[pallet::storage]626 #[pallet::getter(fn collection_properties)]627 pub type CollectionProperties<T> = StorageMap<628 Hasher = Blake2_128Concat,629 Key = CollectionId,630 Value = Properties,631 QueryKind = ValueQuery,632 OnEmpty = up_data_structs::CollectionProperties,633 >;634635 /// Storage of token property permissions of a collection.636 #[pallet::storage]637 #[pallet::getter(fn property_permissions)]638 pub type CollectionPropertyPermissions<T> = StorageMap<639 Hasher = Blake2_128Concat,640 Key = CollectionId,641 Value = PropertiesPermissionMap,642 QueryKind = ValueQuery,643 >;644645 /// Storage of the amount of collection admins.646 #[pallet::storage]647 pub type AdminAmount<T> = StorageMap<648 Hasher = Blake2_128Concat,649 Key = CollectionId,650 Value = u32,651 QueryKind = ValueQuery,652 >;653654 /// List of collection admins.655 #[pallet::storage]656 pub type IsAdmin<T: Config> = StorageNMap<657 Key = (658 Key<Blake2_128Concat, CollectionId>,659 Key<Blake2_128Concat, T::CrossAccountId>,660 ),661 Value = bool,662 QueryKind = ValueQuery,663 >;664665 /// Allowlisted collection users.666 #[pallet::storage]667 pub type Allowlist<T: Config> = StorageNMap<668 Key = (669 Key<Blake2_128Concat, CollectionId>,670 Key<Blake2_128Concat, T::CrossAccountId>,671 ),672 Value = bool,673 QueryKind = ValueQuery,674 >;675676 /// Not used by code, exists only to provide some types to metadata.677 #[pallet::storage]678 pub type DummyStorageValue<T: Config> = StorageValue<679 Value = (680 CollectionStats,681 CollectionId,682 TokenId,683 TokenChild,684 PhantomType<(685 TokenData<T::CrossAccountId>,686 RpcCollection<T::AccountId>,687 // RMRK688 RmrkCollectionInfo<T::AccountId>,689 RmrkInstanceInfo<T::AccountId>,690 RmrkResourceInfo,691 RmrkPropertyInfo,692 RmrkBaseInfo<T::AccountId>,693 RmrkPartType,694 RmrkBoundedTheme,695 RmrkNftChild,696 )>,697 ),698 QueryKind = OptionQuery,699 >;700701 #[pallet::hooks]702 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {703 fn on_runtime_upgrade() -> Weight {704 StorageVersion::new(1).put::<Pallet<T>>();705706 Weight::zero()707 }708 }709}710711impl<T: Config> Pallet<T> {712 /// Enshure that receiver address is correct.713 ///714 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.715 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {716 ensure!(717 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,718 <Error<T>>::AddressIsZero719 );720 Ok(())721 }722723 /// Get a vector of collection admins.724 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {725 <IsAdmin<T>>::iter_prefix((collection,))726 .map(|(a, _)| a)727 .collect()728 }729730 /// Get a vector of users allowed to mint tokens.731 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {732 <Allowlist<T>>::iter_prefix((collection,))733 .map(|(a, _)| a)734 .collect()735 }736737 /// Is `user` allowed to mint token in `collection`.738 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {739 <Allowlist<T>>::get((collection, user))740 }741742 /// Get statistics of collections.743 pub fn collection_stats() -> CollectionStats {744 let created = <CreatedCollectionCount<T>>::get();745 let destroyed = <DestroyedCollectionCount<T>>::get();746 CollectionStats {747 created: created.0,748 destroyed: destroyed.0,749 alive: created.0 - destroyed.0,750 }751 }752753 /// Get the effective limits for the collection.754 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {755 let collection = <CollectionById<T>>::get(collection)?;756 let limits = collection.limits;757 let effective_limits = CollectionLimits {758 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),759 sponsored_data_size: Some(limits.sponsored_data_size()),760 sponsored_data_rate_limit: Some(761 limits762 .sponsored_data_rate_limit763 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),764 ),765 token_limit: Some(limits.token_limit()),766 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(767 match collection.mode {768 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,769 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,770 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,771 },772 )),773 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),774 owner_can_transfer: Some(limits.owner_can_transfer()),775 owner_can_destroy: Some(limits.owner_can_destroy()),776 transfers_enabled: Some(limits.transfers_enabled()),777 };778779 Some(effective_limits)780 }781782 /// Returns information about the `collection` adapted for rpc.783 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {784 let Collection {785 name,786 description,787 owner,788 mode,789 token_prefix,790 sponsorship,791 limits,792 permissions,793 flags,794 } = <CollectionById<T>>::get(collection)?;795796 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)797 .into_iter()798 .map(|(key, permission)| PropertyKeyPermission { key, permission })799 .collect();800801 let properties = <CollectionProperties<T>>::get(collection)802 .into_iter()803 .map(|(key, value)| Property { key, value })804 .collect();805806 let permissions = CollectionPermissions {807 access: Some(permissions.access()),808 mint_mode: Some(permissions.mint_mode()),809 nesting: Some(permissions.nesting().clone()),810 };811812 Some(RpcCollection {813 name: name.into_inner(),814 description: description.into_inner(),815 owner,816 mode,817 token_prefix: token_prefix.into_inner(),818 sponsorship,819 limits,820 permissions,821 token_property_permissions,822 properties,823 read_only: flags.external,824825 flags: RpcCollectionFlags {826 foreign: flags.foreign,827 erc721metadata: flags.erc721metadata,828 },829 })830 }831}832833macro_rules! limit_default {834 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{835 $(836 if let Some($new) = $new.$field {837 let $old = $old.$field($($arg)?);838 let _ = $new;839 let _ = $old;840 $check841 } else {842 $new.$field = $old.$field843 }844 )*845 }};846}847macro_rules! limit_default_clone {848 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{849 $(850 if let Some($new) = $new.$field.clone() {851 let $old = $old.$field($($arg)?);852 let _ = $new;853 let _ = $old;854 $check855 } else {856 $new.$field = $old.$field.clone()857 }858 )*859 }};860}861862impl<T: Config> Pallet<T> {863 /// Create new collection.864 ///865 /// * `owner` - The owner of the collection.866 /// * `data` - Description of the created collection.867 /// * `flags` - Extra flags to store.868 pub fn init_collection(869 owner: T::CrossAccountId,870 payer: T::CrossAccountId,871 data: CreateCollectionData<T::AccountId>,872 flags: CollectionFlags,873 ) -> Result<CollectionId, DispatchError> {874 {875 ensure!(876 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,877 Error::<T>::CollectionTokenPrefixLimitExceeded878 );879 }880881 let created_count = <CreatedCollectionCount<T>>::get()882 .0883 .checked_add(1)884 .ok_or(ArithmeticError::Overflow)?;885 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;886 let id = CollectionId(created_count);887888 // bound Total number of collections889 ensure!(890 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,891 <Error<T>>::TotalCollectionsLimitExceeded892 );893894 // =========895896 let collection = Collection {897 owner: owner.as_sub().clone(),898 name: data.name,899 mode: data.mode.clone(),900 description: data.description,901 token_prefix: data.token_prefix,902 sponsorship: data903 .pending_sponsor904 .map(SponsorshipState::Unconfirmed)905 .unwrap_or_default(),906 limits: data907 .limits908 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))909 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,910 permissions: data911 .permissions912 .map(|permissions| {913 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)914 })915 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,916 flags,917 };918919 let mut collection_properties = up_data_structs::CollectionProperties::get();920 collection_properties921 .try_set_from_iter(data.properties.into_iter())922 .map_err(<Error<T>>::from)?;923924 CollectionProperties::<T>::insert(id, collection_properties);925926 let mut token_props_permissions = PropertiesPermissionMap::new();927 token_props_permissions928 .try_set_from_iter(data.token_property_permissions.into_iter())929 .map_err(<Error<T>>::from)?;930931 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);932933 // Take a (non-refundable) deposit of collection creation934 {935 let mut imbalance =936 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();937 imbalance.subsume(938 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(939 &T::TreasuryAccountId::get(),940 T::CollectionCreationPrice::get(),941 ),942 );943 <T as Config>::Currency::settle(944 payer.as_sub(),945 imbalance,946 WithdrawReasons::TRANSFER,947 ExistenceRequirement::KeepAlive,948 )949 .map_err(|_| Error::<T>::NotSufficientFounds)?;950 }951952 <CreatedCollectionCount<T>>::put(created_count);953 <Pallet<T>>::deposit_event(Event::CollectionCreated(954 id,955 data.mode.id(),956 owner.as_sub().clone(),957 ));958 <PalletEvm<T>>::deposit_log(959 erc::CollectionHelpersEvents::CollectionCreated {960 owner: *owner.as_eth(),961 collection_id: eth::collection_id_to_address(id),962 }963 .to_log(T::ContractAddress::get()),964 );965 <CollectionById<T>>::insert(id, collection);966 Ok(id)967 }968969 /// Destroy collection.970 ///971 /// * `collection` - Collection handler.972 /// * `sender` - The owner or administrator of the collection.973 pub fn destroy_collection(974 collection: CollectionHandle<T>,975 sender: &T::CrossAccountId,976 ) -> DispatchResult {977 ensure!(978 collection.limits.owner_can_destroy(),979 <Error<T>>::NoPermission,980 );981 collection.check_is_owner(sender)?;982983 let destroyed_collections = <DestroyedCollectionCount<T>>::get()984 .0985 .checked_add(1)986 .ok_or(ArithmeticError::Overflow)?;987988 // =========989990 <DestroyedCollectionCount<T>>::put(destroyed_collections);991 <CollectionById<T>>::remove(collection.id);992 <AdminAmount<T>>::remove(collection.id);993 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);994 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);995 <CollectionProperties<T>>::remove(collection.id);996997 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));998999 <PalletEvm<T>>::deposit_log(1000 erc::CollectionHelpersEvents::CollectionDestroyed {1001 collection_id: eth::collection_id_to_address(collection.id),1002 }1003 .to_log(T::ContractAddress::get()),1004 );1005 Ok(())1006 }10071008 /// Set collection property.1009 ///1010 /// * `collection` - Collection handler.1011 /// * `sender` - The owner or administrator of the collection.1012 /// * `property` - The property to set.1013 pub fn set_collection_property(1014 collection: &CollectionHandle<T>,1015 sender: &T::CrossAccountId,1016 property: Property,1017 ) -> DispatchResult {1018 collection.check_is_owner_or_admin(sender)?;10191020 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1021 let property = property.clone();1022 properties.try_set(property.key, property.value)1023 })1024 .map_err(<Error<T>>::from)?;10251026 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10271028 Ok(())1029 }10301031 /// Set scouped collection property.1032 ///1033 /// * `collection_id` - ID of the collection for which the property is being set.1034 /// * `scope` - Property scope.1035 /// * `property` - The property to set.1036 pub fn set_scoped_collection_property(1037 collection_id: CollectionId,1038 scope: PropertyScope,1039 property: Property,1040 ) -> DispatchResult {1041 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1042 properties.try_scoped_set(scope, property.key, property.value)1043 })1044 .map_err(<Error<T>>::from)?;10451046 Ok(())1047 }10481049 /// Set scouped collection properties.1050 ///1051 /// * `collection_id` - ID of the collection for which the properties is being set.1052 /// * `scope` - Property scope.1053 /// * `properties` - The properties to set.1054 pub fn set_scoped_collection_properties(1055 collection_id: CollectionId,1056 scope: PropertyScope,1057 properties: impl Iterator<Item = Property>,1058 ) -> DispatchResult {1059 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1060 stored_properties.try_scoped_set_from_iter(scope, properties)1061 })1062 .map_err(<Error<T>>::from)?;10631064 Ok(())1065 }10661067 /// Set collection properties.1068 ///1069 /// * `collection` - Collection handler.1070 /// * `sender` - The owner or administrator of the collection.1071 /// * `properties` - The properties to set.1072 #[transactional]1073 pub fn set_collection_properties(1074 collection: &CollectionHandle<T>,1075 sender: &T::CrossAccountId,1076 properties: Vec<Property>,1077 ) -> DispatchResult {1078 for property in properties {1079 Self::set_collection_property(collection, sender, property)?;1080 }10811082 Ok(())1083 }10841085 /// Delete collection property.1086 ///1087 /// * `collection` - Collection handler.1088 /// * `sender` - The owner or administrator of the collection.1089 /// * `property` - The property to delete.1090 pub fn delete_collection_property(1091 collection: &CollectionHandle<T>,1092 sender: &T::CrossAccountId,1093 property_key: PropertyKey,1094 ) -> DispatchResult {1095 collection.check_is_owner_or_admin(sender)?;10961097 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1098 properties.remove(&property_key)1099 })1100 .map_err(<Error<T>>::from)?;11011102 Self::deposit_event(Event::CollectionPropertyDeleted(1103 collection.id,1104 property_key,1105 ));11061107 Ok(())1108 }11091110 /// Delete collection properties.1111 ///1112 /// * `collection` - Collection handler.1113 /// * `sender` - The owner or administrator of the collection.1114 /// * `properties` - The properties to delete.1115 #[transactional]1116 pub fn delete_collection_properties(1117 collection: &CollectionHandle<T>,1118 sender: &T::CrossAccountId,1119 property_keys: Vec<PropertyKey>,1120 ) -> DispatchResult {1121 for key in property_keys {1122 Self::delete_collection_property(collection, sender, key)?;1123 }11241125 Ok(())1126 }11271128 /// Set collection propetry permission without any checks.1129 ///1130 /// Used for migrations.1131 ///1132 /// * `collection` - Collection handler.1133 /// * `property_permissions` - Property permissions.1134 pub fn set_property_permission_unchecked(1135 collection: CollectionId,1136 property_permission: PropertyKeyPermission,1137 ) -> DispatchResult {1138 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1139 permissions.try_set(property_permission.key, property_permission.permission)1140 })1141 .map_err(<Error<T>>::from)?;1142 Ok(())1143 }11441145 /// Set collection property permission.1146 ///1147 /// * `collection` - Collection handler.1148 /// * `sender` - The owner or administrator of the collection.1149 /// * `property_permission` - Property permission.1150 pub fn set_property_permission(1151 collection: &CollectionHandle<T>,1152 sender: &T::CrossAccountId,1153 property_permission: PropertyKeyPermission,1154 ) -> DispatchResult {1155 Self::set_scoped_property_permission(1156 collection,1157 sender,1158 PropertyScope::None,1159 property_permission,1160 )1161 }11621163 /// Set collection property permission with scope.1164 ///1165 /// * `collection` - Collection handler.1166 /// * `sender` - The owner or administrator of the collection.1167 /// * `scope` - Property scope.1168 /// * `property_permission` - Property permission.1169 pub fn set_scoped_property_permission(1170 collection: &CollectionHandle<T>,1171 sender: &T::CrossAccountId,1172 scope: PropertyScope,1173 property_permission: PropertyKeyPermission,1174 ) -> DispatchResult {1175 collection.check_is_owner_or_admin(sender)?;11761177 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1178 let current_permission = all_permissions.get(&property_permission.key);1179 if matches![1180 current_permission,1181 Some(PropertyPermission { mutable: false, .. })1182 ] {1183 return Err(<Error<T>>::NoPermission.into());1184 }11851186 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1187 let property_permission = property_permission.clone();1188 permissions.try_scoped_set(1189 scope,1190 property_permission.key,1191 property_permission.permission,1192 )1193 })1194 .map_err(<Error<T>>::from)?;11951196 Self::deposit_event(Event::PropertyPermissionSet(1197 collection.id,1198 property_permission.key,1199 ));12001201 Ok(())1202 }12031204 /// Set token property permission.1205 ///1206 /// * `collection` - Collection handler.1207 /// * `sender` - The owner or administrator of the collection.1208 /// * `property_permissions` - Property permissions.1209 #[transactional]1210 pub fn set_token_property_permissions(1211 collection: &CollectionHandle<T>,1212 sender: &T::CrossAccountId,1213 property_permissions: Vec<PropertyKeyPermission>,1214 ) -> DispatchResult {1215 Self::set_scoped_token_property_permissions(1216 collection,1217 sender,1218 PropertyScope::None,1219 property_permissions,1220 )1221 }12221223 /// Set token property permission with scope.1224 ///1225 /// * `collection` - Collection handler.1226 /// * `sender` - The owner or administrator of the collection.1227 /// * `scope` - Property scope.1228 /// * `property_permissions` - Property permissions.1229 #[transactional]1230 pub fn set_scoped_token_property_permissions(1231 collection: &CollectionHandle<T>,1232 sender: &T::CrossAccountId,1233 scope: PropertyScope,1234 property_permissions: Vec<PropertyKeyPermission>,1235 ) -> DispatchResult {1236 for prop_pemission in property_permissions {1237 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1238 }12391240 Ok(())1241 }12421243 /// Get collection property.1244 pub fn get_collection_property(1245 collection_id: CollectionId,1246 key: &PropertyKey,1247 ) -> Option<PropertyValue> {1248 Self::collection_properties(collection_id).get(key).cloned()1249 }12501251 /// Convert byte vector to property key vector.1252 pub fn bytes_keys_to_property_keys(1253 keys: Vec<Vec<u8>>,1254 ) -> Result<Vec<PropertyKey>, DispatchError> {1255 keys.into_iter()1256 .map(|key| -> Result<PropertyKey, DispatchError> {1257 key.try_into()1258 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1259 })1260 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1261 }12621263 /// Get properties according to given keys.1264 pub fn filter_collection_properties(1265 collection_id: CollectionId,1266 keys: Option<Vec<PropertyKey>>,1267 ) -> Result<Vec<Property>, DispatchError> {1268 let properties = Self::collection_properties(collection_id);12691270 let properties = keys1271 .map(|keys| {1272 keys.into_iter()1273 .filter_map(|key| {1274 properties.get(&key).map(|value| Property {1275 key,1276 value: value.clone(),1277 })1278 })1279 .collect()1280 })1281 .unwrap_or_else(|| {1282 properties1283 .into_iter()1284 .map(|(key, value)| Property { key, value })1285 .collect()1286 });12871288 Ok(properties)1289 }12901291 /// Get property permissions according to given keys.1292 pub fn filter_property_permissions(1293 collection_id: CollectionId,1294 keys: Option<Vec<PropertyKey>>,1295 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1296 let permissions = Self::property_permissions(collection_id);12971298 let key_permissions = keys1299 .map(|keys| {1300 keys.into_iter()1301 .filter_map(|key| {1302 permissions1303 .get(&key)1304 .map(|permission| PropertyKeyPermission {1305 key,1306 permission: permission.clone(),1307 })1308 })1309 .collect()1310 })1311 .unwrap_or_else(|| {1312 permissions1313 .into_iter()1314 .map(|(key, permission)| PropertyKeyPermission { key, permission })1315 .collect()1316 });13171318 Ok(key_permissions)1319 }13201321 /// Toggle `user` participation in the `collection`'s allow list.1322 /// #### Store read/writes1323 /// 1 writes1324 pub fn toggle_allowlist(1325 collection: &CollectionHandle<T>,1326 sender: &T::CrossAccountId,1327 user: &T::CrossAccountId,1328 allowed: bool,1329 ) -> DispatchResult {1330 collection.check_is_owner_or_admin(sender)?;13311332 // =========13331334 if allowed {1335 <Allowlist<T>>::insert((collection.id, user), true);1336 } else {1337 <Allowlist<T>>::remove((collection.id, user));1338 }13391340 Ok(())1341 }13421343 /// Toggle `user` participation in the `collection`'s admin list.1344 /// #### Store read/writes1345 /// 2 writes1346 pub fn toggle_admin(1347 collection: &CollectionHandle<T>,1348 sender: &T::CrossAccountId,1349 user: &T::CrossAccountId,1350 admin: bool,1351 ) -> DispatchResult {1352 collection.check_is_owner(sender)?;13531354 let was_admin = <IsAdmin<T>>::get((collection.id, user));1355 if was_admin == admin {1356 return Ok(());1357 }1358 let amount = <AdminAmount<T>>::get(collection.id);13591360 if admin {1361 let amount = amount1362 .checked_add(1)1363 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1364 ensure!(1365 amount <= Self::collection_admins_limit(),1366 <Error<T>>::CollectionAdminCountExceeded,1367 );13681369 // =========13701371 <AdminAmount<T>>::insert(collection.id, amount);1372 <IsAdmin<T>>::insert((collection.id, user), true);1373 } else {1374 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1375 <IsAdmin<T>>::remove((collection.id, user));1376 }13771378 Ok(())1379 }13801381 /// Merge set fields from `new_limit` to `old_limit`.1382 pub fn clamp_limits(1383 mode: CollectionMode,1384 old_limit: &CollectionLimits,1385 mut new_limit: CollectionLimits,1386 ) -> Result<CollectionLimits, DispatchError> {1387 let limits = old_limit;1388 limit_default!(old_limit, new_limit,1389 account_token_ownership_limit => ensure!(1390 new_limit <= MAX_TOKEN_OWNERSHIP,1391 <Error<T>>::CollectionLimitBoundsExceeded,1392 ),1393 sponsored_data_size => ensure!(1394 new_limit <= CUSTOM_DATA_LIMIT,1395 <Error<T>>::CollectionLimitBoundsExceeded,1396 ),13971398 sponsored_data_rate_limit => {},1399 token_limit => ensure!(1400 old_limit >= new_limit && new_limit > 0,1401 <Error<T>>::CollectionTokenLimitExceeded1402 ),14031404 sponsor_transfer_timeout(match mode {1405 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1406 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1407 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1408 }) => ensure!(1409 new_limit <= MAX_SPONSOR_TIMEOUT,1410 <Error<T>>::CollectionLimitBoundsExceeded,1411 ),1412 sponsor_approve_timeout => {},1413 owner_can_transfer => ensure!(1414 !limits.owner_can_transfer_instaled() ||1415 old_limit || !new_limit,1416 <Error<T>>::OwnerPermissionsCantBeReverted,1417 ),1418 owner_can_destroy => ensure!(1419 old_limit || !new_limit,1420 <Error<T>>::OwnerPermissionsCantBeReverted,1421 ),1422 transfers_enabled => {},1423 );1424 Ok(new_limit)1425 }14261427 /// Merge set fields from `new_permission` to `old_permission`.1428 pub fn clamp_permissions(1429 _mode: CollectionMode,1430 old_permission: &CollectionPermissions,1431 mut new_permission: CollectionPermissions,1432 ) -> Result<CollectionPermissions, DispatchError> {1433 limit_default_clone!(old_permission, new_permission,1434 access => {},1435 mint_mode => {},1436 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1437 );1438 Ok(new_permission)1439 }1440}14411442/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1443#[macro_export]1444macro_rules! unsupported {1445 ($runtime:path) => {1446 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1447 };1448}14491450/// Return weights for various worst-case operations.1451pub trait CommonWeightInfo<CrossAccountId> {1452 /// Weight of item creation.1453 fn create_item() -> Weight;14541455 /// Weight of items creation.1456 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14571458 /// Weight of items creation.1459 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14601461 /// The weight of the burning item.1462 fn burn_item() -> Weight;14631464 /// Property setting weight.1465 ///1466 /// * `amount`- The number of properties to set.1467 fn set_collection_properties(amount: u32) -> Weight;14681469 /// Collection property deletion weight.1470 ///1471 /// * `amount`- The number of properties to set.1472 fn delete_collection_properties(amount: u32) -> Weight;14731474 /// Token property setting weight.1475 ///1476 /// * `amount`- The number of properties to set.1477 fn set_token_properties(amount: u32) -> Weight;14781479 /// Token property deletion weight.1480 ///1481 /// * `amount`- The number of properties to delete.1482 fn delete_token_properties(amount: u32) -> Weight;14831484 /// Token property permissions set weight.1485 ///1486 /// * `amount`- The number of property permissions to set.1487 fn set_token_property_permissions(amount: u32) -> Weight;14881489 /// Transfer price of the token or its parts.1490 fn transfer() -> Weight;14911492 /// The price of setting the permission of the operation from another user.1493 fn approve() -> Weight;14941495 /// Transfer price from another user.1496 fn transfer_from() -> Weight;14971498 /// The price of burning a token from another user.1499 fn burn_from() -> Weight;15001501 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1502 /// whole users's balance.1503 ///1504 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1505 fn burn_recursively_self_raw() -> Weight;15061507 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1508 ///1509 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1510 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15111512 /// The price of recursive burning a token.1513 ///1514 /// `max_selfs` - The maximum burning weight of the token itself.1515 /// `max_breadth` - The maximum number of nested tokens to burn.1516 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1517 Self::burn_recursively_self_raw()1518 .saturating_mul(max_selfs.max(1) as u64)1519 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1520 }15211522 /// The price of retrieving token owner1523 fn token_owner() -> Weight;1524}15251526/// Weight info extension trait for refungible pallet.1527pub trait RefungibleExtensionsWeightInfo {1528 /// Weight of token repartition.1529 fn repartition() -> Weight;1530}15311532/// Common collection operations.1533///1534/// It wraps methods in Fungible, Nonfungible and Refungible pallets1535/// and adds weight info.1536pub trait CommonCollectionOperations<T: Config> {1537 /// Create token.1538 ///1539 /// * `sender` - The user who mint the token and pays for the transaction.1540 /// * `to` - The user who will own the token.1541 /// * `data` - Token data.1542 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1543 fn create_item(1544 &self,1545 sender: T::CrossAccountId,1546 to: T::CrossAccountId,1547 data: CreateItemData,1548 nesting_budget: &dyn Budget,1549 ) -> DispatchResultWithPostInfo;15501551 /// Create multiple tokens.1552 ///1553 /// * `sender` - The user who mint the token and pays for the transaction.1554 /// * `to` - The user who will own the token.1555 /// * `data` - Token data.1556 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1557 fn create_multiple_items(1558 &self,1559 sender: T::CrossAccountId,1560 to: T::CrossAccountId,1561 data: Vec<CreateItemData>,1562 nesting_budget: &dyn Budget,1563 ) -> DispatchResultWithPostInfo;15641565 /// Create multiple tokens.1566 ///1567 /// * `sender` - The user who mint the token and pays for the transaction.1568 /// * `to` - The user who will own the token.1569 /// * `data` - Token data.1570 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1571 fn create_multiple_items_ex(1572 &self,1573 sender: T::CrossAccountId,1574 data: CreateItemExData<T::CrossAccountId>,1575 nesting_budget: &dyn Budget,1576 ) -> DispatchResultWithPostInfo;15771578 /// Burn token.1579 ///1580 /// * `sender` - The user who owns the token.1581 /// * `token` - Token id that will burned.1582 /// * `amount` - The number of parts of the token that will be burned.1583 fn burn_item(1584 &self,1585 sender: T::CrossAccountId,1586 token: TokenId,1587 amount: u128,1588 ) -> DispatchResultWithPostInfo;15891590 /// Burn token and all nested tokens recursievly.1591 ///1592 /// * `sender` - The user who owns the token.1593 /// * `token` - Token id that will burned.1594 /// * `self_budget` - The budget that can be spent on burning tokens.1595 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.1596 fn burn_item_recursively(1597 &self,1598 sender: T::CrossAccountId,1599 token: TokenId,1600 self_budget: &dyn Budget,1601 breadth_budget: &dyn Budget,1602 ) -> DispatchResultWithPostInfo;16031604 /// Set collection properties.1605 ///1606 /// * `sender` - Must be either the owner of the collection or its admin.1607 /// * `properties` - Properties to be set.1608 fn set_collection_properties(1609 &self,1610 sender: T::CrossAccountId,1611 properties: Vec<Property>,1612 ) -> DispatchResultWithPostInfo;16131614 /// Delete collection properties.1615 ///1616 /// * `sender` - Must be either the owner of the collection or its admin.1617 /// * `properties` - The properties to be removed.1618 fn delete_collection_properties(1619 &self,1620 sender: &T::CrossAccountId,1621 property_keys: Vec<PropertyKey>,1622 ) -> DispatchResultWithPostInfo;16231624 /// Set token properties.1625 ///1626 /// The appropriate [`PropertyPermission`] for the token property1627 /// must be set with [`Self::set_token_property_permissions`].1628 ///1629 /// * `sender` - Must be either the owner of the token or its admin.1630 /// * `token_id` - The token for which the properties are being set.1631 /// * `properties` - Properties to be set.1632 /// * `budget` - Budget for setting properties.1633 fn set_token_properties(1634 &self,1635 sender: T::CrossAccountId,1636 token_id: TokenId,1637 properties: Vec<Property>,1638 budget: &dyn Budget,1639 ) -> DispatchResultWithPostInfo;16401641 /// Remove token properties.1642 ///1643 /// The appropriate [`PropertyPermission`] for the token property1644 /// must be set with [`Self::set_token_property_permissions`].1645 ///1646 /// * `sender` - Must be either the owner of the token or its admin.1647 /// * `token_id` - The token for which the properties are being remove.1648 /// * `property_keys` - Keys to remove corresponding properties.1649 /// * `budget` - Budget for removing properties.1650 fn delete_token_properties(1651 &self,1652 sender: T::CrossAccountId,1653 token_id: TokenId,1654 property_keys: Vec<PropertyKey>,1655 budget: &dyn Budget,1656 ) -> DispatchResultWithPostInfo;16571658 /// Set token property permissions.1659 ///1660 /// * `sender` - Must be either the owner of the token or its admin.1661 /// * `token_id` - The token for which the properties are being set.1662 /// * `property_permissions` - Property permissions to be set.1663 /// * `budget` - Budget for setting properties.1664 fn set_token_property_permissions(1665 &self,1666 sender: &T::CrossAccountId,1667 property_permissions: Vec<PropertyKeyPermission>,1668 ) -> DispatchResultWithPostInfo;16691670 /// Transfer amount of token pieces.1671 ///1672 /// * `sender` - Donor user.1673 /// * `to` - Recepient user.1674 /// * `token` - The token of which parts are being sent.1675 /// * `amount` - The number of parts of the token that will be transferred.1676 /// * `budget` - The maximum budget that can be spent on the transfer.1677 fn transfer(1678 &self,1679 sender: T::CrossAccountId,1680 to: T::CrossAccountId,1681 token: TokenId,1682 amount: u128,1683 budget: &dyn Budget,1684 ) -> DispatchResultWithPostInfo;16851686 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].1687 ///1688 /// * `sender` - The user who grants access to the token.1689 /// * `spender` - The user to whom the rights are granted.1690 /// * `token` - The token to which access is granted.1691 /// * `amount` - The amount of pieces that another user can dispose of.1692 fn approve(1693 &self,1694 sender: T::CrossAccountId,1695 spender: T::CrossAccountId,1696 token: TokenId,1697 amount: u128,1698 ) -> DispatchResultWithPostInfo;16991700 /// Send parts of a token owned by another user.1701 ///1702 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1703 ///1704 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).1705 /// * `from` - The user who owns the token.1706 /// * `to` - Recepient user.1707 /// * `token` - The token of which parts are being sent.1708 /// * `amount` - The number of parts of the token that will be transferred.1709 /// * `budget` - The maximum budget that can be spent on the transfer.1710 fn transfer_from(1711 &self,1712 sender: T::CrossAccountId,1713 from: T::CrossAccountId,1714 to: T::CrossAccountId,1715 token: TokenId,1716 amount: u128,1717 budget: &dyn Budget,1718 ) -> DispatchResultWithPostInfo;17191720 /// Burn parts of a token owned by another user.1721 ///1722 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1723 ///1724 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).1725 /// * `from` - The user who owns the token.1726 /// * `token` - The token of which parts are being sent.1727 /// * `amount` - The number of parts of the token that will be transferred.1728 /// * `budget` - The maximum budget that can be spent on the burn.1729 fn burn_from(1730 &self,1731 sender: T::CrossAccountId,1732 from: T::CrossAccountId,1733 token: TokenId,1734 amount: u128,1735 budget: &dyn Budget,1736 ) -> DispatchResultWithPostInfo;17371738 /// Check permission to nest token.1739 ///1740 /// * `sender` - The user who initiated the check.1741 /// * `from` - The token that is checked for embedding.1742 /// * `under` - Token under which to check.1743 /// * `budget` - The maximum budget that can be spent on the check.1744 fn check_nesting(1745 &self,1746 sender: T::CrossAccountId,1747 from: (CollectionId, TokenId),1748 under: TokenId,1749 budget: &dyn Budget,1750 ) -> DispatchResult;17511752 /// Nest one token into another.1753 ///1754 /// * `under` - Token holder.1755 /// * `to_nest` - Nested token.1756 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17571758 /// Unnest token.1759 ///1760 /// * `under` - Token holder.1761 /// * `to_nest` - Token to unnest.1762 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17631764 /// Get all user tokens.1765 ///1766 /// * `account` - Account for which you need to get tokens.1767 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17681769 /// Get all the tokens in the collection.1770 fn collection_tokens(&self) -> Vec<TokenId>;17711772 /// Check if the token exists.1773 ///1774 /// * `token` - Id token to check.1775 fn token_exists(&self, token: TokenId) -> bool;17761777 /// Get the id of the last minted token.1778 fn last_token_id(&self) -> TokenId;17791780 /// Get the owner of the token.1781 ///1782 /// * `token` - The token for which you need to find out the owner.1783 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17841785 /// Returns 10 tokens owners in no particular order.1786 ///1787 /// * `token` - The token for which you need to find out the owners.1788 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17891790 /// Get the value of the token property by key.1791 ///1792 /// * `token` - Token with the property to get.1793 /// * `key` - Property name.1794 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17951796 /// Get a set of token properties by key vector.1797 ///1798 /// * `token` - Token with the property to get.1799 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),1800 /// then all properties are returned.1801 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18021803 /// Amount of unique collection tokens1804 fn total_supply(&self) -> u32;18051806 /// Amount of different tokens account has.1807 ///1808 /// * `account` - The account for which need to get the balance.1809 fn account_balance(&self, account: T::CrossAccountId) -> u32;18101811 /// Amount of specific token account have.1812 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18131814 /// Amount of token pieces1815 fn total_pieces(&self, token: TokenId) -> Option<u128>;18161817 /// Get the number of parts of the token that a trusted user can manage.1818 ///1819 /// * `sender` - Trusted user.1820 /// * `spender` - Owner of the token.1821 /// * `token` - The token for which to get the value.1822 fn allowance(1823 &self,1824 sender: T::CrossAccountId,1825 spender: T::CrossAccountId,1826 token: TokenId,1827 ) -> u128;18281829 /// Get extension for RFT collection.1830 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1831}18321833/// Extension for RFT collection.1834pub trait RefungibleExtensions<T>1835where1836 T: Config,1837{1838 /// Change the number of parts of the token.1839 ///1840 /// When the value changes down, this function is equivalent to burning parts of the token.1841 ///1842 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.1843 /// * `token` - The token for which you want to change the number of parts.1844 /// * `amount` - The new value of the parts of the token.1845 fn repartition(1846 &self,1847 sender: &T::CrossAccountId,1848 token: TokenId,1849 amount: u128,1850 ) -> DispatchResultWithPostInfo;1851}18521853/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].1854///1855/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.1856pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1857 let post_info = PostDispatchInfo {1858 actual_weight: Some(weight),1859 pays_fee: Pays::Yes,1860 };1861 match res {1862 Ok(()) => Ok(post_info),1863 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1864 }1865}18661867impl<T: Config> From<PropertiesError> for Error<T> {1868 fn from(error: PropertiesError) -> Self {1869 match error {1870 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1871 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1872 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1873 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1874 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1875 }1876 }1877}pallets/fungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/fungible/CHANGELOG.md
+++ b/pallets/fungible/CHANGELOG.md
@@ -4,12 +4,6 @@
<!-- bureaucrate goes here -->
-## [0.1.9] - 2022-12-05
-
-### Fixed
-
-- Transfer with zero tokens.
-
## [0.1.8] - 2022-11-18
### Added
pallets/fungible/Cargo.tomldiffbeforeafterboth--- a/pallets/fungible/Cargo.toml
+++ b/pallets/fungible/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-fungible"
-version = "0.1.8"
+version = "0.1.7"
license = "GPLv3"
edition = "2021"
pallets/fungible/src/lib.rsdiffbeforeafterboth--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -365,8 +365,6 @@
amount: u128,
nesting_budget: &dyn Budget,
) -> DispatchResult {
- ensure!(amount > 0, <CommonError<T>>::ZeroTransferNotAllowed);
-
ensure!(
collection.limits.transfers_enabled(),
<CommonError<T>>::TransferNotAllowed,
pallets/nonfungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/nonfungible/CHANGELOG.md
+++ b/pallets/nonfungible/CHANGELOG.md
@@ -4,12 +4,6 @@
<!-- bureaucrate goes here -->
-## [0.1.11] - 2022-12-05
-
-### Fixed
-
-- Transfer with zero tokens.
-
## [0.1.10] - 2022-11-18
### Added
pallets/nonfungible/Cargo.tomldiffbeforeafterboth--- a/pallets/nonfungible/Cargo.toml
+++ b/pallets/nonfungible/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-nonfungible"
-version = "0.1.10"
+version = "0.1.9"
license = "GPLv3"
edition = "2021"
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -23,7 +23,7 @@
};
use pallet_common::{
CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,
- weights::WeightInfo as _, Error as CommonError,
+ weights::WeightInfo as _,
};
use sp_runtime::DispatchError;
use sp_std::{vec::Vec, vec};
@@ -314,12 +314,14 @@
nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
- ensure!(amount > 0, <CommonError<T>>::ZeroTransferNotAllowed);
-
- with_weight(
- <Pallet<T>>::transfer(self, &from, &to, token, nesting_budget),
- <CommonWeights<T>>::transfer(),
- )
+ if amount == 1 {
+ with_weight(
+ <Pallet<T>>::transfer(self, &from, &to, token, nesting_budget),
+ <CommonWeights<T>>::transfer(),
+ )
+ } else {
+ Ok(().into())
+ }
}
fn approve(
@@ -351,12 +353,15 @@
nesting_budget: &dyn Budget,
) -> DispatchResultWithPostInfo {
ensure!(amount <= 1, <Error<T>>::NonfungibleItemsHaveNoAmount);
- ensure!(amount > 0, <CommonError<T>>::ZeroTransferNotAllowed);
- with_weight(
- <Pallet<T>>::transfer_from(self, &sender, &from, &to, token, nesting_budget),
- <CommonWeights<T>>::transfer_from(),
- )
+ if amount == 1 {
+ with_weight(
+ <Pallet<T>>::transfer_from(self, &sender, &from, &to, token, nesting_budget),
+ <CommonWeights<T>>::transfer_from(),
+ )
+ } else {
+ Ok(().into())
+ }
}
fn burn_from(
pallets/refungible/CHANGELOG.mddiffbeforeafterboth--- a/pallets/refungible/CHANGELOG.md
+++ b/pallets/refungible/CHANGELOG.md
@@ -3,11 +3,6 @@
All notable changes to this project will be documented in this file.
<!-- bureaucrate goes here -->
-## [0.2.10] - 2022-12-05
-
-### Fixed
-
-- Transfer with zero pieces.
## [0.2.9] - 2022-11-18
pallets/refungible/Cargo.tomldiffbeforeafterboth--- a/pallets/refungible/Cargo.toml
+++ b/pallets/refungible/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "pallet-refungible"
-version = "0.2.9"
+version = "0.2.8"
license = "GPLv3"
edition = "2021"
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -727,8 +727,6 @@
amount: u128,
nesting_budget: &dyn Budget,
) -> DispatchResult {
- ensure!(amount > 0, <CommonError<T>>::ZeroTransferNotAllowed);
-
ensure!(
collection.limits.transfers_enabled(),
<CommonError<T>>::TransferNotAllowed