difftreelog
fix find_parent
in: master
8 files changed
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};118use up_pov_estimate_rpc::PovInfo;119120pub use pallet::*;121use sp_core::H160;122use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};123#[cfg(feature = "runtime-benchmarks")]124pub mod benchmarking;125pub mod dispatch;126pub mod erc;127pub mod eth;128pub mod weights;129130/// Weight info.131pub type SelfWeightOf<T> = <T as Config>::WeightInfo;132133/// Collection handle contains information about collection data and id.134/// Also provides functionality to count consumed gas.135///136/// CollectionHandle is used as a generic wrapper for collections of all types.137/// It allows to perform common operations and queries on any collection type,138/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].139#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]140pub struct CollectionHandle<T: Config> {141 /// Collection id142 pub id: CollectionId,143 collection: Collection<T::AccountId>,144 /// Substrate recorder for counting consumed gas145 pub recorder: SubstrateRecorder<T>,146}147148impl<T: Config> WithRecorder<T> for CollectionHandle<T> {149 fn recorder(&self) -> &SubstrateRecorder<T> {150 &self.recorder151 }152 fn into_recorder(self) -> SubstrateRecorder<T> {153 self.recorder154 }155}156157impl<T: Config> CollectionHandle<T> {158 /// Same as [CollectionHandle::new] but with an explicit gas limit.159 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {160 <CollectionById<T>>::get(id).map(|collection| Self {161 id,162 collection,163 recorder: SubstrateRecorder::new(gas_limit),164 })165 }166167 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].168 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {169 <CollectionById<T>>::get(id).map(|collection| Self {170 id,171 collection,172 recorder,173 })174 }175176 /// Retrives collection data from storage and creates collection handle with default parameters.177 /// If collection not found return `None`178 pub fn new(id: CollectionId) -> Option<Self> {179 Self::new_with_gas_limit(id, u64::MAX)180 }181182 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.183 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {184 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)185 }186187 /// Consume gas for reading.188 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {189 self.recorder190 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(191 <T as frame_system::Config>::DbWeight::get()192 .read193 .saturating_mul(reads),194 )))195 }196197 /// Consume gas for writing.198 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {199 self.recorder200 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(201 <T as frame_system::Config>::DbWeight::get()202 .write203 .saturating_mul(writes),204 )))205 }206207 /// Consume gas for reading and writing.208 pub fn consume_store_reads_and_writes(209 &self,210 reads: u64,211 writes: u64,212 ) -> evm_coder::execution::Result<()> {213 let weight = <T as frame_system::Config>::DbWeight::get();214 let reads = weight.read.saturating_mul(reads);215 let writes = weight.read.saturating_mul(writes);216 self.recorder217 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(218 reads.saturating_add(writes),219 )))220 }221222 /// Save collection to storage.223 pub fn save(&self) -> DispatchResult {224 <CollectionById<T>>::insert(self.id, &self.collection);225 Ok(())226 }227228 /// Set collection sponsor.229 ///230 /// Unique collections allows sponsoring for certain actions.231 /// This method allows you to set the sponsor of the collection.232 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].233 pub fn set_sponsor(234 &mut self,235 sender: &T::CrossAccountId,236 sponsor: T::AccountId,237 ) -> DispatchResult {238 self.check_is_internal()?;239 self.check_is_owner_or_admin(sender)?;240241 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());242243 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));244 <PalletEvm<T>>::deposit_log(245 erc::CollectionHelpersEvents::CollectionChanged {246 collection_id: eth::collection_id_to_address(self.id),247 }248 .to_log(T::ContractAddress::get()),249 );250251 self.save()252 }253254 /// Force set `sponsor`.255 ///256 /// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation257 /// from the `sponsor` is not required.258 ///259 /// # Arguments260 ///261 /// * `sender`: Caller's account.262 /// * `sponsor`: ID of the account of the sponsor-to-be.263 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {264 self.check_is_internal()?;265266 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());267268 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));269 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));270 <PalletEvm<T>>::deposit_log(271 erc::CollectionHelpersEvents::CollectionChanged {272 collection_id: eth::collection_id_to_address(self.id),273 }274 .to_log(T::ContractAddress::get()),275 );276277 self.save()278 }279280 /// Confirm sponsorship281 ///282 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.283 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].284 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {285 self.check_is_internal()?;286 ensure!(287 self.collection.sponsorship.pending_sponsor() == Some(sender),288 Error::<T>::ConfirmSponsorshipFail289 );290291 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());292293 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));294 <PalletEvm<T>>::deposit_log(295 erc::CollectionHelpersEvents::CollectionChanged {296 collection_id: eth::collection_id_to_address(self.id),297 }298 .to_log(T::ContractAddress::get()),299 );300301 self.save()302 }303304 /// Remove collection sponsor.305 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {306 self.check_is_internal()?;307 self.check_is_owner_or_admin(sender)?;308309 self.collection.sponsorship = SponsorshipState::Disabled;310311 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));312 <PalletEvm<T>>::deposit_log(313 erc::CollectionHelpersEvents::CollectionChanged {314 collection_id: eth::collection_id_to_address(self.id),315 }316 .to_log(T::ContractAddress::get()),317 );318 self.save()319 }320321 /// Force remove `sponsor`.322 ///323 /// Differs from `remove_sponsor` in that324 /// it doesn't require consent from the `owner` of the collection.325 pub fn force_remove_sponsor(&mut self) -> DispatchResult {326 self.check_is_internal()?;327328 self.collection.sponsorship = SponsorshipState::Disabled;329330 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));331 <PalletEvm<T>>::deposit_log(332 erc::CollectionHelpersEvents::CollectionChanged {333 collection_id: eth::collection_id_to_address(self.id),334 }335 .to_log(T::ContractAddress::get()),336 );337 self.save()338 }339340 /// Checks that the collection was created with, and must be operated upon through **Unique API**.341 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.342 pub fn check_is_internal(&self) -> DispatchResult {343 if self.flags.external {344 return Err(<Error<T>>::CollectionIsExternal)?;345 }346347 Ok(())348 }349350 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.351 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.352 pub fn check_is_external(&self) -> DispatchResult {353 if !self.flags.external {354 return Err(<Error<T>>::CollectionIsInternal)?;355 }356357 Ok(())358 }359}360361impl<T: Config> Deref for CollectionHandle<T> {362 type Target = Collection<T::AccountId>;363364 fn deref(&self) -> &Self::Target {365 &self.collection366 }367}368369impl<T: Config> DerefMut for CollectionHandle<T> {370 fn deref_mut(&mut self) -> &mut Self::Target {371 &mut self.collection372 }373}374375impl<T: Config> CollectionHandle<T> {376 /// Checks if the `user` is the owner of the collection.377 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {378 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);379 Ok(())380 }381382 /// Returns **true** if the `user` is the owner or administrator of the collection.383 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {384 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))385 }386387 /// Checks if the `user` is the owner or administrator of the collection.388 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {389 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);390 Ok(())391 }392393 /// Returns **true** if394 /// * the `user`is a collection owner or admin395 /// * the collection limits allow the owner/admins to transfer/burn any collection token396 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {397 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)398 }399400 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.401 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {402 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)403 }404405 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.406 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {407 ensure!(408 <Allowlist<T>>::get((self.id, user)),409 <Error<T>>::AddressNotInAllowlist410 );411 Ok(())412 }413414 /// Changes collection owner to another account415 /// #### Store read/writes416 /// 1 writes417 pub fn change_owner(418 &mut self,419 caller: T::CrossAccountId,420 new_owner: T::CrossAccountId,421 ) -> DispatchResult {422 self.check_is_internal()?;423 self.check_is_owner(&caller)?;424 self.collection.owner = new_owner.as_sub().clone();425426 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(427 self.id,428 new_owner.as_sub().clone(),429 ));430 <PalletEvm<T>>::deposit_log(431 erc::CollectionHelpersEvents::CollectionChanged {432 collection_id: eth::collection_id_to_address(self.id),433 }434 .to_log(T::ContractAddress::get()),435 );436437 self.save()438 }439}440441#[frame_support::pallet]442pub mod pallet {443 use super::*;444 use dispatch::CollectionDispatch;445 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};446 use frame_system::pallet_prelude::*;447 use frame_support::traits::Currency;448 use up_data_structs::{TokenId, mapping::TokenAddressMapping};449 use scale_info::TypeInfo;450 use weights::WeightInfo;451452 #[pallet::config]453 pub trait Config:454 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo455 {456 /// Weight information for functions of this pallet.457 type WeightInfo: WeightInfo;458459 /// Events compatible with [`frame_system::Config::Event`].460 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;461462 /// Handler of accounts and payment.463 type Currency: Currency<Self::AccountId>;464465 /// Set price to create a collection.466 #[pallet::constant]467 type CollectionCreationPrice: Get<468 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,469 >;470471 /// Dispatcher of operations on collections.472 type CollectionDispatch: CollectionDispatch<Self>;473474 /// Account which holds the chain's treasury.475 type TreasuryAccountId: Get<Self::AccountId>;476477 /// Address under which the CollectionHelper contract would be available.478 #[pallet::constant]479 type ContractAddress: Get<H160>;480481 /// Mapper for token addresses to Ethereum addresses.482 type EvmTokenAddressMapping: TokenAddressMapping<H160>;483484 /// Mapper for token addresses to [`CrossAccountId`].485 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;486 }487488 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);489490 #[pallet::pallet]491 #[pallet::storage_version(STORAGE_VERSION)]492 #[pallet::generate_store(pub(super) trait Store)]493 pub struct Pallet<T>(_);494495 #[pallet::extra_constants]496 impl<T: Config> Pallet<T> {497 /// Maximum admins per collection.498 pub fn collection_admins_limit() -> u32 {499 COLLECTION_ADMINS_LIMIT500 }501 }502503 impl<T: Config> Pallet<T> {504 /// Helper function that handles deposit events505 pub fn deposit_event(event: Event<T>) {506 let event = <T as Config>::RuntimeEvent::from(event);507 let event = event.into();508 <frame_system::Pallet<T>>::deposit_event(event)509 }510 }511512 #[pallet::event]513 pub enum Event<T: Config> {514 /// New collection was created515 CollectionCreated(516 /// Globally unique identifier of newly created collection.517 CollectionId,518 /// [`CollectionMode`] converted into _u8_.519 u8,520 /// Collection owner.521 T::AccountId,522 ),523524 /// New collection was destroyed525 CollectionDestroyed(526 /// Globally unique identifier of collection.527 CollectionId,528 ),529530 /// New item was created.531 ItemCreated(532 /// Id of the collection where item was created.533 CollectionId,534 /// Id of an item. Unique within the collection.535 TokenId,536 /// Owner of newly created item537 T::CrossAccountId,538 /// Always 1 for NFT539 u128,540 ),541542 /// Collection item was burned.543 ItemDestroyed(544 /// Id of the collection where item was destroyed.545 CollectionId,546 /// Identifier of burned NFT.547 TokenId,548 /// Which user has destroyed its tokens.549 T::CrossAccountId,550 /// Amount of token pieces destroed. Always 1 for NFT.551 u128,552 ),553554 /// Item was transferred555 Transfer(556 /// Id of collection to which item is belong.557 CollectionId,558 /// Id of an item.559 TokenId,560 /// Original owner of item.561 T::CrossAccountId,562 /// New owner of item.563 T::CrossAccountId,564 /// Amount of token pieces transfered. Always 1 for NFT.565 u128,566 ),567568 /// Amount pieces of token owned by `sender` was approved for `spender`.569 Approved(570 /// Id of collection to which item is belong.571 CollectionId,572 /// Id of an item.573 TokenId,574 /// Original owner of item.575 T::CrossAccountId,576 /// Id for which the approval was granted.577 T::CrossAccountId,578 /// Amount of token pieces transfered. Always 1 for NFT.579 u128,580 ),581582 /// A `sender` approves operations on all owned tokens for `spender`.583 ApprovedForAll(584 /// Id of collection to which item is belong.585 CollectionId,586 /// Owner of a wallet.587 T::CrossAccountId,588 /// Id for which operator status was granted or rewoked.589 T::CrossAccountId,590 /// Is operator status granted or revoked?591 bool,592 ),593594 /// The colletion property has been added or edited.595 CollectionPropertySet(596 /// Id of collection to which property has been set.597 CollectionId,598 /// The property that was set.599 PropertyKey,600 ),601602 /// The property has been deleted.603 CollectionPropertyDeleted(604 /// Id of collection to which property has been deleted.605 CollectionId,606 /// The property that was deleted.607 PropertyKey,608 ),609610 /// The token property has been added or edited.611 TokenPropertySet(612 /// Identifier of the collection whose token has the property set.613 CollectionId,614 /// The token for which the property was set.615 TokenId,616 /// The property that was set.617 PropertyKey,618 ),619620 /// The token property has been deleted.621 TokenPropertyDeleted(622 /// Identifier of the collection whose token has the property deleted.623 CollectionId,624 /// The token for which the property was deleted.625 TokenId,626 /// The property that was deleted.627 PropertyKey,628 ),629630 /// The token property permission of a collection has been set.631 PropertyPermissionSet(632 /// ID of collection to which property permission has been set.633 CollectionId,634 /// The property permission that was set.635 PropertyKey,636 ),637638 /// Address was added to the allow list.639 AllowListAddressAdded(640 /// ID of the affected collection.641 CollectionId,642 /// Address of the added account.643 T::CrossAccountId,644 ),645646 /// Address was removed from the allow list.647 AllowListAddressRemoved(648 /// ID of the affected collection.649 CollectionId,650 /// Address of the removed account.651 T::CrossAccountId,652 ),653654 /// Collection admin was added.655 CollectionAdminAdded(656 /// ID of the affected collection.657 CollectionId,658 /// Admin address.659 T::CrossAccountId,660 ),661662 /// Collection admin was removed.663 CollectionAdminRemoved(664 /// ID of the affected collection.665 CollectionId,666 /// Removed admin address.667 T::CrossAccountId,668 ),669670 /// Collection limits were set.671 CollectionLimitSet(672 /// ID of the affected collection.673 CollectionId,674 ),675676 /// Collection owned was changed.677 CollectionOwnerChanged(678 /// ID of the affected collection.679 CollectionId,680 /// New owner address.681 T::AccountId,682 ),683684 /// Collection permissions were set.685 CollectionPermissionSet(686 /// ID of the affected collection.687 CollectionId,688 ),689690 /// Collection sponsor was set.691 CollectionSponsorSet(692 /// ID of the affected collection.693 CollectionId,694 /// New sponsor address.695 T::AccountId,696 ),697698 /// New sponsor was confirm.699 SponsorshipConfirmed(700 /// ID of the affected collection.701 CollectionId,702 /// New sponsor address.703 T::AccountId,704 ),705706 /// Collection sponsor was removed.707 CollectionSponsorRemoved(708 /// ID of the affected collection.709 CollectionId,710 ),711 }712713 #[pallet::error]714 pub enum Error<T> {715 /// This collection does not exist.716 CollectionNotFound,717 /// Sender parameter and item owner must be equal.718 MustBeTokenOwner,719 /// No permission to perform action720 NoPermission,721 /// Destroying only empty collections is allowed722 CantDestroyNotEmptyCollection,723 /// Collection is not in mint mode.724 PublicMintingNotAllowed,725 /// Address is not in allow list.726 AddressNotInAllowlist,727728 /// Collection name can not be longer than 63 char.729 CollectionNameLimitExceeded,730 /// Collection description can not be longer than 255 char.731 CollectionDescriptionLimitExceeded,732 /// Token prefix can not be longer than 15 char.733 CollectionTokenPrefixLimitExceeded,734 /// Total collections bound exceeded.735 TotalCollectionsLimitExceeded,736 /// Exceeded max admin count737 CollectionAdminCountExceeded,738 /// Collection limit bounds per collection exceeded739 CollectionLimitBoundsExceeded,740 /// Tried to enable permissions which are only permitted to be disabled741 OwnerPermissionsCantBeReverted,742 /// Collection settings not allowing items transferring743 TransferNotAllowed,744 /// Account token limit exceeded per collection745 AccountTokenLimitExceeded,746 /// Collection token limit exceeded747 CollectionTokenLimitExceeded,748 /// Metadata flag frozen749 MetadataFlagFrozen,750751 /// Item does not exist752 TokenNotFound,753 /// Item is balance not enough754 TokenValueTooLow,755 /// Requested value is more than the approved756 ApprovedValueTooLow,757 /// Tried to approve more than owned758 CantApproveMoreThanOwned,759 /// Only spending from eth mirror could be approved760 AddressIsNotEthMirror,761762 /// Can't transfer tokens to ethereum zero address763 AddressIsZero,764765 /// The operation is not supported766 UnsupportedOperation,767768 /// Insufficient funds to perform an action769 NotSufficientFounds,770771 /// User does not satisfy the nesting rule772 UserIsNotAllowedToNest,773 /// Only tokens from specific collections may nest tokens under this one774 SourceCollectionIsNotAllowedToNest,775776 /// Tried to store more data than allowed in collection field777 CollectionFieldSizeExceeded,778779 /// Tried to store more property data than allowed780 NoSpaceForProperty,781782 /// Tried to store more property keys than allowed783 PropertyLimitReached,784785 /// Property key is too long786 PropertyKeyIsTooLong,787788 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed789 InvalidCharacterInPropertyKey,790791 /// Empty property keys are forbidden792 EmptyPropertyKey,793794 /// Tried to access an external collection with an internal API795 CollectionIsExternal,796797 /// Tried to access an internal collection with an external API798 CollectionIsInternal,799800 /// This address is not set as sponsor, use setCollectionSponsor first.801 ConfirmSponsorshipFail,802803 /// The user is not an administrator.804 UserIsNotCollectionAdmin,805 }806807 /// Storage of the count of created collections. Essentially contains the last collection ID.808 #[pallet::storage]809 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;810811 /// Storage of the count of deleted collections.812 #[pallet::storage]813 pub type DestroyedCollectionCount<T> =814 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;815816 /// Storage of collection info.817 #[pallet::storage]818 pub type CollectionById<T> = StorageMap<819 Hasher = Blake2_128Concat,820 Key = CollectionId,821 Value = Collection<<T as frame_system::Config>::AccountId>,822 QueryKind = OptionQuery,823 >;824825 /// Storage of collection properties.826 #[pallet::storage]827 #[pallet::getter(fn collection_properties)]828 pub type CollectionProperties<T> = StorageMap<829 Hasher = Blake2_128Concat,830 Key = CollectionId,831 Value = Properties,832 QueryKind = ValueQuery,833 OnEmpty = up_data_structs::CollectionProperties,834 >;835836 /// Storage of token property permissions of a collection.837 #[pallet::storage]838 #[pallet::getter(fn property_permissions)]839 pub type CollectionPropertyPermissions<T> = StorageMap<840 Hasher = Blake2_128Concat,841 Key = CollectionId,842 Value = PropertiesPermissionMap,843 QueryKind = ValueQuery,844 >;845846 /// Storage of the amount of collection admins.847 #[pallet::storage]848 pub type AdminAmount<T> = StorageMap<849 Hasher = Blake2_128Concat,850 Key = CollectionId,851 Value = u32,852 QueryKind = ValueQuery,853 >;854855 /// List of collection admins.856 #[pallet::storage]857 pub type IsAdmin<T: Config> = StorageNMap<858 Key = (859 Key<Blake2_128Concat, CollectionId>,860 Key<Blake2_128Concat, T::CrossAccountId>,861 ),862 Value = bool,863 QueryKind = ValueQuery,864 >;865866 /// Allowlisted collection users.867 #[pallet::storage]868 pub type Allowlist<T: Config> = StorageNMap<869 Key = (870 Key<Blake2_128Concat, CollectionId>,871 Key<Blake2_128Concat, T::CrossAccountId>,872 ),873 Value = bool,874 QueryKind = ValueQuery,875 >;876877 /// Not used by code, exists only to provide some types to metadata.878 #[pallet::storage]879 pub type DummyStorageValue<T: Config> = StorageValue<880 Value = (881 CollectionStats,882 CollectionId,883 TokenId,884 TokenChild,885 PhantomType<(886 TokenData<T::CrossAccountId>,887 RpcCollection<T::AccountId>,888 // RMRK889 RmrkCollectionInfo<T::AccountId>,890 RmrkInstanceInfo<T::AccountId>,891 RmrkResourceInfo,892 RmrkPropertyInfo,893 RmrkBaseInfo<T::AccountId>,894 RmrkPartType,895 RmrkBoundedTheme,896 RmrkNftChild,897 // PoV Estimate Info898 PovInfo,899 )>,900 ),901 QueryKind = OptionQuery,902 >;903904 #[pallet::hooks]905 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {906 fn on_runtime_upgrade() -> Weight {907 StorageVersion::new(1).put::<Pallet<T>>();908909 Weight::zero()910 }911 }912}913914impl<T: Config> Pallet<T> {915 /// Enshure that receiver address is correct.916 ///917 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.918 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {919 ensure!(920 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,921 <Error<T>>::AddressIsZero922 );923 Ok(())924 }925926 /// Get a vector of collection admins.927 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {928 <IsAdmin<T>>::iter_prefix((collection,))929 .map(|(a, _)| a)930 .collect()931 }932933 /// Get a vector of users allowed to mint tokens.934 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {935 <Allowlist<T>>::iter_prefix((collection,))936 .map(|(a, _)| a)937 .collect()938 }939940 /// Is `user` allowed to mint token in `collection`.941 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {942 <Allowlist<T>>::get((collection, user))943 }944945 /// Get statistics of collections.946 pub fn collection_stats() -> CollectionStats {947 let created = <CreatedCollectionCount<T>>::get();948 let destroyed = <DestroyedCollectionCount<T>>::get();949 CollectionStats {950 created: created.0,951 destroyed: destroyed.0,952 alive: created.0 - destroyed.0,953 }954 }955956 /// Get the effective limits for the collection.957 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {958 let collection = <CollectionById<T>>::get(collection)?;959 let limits = collection.limits;960 let effective_limits = CollectionLimits {961 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),962 sponsored_data_size: Some(limits.sponsored_data_size()),963 sponsored_data_rate_limit: Some(964 limits965 .sponsored_data_rate_limit966 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),967 ),968 token_limit: Some(limits.token_limit()),969 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(970 match collection.mode {971 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,972 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,973 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,974 },975 )),976 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),977 owner_can_transfer: Some(limits.owner_can_transfer()),978 owner_can_destroy: Some(limits.owner_can_destroy()),979 transfers_enabled: Some(limits.transfers_enabled()),980 };981982 Some(effective_limits)983 }984985 /// Returns information about the `collection` adapted for rpc.986 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {987 let Collection {988 name,989 description,990 owner,991 mode,992 token_prefix,993 sponsorship,994 limits,995 permissions,996 flags,997 } = <CollectionById<T>>::get(collection)?;998999 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1000 .into_iter()1001 .map(|(key, permission)| PropertyKeyPermission { key, permission })1002 .collect();10031004 let properties = <CollectionProperties<T>>::get(collection)1005 .into_iter()1006 .map(|(key, value)| Property { key, value })1007 .collect();10081009 let permissions = CollectionPermissions {1010 access: Some(permissions.access()),1011 mint_mode: Some(permissions.mint_mode()),1012 nesting: Some(permissions.nesting().clone()),1013 };10141015 Some(RpcCollection {1016 name: name.into_inner(),1017 description: description.into_inner(),1018 owner,1019 mode,1020 token_prefix: token_prefix.into_inner(),1021 sponsorship,1022 limits,1023 permissions,1024 token_property_permissions,1025 properties,1026 read_only: flags.external,10271028 flags: RpcCollectionFlags {1029 foreign: flags.foreign,1030 erc721metadata: flags.erc721metadata,1031 },1032 })1033 }1034}10351036macro_rules! limit_default {1037 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1038 $(1039 if let Some($new) = $new.$field {1040 let $old = $old.$field($($arg)?);1041 let _ = $new;1042 let _ = $old;1043 $check1044 } else {1045 $new.$field = $old.$field1046 }1047 )*1048 }};1049}1050macro_rules! limit_default_clone {1051 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1052 $(1053 if let Some($new) = $new.$field.clone() {1054 let $old = $old.$field($($arg)?);1055 let _ = $new;1056 let _ = $old;1057 $check1058 } else {1059 $new.$field = $old.$field.clone()1060 }1061 )*1062 }};1063}10641065impl<T: Config> Pallet<T> {1066 /// Create new collection.1067 ///1068 /// * `owner` - The owner of the collection.1069 /// * `data` - Description of the created collection.1070 /// * `flags` - Extra flags to store.1071 pub fn init_collection(1072 owner: T::CrossAccountId,1073 payer: T::CrossAccountId,1074 data: CreateCollectionData<T::AccountId>,1075 flags: CollectionFlags,1076 ) -> Result<CollectionId, DispatchError> {1077 {1078 ensure!(1079 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1080 Error::<T>::CollectionTokenPrefixLimitExceeded1081 );1082 }10831084 let created_count = <CreatedCollectionCount<T>>::get()1085 .01086 .checked_add(1)1087 .ok_or(ArithmeticError::Overflow)?;1088 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1089 let id = CollectionId(created_count);10901091 // bound Total number of collections1092 ensure!(1093 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1094 <Error<T>>::TotalCollectionsLimitExceeded1095 );10961097 // =========10981099 let collection = Collection {1100 owner: owner.as_sub().clone(),1101 name: data.name,1102 mode: data.mode.clone(),1103 description: data.description,1104 token_prefix: data.token_prefix,1105 sponsorship: data1106 .pending_sponsor1107 .map(SponsorshipState::Unconfirmed)1108 .unwrap_or_default(),1109 limits: data1110 .limits1111 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1112 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1113 permissions: data1114 .permissions1115 .map(|permissions| {1116 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1117 })1118 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1119 flags,1120 };11211122 let mut collection_properties = up_data_structs::CollectionProperties::get();1123 collection_properties1124 .try_set_from_iter(data.properties.into_iter())1125 .map_err(<Error<T>>::from)?;11261127 CollectionProperties::<T>::insert(id, collection_properties);11281129 let mut token_props_permissions = PropertiesPermissionMap::new();1130 token_props_permissions1131 .try_set_from_iter(data.token_property_permissions.into_iter())1132 .map_err(<Error<T>>::from)?;11331134 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11351136 // Take a (non-refundable) deposit of collection creation1137 {1138 let mut imbalance =1139 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1140 imbalance.subsume(1141 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1142 &T::TreasuryAccountId::get(),1143 T::CollectionCreationPrice::get(),1144 ),1145 );1146 <T as Config>::Currency::settle(1147 payer.as_sub(),1148 imbalance,1149 WithdrawReasons::TRANSFER,1150 ExistenceRequirement::KeepAlive,1151 )1152 .map_err(|_| Error::<T>::NotSufficientFounds)?;1153 }11541155 <CreatedCollectionCount<T>>::put(created_count);1156 <Pallet<T>>::deposit_event(Event::CollectionCreated(1157 id,1158 data.mode.id(),1159 owner.as_sub().clone(),1160 ));1161 <PalletEvm<T>>::deposit_log(1162 erc::CollectionHelpersEvents::CollectionCreated {1163 owner: *owner.as_eth(),1164 collection_id: eth::collection_id_to_address(id),1165 }1166 .to_log(T::ContractAddress::get()),1167 );1168 <CollectionById<T>>::insert(id, collection);1169 Ok(id)1170 }11711172 /// Destroy collection.1173 ///1174 /// * `collection` - Collection handler.1175 /// * `sender` - The owner or administrator of the collection.1176 pub fn destroy_collection(1177 collection: CollectionHandle<T>,1178 sender: &T::CrossAccountId,1179 ) -> DispatchResult {1180 ensure!(1181 collection.limits.owner_can_destroy(),1182 <Error<T>>::NoPermission,1183 );1184 collection.check_is_owner(sender)?;11851186 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1187 .01188 .checked_add(1)1189 .ok_or(ArithmeticError::Overflow)?;11901191 // =========11921193 <DestroyedCollectionCount<T>>::put(destroyed_collections);1194 <CollectionById<T>>::remove(collection.id);1195 <AdminAmount<T>>::remove(collection.id);1196 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1197 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1198 <CollectionProperties<T>>::remove(collection.id);11991200 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12011202 <PalletEvm<T>>::deposit_log(1203 erc::CollectionHelpersEvents::CollectionDestroyed {1204 collection_id: eth::collection_id_to_address(collection.id),1205 }1206 .to_log(T::ContractAddress::get()),1207 );1208 Ok(())1209 }12101211 /// This function sets or removes a collection properties according to1212 /// `properties_updates` contents:1213 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1214 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1215 ///1216 /// This function fires an event for each property change.1217 /// In case of an error, all the changes (including the events) will be reverted1218 /// since the function is transactional.1219 #[transactional]1220 fn modify_collection_properties(1221 collection: &CollectionHandle<T>,1222 sender: &T::CrossAccountId,1223 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1224 ) -> DispatchResult {1225 collection.check_is_owner_or_admin(sender)?;12261227 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12281229 for (key, value) in properties_updates {1230 match value {1231 Some(value) => {1232 stored_properties1233 .try_set(key.clone(), value)1234 .map_err(<Error<T>>::from)?;12351236 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1237 <PalletEvm<T>>::deposit_log(1238 erc::CollectionHelpersEvents::CollectionChanged {1239 collection_id: eth::collection_id_to_address(collection.id),1240 }1241 .to_log(T::ContractAddress::get()),1242 );1243 }1244 None => {1245 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12461247 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1248 <PalletEvm<T>>::deposit_log(1249 erc::CollectionHelpersEvents::CollectionChanged {1250 collection_id: eth::collection_id_to_address(collection.id),1251 }1252 .to_log(T::ContractAddress::get()),1253 );1254 }1255 }1256 }12571258 <CollectionProperties<T>>::set(collection.id, stored_properties);12591260 Ok(())1261 }12621263 /// Set collection property.1264 ///1265 /// * `collection` - Collection handler.1266 /// * `sender` - The owner or administrator of the collection.1267 /// * `property` - The property to set.1268 pub fn set_collection_property(1269 collection: &CollectionHandle<T>,1270 sender: &T::CrossAccountId,1271 property: Property,1272 ) -> DispatchResult {1273 Self::set_collection_properties(collection, sender, [property].into_iter())1274 }12751276 /// Set a scoped collection property, where the scope is a special prefix1277 /// prohibiting a user access to change the property directly.1278 ///1279 /// * `collection_id` - ID of the collection for which the property is being set.1280 /// * `scope` - Property scope.1281 /// * `property` - The property to set.1282 pub fn set_scoped_collection_property(1283 collection_id: CollectionId,1284 scope: PropertyScope,1285 property: Property,1286 ) -> DispatchResult {1287 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1288 properties.try_scoped_set(scope, property.key, property.value)1289 })1290 .map_err(<Error<T>>::from)?;12911292 Ok(())1293 }12941295 /// Set scoped collection properties, where the scope is a special prefix1296 /// prohibiting a user access to change the properties directly.1297 ///1298 /// * `collection_id` - ID of the collection for which the properties is being set.1299 /// * `scope` - Property scope.1300 /// * `properties` - The properties to set.1301 pub fn set_scoped_collection_properties(1302 collection_id: CollectionId,1303 scope: PropertyScope,1304 properties: impl Iterator<Item = Property>,1305 ) -> DispatchResult {1306 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1307 stored_properties.try_scoped_set_from_iter(scope, properties)1308 })1309 .map_err(<Error<T>>::from)?;13101311 Ok(())1312 }13131314 /// Set collection properties.1315 ///1316 /// * `collection` - Collection handler.1317 /// * `sender` - The owner or administrator of the collection.1318 /// * `properties` - The properties to set.1319 pub fn set_collection_properties(1320 collection: &CollectionHandle<T>,1321 sender: &T::CrossAccountId,1322 properties: impl Iterator<Item = Property>,1323 ) -> DispatchResult {1324 Self::modify_collection_properties(1325 collection,1326 sender,1327 properties.map(|property| (property.key, Some(property.value))),1328 )1329 }13301331 /// Delete collection property.1332 ///1333 /// * `collection` - Collection handler.1334 /// * `sender` - The owner or administrator of the collection.1335 /// * `property` - The property to delete.1336 pub fn delete_collection_property(1337 collection: &CollectionHandle<T>,1338 sender: &T::CrossAccountId,1339 property_key: PropertyKey,1340 ) -> DispatchResult {1341 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1342 }13431344 /// Delete collection properties.1345 ///1346 /// * `collection` - Collection handler.1347 /// * `sender` - The owner or administrator of the collection.1348 /// * `properties` - The properties to delete.1349 pub fn delete_collection_properties(1350 collection: &CollectionHandle<T>,1351 sender: &T::CrossAccountId,1352 property_keys: impl Iterator<Item = PropertyKey>,1353 ) -> DispatchResult {1354 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1355 }13561357 /// Set collection propetry permission without any checks.1358 ///1359 /// Used for migrations.1360 ///1361 /// * `collection` - Collection handler.1362 /// * `property_permissions` - Property permissions.1363 pub fn set_property_permission_unchecked(1364 collection: CollectionId,1365 property_permission: PropertyKeyPermission,1366 ) -> DispatchResult {1367 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1368 permissions.try_set(property_permission.key, property_permission.permission)1369 })1370 .map_err(<Error<T>>::from)?;1371 Ok(())1372 }13731374 /// Set collection property permission.1375 ///1376 /// * `collection` - Collection handler.1377 /// * `sender` - The owner or administrator of the collection.1378 /// * `property_permission` - Property permission.1379 pub fn set_property_permission(1380 collection: &CollectionHandle<T>,1381 sender: &T::CrossAccountId,1382 property_permission: PropertyKeyPermission,1383 ) -> DispatchResult {1384 Self::set_scoped_property_permission(1385 collection,1386 sender,1387 PropertyScope::None,1388 property_permission,1389 )1390 }13911392 /// Set collection property permission with scope.1393 ///1394 /// * `collection` - Collection handler.1395 /// * `sender` - The owner or administrator of the collection.1396 /// * `scope` - Property scope.1397 /// * `property_permission` - Property permission.1398 pub fn set_scoped_property_permission(1399 collection: &CollectionHandle<T>,1400 sender: &T::CrossAccountId,1401 scope: PropertyScope,1402 property_permission: PropertyKeyPermission,1403 ) -> DispatchResult {1404 collection.check_is_owner_or_admin(sender)?;14051406 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1407 let current_permission = all_permissions.get(&property_permission.key);1408 if matches![1409 current_permission,1410 Some(PropertyPermission { mutable: false, .. })1411 ] {1412 return Err(<Error<T>>::NoPermission.into());1413 }14141415 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1416 let property_permission = property_permission.clone();1417 permissions.try_scoped_set(1418 scope,1419 property_permission.key,1420 property_permission.permission,1421 )1422 })1423 .map_err(<Error<T>>::from)?;14241425 Self::deposit_event(Event::PropertyPermissionSet(1426 collection.id,1427 property_permission.key,1428 ));1429 <PalletEvm<T>>::deposit_log(1430 erc::CollectionHelpersEvents::CollectionChanged {1431 collection_id: eth::collection_id_to_address(collection.id),1432 }1433 .to_log(T::ContractAddress::get()),1434 );14351436 Ok(())1437 }14381439 /// Set token property permission.1440 ///1441 /// * `collection` - Collection handler.1442 /// * `sender` - The owner or administrator of the collection.1443 /// * `property_permissions` - Property permissions.1444 #[transactional]1445 pub fn set_token_property_permissions(1446 collection: &CollectionHandle<T>,1447 sender: &T::CrossAccountId,1448 property_permissions: Vec<PropertyKeyPermission>,1449 ) -> DispatchResult {1450 Self::set_scoped_token_property_permissions(1451 collection,1452 sender,1453 PropertyScope::None,1454 property_permissions,1455 )1456 }14571458 /// Set token property permission with scope.1459 ///1460 /// * `collection` - Collection handler.1461 /// * `sender` - The owner or administrator of the collection.1462 /// * `scope` - Property scope.1463 /// * `property_permissions` - Property permissions.1464 #[transactional]1465 pub fn set_scoped_token_property_permissions(1466 collection: &CollectionHandle<T>,1467 sender: &T::CrossAccountId,1468 scope: PropertyScope,1469 property_permissions: Vec<PropertyKeyPermission>,1470 ) -> DispatchResult {1471 for prop_pemission in property_permissions {1472 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1473 }14741475 Ok(())1476 }14771478 /// Get collection property.1479 pub fn get_collection_property(1480 collection_id: CollectionId,1481 key: &PropertyKey,1482 ) -> Option<PropertyValue> {1483 Self::collection_properties(collection_id).get(key).cloned()1484 }14851486 /// Convert byte vector to property key vector.1487 pub fn bytes_keys_to_property_keys(1488 keys: Vec<Vec<u8>>,1489 ) -> Result<Vec<PropertyKey>, DispatchError> {1490 keys.into_iter()1491 .map(|key| -> Result<PropertyKey, DispatchError> {1492 key.try_into()1493 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1494 })1495 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1496 }14971498 /// Get properties according to given keys.1499 pub fn filter_collection_properties(1500 collection_id: CollectionId,1501 keys: Option<Vec<PropertyKey>>,1502 ) -> Result<Vec<Property>, DispatchError> {1503 let properties = Self::collection_properties(collection_id);15041505 let properties = keys1506 .map(|keys| {1507 keys.into_iter()1508 .filter_map(|key| {1509 properties.get(&key).map(|value| Property {1510 key,1511 value: value.clone(),1512 })1513 })1514 .collect()1515 })1516 .unwrap_or_else(|| {1517 properties1518 .into_iter()1519 .map(|(key, value)| Property { key, value })1520 .collect()1521 });15221523 Ok(properties)1524 }15251526 /// Get property permissions according to given keys.1527 pub fn filter_property_permissions(1528 collection_id: CollectionId,1529 keys: Option<Vec<PropertyKey>>,1530 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1531 let permissions = Self::property_permissions(collection_id);15321533 let key_permissions = keys1534 .map(|keys| {1535 keys.into_iter()1536 .filter_map(|key| {1537 permissions1538 .get(&key)1539 .map(|permission| PropertyKeyPermission {1540 key,1541 permission: permission.clone(),1542 })1543 })1544 .collect()1545 })1546 .unwrap_or_else(|| {1547 permissions1548 .into_iter()1549 .map(|(key, permission)| PropertyKeyPermission { key, permission })1550 .collect()1551 });15521553 Ok(key_permissions)1554 }15551556 /// Toggle `user` participation in the `collection`'s allow list.1557 /// #### Store read/writes1558 /// 1 writes1559 pub fn toggle_allowlist(1560 collection: &CollectionHandle<T>,1561 sender: &T::CrossAccountId,1562 user: &T::CrossAccountId,1563 allowed: bool,1564 ) -> DispatchResult {1565 collection.check_is_owner_or_admin(sender)?;15661567 // =========15681569 if allowed {1570 <Allowlist<T>>::insert((collection.id, user), true);1571 Self::deposit_event(Event::<T>::AllowListAddressAdded(1572 collection.id,1573 user.clone(),1574 ));1575 } else {1576 <Allowlist<T>>::remove((collection.id, user));1577 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1578 collection.id,1579 user.clone(),1580 ));1581 }15821583 <PalletEvm<T>>::deposit_log(1584 erc::CollectionHelpersEvents::CollectionChanged {1585 collection_id: eth::collection_id_to_address(collection.id),1586 }1587 .to_log(T::ContractAddress::get()),1588 );15891590 Ok(())1591 }15921593 /// Toggle `user` participation in the `collection`'s admin list.1594 /// #### Store read/writes1595 /// 2 reads, 2 writes1596 pub fn toggle_admin(1597 collection: &CollectionHandle<T>,1598 sender: &T::CrossAccountId,1599 user: &T::CrossAccountId,1600 admin: bool,1601 ) -> DispatchResult {1602 collection.check_is_internal()?;1603 collection.check_is_owner(sender)?;16041605 let is_admin = <IsAdmin<T>>::get((collection.id, user));1606 if is_admin == admin {1607 if admin {1608 return Ok(());1609 } else {1610 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1611 }1612 }1613 let amount = <AdminAmount<T>>::get(collection.id);16141615 // =========16161617 if admin {1618 let amount = amount1619 .checked_add(1)1620 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1621 ensure!(1622 amount <= Self::collection_admins_limit(),1623 <Error<T>>::CollectionAdminCountExceeded,1624 );16251626 <AdminAmount<T>>::insert(collection.id, amount);1627 <IsAdmin<T>>::insert((collection.id, user), true);16281629 Self::deposit_event(Event::<T>::CollectionAdminAdded(1630 collection.id,1631 user.clone(),1632 ));1633 } else {1634 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1635 <IsAdmin<T>>::remove((collection.id, user));16361637 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1638 collection.id,1639 user.clone(),1640 ));1641 }16421643 <PalletEvm<T>>::deposit_log(1644 erc::CollectionHelpersEvents::CollectionChanged {1645 collection_id: eth::collection_id_to_address(collection.id),1646 }1647 .to_log(T::ContractAddress::get()),1648 );16491650 Ok(())1651 }16521653 /// Update collection limits.1654 pub fn update_limits(1655 user: &T::CrossAccountId,1656 collection: &mut CollectionHandle<T>,1657 new_limit: CollectionLimits,1658 ) -> DispatchResult {1659 collection.check_is_internal()?;1660 collection.check_is_owner_or_admin(user)?;16611662 collection.limits =1663 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;16641665 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1666 <PalletEvm<T>>::deposit_log(1667 erc::CollectionHelpersEvents::CollectionChanged {1668 collection_id: eth::collection_id_to_address(collection.id),1669 }1670 .to_log(T::ContractAddress::get()),1671 );16721673 collection.save()1674 }16751676 /// Merge set fields from `new_limit` to `old_limit`.1677 fn clamp_limits(1678 mode: CollectionMode,1679 old_limit: &CollectionLimits,1680 mut new_limit: CollectionLimits,1681 ) -> Result<CollectionLimits, DispatchError> {1682 let limits = old_limit;1683 limit_default!(old_limit, new_limit,1684 account_token_ownership_limit => ensure!(1685 new_limit <= MAX_TOKEN_OWNERSHIP,1686 <Error<T>>::CollectionLimitBoundsExceeded,1687 ),1688 sponsored_data_size => ensure!(1689 new_limit <= CUSTOM_DATA_LIMIT,1690 <Error<T>>::CollectionLimitBoundsExceeded,1691 ),16921693 sponsored_data_rate_limit => {},1694 token_limit => ensure!(1695 old_limit >= new_limit && new_limit > 0,1696 <Error<T>>::CollectionTokenLimitExceeded1697 ),16981699 sponsor_transfer_timeout(match mode {1700 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1701 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1702 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1703 }) => ensure!(1704 new_limit <= MAX_SPONSOR_TIMEOUT,1705 <Error<T>>::CollectionLimitBoundsExceeded,1706 ),1707 sponsor_approve_timeout => {},1708 owner_can_transfer => ensure!(1709 !limits.owner_can_transfer_instaled() ||1710 old_limit || !new_limit,1711 <Error<T>>::OwnerPermissionsCantBeReverted,1712 ),1713 owner_can_destroy => ensure!(1714 old_limit || !new_limit,1715 <Error<T>>::OwnerPermissionsCantBeReverted,1716 ),1717 transfers_enabled => {},1718 );1719 Ok(new_limit)1720 }17211722 /// Update collection permissions.1723 pub fn update_permissions(1724 user: &T::CrossAccountId,1725 collection: &mut CollectionHandle<T>,1726 new_permission: CollectionPermissions,1727 ) -> DispatchResult {1728 collection.check_is_internal()?;1729 collection.check_is_owner_or_admin(user)?;1730 collection.permissions = Self::clamp_permissions(1731 collection.mode.clone(),1732 &collection.permissions,1733 new_permission,1734 )?;17351736 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1737 <PalletEvm<T>>::deposit_log(1738 erc::CollectionHelpersEvents::CollectionChanged {1739 collection_id: eth::collection_id_to_address(collection.id),1740 }1741 .to_log(T::ContractAddress::get()),1742 );17431744 collection.save()1745 }17461747 /// Merge set fields from `new_permission` to `old_permission`.1748 fn clamp_permissions(1749 _mode: CollectionMode,1750 old_permission: &CollectionPermissions,1751 mut new_permission: CollectionPermissions,1752 ) -> Result<CollectionPermissions, DispatchError> {1753 limit_default_clone!(old_permission, new_permission,1754 access => {},1755 mint_mode => {},1756 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1757 );1758 Ok(new_permission)1759 }17601761 /// Repair possibly broken properties of a collection.1762 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1763 CollectionProperties::<T>::mutate(collection_id, |properties| {1764 properties.recompute_consumed_space();1765 });17661767 Ok(())1768 }1769}17701771/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1772#[macro_export]1773macro_rules! unsupported {1774 ($runtime:path) => {1775 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1776 };1777}17781779/// Return weights for various worst-case operations.1780pub trait CommonWeightInfo<CrossAccountId> {1781 /// Weight of item creation.1782 fn create_item() -> Weight;17831784 /// Weight of items creation.1785 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;17861787 /// Weight of items creation.1788 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;17891790 /// The weight of the burning item.1791 fn burn_item() -> Weight;17921793 /// Property setting weight.1794 ///1795 /// * `amount`- The number of properties to set.1796 fn set_collection_properties(amount: u32) -> Weight;17971798 /// Collection property deletion weight.1799 ///1800 /// * `amount`- The number of properties to set.1801 fn delete_collection_properties(amount: u32) -> Weight;18021803 /// Token property setting weight.1804 ///1805 /// * `amount`- The number of properties to set.1806 fn set_token_properties(amount: u32) -> Weight;18071808 /// Token property deletion weight.1809 ///1810 /// * `amount`- The number of properties to delete.1811 fn delete_token_properties(amount: u32) -> Weight;18121813 /// Token property permissions set weight.1814 ///1815 /// * `amount`- The number of property permissions to set.1816 fn set_token_property_permissions(amount: u32) -> Weight;18171818 /// Transfer price of the token or its parts.1819 fn transfer() -> Weight;18201821 /// The price of setting the permission of the operation from another user.1822 fn approve() -> Weight;18231824 /// The price of setting the permission of the operation from another user for eth mirror.1825 fn approve_from() -> Weight;18261827 /// Transfer price from another user.1828 fn transfer_from() -> Weight;18291830 /// The price of burning a token from another user.1831 fn burn_from() -> Weight;18321833 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1834 /// whole users's balance.1835 ///1836 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1837 fn burn_recursively_self_raw() -> Weight;18381839 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1840 ///1841 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1842 fn burn_recursively_breadth_raw(amount: u32) -> Weight;18431844 /// The price of recursive burning a token.1845 ///1846 /// `max_selfs` - The maximum burning weight of the token itself.1847 /// `max_breadth` - The maximum number of nested tokens to burn.1848 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1849 Self::burn_recursively_self_raw()1850 .saturating_mul(max_selfs.max(1) as u64)1851 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1852 }18531854 /// The price of retrieving token owner1855 fn token_owner() -> Weight;18561857 /// The price of setting approval for all1858 fn set_allowance_for_all() -> Weight;18591860 /// The price of repairing an item.1861 fn force_repair_item() -> Weight;1862}18631864/// Weight info extension trait for refungible pallet.1865pub trait RefungibleExtensionsWeightInfo {1866 /// Weight of token repartition.1867 fn repartition() -> Weight;1868}18691870/// Common collection operations.1871///1872/// It wraps methods in Fungible, Nonfungible and Refungible pallets1873/// and adds weight info.1874pub trait CommonCollectionOperations<T: Config> {1875 /// Create token.1876 ///1877 /// * `sender` - The user who mint the token and pays for the transaction.1878 /// * `to` - The user who will own the token.1879 /// * `data` - Token data.1880 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1881 fn create_item(1882 &self,1883 sender: T::CrossAccountId,1884 to: T::CrossAccountId,1885 data: CreateItemData,1886 nesting_budget: &dyn Budget,1887 ) -> DispatchResultWithPostInfo;18881889 /// Create multiple tokens.1890 ///1891 /// * `sender` - The user who mint the token and pays for the transaction.1892 /// * `to` - The user who will own the token.1893 /// * `data` - Token data.1894 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1895 fn create_multiple_items(1896 &self,1897 sender: T::CrossAccountId,1898 to: T::CrossAccountId,1899 data: Vec<CreateItemData>,1900 nesting_budget: &dyn Budget,1901 ) -> DispatchResultWithPostInfo;19021903 /// Create multiple tokens.1904 ///1905 /// * `sender` - The user who mint the token and pays for the transaction.1906 /// * `to` - The user who will own the token.1907 /// * `data` - Token data.1908 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1909 fn create_multiple_items_ex(1910 &self,1911 sender: T::CrossAccountId,1912 data: CreateItemExData<T::CrossAccountId>,1913 nesting_budget: &dyn Budget,1914 ) -> DispatchResultWithPostInfo;19151916 /// Burn token.1917 ///1918 /// * `sender` - The user who owns the token.1919 /// * `token` - Token id that will burned.1920 /// * `amount` - The number of parts of the token that will be burned.1921 fn burn_item(1922 &self,1923 sender: T::CrossAccountId,1924 token: TokenId,1925 amount: u128,1926 ) -> DispatchResultWithPostInfo;19271928 /// Burn token and all nested tokens recursievly.1929 ///1930 /// * `sender` - The user who owns the token.1931 /// * `token` - Token id that will burned.1932 /// * `self_budget` - The budget that can be spent on burning tokens.1933 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.1934 fn burn_item_recursively(1935 &self,1936 sender: T::CrossAccountId,1937 token: TokenId,1938 self_budget: &dyn Budget,1939 breadth_budget: &dyn Budget,1940 ) -> DispatchResultWithPostInfo;19411942 /// Set collection properties.1943 ///1944 /// * `sender` - Must be either the owner of the collection or its admin.1945 /// * `properties` - Properties to be set.1946 fn set_collection_properties(1947 &self,1948 sender: T::CrossAccountId,1949 properties: Vec<Property>,1950 ) -> DispatchResultWithPostInfo;19511952 /// Delete collection properties.1953 ///1954 /// * `sender` - Must be either the owner of the collection or its admin.1955 /// * `properties` - The properties to be removed.1956 fn delete_collection_properties(1957 &self,1958 sender: &T::CrossAccountId,1959 property_keys: Vec<PropertyKey>,1960 ) -> DispatchResultWithPostInfo;19611962 /// Set token properties.1963 ///1964 /// The appropriate [`PropertyPermission`] for the token property1965 /// must be set with [`Self::set_token_property_permissions`].1966 ///1967 /// * `sender` - Must be either the owner of the token or its admin.1968 /// * `token_id` - The token for which the properties are being set.1969 /// * `properties` - Properties to be set.1970 /// * `budget` - Budget for setting properties.1971 fn set_token_properties(1972 &self,1973 sender: T::CrossAccountId,1974 token_id: TokenId,1975 properties: Vec<Property>,1976 budget: &dyn Budget,1977 ) -> DispatchResultWithPostInfo;19781979 /// Remove token properties.1980 ///1981 /// The appropriate [`PropertyPermission`] for the token property1982 /// must be set with [`Self::set_token_property_permissions`].1983 ///1984 /// * `sender` - Must be either the owner of the token or its admin.1985 /// * `token_id` - The token for which the properties are being remove.1986 /// * `property_keys` - Keys to remove corresponding properties.1987 /// * `budget` - Budget for removing properties.1988 fn delete_token_properties(1989 &self,1990 sender: T::CrossAccountId,1991 token_id: TokenId,1992 property_keys: Vec<PropertyKey>,1993 budget: &dyn Budget,1994 ) -> DispatchResultWithPostInfo;19951996 /// Set token property permissions.1997 ///1998 /// * `sender` - Must be either the owner of the token or its admin.1999 /// * `token_id` - The token for which the properties are being set.2000 /// * `property_permissions` - Property permissions to be set.2001 /// * `budget` - Budget for setting properties.2002 fn set_token_property_permissions(2003 &self,2004 sender: &T::CrossAccountId,2005 property_permissions: Vec<PropertyKeyPermission>,2006 ) -> DispatchResultWithPostInfo;20072008 /// Transfer amount of token pieces.2009 ///2010 /// * `sender` - Donor user.2011 /// * `to` - Recepient user.2012 /// * `token` - The token of which parts are being sent.2013 /// * `amount` - The number of parts of the token that will be transferred.2014 /// * `budget` - The maximum budget that can be spent on the transfer.2015 fn transfer(2016 &self,2017 sender: T::CrossAccountId,2018 to: T::CrossAccountId,2019 token: TokenId,2020 amount: u128,2021 budget: &dyn Budget,2022 ) -> DispatchResultWithPostInfo;20232024 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2025 ///2026 /// * `sender` - The user who grants access to the token.2027 /// * `spender` - The user to whom the rights are granted.2028 /// * `token` - The token to which access is granted.2029 /// * `amount` - The amount of pieces that another user can dispose of.2030 fn approve(2031 &self,2032 sender: T::CrossAccountId,2033 spender: T::CrossAccountId,2034 token: TokenId,2035 amount: u128,2036 ) -> DispatchResultWithPostInfo;20372038 /// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2039 ///2040 /// * `sender` - The user who grants access to the token.2041 /// * `from` - Spender's eth mirror.2042 /// * `to` - The user to whom the rights are granted.2043 /// * `token` - The token to which access is granted.2044 /// * `amount` - The amount of pieces that another user can dispose of.2045 fn approve_from(2046 &self,2047 sender: T::CrossAccountId,2048 from: T::CrossAccountId,2049 to: T::CrossAccountId,2050 token: TokenId,2051 amount: u128,2052 ) -> DispatchResultWithPostInfo;20532054 /// Send parts of a token owned by another user.2055 ///2056 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2057 ///2058 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2059 /// * `from` - The user who owns the token.2060 /// * `to` - Recepient user.2061 /// * `token` - The token of which parts are being sent.2062 /// * `amount` - The number of parts of the token that will be transferred.2063 /// * `budget` - The maximum budget that can be spent on the transfer.2064 fn transfer_from(2065 &self,2066 sender: T::CrossAccountId,2067 from: T::CrossAccountId,2068 to: T::CrossAccountId,2069 token: TokenId,2070 amount: u128,2071 budget: &dyn Budget,2072 ) -> DispatchResultWithPostInfo;20732074 /// Burn parts of a token owned by another user.2075 ///2076 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2077 ///2078 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2079 /// * `from` - The user who owns the token.2080 /// * `token` - The token of which parts are being sent.2081 /// * `amount` - The number of parts of the token that will be transferred.2082 /// * `budget` - The maximum budget that can be spent on the burn.2083 fn burn_from(2084 &self,2085 sender: T::CrossAccountId,2086 from: T::CrossAccountId,2087 token: TokenId,2088 amount: u128,2089 budget: &dyn Budget,2090 ) -> DispatchResultWithPostInfo;20912092 /// Check permission to nest token.2093 ///2094 /// * `sender` - The user who initiated the check.2095 /// * `from` - The token that is checked for embedding.2096 /// * `under` - Token under which to check.2097 /// * `budget` - The maximum budget that can be spent on the check.2098 fn check_nesting(2099 &self,2100 sender: T::CrossAccountId,2101 from: (CollectionId, TokenId),2102 under: TokenId,2103 budget: &dyn Budget,2104 ) -> DispatchResult;21052106 /// Nest one token into another.2107 ///2108 /// * `under` - Token holder.2109 /// * `to_nest` - Nested token.2110 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21112112 /// Unnest token.2113 ///2114 /// * `under` - Token holder.2115 /// * `to_nest` - Token to unnest.2116 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21172118 /// Get all user tokens.2119 ///2120 /// * `account` - Account for which you need to get tokens.2121 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;21222123 /// Get all the tokens in the collection.2124 fn collection_tokens(&self) -> Vec<TokenId>;21252126 /// Check if the token exists.2127 ///2128 /// * `token` - Id token to check.2129 fn token_exists(&self, token: TokenId) -> bool;21302131 /// Get the id of the last minted token.2132 fn last_token_id(&self) -> TokenId;21332134 /// Get the owner of the token.2135 ///2136 /// * `token` - The token for which you need to find out the owner.2137 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;21382139 /// Returns 10 tokens owners in no particular order.2140 ///2141 /// * `token` - The token for which you need to find out the owners.2142 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;21432144 /// Get the value of the token property by key.2145 ///2146 /// * `token` - Token with the property to get.2147 /// * `key` - Property name.2148 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;21492150 /// Get a set of token properties by key vector.2151 ///2152 /// * `token` - Token with the property to get.2153 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2154 /// then all properties are returned.2155 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;21562157 /// Amount of unique collection tokens2158 fn total_supply(&self) -> u32;21592160 /// Amount of different tokens account has.2161 ///2162 /// * `account` - The account for which need to get the balance.2163 fn account_balance(&self, account: T::CrossAccountId) -> u32;21642165 /// Amount of specific token account have.2166 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;21672168 /// Amount of token pieces2169 fn total_pieces(&self, token: TokenId) -> Option<u128>;21702171 /// Get the number of parts of the token that a trusted user can manage.2172 ///2173 /// * `sender` - Trusted user.2174 /// * `spender` - Owner of the token.2175 /// * `token` - The token for which to get the value.2176 fn allowance(2177 &self,2178 sender: T::CrossAccountId,2179 spender: T::CrossAccountId,2180 token: TokenId,2181 ) -> u128;21822183 /// Get extension for RFT collection.2184 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;21852186 /// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2187 /// * `owner` - Token owner2188 /// * `operator` - Operator2189 /// * `approve` - Should operator status be granted or revoked?2190 fn set_allowance_for_all(2191 &self,2192 owner: T::CrossAccountId,2193 operator: T::CrossAccountId,2194 approve: bool,2195 ) -> DispatchResultWithPostInfo;21962197 /// Tells whether the given `owner` approves the `operator`.2198 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;21992200 /// Repairs a possibly broken item.2201 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2202}22032204/// Extension for RFT collection.2205pub trait RefungibleExtensions<T>2206where2207 T: Config,2208{2209 /// Change the number of parts of the token.2210 ///2211 /// When the value changes down, this function is equivalent to burning parts of the token.2212 ///2213 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.2214 /// * `token` - The token for which you want to change the number of parts.2215 /// * `amount` - The new value of the parts of the token.2216 fn repartition(2217 &self,2218 sender: &T::CrossAccountId,2219 token: TokenId,2220 amount: u128,2221 ) -> DispatchResultWithPostInfo;2222}22232224/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2225///2226/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2227pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2228 let post_info = PostDispatchInfo {2229 actual_weight: Some(weight),2230 pays_fee: Pays::Yes,2231 };2232 match res {2233 Ok(()) => Ok(post_info),2234 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2235 }2236}22372238impl<T: Config> From<PropertiesError> for Error<T> {2239 fn from(error: PropertiesError) -> Self {2240 match error {2241 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2242 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2243 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2244 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2245 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2246 }2247 }2248}pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -125,6 +125,10 @@
/// Implementation of `CommonCollectionOperations` for `FungibleHandle`. It wraps FungibleHandle Pallete
/// methods and adds weight info.
impl<T: Config> CommonCollectionOperations<T> for FungibleHandle<T> {
+ fn mode(&self) -> up_data_structs::CollectionMode {
+ self.0.mode()
+ }
+
fn create_item(
&self,
sender: T::CrossAccountId,
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -152,6 +152,10 @@
/// Implementation of `CommonCollectionOperations` for `NonfungibleHandle`. It wraps Nonfungible Pallete
/// methods and adds weight info.
impl<T: Config> CommonCollectionOperations<T> for NonfungibleHandle<T> {
+ fn mode(&self) -> up_data_structs::CollectionMode {
+ self.0.mode()
+ }
+
fn create_item(
&self,
sender: T::CrossAccountId,
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -741,7 +741,8 @@
Some((collection_id, nft_id)),
&target_nft_budget,
)
- .map_err(Self::map_unique_err_to_proxy)?;
+ .map_err(Self::map_unique_err_to_proxy)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
approval_required = cross_sender != target_nft_owner;
@@ -989,7 +990,8 @@
let nft_owner =
<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)
- .map_err(|_| <Error<T>>::ResourceDoesntExist)?;
+ .map_err(|_| <Error<T>>::ResourceDoesntExist)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {
ensure!(res.pending, <Error<T>>::ResourceNotPending);
@@ -1044,7 +1046,8 @@
let nft_owner =
<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)
- .map_err(|_| <Error<T>>::ResourceDoesntExist)?;
+ .map_err(|_| <Error<T>>::ResourceDoesntExist)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);
@@ -1666,7 +1669,8 @@
let budget = budget::Value::new(NESTING_BUDGET);
let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)
- .map_err(Self::map_unique_err_to_proxy)?;
+ .map_err(Self::map_unique_err_to_proxy)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
let pending = sender != nft_owner;
@@ -1720,7 +1724,8 @@
let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);
let topmost_owner =
- <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;
+ <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
let sender = T::CrossAccountId::from_sub(sender);
if topmost_owner == sender {
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -186,6 +186,10 @@
/// Implementation of `CommonCollectionOperations` for `RefungibleHandle`. It wraps Refungible Pallete
/// methods and adds weight info.
impl<T: Config> CommonCollectionOperations<T> for RefungibleHandle<T> {
+ fn mode(&self) -> up_data_structs::CollectionMode {
+ self.0.mode()
+ }
+
fn create_item(
&self,
sender: T::CrossAccountId,
pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -61,6 +61,7 @@
use frame_support::fail;
pub use pallet::*;
use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};
+use up_data_structs::CollectionMode;
use up_data_structs::{CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget};
#[cfg(feature = "runtime-benchmarks")]
@@ -135,6 +136,8 @@
User(CrossAccountId),
/// Could not find the token provided as the owner.
TokenNotFound,
+ /// Nested token has multiple owners.
+ MultipleOwners,
/// Token owner is another token (still, the target token may not exist).
Token(CollectionId, TokenId),
}
@@ -163,6 +166,10 @@
Some((collection, token)) => Parent::Token(collection, token),
None => Parent::User(owner),
},
+ None if handle.mode() == CollectionMode::ReFungible => handle
+ .total_pieces(token)
+ .map(|_| Parent::MultipleOwners)
+ .unwrap_or(Parent::TokenNotFound),
None => Parent::TokenNotFound,
})
}
@@ -203,19 +210,27 @@
///
/// May return token address if parent token not yet exists
///
+ /// Returns `None` if the token has multiple owners.
+ ///
/// - `budget`: Limit for searching parents in depth.
pub fn find_topmost_owner(
collection: CollectionId,
token: TokenId,
budget: &dyn Budget,
- ) -> Result<T::CrossAccountId, DispatchError> {
+ ) -> Result<Option<T::CrossAccountId>, DispatchError> {
let owner = Self::parent_chain(collection, token)
.take_while(|_| budget.consume())
- .find(|p| matches!(p, Ok(Parent::User(_) | Parent::TokenNotFound)))
+ .find(|p| {
+ matches!(
+ p,
+ Ok(Parent::User(_) | Parent::TokenNotFound | Parent::MultipleOwners)
+ )
+ })
.ok_or(<Error<T>>::DepthLimit)??;
Ok(match owner {
- Parent::User(v) => v,
+ Parent::User(v) => Some(v),
+ Parent::MultipleOwners => None,
_ => fail!(<Error<T>>::TokenNotFound),
})
}
@@ -223,13 +238,15 @@
/// Find the topmost parent and check that assigning `for_nest` token as a child for
/// `token` wouldn't create a cycle.
///
+ /// Returns `None` if the token has multiple owners.
+ ///
/// - `budget`: Limit for searching parents in depth.
pub fn get_checked_topmost_owner(
collection: CollectionId,
token: TokenId,
for_nest: Option<(CollectionId, TokenId)>,
budget: &dyn Budget,
- ) -> Result<T::CrossAccountId, DispatchError> {
+ ) -> Result<Option<T::CrossAccountId>, DispatchError> {
// Tried to nest token in itself
if Some((collection, token)) == for_nest {
return Err(<Error<T>>::OuroborosDetected.into());
@@ -242,8 +259,9 @@
return Err(<Error<T>>::OuroborosDetected.into())
}
// Token is owned by other user
- Parent::User(user) => return Ok(user),
+ Parent::User(user) => return Ok(Some(user)),
Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),
+ Parent::MultipleOwners => return Ok(None),
// Continue parent chain
Parent::Token(_, _) => {}
}
@@ -284,12 +302,17 @@
budget: &dyn Budget,
) -> Result<bool, DispatchError> {
let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {
- Some((collection, token)) => Self::find_topmost_owner(collection, token, budget)?,
+ Some((collection, token)) => match Self::find_topmost_owner(collection, token, budget)?
+ {
+ Some(topmost_owner) => topmost_owner,
+ None => return Ok(false),
+ },
None => user,
};
- Self::get_checked_topmost_owner(collection, token, for_nest, budget)
- .map(|indirect_owner| indirect_owner == target_parent)
+ Self::get_checked_topmost_owner(collection, token, for_nest, budget).map(|indirect_owner| {
+ indirect_owner.map_or(false, |indirect_owner| indirect_owner == target_parent)
+ })
}
/// Checks that `under` is valid token and that `token_id` could be nested under it
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -252,7 +252,7 @@
/// Collection can represent various types of tokens.
/// Each collection can contain only one type of tokens at a time.
/// This type helps to understand which tokens the collection contains.
-#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq, TypeInfo, MaxEncodedLen)]
+#[derive(Encode, Decode, Eq, Debug, Clone, Copy, PartialEq, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
pub enum CollectionMode {
/// Non fungible tokens.
runtime/common/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -83,7 +83,7 @@
fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
let budget = up_data_structs::budget::Value::new(10);
- Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))
+ Ok(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?)
}
fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>, DispatchError> {
Ok(<pallet_nonfungible::Pallet<Runtime>>::token_children_ids(collection, token))