difftreelog
feat(pallet nft) added GenesisConfig
in: master
3 files changed
node/cli/src/chain_spec.rsdiffbeforeafterboth--- a/node/cli/src/chain_spec.rs
+++ b/node/cli/src/chain_spec.rs
@@ -168,6 +168,7 @@
.collect(),
},
common: Default::default(),
+ nonfungible: Default::default(),
treasury: Default::default(),
tokens: TokensConfig { balances: vec![] },
sudo: SudoConfig {
@@ -227,6 +228,7 @@
.to_vec(),
},
common: Default::default(),
+ nonfungible: Default::default(),
balances: BalancesConfig {
balances: $endowed_accounts
.iter()
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::{57 ops::{Deref, DerefMut},58 slice::from_ref,59};60use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};61use sp_std::vec::Vec;62use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};63use evm_coder::ToLog;64use frame_support::{65 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},66 ensure,67 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},68 dispatch::Pays,69 transactional, fail,70};71use pallet_evm::GasWeightMapping;72use up_data_structs::{73 AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,74 RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,75 COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP,76 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,77 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,78 CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,79 PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties,80 PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError,81 TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope,82 CollectionPermissions,83};84use up_pov_estimate_rpc::PovInfo;8586pub use pallet::*;87use sp_core::H160;88use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};8990#[cfg(feature = "runtime-benchmarks")]91pub mod benchmarking;92pub mod dispatch;93pub mod erc;94pub mod eth;95#[allow(missing_docs)]96pub mod weights;9798/// Weight info.99pub type SelfWeightOf<T> = <T as Config>::WeightInfo;100101/// Collection handle contains information about collection data and id.102/// Also provides functionality to count consumed gas.103///104/// CollectionHandle is used as a generic wrapper for collections of all types.105/// It allows to perform common operations and queries on any collection type,106/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].107#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]108pub struct CollectionHandle<T: Config> {109 /// Collection id110 pub id: CollectionId,111 collection: Collection<T::AccountId>,112 /// Substrate recorder for counting consumed gas113 pub recorder: SubstrateRecorder<T>,114}115116impl<T: Config> WithRecorder<T> for CollectionHandle<T> {117 fn recorder(&self) -> &SubstrateRecorder<T> {118 &self.recorder119 }120 fn into_recorder(self) -> SubstrateRecorder<T> {121 self.recorder122 }123}124125impl<T: Config> CollectionHandle<T> {126 /// Same as [CollectionHandle::new] but with an explicit gas limit.127 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {128 <CollectionById<T>>::get(id).map(|collection| Self {129 id,130 collection,131 recorder: SubstrateRecorder::new(gas_limit),132 })133 }134135 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].136 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {137 <CollectionById<T>>::get(id).map(|collection| Self {138 id,139 collection,140 recorder,141 })142 }143144 /// Retrives collection data from storage and creates collection handle with default parameters.145 /// If collection not found return `None`146 pub fn new(id: CollectionId) -> Option<Self> {147 Self::new_with_gas_limit(id, u64::MAX)148 }149150 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.151 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {152 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)153 }154155 /// Consume gas for reading.156 pub fn consume_store_reads(157 &self,158 reads: u64,159 ) -> pallet_evm_coder_substrate::execution::Result<()> {160 self.recorder161 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(162 <T as frame_system::Config>::DbWeight::get()163 .read164 .saturating_mul(reads),165 // TODO: measure proof166 0,167 )))168 }169170 /// Consume gas for writing.171 pub fn consume_store_writes(172 &self,173 writes: u64,174 ) -> pallet_evm_coder_substrate::execution::Result<()> {175 self.recorder176 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(177 <T as frame_system::Config>::DbWeight::get()178 .write179 .saturating_mul(writes),180 // TODO: measure proof181 0,182 )))183 }184185 /// Consume gas for reading and writing.186 pub fn consume_store_reads_and_writes(187 &self,188 reads: u64,189 writes: u64,190 ) -> pallet_evm_coder_substrate::execution::Result<()> {191 let weight = <T as frame_system::Config>::DbWeight::get();192 let reads = weight.read.saturating_mul(reads);193 let writes = weight.read.saturating_mul(writes);194 self.recorder195 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(196 reads.saturating_add(writes),197 // TODO: measure proof198 0,199 )))200 }201202 /// Save collection to storage.203 pub fn save(&self) -> DispatchResult {204 <CollectionById<T>>::insert(self.id, &self.collection);205 Ok(())206 }207208 /// Set collection sponsor.209 ///210 /// Unique collections allows sponsoring for certain actions.211 /// This method allows you to set the sponsor of the collection.212 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].213 pub fn set_sponsor(214 &mut self,215 sender: &T::CrossAccountId,216 sponsor: T::AccountId,217 ) -> DispatchResult {218 self.check_is_internal()?;219 self.check_is_owner_or_admin(sender)?;220221 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());222223 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));224 <PalletEvm<T>>::deposit_log(225 erc::CollectionHelpersEvents::CollectionChanged {226 collection_id: eth::collection_id_to_address(self.id),227 }228 .to_log(T::ContractAddress::get()),229 );230231 self.save()232 }233234 /// Force set `sponsor`.235 ///236 /// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation237 /// from the `sponsor` is not required.238 ///239 /// # Arguments240 ///241 /// * `sender`: Caller's account.242 /// * `sponsor`: ID of the account of the sponsor-to-be.243 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {244 self.check_is_internal()?;245246 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());247248 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));249 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));250 <PalletEvm<T>>::deposit_log(251 erc::CollectionHelpersEvents::CollectionChanged {252 collection_id: eth::collection_id_to_address(self.id),253 }254 .to_log(T::ContractAddress::get()),255 );256257 self.save()258 }259260 /// Confirm sponsorship261 ///262 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.263 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].264 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {265 self.check_is_internal()?;266 ensure!(267 self.collection.sponsorship.pending_sponsor() == Some(sender),268 Error::<T>::ConfirmSponsorshipFail269 );270271 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());272273 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));274 <PalletEvm<T>>::deposit_log(275 erc::CollectionHelpersEvents::CollectionChanged {276 collection_id: eth::collection_id_to_address(self.id),277 }278 .to_log(T::ContractAddress::get()),279 );280281 self.save()282 }283284 /// Remove collection sponsor.285 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {286 self.check_is_internal()?;287 self.check_is_owner_or_admin(sender)?;288289 self.collection.sponsorship = SponsorshipState::Disabled;290291 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));292 <PalletEvm<T>>::deposit_log(293 erc::CollectionHelpersEvents::CollectionChanged {294 collection_id: eth::collection_id_to_address(self.id),295 }296 .to_log(T::ContractAddress::get()),297 );298 self.save()299 }300301 /// Force remove `sponsor`.302 ///303 /// Differs from `remove_sponsor` in that304 /// it doesn't require consent from the `owner` of the collection.305 pub fn force_remove_sponsor(&mut self) -> DispatchResult {306 self.check_is_internal()?;307308 self.collection.sponsorship = SponsorshipState::Disabled;309310 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));311 <PalletEvm<T>>::deposit_log(312 erc::CollectionHelpersEvents::CollectionChanged {313 collection_id: eth::collection_id_to_address(self.id),314 }315 .to_log(T::ContractAddress::get()),316 );317 self.save()318 }319320 /// Checks that the collection was created with, and must be operated upon through **Unique API**.321 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.322 pub fn check_is_internal(&self) -> DispatchResult {323 if self.flags.external {324 return Err(<Error<T>>::CollectionIsExternal)?;325 }326327 Ok(())328 }329330 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.331 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.332 pub fn check_is_external(&self) -> DispatchResult {333 if !self.flags.external {334 return Err(<Error<T>>::CollectionIsInternal)?;335 }336337 Ok(())338 }339}340341impl<T: Config> Deref for CollectionHandle<T> {342 type Target = Collection<T::AccountId>;343344 fn deref(&self) -> &Self::Target {345 &self.collection346 }347}348349impl<T: Config> DerefMut for CollectionHandle<T> {350 fn deref_mut(&mut self) -> &mut Self::Target {351 &mut self.collection352 }353}354355impl<T: Config> CollectionHandle<T> {356 /// Checks if the `user` is the owner of the collection.357 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {358 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);359 Ok(())360 }361362 /// Returns **true** if the `user` is the owner or administrator of the collection.363 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {364 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))365 }366367 /// Checks if the `user` is the owner or administrator of the collection.368 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {369 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);370 Ok(())371 }372373 /// Returns **true** if374 /// * the `user`is a collection owner or admin375 /// * the collection limits allow the owner/admins to transfer/burn any collection token376 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {377 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)378 }379380 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.381 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {382 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)383 }384385 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.386 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {387 ensure!(388 <Allowlist<T>>::get((self.id, user)),389 <Error<T>>::AddressNotInAllowlist390 );391 Ok(())392 }393394 /// Changes collection owner to another account395 /// #### Store read/writes396 /// 1 writes397 pub fn change_owner(398 &mut self,399 caller: T::CrossAccountId,400 new_owner: T::CrossAccountId,401 ) -> DispatchResult {402 self.check_is_internal()?;403 self.check_is_owner(&caller)?;404 self.collection.owner = new_owner.as_sub().clone();405406 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(407 self.id,408 new_owner.as_sub().clone(),409 ));410 <PalletEvm<T>>::deposit_log(411 erc::CollectionHelpersEvents::CollectionChanged {412 collection_id: eth::collection_id_to_address(self.id),413 }414 .to_log(T::ContractAddress::get()),415 );416417 self.save()418 }419}420421#[frame_support::pallet]422pub mod pallet {423 use core::marker::PhantomData;424425 use super::*;426 use dispatch::CollectionDispatch;427 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};428 use frame_support::traits::Currency;429 use up_data_structs::{TokenId, mapping::TokenAddressMapping};430 use scale_info::TypeInfo;431 use weights::WeightInfo;432433 #[pallet::config]434 pub trait Config:435 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo436 {437 /// Weight information for functions of this pallet.438 type WeightInfo: WeightInfo;439440 /// Events compatible with [`frame_system::Config::Event`].441 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;442443 /// Handler of accounts and payment.444 type Currency: Currency<Self::AccountId>;445446 /// Set price to create a collection.447 #[pallet::constant]448 type CollectionCreationPrice: Get<449 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,450 >;451452 /// Dispatcher of operations on collections.453 type CollectionDispatch: CollectionDispatch<Self>;454455 /// Account which holds the chain's treasury.456 type TreasuryAccountId: Get<Self::AccountId>;457458 /// Address under which the CollectionHelper contract would be available.459 #[pallet::constant]460 type ContractAddress: Get<H160>;461462 /// Mapper for token addresses to Ethereum addresses.463 type EvmTokenAddressMapping: TokenAddressMapping<H160>;464465 /// Mapper for token addresses to [`CrossAccountId`].466 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;467 }468469 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);470471 #[pallet::pallet]472 #[pallet::storage_version(STORAGE_VERSION)]473 pub struct Pallet<T>(_);474475 #[pallet::extra_constants]476 impl<T: Config> Pallet<T> {477 /// Maximum admins per collection.478 pub fn collection_admins_limit() -> u32 {479 COLLECTION_ADMINS_LIMIT480 }481 }482483 #[pallet::genesis_config]484 pub struct GenesisConfig<T>(PhantomData<T>);485486 #[cfg(feature = "std")]487 impl<T: Config> Default for GenesisConfig<T> {488 fn default() -> Self {489 Self(Default::default())490 }491 }492493 #[pallet::genesis_build]494 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {495 fn build(&self) {496 StorageVersion::new(1).put::<Pallet<T>>();497 }498 }499 impl<T: Config> Pallet<T> {500 /// Helper function that handles deposit events501 pub fn deposit_event(event: Event<T>) {502 let event = <T as Config>::RuntimeEvent::from(event);503 let event = event.into();504 <frame_system::Pallet<T>>::deposit_event(event)505 }506 }507508 #[pallet::event]509 pub enum Event<T: Config> {510 /// New collection was created511 CollectionCreated(512 /// Globally unique identifier of newly created collection.513 CollectionId,514 /// [`CollectionMode`] converted into _u8_.515 u8,516 /// Collection owner.517 T::AccountId,518 ),519520 /// New collection was destroyed521 CollectionDestroyed(522 /// Globally unique identifier of collection.523 CollectionId,524 ),525526 /// New item was created.527 ItemCreated(528 /// Id of the collection where item was created.529 CollectionId,530 /// Id of an item. Unique within the collection.531 TokenId,532 /// Owner of newly created item533 T::CrossAccountId,534 /// Always 1 for NFT535 u128,536 ),537538 /// Collection item was burned.539 ItemDestroyed(540 /// Id of the collection where item was destroyed.541 CollectionId,542 /// Identifier of burned NFT.543 TokenId,544 /// Which user has destroyed its tokens.545 T::CrossAccountId,546 /// Amount of token pieces destroed. Always 1 for NFT.547 u128,548 ),549550 /// Item was transferred551 Transfer(552 /// Id of collection to which item is belong.553 CollectionId,554 /// Id of an item.555 TokenId,556 /// Original owner of item.557 T::CrossAccountId,558 /// New owner of item.559 T::CrossAccountId,560 /// Amount of token pieces transfered. Always 1 for NFT.561 u128,562 ),563564 /// Amount pieces of token owned by `sender` was approved for `spender`.565 Approved(566 /// Id of collection to which item is belong.567 CollectionId,568 /// Id of an item.569 TokenId,570 /// Original owner of item.571 T::CrossAccountId,572 /// Id for which the approval was granted.573 T::CrossAccountId,574 /// Amount of token pieces transfered. Always 1 for NFT.575 u128,576 ),577578 /// A `sender` approves operations on all owned tokens for `spender`.579 ApprovedForAll(580 /// Id of collection to which item is belong.581 CollectionId,582 /// Owner of a wallet.583 T::CrossAccountId,584 /// Id for which operator status was granted or rewoked.585 T::CrossAccountId,586 /// Is operator status granted or revoked?587 bool,588 ),589590 /// The colletion property has been added or edited.591 CollectionPropertySet(592 /// Id of collection to which property has been set.593 CollectionId,594 /// The property that was set.595 PropertyKey,596 ),597598 /// The property has been deleted.599 CollectionPropertyDeleted(600 /// Id of collection to which property has been deleted.601 CollectionId,602 /// The property that was deleted.603 PropertyKey,604 ),605606 /// The token property has been added or edited.607 TokenPropertySet(608 /// Identifier of the collection whose token has the property set.609 CollectionId,610 /// The token for which the property was set.611 TokenId,612 /// The property that was set.613 PropertyKey,614 ),615616 /// The token property has been deleted.617 TokenPropertyDeleted(618 /// Identifier of the collection whose token has the property deleted.619 CollectionId,620 /// The token for which the property was deleted.621 TokenId,622 /// The property that was deleted.623 PropertyKey,624 ),625626 /// The token property permission of a collection has been set.627 PropertyPermissionSet(628 /// ID of collection to which property permission has been set.629 CollectionId,630 /// The property permission that was set.631 PropertyKey,632 ),633634 /// Address was added to the allow list.635 AllowListAddressAdded(636 /// ID of the affected collection.637 CollectionId,638 /// Address of the added account.639 T::CrossAccountId,640 ),641642 /// Address was removed from the allow list.643 AllowListAddressRemoved(644 /// ID of the affected collection.645 CollectionId,646 /// Address of the removed account.647 T::CrossAccountId,648 ),649650 /// Collection admin was added.651 CollectionAdminAdded(652 /// ID of the affected collection.653 CollectionId,654 /// Admin address.655 T::CrossAccountId,656 ),657658 /// Collection admin was removed.659 CollectionAdminRemoved(660 /// ID of the affected collection.661 CollectionId,662 /// Removed admin address.663 T::CrossAccountId,664 ),665666 /// Collection limits were set.667 CollectionLimitSet(668 /// ID of the affected collection.669 CollectionId,670 ),671672 /// Collection owned was changed.673 CollectionOwnerChanged(674 /// ID of the affected collection.675 CollectionId,676 /// New owner address.677 T::AccountId,678 ),679680 /// Collection permissions were set.681 CollectionPermissionSet(682 /// ID of the affected collection.683 CollectionId,684 ),685686 /// Collection sponsor was set.687 CollectionSponsorSet(688 /// ID of the affected collection.689 CollectionId,690 /// New sponsor address.691 T::AccountId,692 ),693694 /// New sponsor was confirm.695 SponsorshipConfirmed(696 /// ID of the affected collection.697 CollectionId,698 /// New sponsor address.699 T::AccountId,700 ),701702 /// Collection sponsor was removed.703 CollectionSponsorRemoved(704 /// ID of the affected collection.705 CollectionId,706 ),707 }708709 #[pallet::error]710 pub enum Error<T> {711 /// This collection does not exist.712 CollectionNotFound,713 /// Sender parameter and item owner must be equal.714 MustBeTokenOwner,715 /// No permission to perform action716 NoPermission,717 /// Destroying only empty collections is allowed718 CantDestroyNotEmptyCollection,719 /// Collection is not in mint mode.720 PublicMintingNotAllowed,721 /// Address is not in allow list.722 AddressNotInAllowlist,723724 /// Collection name can not be longer than 63 char.725 CollectionNameLimitExceeded,726 /// Collection description can not be longer than 255 char.727 CollectionDescriptionLimitExceeded,728 /// Token prefix can not be longer than 15 char.729 CollectionTokenPrefixLimitExceeded,730 /// Total collections bound exceeded.731 TotalCollectionsLimitExceeded,732 /// Exceeded max admin count733 CollectionAdminCountExceeded,734 /// Collection limit bounds per collection exceeded735 CollectionLimitBoundsExceeded,736 /// Tried to enable permissions which are only permitted to be disabled737 OwnerPermissionsCantBeReverted,738 /// Collection settings not allowing items transferring739 TransferNotAllowed,740 /// Account token limit exceeded per collection741 AccountTokenLimitExceeded,742 /// Collection token limit exceeded743 CollectionTokenLimitExceeded,744 /// Metadata flag frozen745 MetadataFlagFrozen,746747 /// Item does not exist748 TokenNotFound,749 /// Item is balance not enough750 TokenValueTooLow,751 /// Requested value is more than the approved752 ApprovedValueTooLow,753 /// Tried to approve more than owned754 CantApproveMoreThanOwned,755 /// Only spending from eth mirror could be approved756 AddressIsNotEthMirror,757758 /// Can't transfer tokens to ethereum zero address759 AddressIsZero,760761 /// The operation is not supported762 UnsupportedOperation,763764 /// Insufficient funds to perform an action765 NotSufficientFounds,766767 /// User does not satisfy the nesting rule768 UserIsNotAllowedToNest,769 /// Only tokens from specific collections may nest tokens under this one770 SourceCollectionIsNotAllowedToNest,771772 /// Tried to store more data than allowed in collection field773 CollectionFieldSizeExceeded,774775 /// Tried to store more property data than allowed776 NoSpaceForProperty,777778 /// Tried to store more property keys than allowed779 PropertyLimitReached,780781 /// Property key is too long782 PropertyKeyIsTooLong,783784 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed785 InvalidCharacterInPropertyKey,786787 /// Empty property keys are forbidden788 EmptyPropertyKey,789790 /// Tried to access an external collection with an internal API791 CollectionIsExternal,792793 /// Tried to access an internal collection with an external API794 CollectionIsInternal,795796 /// This address is not set as sponsor, use setCollectionSponsor first.797 ConfirmSponsorshipFail,798799 /// The user is not an administrator.800 UserIsNotCollectionAdmin,801 }802803 /// Storage of the count of created collections. Essentially contains the last collection ID.804 #[pallet::storage]805 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;806807 /// Storage of the count of deleted collections.808 #[pallet::storage]809 pub type DestroyedCollectionCount<T> =810 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;811812 /// Storage of collection info.813 #[pallet::storage]814 pub type CollectionById<T> = StorageMap<815 Hasher = Blake2_128Concat,816 Key = CollectionId,817 Value = Collection<<T as frame_system::Config>::AccountId>,818 QueryKind = OptionQuery,819 >;820821 /// Storage of collection properties.822 #[pallet::storage]823 #[pallet::getter(fn collection_properties)]824 pub type CollectionProperties<T> = StorageMap<825 Hasher = Blake2_128Concat,826 Key = CollectionId,827 Value = CollectionPropertiesT,828 QueryKind = ValueQuery,829 >;830831 /// Storage of token property permissions of a collection.832 #[pallet::storage]833 #[pallet::getter(fn property_permissions)]834 pub type CollectionPropertyPermissions<T> = StorageMap<835 Hasher = Blake2_128Concat,836 Key = CollectionId,837 Value = PropertiesPermissionMap,838 QueryKind = ValueQuery,839 >;840841 /// Storage of the amount of collection admins.842 #[pallet::storage]843 pub type AdminAmount<T> = StorageMap<844 Hasher = Blake2_128Concat,845 Key = CollectionId,846 Value = u32,847 QueryKind = ValueQuery,848 >;849850 /// List of collection admins.851 #[pallet::storage]852 pub type IsAdmin<T: Config> = StorageNMap<853 Key = (854 Key<Blake2_128Concat, CollectionId>,855 Key<Blake2_128Concat, T::CrossAccountId>,856 ),857 Value = bool,858 QueryKind = ValueQuery,859 >;860861 /// Allowlisted collection users.862 #[pallet::storage]863 pub type Allowlist<T: Config> = StorageNMap<864 Key = (865 Key<Blake2_128Concat, CollectionId>,866 Key<Blake2_128Concat, T::CrossAccountId>,867 ),868 Value = bool,869 QueryKind = ValueQuery,870 >;871872 /// Not used by code, exists only to provide some types to metadata.873 #[pallet::storage]874 pub type DummyStorageValue<T: Config> = StorageValue<875 Value = (876 CollectionStats,877 CollectionId,878 TokenId,879 TokenChild,880 PhantomType<(881 TokenData<T::CrossAccountId>,882 RpcCollection<T::AccountId>,883 // PoV Estimate Info884 PovInfo,885 )>,886 ),887 QueryKind = OptionQuery,888 >;889}890891impl<T: Config> Pallet<T> {892 /// Enshure that receiver address is correct.893 ///894 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.895 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {896 ensure!(897 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,898 <Error<T>>::AddressIsZero899 );900 Ok(())901 }902903 /// Get a vector of collection admins.904 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {905 <IsAdmin<T>>::iter_prefix((collection,))906 .map(|(a, _)| a)907 .collect()908 }909910 /// Get a vector of users allowed to mint tokens.911 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {912 <Allowlist<T>>::iter_prefix((collection,))913 .map(|(a, _)| a)914 .collect()915 }916917 /// Is `user` allowed to mint token in `collection`.918 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {919 <Allowlist<T>>::get((collection, user))920 }921922 /// Get statistics of collections.923 pub fn collection_stats() -> CollectionStats {924 let created = <CreatedCollectionCount<T>>::get();925 let destroyed = <DestroyedCollectionCount<T>>::get();926 CollectionStats {927 created: created.0,928 destroyed: destroyed.0,929 alive: created.0 - destroyed.0,930 }931 }932933 /// Get the effective limits for the collection.934 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {935 let collection = <CollectionById<T>>::get(collection)?;936 let limits = collection.limits;937 let effective_limits = CollectionLimits {938 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),939 sponsored_data_size: Some(limits.sponsored_data_size()),940 sponsored_data_rate_limit: Some(941 limits942 .sponsored_data_rate_limit943 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),944 ),945 token_limit: Some(limits.token_limit()),946 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(947 match collection.mode {948 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,949 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,950 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,951 },952 )),953 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),954 owner_can_transfer: Some(limits.owner_can_transfer()),955 owner_can_destroy: Some(limits.owner_can_destroy()),956 transfers_enabled: Some(limits.transfers_enabled()),957 };958959 Some(effective_limits)960 }961962 /// Returns information about the `collection` adapted for rpc.963 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {964 let Collection {965 name,966 description,967 owner,968 mode,969 token_prefix,970 sponsorship,971 limits,972 permissions,973 flags,974 } = <CollectionById<T>>::get(collection)?;975976 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)977 .into_iter()978 .map(|(key, permission)| PropertyKeyPermission { key, permission })979 .collect();980981 let properties = <CollectionProperties<T>>::get(collection)982 .into_iter()983 .map(|(key, value)| Property { key, value })984 .collect();985986 let permissions = CollectionPermissions {987 access: Some(permissions.access()),988 mint_mode: Some(permissions.mint_mode()),989 nesting: Some(permissions.nesting().clone()),990 };991992 Some(RpcCollection {993 name: name.into_inner(),994 description: description.into_inner(),995 owner,996 mode,997 token_prefix: token_prefix.into_inner(),998 sponsorship,999 limits,1000 permissions,1001 token_property_permissions,1002 properties,1003 read_only: flags.external,10041005 flags: RpcCollectionFlags {1006 foreign: flags.foreign,1007 erc721metadata: flags.erc721metadata,1008 },1009 })1010 }1011}10121013macro_rules! limit_default {1014 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1015 $(1016 if let Some($new) = $new.$field {1017 let $old = $old.$field($($arg)?);1018 let _ = $new;1019 let _ = $old;1020 $check1021 } else {1022 $new.$field = $old.$field1023 }1024 )*1025 }};1026}1027macro_rules! limit_default_clone {1028 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1029 $(1030 if let Some($new) = $new.$field.clone() {1031 let $old = $old.$field($($arg)?);1032 let _ = $new;1033 let _ = $old;1034 $check1035 } else {1036 $new.$field = $old.$field.clone()1037 }1038 )*1039 }};1040}10411042impl<T: Config> Pallet<T> {1043 /// Create new collection.1044 ///1045 /// * `owner` - The owner of the collection.1046 /// * `data` - Description of the created collection.1047 /// * `flags` - Extra flags to store.1048 pub fn init_collection(1049 owner: T::CrossAccountId,1050 payer: T::CrossAccountId,1051 data: CreateCollectionData<T::AccountId>,1052 flags: CollectionFlags,1053 ) -> Result<CollectionId, DispatchError> {1054 {1055 ensure!(1056 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1057 Error::<T>::CollectionTokenPrefixLimitExceeded1058 );1059 }10601061 let created_count = <CreatedCollectionCount<T>>::get()1062 .01063 .checked_add(1)1064 .ok_or(ArithmeticError::Overflow)?;1065 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1066 let id = CollectionId(created_count);10671068 // bound Total number of collections1069 ensure!(1070 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1071 <Error<T>>::TotalCollectionsLimitExceeded1072 );10731074 // =========10751076 let collection = Collection {1077 owner: owner.as_sub().clone(),1078 name: data.name,1079 mode: data.mode.clone(),1080 description: data.description,1081 token_prefix: data.token_prefix,1082 sponsorship: data1083 .pending_sponsor1084 .map(SponsorshipState::Unconfirmed)1085 .unwrap_or_default(),1086 limits: data1087 .limits1088 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1089 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1090 permissions: data1091 .permissions1092 .map(|permissions| {1093 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1094 })1095 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1096 flags,1097 };10981099 let mut collection_properties = CollectionPropertiesT::new();1100 collection_properties1101 .try_set_from_iter(data.properties.into_iter())1102 .map_err(<Error<T>>::from)?;11031104 CollectionProperties::<T>::insert(id, collection_properties);11051106 let mut token_props_permissions = PropertiesPermissionMap::new();1107 token_props_permissions1108 .try_set_from_iter(data.token_property_permissions.into_iter())1109 .map_err(<Error<T>>::from)?;11101111 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11121113 // Take a (non-refundable) deposit of collection creation1114 {1115 let mut imbalance =1116 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1117 imbalance.subsume(1118 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1119 &T::TreasuryAccountId::get(),1120 T::CollectionCreationPrice::get(),1121 ),1122 );1123 <T as Config>::Currency::settle(1124 payer.as_sub(),1125 imbalance,1126 WithdrawReasons::TRANSFER,1127 ExistenceRequirement::KeepAlive,1128 )1129 .map_err(|_| Error::<T>::NotSufficientFounds)?;1130 }11311132 <CreatedCollectionCount<T>>::put(created_count);1133 <Pallet<T>>::deposit_event(Event::CollectionCreated(1134 id,1135 data.mode.id(),1136 owner.as_sub().clone(),1137 ));1138 <PalletEvm<T>>::deposit_log(1139 erc::CollectionHelpersEvents::CollectionCreated {1140 owner: *owner.as_eth(),1141 collection_id: eth::collection_id_to_address(id),1142 }1143 .to_log(T::ContractAddress::get()),1144 );1145 <CollectionById<T>>::insert(id, collection);1146 Ok(id)1147 }11481149 /// Destroy collection.1150 ///1151 /// * `collection` - Collection handler.1152 /// * `sender` - The owner or administrator of the collection.1153 pub fn destroy_collection(1154 collection: CollectionHandle<T>,1155 sender: &T::CrossAccountId,1156 ) -> DispatchResult {1157 ensure!(1158 collection.limits.owner_can_destroy(),1159 <Error<T>>::NoPermission,1160 );1161 collection.check_is_owner(sender)?;11621163 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1164 .01165 .checked_add(1)1166 .ok_or(ArithmeticError::Overflow)?;11671168 // =========11691170 <DestroyedCollectionCount<T>>::put(destroyed_collections);1171 <CollectionById<T>>::remove(collection.id);1172 <AdminAmount<T>>::remove(collection.id);1173 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1174 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1175 <CollectionProperties<T>>::remove(collection.id);11761177 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11781179 <PalletEvm<T>>::deposit_log(1180 erc::CollectionHelpersEvents::CollectionDestroyed {1181 collection_id: eth::collection_id_to_address(collection.id),1182 }1183 .to_log(T::ContractAddress::get()),1184 );1185 Ok(())1186 }11871188 /// This function sets or removes a collection properties according to1189 /// `properties_updates` contents:1190 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1191 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1192 ///1193 /// This function fires an event for each property change.1194 /// In case of an error, all the changes (including the events) will be reverted1195 /// since the function is transactional.1196 #[transactional]1197 fn modify_collection_properties(1198 collection: &CollectionHandle<T>,1199 sender: &T::CrossAccountId,1200 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1201 ) -> DispatchResult {1202 collection.check_is_owner_or_admin(sender)?;12031204 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12051206 for (key, value) in properties_updates {1207 match value {1208 Some(value) => {1209 stored_properties1210 .try_set(key.clone(), value)1211 .map_err(<Error<T>>::from)?;12121213 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1214 <PalletEvm<T>>::deposit_log(1215 erc::CollectionHelpersEvents::CollectionChanged {1216 collection_id: eth::collection_id_to_address(collection.id),1217 }1218 .to_log(T::ContractAddress::get()),1219 );1220 }1221 None => {1222 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12231224 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1225 <PalletEvm<T>>::deposit_log(1226 erc::CollectionHelpersEvents::CollectionChanged {1227 collection_id: eth::collection_id_to_address(collection.id),1228 }1229 .to_log(T::ContractAddress::get()),1230 );1231 }1232 }1233 }12341235 <CollectionProperties<T>>::set(collection.id, stored_properties);12361237 Ok(())1238 }12391240 /// A batch operation to add, edit or remove properties for a token.1241 /// It sets or removes a token's properties according to1242 /// `properties_updates` contents:1243 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1244 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1245 ///1246 /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.1247 /// - `is_token_create`: Indicates that method is called during token initialization.1248 /// Allows to bypass ownership check.1249 ///1250 /// All affected properties should have `mutable` permission1251 /// to be **deleted** or to be **set more than once**,1252 /// and the sender should have permission to edit those properties.1253 ///1254 /// This function fires an event for each property change.1255 /// In case of an error, all the changes (including the events) will be reverted1256 /// since the function is transactional.1257 pub fn modify_token_properties(1258 collection: &CollectionHandle<T>,1259 sender: &T::CrossAccountId,1260 token_id: TokenId,1261 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1262 is_token_create: bool,1263 mut stored_properties: TokenProperties,1264 is_token_owner: impl Fn() -> Result<bool, DispatchError>,1265 set_token_properties: impl FnOnce(TokenProperties),1266 log: evm_coder::ethereum::Log,1267 ) -> DispatchResult {1268 let is_collection_admin = collection.is_owner_or_admin(sender);1269 let permissions = Self::property_permissions(collection.id);12701271 let mut token_owner_result = None;1272 let mut is_token_owner = || -> Result<bool, DispatchError> {1273 *token_owner_result.get_or_insert_with(&is_token_owner)1274 };12751276 for (key, value) in properties_updates {1277 let permission = permissions1278 .get(&key)1279 .cloned()1280 .unwrap_or_else(PropertyPermission::none);12811282 let is_property_exists = stored_properties.get(&key).is_some();12831284 match permission {1285 PropertyPermission { mutable: false, .. } if is_property_exists => {1286 return Err(<Error<T>>::NoPermission.into());1287 }12881289 PropertyPermission {1290 collection_admin,1291 token_owner,1292 ..1293 } => {1294 //TODO: investigate threats during public minting.1295 let is_token_create =1296 is_token_create && (collection_admin || token_owner) && value.is_some();1297 if !(is_token_create1298 || (collection_admin && is_collection_admin)1299 || (token_owner && is_token_owner()?))1300 {1301 fail!(<Error<T>>::NoPermission);1302 }1303 }1304 }13051306 match value {1307 Some(value) => {1308 stored_properties1309 .try_set(key.clone(), value)1310 .map_err(<Error<T>>::from)?;13111312 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1313 }1314 None => {1315 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13161317 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1318 }1319 }13201321 <PalletEvm<T>>::deposit_log(log.clone());1322 }13231324 set_token_properties(stored_properties);13251326 Ok(())1327 }13281329 /// Sets or unsets the approval of a given operator.1330 ///1331 /// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1332 /// - `owner`: Token owner1333 /// - `operator`: Operator1334 /// - `approve`: Should operator status be granted or revoked?1335 pub fn set_allowance_for_all(1336 collection: &CollectionHandle<T>,1337 owner: &T::CrossAccountId,1338 operator: &T::CrossAccountId,1339 approve: bool,1340 set_allowance: impl FnOnce(),1341 log: evm_coder::ethereum::Log,1342 ) -> DispatchResult {1343 if collection.permissions.access() == AccessMode::AllowList {1344 collection.check_allowlist(owner)?;1345 collection.check_allowlist(operator)?;1346 }13471348 Self::ensure_correct_receiver(operator)?;13491350 set_allowance();13511352 <PalletEvm<T>>::deposit_log(log);1353 Self::deposit_event(Event::ApprovedForAll(1354 collection.id,1355 owner.clone(),1356 operator.clone(),1357 approve,1358 ));1359 Ok(())1360 }13611362 /// Set collection property.1363 ///1364 /// * `collection` - Collection handler.1365 /// * `sender` - The owner or administrator of the collection.1366 /// * `property` - The property to set.1367 pub fn set_collection_property(1368 collection: &CollectionHandle<T>,1369 sender: &T::CrossAccountId,1370 property: Property,1371 ) -> DispatchResult {1372 Self::set_collection_properties(collection, sender, [property].into_iter())1373 }13741375 /// Set a scoped collection property, where the scope is a special prefix1376 /// prohibiting a user access to change the property directly.1377 ///1378 /// * `collection_id` - ID of the collection for which the property is being set.1379 /// * `scope` - Property scope.1380 /// * `property` - The property to set.1381 pub fn set_scoped_collection_property(1382 collection_id: CollectionId,1383 scope: PropertyScope,1384 property: Property,1385 ) -> DispatchResult {1386 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1387 properties.try_scoped_set(scope, property.key, property.value)1388 })1389 .map_err(<Error<T>>::from)?;13901391 Ok(())1392 }13931394 /// Set scoped collection properties, where the scope is a special prefix1395 /// prohibiting a user access to change the properties directly.1396 ///1397 /// * `collection_id` - ID of the collection for which the properties is being set.1398 /// * `scope` - Property scope.1399 /// * `properties` - The properties to set.1400 pub fn set_scoped_collection_properties(1401 collection_id: CollectionId,1402 scope: PropertyScope,1403 properties: impl Iterator<Item = Property>,1404 ) -> DispatchResult {1405 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1406 stored_properties.try_scoped_set_from_iter(scope, properties)1407 })1408 .map_err(<Error<T>>::from)?;14091410 Ok(())1411 }14121413 /// Set collection properties.1414 ///1415 /// * `collection` - Collection handler.1416 /// * `sender` - The owner or administrator of the collection.1417 /// * `properties` - The properties to set.1418 pub fn set_collection_properties(1419 collection: &CollectionHandle<T>,1420 sender: &T::CrossAccountId,1421 properties: impl Iterator<Item = Property>,1422 ) -> DispatchResult {1423 Self::modify_collection_properties(1424 collection,1425 sender,1426 properties.map(|property| (property.key, Some(property.value))),1427 )1428 }14291430 /// Delete collection property.1431 ///1432 /// * `collection` - Collection handler.1433 /// * `sender` - The owner or administrator of the collection.1434 /// * `property` - The property to delete.1435 pub fn delete_collection_property(1436 collection: &CollectionHandle<T>,1437 sender: &T::CrossAccountId,1438 property_key: PropertyKey,1439 ) -> DispatchResult {1440 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1441 }14421443 /// Delete collection properties.1444 ///1445 /// * `collection` - Collection handler.1446 /// * `sender` - The owner or administrator of the collection.1447 /// * `properties` - The properties to delete.1448 pub fn delete_collection_properties(1449 collection: &CollectionHandle<T>,1450 sender: &T::CrossAccountId,1451 property_keys: impl Iterator<Item = PropertyKey>,1452 ) -> DispatchResult {1453 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1454 }14551456 /// Set collection propetry permission without any checks.1457 ///1458 /// Used for migrations.1459 ///1460 /// * `collection` - Collection handler.1461 /// * `property_permissions` - Property permissions.1462 pub fn set_property_permission_unchecked(1463 collection: CollectionId,1464 property_permission: PropertyKeyPermission,1465 ) -> DispatchResult {1466 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1467 permissions.try_set(property_permission.key, property_permission.permission)1468 })1469 .map_err(<Error<T>>::from)?;1470 Ok(())1471 }14721473 /// Set collection property permission.1474 ///1475 /// * `collection` - Collection handler.1476 /// * `sender` - The owner or administrator of the collection.1477 /// * `property_permission` - Property permission.1478 pub fn set_property_permission(1479 collection: &CollectionHandle<T>,1480 sender: &T::CrossAccountId,1481 property_permission: PropertyKeyPermission,1482 ) -> DispatchResult {1483 Self::set_scoped_property_permission(1484 collection,1485 sender,1486 PropertyScope::None,1487 property_permission,1488 )1489 }14901491 /// Set collection property permission with scope.1492 ///1493 /// * `collection` - Collection handler.1494 /// * `sender` - The owner or administrator of the collection.1495 /// * `scope` - Property scope.1496 /// * `property_permission` - Property permission.1497 pub fn set_scoped_property_permission(1498 collection: &CollectionHandle<T>,1499 sender: &T::CrossAccountId,1500 scope: PropertyScope,1501 property_permission: PropertyKeyPermission,1502 ) -> DispatchResult {1503 collection.check_is_owner_or_admin(sender)?;15041505 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1506 let current_permission = all_permissions.get(&property_permission.key);1507 if matches![1508 current_permission,1509 Some(PropertyPermission { mutable: false, .. })1510 ] {1511 return Err(<Error<T>>::NoPermission.into());1512 }15131514 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1515 let property_permission = property_permission.clone();1516 permissions.try_scoped_set(1517 scope,1518 property_permission.key,1519 property_permission.permission,1520 )1521 })1522 .map_err(<Error<T>>::from)?;15231524 Self::deposit_event(Event::PropertyPermissionSet(1525 collection.id,1526 property_permission.key,1527 ));1528 <PalletEvm<T>>::deposit_log(1529 erc::CollectionHelpersEvents::CollectionChanged {1530 collection_id: eth::collection_id_to_address(collection.id),1531 }1532 .to_log(T::ContractAddress::get()),1533 );15341535 Ok(())1536 }15371538 /// Set token property permission.1539 ///1540 /// * `collection` - Collection handler.1541 /// * `sender` - The owner or administrator of the collection.1542 /// * `property_permissions` - Property permissions.1543 #[transactional]1544 pub fn set_token_property_permissions(1545 collection: &CollectionHandle<T>,1546 sender: &T::CrossAccountId,1547 property_permissions: Vec<PropertyKeyPermission>,1548 ) -> DispatchResult {1549 Self::set_scoped_token_property_permissions(1550 collection,1551 sender,1552 PropertyScope::None,1553 property_permissions,1554 )1555 }15561557 /// Set token property permission with scope.1558 ///1559 /// * `collection` - Collection handler.1560 /// * `sender` - The owner or administrator of the collection.1561 /// * `scope` - Property scope.1562 /// * `property_permissions` - Property permissions.1563 #[transactional]1564 pub fn set_scoped_token_property_permissions(1565 collection: &CollectionHandle<T>,1566 sender: &T::CrossAccountId,1567 scope: PropertyScope,1568 property_permissions: Vec<PropertyKeyPermission>,1569 ) -> DispatchResult {1570 for prop_pemission in property_permissions {1571 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1572 }15731574 Ok(())1575 }15761577 /// Get collection property.1578 pub fn get_collection_property(1579 collection_id: CollectionId,1580 key: &PropertyKey,1581 ) -> Option<PropertyValue> {1582 Self::collection_properties(collection_id).get(key).cloned()1583 }15841585 /// Convert byte vector to property key vector.1586 pub fn bytes_keys_to_property_keys(1587 keys: Vec<Vec<u8>>,1588 ) -> Result<Vec<PropertyKey>, DispatchError> {1589 keys.into_iter()1590 .map(|key| -> Result<PropertyKey, DispatchError> {1591 key.try_into()1592 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1593 })1594 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1595 }15961597 /// Get properties according to given keys.1598 pub fn filter_collection_properties(1599 collection_id: CollectionId,1600 keys: Option<Vec<PropertyKey>>,1601 ) -> Result<Vec<Property>, DispatchError> {1602 let properties = Self::collection_properties(collection_id);16031604 let properties = keys1605 .map(|keys| {1606 keys.into_iter()1607 .filter_map(|key| {1608 properties.get(&key).map(|value| Property {1609 key,1610 value: value.clone(),1611 })1612 })1613 .collect()1614 })1615 .unwrap_or_else(|| {1616 properties1617 .into_iter()1618 .map(|(key, value)| Property { key, value })1619 .collect()1620 });16211622 Ok(properties)1623 }16241625 /// Get property permissions according to given keys.1626 pub fn filter_property_permissions(1627 collection_id: CollectionId,1628 keys: Option<Vec<PropertyKey>>,1629 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1630 let permissions = Self::property_permissions(collection_id);16311632 let key_permissions = keys1633 .map(|keys| {1634 keys.into_iter()1635 .filter_map(|key| {1636 permissions1637 .get(&key)1638 .map(|permission| PropertyKeyPermission {1639 key,1640 permission: permission.clone(),1641 })1642 })1643 .collect()1644 })1645 .unwrap_or_else(|| {1646 permissions1647 .into_iter()1648 .map(|(key, permission)| PropertyKeyPermission { key, permission })1649 .collect()1650 });16511652 Ok(key_permissions)1653 }16541655 /// Toggle `user` participation in the `collection`'s allow list.1656 /// #### Store read/writes1657 /// 1 writes1658 pub fn toggle_allowlist(1659 collection: &CollectionHandle<T>,1660 sender: &T::CrossAccountId,1661 user: &T::CrossAccountId,1662 allowed: bool,1663 ) -> DispatchResult {1664 collection.check_is_owner_or_admin(sender)?;16651666 // =========16671668 if allowed {1669 <Allowlist<T>>::insert((collection.id, user), true);1670 Self::deposit_event(Event::<T>::AllowListAddressAdded(1671 collection.id,1672 user.clone(),1673 ));1674 } else {1675 <Allowlist<T>>::remove((collection.id, user));1676 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1677 collection.id,1678 user.clone(),1679 ));1680 }16811682 <PalletEvm<T>>::deposit_log(1683 erc::CollectionHelpersEvents::CollectionChanged {1684 collection_id: eth::collection_id_to_address(collection.id),1685 }1686 .to_log(T::ContractAddress::get()),1687 );16881689 Ok(())1690 }16911692 /// Toggle `user` participation in the `collection`'s admin list.1693 /// #### Store read/writes1694 /// 2 reads, 2 writes1695 pub fn toggle_admin(1696 collection: &CollectionHandle<T>,1697 sender: &T::CrossAccountId,1698 user: &T::CrossAccountId,1699 admin: bool,1700 ) -> DispatchResult {1701 collection.check_is_internal()?;1702 collection.check_is_owner(sender)?;17031704 let is_admin = <IsAdmin<T>>::get((collection.id, user));1705 if is_admin == admin {1706 if admin {1707 return Ok(());1708 } else {1709 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1710 }1711 }1712 let amount = <AdminAmount<T>>::get(collection.id);17131714 // =========17151716 if admin {1717 let amount = amount1718 .checked_add(1)1719 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1720 ensure!(1721 amount <= Self::collection_admins_limit(),1722 <Error<T>>::CollectionAdminCountExceeded,1723 );17241725 <AdminAmount<T>>::insert(collection.id, amount);1726 <IsAdmin<T>>::insert((collection.id, user), true);17271728 Self::deposit_event(Event::<T>::CollectionAdminAdded(1729 collection.id,1730 user.clone(),1731 ));1732 } else {1733 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1734 <IsAdmin<T>>::remove((collection.id, user));17351736 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1737 collection.id,1738 user.clone(),1739 ));1740 }17411742 <PalletEvm<T>>::deposit_log(1743 erc::CollectionHelpersEvents::CollectionChanged {1744 collection_id: eth::collection_id_to_address(collection.id),1745 }1746 .to_log(T::ContractAddress::get()),1747 );17481749 Ok(())1750 }17511752 /// Update collection limits.1753 pub fn update_limits(1754 user: &T::CrossAccountId,1755 collection: &mut CollectionHandle<T>,1756 new_limit: CollectionLimits,1757 ) -> DispatchResult {1758 collection.check_is_internal()?;1759 collection.check_is_owner_or_admin(user)?;17601761 collection.limits =1762 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17631764 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1765 <PalletEvm<T>>::deposit_log(1766 erc::CollectionHelpersEvents::CollectionChanged {1767 collection_id: eth::collection_id_to_address(collection.id),1768 }1769 .to_log(T::ContractAddress::get()),1770 );17711772 collection.save()1773 }17741775 /// Merge set fields from `new_limit` to `old_limit`.1776 fn clamp_limits(1777 mode: CollectionMode,1778 old_limit: &CollectionLimits,1779 mut new_limit: CollectionLimits,1780 ) -> Result<CollectionLimits, DispatchError> {1781 let limits = old_limit;1782 limit_default!(old_limit, new_limit,1783 account_token_ownership_limit => ensure!(1784 new_limit <= MAX_TOKEN_OWNERSHIP,1785 <Error<T>>::CollectionLimitBoundsExceeded,1786 ),1787 sponsored_data_size => ensure!(1788 new_limit <= CUSTOM_DATA_LIMIT,1789 <Error<T>>::CollectionLimitBoundsExceeded,1790 ),17911792 sponsored_data_rate_limit => {},1793 token_limit => ensure!(1794 old_limit >= new_limit && new_limit > 0,1795 <Error<T>>::CollectionTokenLimitExceeded1796 ),17971798 sponsor_transfer_timeout(match mode {1799 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1800 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1801 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1802 }) => ensure!(1803 new_limit <= MAX_SPONSOR_TIMEOUT,1804 <Error<T>>::CollectionLimitBoundsExceeded,1805 ),1806 sponsor_approve_timeout => {},1807 owner_can_transfer => ensure!(1808 !limits.owner_can_transfer_instaled() ||1809 old_limit || !new_limit,1810 <Error<T>>::OwnerPermissionsCantBeReverted,1811 ),1812 owner_can_destroy => ensure!(1813 old_limit || !new_limit,1814 <Error<T>>::OwnerPermissionsCantBeReverted,1815 ),1816 transfers_enabled => {},1817 );1818 Ok(new_limit)1819 }18201821 /// Update collection permissions.1822 pub fn update_permissions(1823 user: &T::CrossAccountId,1824 collection: &mut CollectionHandle<T>,1825 new_permission: CollectionPermissions,1826 ) -> DispatchResult {1827 collection.check_is_internal()?;1828 collection.check_is_owner_or_admin(user)?;1829 collection.permissions = Self::clamp_permissions(1830 collection.mode.clone(),1831 &collection.permissions,1832 new_permission,1833 )?;18341835 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1836 <PalletEvm<T>>::deposit_log(1837 erc::CollectionHelpersEvents::CollectionChanged {1838 collection_id: eth::collection_id_to_address(collection.id),1839 }1840 .to_log(T::ContractAddress::get()),1841 );18421843 collection.save()1844 }18451846 /// Merge set fields from `new_permission` to `old_permission`.1847 fn clamp_permissions(1848 _mode: CollectionMode,1849 old_permission: &CollectionPermissions,1850 mut new_permission: CollectionPermissions,1851 ) -> Result<CollectionPermissions, DispatchError> {1852 limit_default_clone!(old_permission, new_permission,1853 access => {},1854 mint_mode => {},1855 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1856 );1857 Ok(new_permission)1858 }18591860 /// Repair possibly broken properties of a collection.1861 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1862 CollectionProperties::<T>::mutate(collection_id, |properties| {1863 properties.recompute_consumed_space();1864 });18651866 Ok(())1867 }1868}18691870/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1871#[macro_export]1872macro_rules! unsupported {1873 ($runtime:path) => {1874 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1875 };1876}18771878/// Return weights for various worst-case operations.1879pub trait CommonWeightInfo<CrossAccountId> {1880 /// Weight of item creation.1881 fn create_item(data: &CreateItemData) -> Weight {1882 Self::create_multiple_items(from_ref(data))1883 }18841885 /// Weight of items creation.1886 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18871888 /// Weight of items creation.1889 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18901891 /// The weight of the burning item.1892 fn burn_item() -> Weight;18931894 /// Property setting weight.1895 ///1896 /// * `amount`- The number of properties to set.1897 fn set_collection_properties(amount: u32) -> Weight;18981899 /// Collection property deletion weight.1900 ///1901 /// * `amount`- The number of properties to set.1902 fn delete_collection_properties(amount: u32) -> Weight;19031904 /// Token property setting weight.1905 ///1906 /// * `amount`- The number of properties to set.1907 fn set_token_properties(amount: u32) -> Weight;19081909 /// Token property deletion weight.1910 ///1911 /// * `amount`- The number of properties to delete.1912 fn delete_token_properties(amount: u32) -> Weight;19131914 /// Token property permissions set weight.1915 ///1916 /// * `amount`- The number of property permissions to set.1917 fn set_token_property_permissions(amount: u32) -> Weight;19181919 /// Transfer price of the token or its parts.1920 fn transfer() -> Weight;19211922 /// The price of setting the permission of the operation from another user.1923 fn approve() -> Weight;19241925 /// The price of setting the permission of the operation from another user for eth mirror.1926 fn approve_from() -> Weight;19271928 /// Transfer price from another user.1929 fn transfer_from() -> Weight;19301931 /// The price of burning a token from another user.1932 fn burn_from() -> Weight;19331934 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1935 /// whole users's balance.1936 ///1937 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1938 fn burn_recursively_self_raw() -> Weight;19391940 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1941 ///1942 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1943 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19441945 /// The price of recursive burning a token.1946 ///1947 /// `max_selfs` - The maximum burning weight of the token itself.1948 /// `max_breadth` - The maximum number of nested tokens to burn.1949 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1950 Self::burn_recursively_self_raw()1951 .saturating_mul(max_selfs.max(1) as u64)1952 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1953 }19541955 /// The price of retrieving token owner1956 fn token_owner() -> Weight;19571958 /// The price of setting approval for all1959 fn set_allowance_for_all() -> Weight;19601961 /// The price of repairing an item.1962 fn force_repair_item() -> Weight;1963}19641965/// Weight info extension trait for refungible pallet.1966pub trait RefungibleExtensionsWeightInfo {1967 /// Weight of token repartition.1968 fn repartition() -> Weight;1969}19701971/// Common collection operations.1972///1973/// It wraps methods in Fungible, Nonfungible and Refungible pallets1974/// and adds weight info.1975pub trait CommonCollectionOperations<T: Config> {1976 /// Create token.1977 ///1978 /// * `sender` - The user who mint the token and pays for the transaction.1979 /// * `to` - The user who will own the token.1980 /// * `data` - Token data.1981 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1982 fn create_item(1983 &self,1984 sender: T::CrossAccountId,1985 to: T::CrossAccountId,1986 data: CreateItemData,1987 nesting_budget: &dyn Budget,1988 ) -> DispatchResultWithPostInfo;19891990 /// Create multiple tokens.1991 ///1992 /// * `sender` - The user who mint the token and pays for the transaction.1993 /// * `to` - The user who will own the token.1994 /// * `data` - Token data.1995 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1996 fn create_multiple_items(1997 &self,1998 sender: T::CrossAccountId,1999 to: T::CrossAccountId,2000 data: Vec<CreateItemData>,2001 nesting_budget: &dyn Budget,2002 ) -> DispatchResultWithPostInfo;20032004 /// Create multiple tokens.2005 ///2006 /// * `sender` - The user who mint the token and pays for the transaction.2007 /// * `to` - The user who will own the token.2008 /// * `data` - Token data.2009 /// * `nesting_budget` - A budget that can be spent on nesting tokens.2010 fn create_multiple_items_ex(2011 &self,2012 sender: T::CrossAccountId,2013 data: CreateItemExData<T::CrossAccountId>,2014 nesting_budget: &dyn Budget,2015 ) -> DispatchResultWithPostInfo;20162017 /// Burn token.2018 ///2019 /// * `sender` - The user who owns the token.2020 /// * `token` - Token id that will burned.2021 /// * `amount` - The number of parts of the token that will be burned.2022 fn burn_item(2023 &self,2024 sender: T::CrossAccountId,2025 token: TokenId,2026 amount: u128,2027 ) -> DispatchResultWithPostInfo;20282029 /// Burn token and all nested tokens recursievly.2030 ///2031 /// * `sender` - The user who owns the token.2032 /// * `token` - Token id that will burned.2033 /// * `self_budget` - The budget that can be spent on burning tokens.2034 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.2035 fn burn_item_recursively(2036 &self,2037 sender: T::CrossAccountId,2038 token: TokenId,2039 self_budget: &dyn Budget,2040 breadth_budget: &dyn Budget,2041 ) -> DispatchResultWithPostInfo;20422043 /// Set collection properties.2044 ///2045 /// * `sender` - Must be either the owner of the collection or its admin.2046 /// * `properties` - Properties to be set.2047 fn set_collection_properties(2048 &self,2049 sender: T::CrossAccountId,2050 properties: Vec<Property>,2051 ) -> DispatchResultWithPostInfo;20522053 /// Delete collection properties.2054 ///2055 /// * `sender` - Must be either the owner of the collection or its admin.2056 /// * `properties` - The properties to be removed.2057 fn delete_collection_properties(2058 &self,2059 sender: &T::CrossAccountId,2060 property_keys: Vec<PropertyKey>,2061 ) -> DispatchResultWithPostInfo;20622063 /// Set token properties.2064 ///2065 /// The appropriate [`PropertyPermission`] for the token property2066 /// must be set with [`Self::set_token_property_permissions`].2067 ///2068 /// * `sender` - Must be either the owner of the token or its admin.2069 /// * `token_id` - The token for which the properties are being set.2070 /// * `properties` - Properties to be set.2071 /// * `budget` - Budget for setting properties.2072 fn set_token_properties(2073 &self,2074 sender: T::CrossAccountId,2075 token_id: TokenId,2076 properties: Vec<Property>,2077 budget: &dyn Budget,2078 ) -> DispatchResultWithPostInfo;20792080 /// Remove token properties.2081 ///2082 /// The appropriate [`PropertyPermission`] for the token property2083 /// must be set with [`Self::set_token_property_permissions`].2084 ///2085 /// * `sender` - Must be either the owner of the token or its admin.2086 /// * `token_id` - The token for which the properties are being remove.2087 /// * `property_keys` - Keys to remove corresponding properties.2088 /// * `budget` - Budget for removing properties.2089 fn delete_token_properties(2090 &self,2091 sender: T::CrossAccountId,2092 token_id: TokenId,2093 property_keys: Vec<PropertyKey>,2094 budget: &dyn Budget,2095 ) -> DispatchResultWithPostInfo;20962097 /// Set token property permissions.2098 ///2099 /// * `sender` - Must be either the owner of the token or its admin.2100 /// * `token_id` - The token for which the properties are being set.2101 /// * `property_permissions` - Property permissions to be set.2102 /// * `budget` - Budget for setting properties.2103 fn set_token_property_permissions(2104 &self,2105 sender: &T::CrossAccountId,2106 property_permissions: Vec<PropertyKeyPermission>,2107 ) -> DispatchResultWithPostInfo;21082109 /// Transfer amount of token pieces.2110 ///2111 /// * `sender` - Donor user.2112 /// * `to` - Recepient user.2113 /// * `token` - The token of which parts are being sent.2114 /// * `amount` - The number of parts of the token that will be transferred.2115 /// * `budget` - The maximum budget that can be spent on the transfer.2116 fn transfer(2117 &self,2118 sender: T::CrossAccountId,2119 to: T::CrossAccountId,2120 token: TokenId,2121 amount: u128,2122 budget: &dyn Budget,2123 ) -> DispatchResultWithPostInfo;21242125 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2126 ///2127 /// * `sender` - The user who grants access to the token.2128 /// * `spender` - The user to whom the rights are granted.2129 /// * `token` - The token to which access is granted.2130 /// * `amount` - The amount of pieces that another user can dispose of.2131 fn approve(2132 &self,2133 sender: T::CrossAccountId,2134 spender: T::CrossAccountId,2135 token: TokenId,2136 amount: u128,2137 ) -> DispatchResultWithPostInfo;21382139 /// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2140 ///2141 /// * `sender` - The user who grants access to the token.2142 /// * `from` - Spender's eth mirror.2143 /// * `to` - The user to whom the rights are granted.2144 /// * `token` - The token to which access is granted.2145 /// * `amount` - The amount of pieces that another user can dispose of.2146 fn approve_from(2147 &self,2148 sender: T::CrossAccountId,2149 from: T::CrossAccountId,2150 to: T::CrossAccountId,2151 token: TokenId,2152 amount: u128,2153 ) -> DispatchResultWithPostInfo;21542155 /// Send parts of a token owned by another user.2156 ///2157 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2158 ///2159 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2160 /// * `from` - The user who owns the token.2161 /// * `to` - Recepient user.2162 /// * `token` - The token of which parts are being sent.2163 /// * `amount` - The number of parts of the token that will be transferred.2164 /// * `budget` - The maximum budget that can be spent on the transfer.2165 fn transfer_from(2166 &self,2167 sender: T::CrossAccountId,2168 from: T::CrossAccountId,2169 to: T::CrossAccountId,2170 token: TokenId,2171 amount: u128,2172 budget: &dyn Budget,2173 ) -> DispatchResultWithPostInfo;21742175 /// Burn parts of a token owned by another user.2176 ///2177 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2178 ///2179 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2180 /// * `from` - The user who owns the token.2181 /// * `token` - The token of which parts are being sent.2182 /// * `amount` - The number of parts of the token that will be transferred.2183 /// * `budget` - The maximum budget that can be spent on the burn.2184 fn burn_from(2185 &self,2186 sender: T::CrossAccountId,2187 from: T::CrossAccountId,2188 token: TokenId,2189 amount: u128,2190 budget: &dyn Budget,2191 ) -> DispatchResultWithPostInfo;21922193 /// Check permission to nest token.2194 ///2195 /// * `sender` - The user who initiated the check.2196 /// * `from` - The token that is checked for embedding.2197 /// * `under` - Token under which to check.2198 /// * `budget` - The maximum budget that can be spent on the check.2199 fn check_nesting(2200 &self,2201 sender: T::CrossAccountId,2202 from: (CollectionId, TokenId),2203 under: TokenId,2204 budget: &dyn Budget,2205 ) -> DispatchResult;22062207 /// Nest one token into another.2208 ///2209 /// * `under` - Token holder.2210 /// * `to_nest` - Nested token.2211 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22122213 /// Unnest token.2214 ///2215 /// * `under` - Token holder.2216 /// * `to_nest` - Token to unnest.2217 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22182219 /// Get all user tokens.2220 ///2221 /// * `account` - Account for which you need to get tokens.2222 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22232224 /// Get all the tokens in the collection.2225 fn collection_tokens(&self) -> Vec<TokenId>;22262227 /// Check if the token exists.2228 ///2229 /// * `token` - Id token to check.2230 fn token_exists(&self, token: TokenId) -> bool;22312232 /// Get the id of the last minted token.2233 fn last_token_id(&self) -> TokenId;22342235 /// Get the owner of the token.2236 ///2237 /// * `token` - The token for which you need to find out the owner.2238 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22392240 /// Returns 10 tokens owners in no particular order.2241 ///2242 /// * `token` - The token for which you need to find out the owners.2243 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22442245 /// Get the value of the token property by key.2246 ///2247 /// * `token` - Token with the property to get.2248 /// * `key` - Property name.2249 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22502251 /// Get a set of token properties by key vector.2252 ///2253 /// * `token` - Token with the property to get.2254 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2255 /// then all properties are returned.2256 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22572258 /// Amount of unique collection tokens2259 fn total_supply(&self) -> u32;22602261 /// Amount of different tokens account has.2262 ///2263 /// * `account` - The account for which need to get the balance.2264 fn account_balance(&self, account: T::CrossAccountId) -> u32;22652266 /// Amount of specific token account have.2267 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22682269 /// Amount of token pieces2270 fn total_pieces(&self, token: TokenId) -> Option<u128>;22712272 /// Get the number of parts of the token that a trusted user can manage.2273 ///2274 /// * `sender` - Trusted user.2275 /// * `spender` - Owner of the token.2276 /// * `token` - The token for which to get the value.2277 fn allowance(2278 &self,2279 sender: T::CrossAccountId,2280 spender: T::CrossAccountId,2281 token: TokenId,2282 ) -> u128;22832284 /// Get extension for RFT collection.2285 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22862287 /// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2288 /// * `owner` - Token owner2289 /// * `operator` - Operator2290 /// * `approve` - Should operator status be granted or revoked?2291 fn set_allowance_for_all(2292 &self,2293 owner: T::CrossAccountId,2294 operator: T::CrossAccountId,2295 approve: bool,2296 ) -> DispatchResultWithPostInfo;22972298 /// Tells whether the given `owner` approves the `operator`.2299 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23002301 /// Repairs a possibly broken item.2302 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2303}23042305/// Extension for RFT collection.2306pub trait RefungibleExtensions<T>2307where2308 T: Config,2309{2310 /// Change the number of parts of the token.2311 ///2312 /// When the value changes down, this function is equivalent to burning parts of the token.2313 ///2314 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.2315 /// * `token` - The token for which you want to change the number of parts.2316 /// * `amount` - The new value of the parts of the token.2317 fn repartition(2318 &self,2319 sender: &T::CrossAccountId,2320 token: TokenId,2321 amount: u128,2322 ) -> DispatchResultWithPostInfo;2323}23242325/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2326///2327/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2328pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2329 let post_info = PostDispatchInfo {2330 actual_weight: Some(weight),2331 pays_fee: Pays::Yes,2332 };2333 match res {2334 Ok(()) => Ok(post_info),2335 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2336 }2337}23382339impl<T: Config> From<PropertiesError> for Error<T> {2340 fn from(error: PropertiesError) -> Self {2341 match error {2342 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2343 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2344 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2345 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2346 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2347 }2348 }2349}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::{57 ops::{Deref, DerefMut},58 slice::from_ref,59};60use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};61use sp_std::vec::Vec;62use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};63use evm_coder::ToLog;64use frame_support::{65 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},66 ensure,67 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},68 dispatch::Pays,69 transactional, fail,70};71use pallet_evm::GasWeightMapping;72use up_data_structs::{73 AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,74 RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,75 COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP,76 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,77 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,78 CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,79 PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties,80 PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError,81 TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope,82 CollectionPermissions,83};84use up_pov_estimate_rpc::PovInfo;8586pub use pallet::*;87use sp_core::H160;88use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};8990#[cfg(feature = "runtime-benchmarks")]91pub mod benchmarking;92pub mod dispatch;93pub mod erc;94pub mod eth;95#[allow(missing_docs)]96pub mod weights;9798/// Weight info.99pub type SelfWeightOf<T> = <T as Config>::WeightInfo;100101/// Collection handle contains information about collection data and id.102/// Also provides functionality to count consumed gas.103///104/// CollectionHandle is used as a generic wrapper for collections of all types.105/// It allows to perform common operations and queries on any collection type,106/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].107#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]108pub struct CollectionHandle<T: Config> {109 /// Collection id110 pub id: CollectionId,111 collection: Collection<T::AccountId>,112 /// Substrate recorder for counting consumed gas113 pub recorder: SubstrateRecorder<T>,114}115116impl<T: Config> WithRecorder<T> for CollectionHandle<T> {117 fn recorder(&self) -> &SubstrateRecorder<T> {118 &self.recorder119 }120 fn into_recorder(self) -> SubstrateRecorder<T> {121 self.recorder122 }123}124125impl<T: Config> CollectionHandle<T> {126 /// Same as [CollectionHandle::new] but with an explicit gas limit.127 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {128 <CollectionById<T>>::get(id).map(|collection| Self {129 id,130 collection,131 recorder: SubstrateRecorder::new(gas_limit),132 })133 }134135 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].136 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {137 <CollectionById<T>>::get(id).map(|collection| Self {138 id,139 collection,140 recorder,141 })142 }143144 /// Retrives collection data from storage and creates collection handle with default parameters.145 /// If collection not found return `None`146 pub fn new(id: CollectionId) -> Option<Self> {147 Self::new_with_gas_limit(id, u64::MAX)148 }149150 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.151 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {152 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)153 }154155 /// Consume gas for reading.156 pub fn consume_store_reads(157 &self,158 reads: u64,159 ) -> pallet_evm_coder_substrate::execution::Result<()> {160 self.recorder161 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(162 <T as frame_system::Config>::DbWeight::get()163 .read164 .saturating_mul(reads),165 // TODO: measure proof166 0,167 )))168 }169170 /// Consume gas for writing.171 pub fn consume_store_writes(172 &self,173 writes: u64,174 ) -> pallet_evm_coder_substrate::execution::Result<()> {175 self.recorder176 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(177 <T as frame_system::Config>::DbWeight::get()178 .write179 .saturating_mul(writes),180 // TODO: measure proof181 0,182 )))183 }184185 /// Consume gas for reading and writing.186 pub fn consume_store_reads_and_writes(187 &self,188 reads: u64,189 writes: u64,190 ) -> pallet_evm_coder_substrate::execution::Result<()> {191 let weight = <T as frame_system::Config>::DbWeight::get();192 let reads = weight.read.saturating_mul(reads);193 let writes = weight.read.saturating_mul(writes);194 self.recorder195 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(196 reads.saturating_add(writes),197 // TODO: measure proof198 0,199 )))200 }201202 /// Save collection to storage.203 pub fn save(&self) -> DispatchResult {204 <CollectionById<T>>::insert(self.id, &self.collection);205 Ok(())206 }207208 /// Set collection sponsor.209 ///210 /// Unique collections allows sponsoring for certain actions.211 /// This method allows you to set the sponsor of the collection.212 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].213 pub fn set_sponsor(214 &mut self,215 sender: &T::CrossAccountId,216 sponsor: T::AccountId,217 ) -> DispatchResult {218 self.check_is_internal()?;219 self.check_is_owner_or_admin(sender)?;220221 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());222223 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));224 <PalletEvm<T>>::deposit_log(225 erc::CollectionHelpersEvents::CollectionChanged {226 collection_id: eth::collection_id_to_address(self.id),227 }228 .to_log(T::ContractAddress::get()),229 );230231 self.save()232 }233234 /// Force set `sponsor`.235 ///236 /// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation237 /// from the `sponsor` is not required.238 ///239 /// # Arguments240 ///241 /// * `sender`: Caller's account.242 /// * `sponsor`: ID of the account of the sponsor-to-be.243 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {244 self.check_is_internal()?;245246 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());247248 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));249 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));250 <PalletEvm<T>>::deposit_log(251 erc::CollectionHelpersEvents::CollectionChanged {252 collection_id: eth::collection_id_to_address(self.id),253 }254 .to_log(T::ContractAddress::get()),255 );256257 self.save()258 }259260 /// Confirm sponsorship261 ///262 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.263 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].264 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {265 self.check_is_internal()?;266 ensure!(267 self.collection.sponsorship.pending_sponsor() == Some(sender),268 Error::<T>::ConfirmSponsorshipFail269 );270271 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());272273 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));274 <PalletEvm<T>>::deposit_log(275 erc::CollectionHelpersEvents::CollectionChanged {276 collection_id: eth::collection_id_to_address(self.id),277 }278 .to_log(T::ContractAddress::get()),279 );280281 self.save()282 }283284 /// Remove collection sponsor.285 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {286 self.check_is_internal()?;287 self.check_is_owner_or_admin(sender)?;288289 self.collection.sponsorship = SponsorshipState::Disabled;290291 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));292 <PalletEvm<T>>::deposit_log(293 erc::CollectionHelpersEvents::CollectionChanged {294 collection_id: eth::collection_id_to_address(self.id),295 }296 .to_log(T::ContractAddress::get()),297 );298 self.save()299 }300301 /// Force remove `sponsor`.302 ///303 /// Differs from `remove_sponsor` in that304 /// it doesn't require consent from the `owner` of the collection.305 pub fn force_remove_sponsor(&mut self) -> DispatchResult {306 self.check_is_internal()?;307308 self.collection.sponsorship = SponsorshipState::Disabled;309310 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));311 <PalletEvm<T>>::deposit_log(312 erc::CollectionHelpersEvents::CollectionChanged {313 collection_id: eth::collection_id_to_address(self.id),314 }315 .to_log(T::ContractAddress::get()),316 );317 self.save()318 }319320 /// Checks that the collection was created with, and must be operated upon through **Unique API**.321 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.322 pub fn check_is_internal(&self) -> DispatchResult {323 if self.flags.external {324 return Err(<Error<T>>::CollectionIsExternal)?;325 }326327 Ok(())328 }329330 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.331 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.332 pub fn check_is_external(&self) -> DispatchResult {333 if !self.flags.external {334 return Err(<Error<T>>::CollectionIsInternal)?;335 }336337 Ok(())338 }339}340341impl<T: Config> Deref for CollectionHandle<T> {342 type Target = Collection<T::AccountId>;343344 fn deref(&self) -> &Self::Target {345 &self.collection346 }347}348349impl<T: Config> DerefMut for CollectionHandle<T> {350 fn deref_mut(&mut self) -> &mut Self::Target {351 &mut self.collection352 }353}354355impl<T: Config> CollectionHandle<T> {356 /// Checks if the `user` is the owner of the collection.357 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {358 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);359 Ok(())360 }361362 /// Returns **true** if the `user` is the owner or administrator of the collection.363 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {364 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))365 }366367 /// Checks if the `user` is the owner or administrator of the collection.368 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {369 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);370 Ok(())371 }372373 /// Returns **true** if374 /// * the `user`is a collection owner or admin375 /// * the collection limits allow the owner/admins to transfer/burn any collection token376 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {377 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)378 }379380 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.381 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {382 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)383 }384385 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.386 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {387 ensure!(388 <Allowlist<T>>::get((self.id, user)),389 <Error<T>>::AddressNotInAllowlist390 );391 Ok(())392 }393394 /// Changes collection owner to another account395 /// #### Store read/writes396 /// 1 writes397 pub fn change_owner(398 &mut self,399 caller: T::CrossAccountId,400 new_owner: T::CrossAccountId,401 ) -> DispatchResult {402 self.check_is_internal()?;403 self.check_is_owner(&caller)?;404 self.collection.owner = new_owner.as_sub().clone();405406 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(407 self.id,408 new_owner.as_sub().clone(),409 ));410 <PalletEvm<T>>::deposit_log(411 erc::CollectionHelpersEvents::CollectionChanged {412 collection_id: eth::collection_id_to_address(self.id),413 }414 .to_log(T::ContractAddress::get()),415 );416417 self.save()418 }419}420421#[frame_support::pallet]422pub mod pallet {423 use core::marker::PhantomData;424425 use super::*;426 use dispatch::CollectionDispatch;427 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};428 use frame_support::traits::Currency;429 use up_data_structs::{TokenId, mapping::TokenAddressMapping};430 use scale_info::TypeInfo;431 use weights::WeightInfo;432433 #[pallet::config]434 pub trait Config:435 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo436 {437 /// Weight information for functions of this pallet.438 type WeightInfo: WeightInfo;439440 /// Events compatible with [`frame_system::Config::Event`].441 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;442443 /// Handler of accounts and payment.444 type Currency: Currency<Self::AccountId>;445446 /// Set price to create a collection.447 #[pallet::constant]448 type CollectionCreationPrice: Get<449 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,450 >;451452 /// Dispatcher of operations on collections.453 type CollectionDispatch: CollectionDispatch<Self>;454455 /// Account which holds the chain's treasury.456 type TreasuryAccountId: Get<Self::AccountId>;457458 /// Address under which the CollectionHelper contract would be available.459 #[pallet::constant]460 type ContractAddress: Get<H160>;461462 /// Mapper for token addresses to Ethereum addresses.463 type EvmTokenAddressMapping: TokenAddressMapping<H160>;464465 /// Mapper for token addresses to [`CrossAccountId`].466 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;467 }468469 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);470471 #[pallet::pallet]472 #[pallet::storage_version(STORAGE_VERSION)]473 pub struct Pallet<T>(_);474475 #[pallet::extra_constants]476 impl<T: Config> Pallet<T> {477 /// Maximum admins per collection.478 pub fn collection_admins_limit() -> u32 {479 COLLECTION_ADMINS_LIMIT480 }481 }482483 #[pallet::genesis_config]484 pub struct GenesisConfig<T>(PhantomData<T>);485486 #[cfg(feature = "std")]487 impl<T: Config> Default for GenesisConfig<T> {488 fn default() -> Self {489 Self(Default::default())490 }491 }492493 #[pallet::genesis_build]494 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {495 fn build(&self) {496 StorageVersion::new(1).put::<Pallet<T>>();497 }498 }499500 impl<T: Config> Pallet<T> {501 /// Helper function that handles deposit events502 pub fn deposit_event(event: Event<T>) {503 let event = <T as Config>::RuntimeEvent::from(event);504 let event = event.into();505 <frame_system::Pallet<T>>::deposit_event(event)506 }507 }508509 #[pallet::event]510 pub enum Event<T: Config> {511 /// New collection was created512 CollectionCreated(513 /// Globally unique identifier of newly created collection.514 CollectionId,515 /// [`CollectionMode`] converted into _u8_.516 u8,517 /// Collection owner.518 T::AccountId,519 ),520521 /// New collection was destroyed522 CollectionDestroyed(523 /// Globally unique identifier of collection.524 CollectionId,525 ),526527 /// New item was created.528 ItemCreated(529 /// Id of the collection where item was created.530 CollectionId,531 /// Id of an item. Unique within the collection.532 TokenId,533 /// Owner of newly created item534 T::CrossAccountId,535 /// Always 1 for NFT536 u128,537 ),538539 /// Collection item was burned.540 ItemDestroyed(541 /// Id of the collection where item was destroyed.542 CollectionId,543 /// Identifier of burned NFT.544 TokenId,545 /// Which user has destroyed its tokens.546 T::CrossAccountId,547 /// Amount of token pieces destroed. Always 1 for NFT.548 u128,549 ),550551 /// Item was transferred552 Transfer(553 /// Id of collection to which item is belong.554 CollectionId,555 /// Id of an item.556 TokenId,557 /// Original owner of item.558 T::CrossAccountId,559 /// New owner of item.560 T::CrossAccountId,561 /// Amount of token pieces transfered. Always 1 for NFT.562 u128,563 ),564565 /// Amount pieces of token owned by `sender` was approved for `spender`.566 Approved(567 /// Id of collection to which item is belong.568 CollectionId,569 /// Id of an item.570 TokenId,571 /// Original owner of item.572 T::CrossAccountId,573 /// Id for which the approval was granted.574 T::CrossAccountId,575 /// Amount of token pieces transfered. Always 1 for NFT.576 u128,577 ),578579 /// A `sender` approves operations on all owned tokens for `spender`.580 ApprovedForAll(581 /// Id of collection to which item is belong.582 CollectionId,583 /// Owner of a wallet.584 T::CrossAccountId,585 /// Id for which operator status was granted or rewoked.586 T::CrossAccountId,587 /// Is operator status granted or revoked?588 bool,589 ),590591 /// The colletion property has been added or edited.592 CollectionPropertySet(593 /// Id of collection to which property has been set.594 CollectionId,595 /// The property that was set.596 PropertyKey,597 ),598599 /// The property has been deleted.600 CollectionPropertyDeleted(601 /// Id of collection to which property has been deleted.602 CollectionId,603 /// The property that was deleted.604 PropertyKey,605 ),606607 /// The token property has been added or edited.608 TokenPropertySet(609 /// Identifier of the collection whose token has the property set.610 CollectionId,611 /// The token for which the property was set.612 TokenId,613 /// The property that was set.614 PropertyKey,615 ),616617 /// The token property has been deleted.618 TokenPropertyDeleted(619 /// Identifier of the collection whose token has the property deleted.620 CollectionId,621 /// The token for which the property was deleted.622 TokenId,623 /// The property that was deleted.624 PropertyKey,625 ),626627 /// The token property permission of a collection has been set.628 PropertyPermissionSet(629 /// ID of collection to which property permission has been set.630 CollectionId,631 /// The property permission that was set.632 PropertyKey,633 ),634635 /// Address was added to the allow list.636 AllowListAddressAdded(637 /// ID of the affected collection.638 CollectionId,639 /// Address of the added account.640 T::CrossAccountId,641 ),642643 /// Address was removed from the allow list.644 AllowListAddressRemoved(645 /// ID of the affected collection.646 CollectionId,647 /// Address of the removed account.648 T::CrossAccountId,649 ),650651 /// Collection admin was added.652 CollectionAdminAdded(653 /// ID of the affected collection.654 CollectionId,655 /// Admin address.656 T::CrossAccountId,657 ),658659 /// Collection admin was removed.660 CollectionAdminRemoved(661 /// ID of the affected collection.662 CollectionId,663 /// Removed admin address.664 T::CrossAccountId,665 ),666667 /// Collection limits were set.668 CollectionLimitSet(669 /// ID of the affected collection.670 CollectionId,671 ),672673 /// Collection owned was changed.674 CollectionOwnerChanged(675 /// ID of the affected collection.676 CollectionId,677 /// New owner address.678 T::AccountId,679 ),680681 /// Collection permissions were set.682 CollectionPermissionSet(683 /// ID of the affected collection.684 CollectionId,685 ),686687 /// Collection sponsor was set.688 CollectionSponsorSet(689 /// ID of the affected collection.690 CollectionId,691 /// New sponsor address.692 T::AccountId,693 ),694695 /// New sponsor was confirm.696 SponsorshipConfirmed(697 /// ID of the affected collection.698 CollectionId,699 /// New sponsor address.700 T::AccountId,701 ),702703 /// Collection sponsor was removed.704 CollectionSponsorRemoved(705 /// ID of the affected collection.706 CollectionId,707 ),708 }709710 #[pallet::error]711 pub enum Error<T> {712 /// This collection does not exist.713 CollectionNotFound,714 /// Sender parameter and item owner must be equal.715 MustBeTokenOwner,716 /// No permission to perform action717 NoPermission,718 /// Destroying only empty collections is allowed719 CantDestroyNotEmptyCollection,720 /// Collection is not in mint mode.721 PublicMintingNotAllowed,722 /// Address is not in allow list.723 AddressNotInAllowlist,724725 /// Collection name can not be longer than 63 char.726 CollectionNameLimitExceeded,727 /// Collection description can not be longer than 255 char.728 CollectionDescriptionLimitExceeded,729 /// Token prefix can not be longer than 15 char.730 CollectionTokenPrefixLimitExceeded,731 /// Total collections bound exceeded.732 TotalCollectionsLimitExceeded,733 /// Exceeded max admin count734 CollectionAdminCountExceeded,735 /// Collection limit bounds per collection exceeded736 CollectionLimitBoundsExceeded,737 /// Tried to enable permissions which are only permitted to be disabled738 OwnerPermissionsCantBeReverted,739 /// Collection settings not allowing items transferring740 TransferNotAllowed,741 /// Account token limit exceeded per collection742 AccountTokenLimitExceeded,743 /// Collection token limit exceeded744 CollectionTokenLimitExceeded,745 /// Metadata flag frozen746 MetadataFlagFrozen,747748 /// Item does not exist749 TokenNotFound,750 /// Item is balance not enough751 TokenValueTooLow,752 /// Requested value is more than the approved753 ApprovedValueTooLow,754 /// Tried to approve more than owned755 CantApproveMoreThanOwned,756 /// Only spending from eth mirror could be approved757 AddressIsNotEthMirror,758759 /// Can't transfer tokens to ethereum zero address760 AddressIsZero,761762 /// The operation is not supported763 UnsupportedOperation,764765 /// Insufficient funds to perform an action766 NotSufficientFounds,767768 /// User does not satisfy the nesting rule769 UserIsNotAllowedToNest,770 /// Only tokens from specific collections may nest tokens under this one771 SourceCollectionIsNotAllowedToNest,772773 /// Tried to store more data than allowed in collection field774 CollectionFieldSizeExceeded,775776 /// Tried to store more property data than allowed777 NoSpaceForProperty,778779 /// Tried to store more property keys than allowed780 PropertyLimitReached,781782 /// Property key is too long783 PropertyKeyIsTooLong,784785 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed786 InvalidCharacterInPropertyKey,787788 /// Empty property keys are forbidden789 EmptyPropertyKey,790791 /// Tried to access an external collection with an internal API792 CollectionIsExternal,793794 /// Tried to access an internal collection with an external API795 CollectionIsInternal,796797 /// This address is not set as sponsor, use setCollectionSponsor first.798 ConfirmSponsorshipFail,799800 /// The user is not an administrator.801 UserIsNotCollectionAdmin,802 }803804 /// Storage of the count of created collections. Essentially contains the last collection ID.805 #[pallet::storage]806 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;807808 /// Storage of the count of deleted collections.809 #[pallet::storage]810 pub type DestroyedCollectionCount<T> =811 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;812813 /// Storage of collection info.814 #[pallet::storage]815 pub type CollectionById<T> = StorageMap<816 Hasher = Blake2_128Concat,817 Key = CollectionId,818 Value = Collection<<T as frame_system::Config>::AccountId>,819 QueryKind = OptionQuery,820 >;821822 /// Storage of collection properties.823 #[pallet::storage]824 #[pallet::getter(fn collection_properties)]825 pub type CollectionProperties<T> = StorageMap<826 Hasher = Blake2_128Concat,827 Key = CollectionId,828 Value = CollectionPropertiesT,829 QueryKind = ValueQuery,830 >;831832 /// Storage of token property permissions of a collection.833 #[pallet::storage]834 #[pallet::getter(fn property_permissions)]835 pub type CollectionPropertyPermissions<T> = StorageMap<836 Hasher = Blake2_128Concat,837 Key = CollectionId,838 Value = PropertiesPermissionMap,839 QueryKind = ValueQuery,840 >;841842 /// Storage of the amount of collection admins.843 #[pallet::storage]844 pub type AdminAmount<T> = StorageMap<845 Hasher = Blake2_128Concat,846 Key = CollectionId,847 Value = u32,848 QueryKind = ValueQuery,849 >;850851 /// List of collection admins.852 #[pallet::storage]853 pub type IsAdmin<T: Config> = StorageNMap<854 Key = (855 Key<Blake2_128Concat, CollectionId>,856 Key<Blake2_128Concat, T::CrossAccountId>,857 ),858 Value = bool,859 QueryKind = ValueQuery,860 >;861862 /// Allowlisted collection users.863 #[pallet::storage]864 pub type Allowlist<T: Config> = StorageNMap<865 Key = (866 Key<Blake2_128Concat, CollectionId>,867 Key<Blake2_128Concat, T::CrossAccountId>,868 ),869 Value = bool,870 QueryKind = ValueQuery,871 >;872873 /// Not used by code, exists only to provide some types to metadata.874 #[pallet::storage]875 pub type DummyStorageValue<T: Config> = StorageValue<876 Value = (877 CollectionStats,878 CollectionId,879 TokenId,880 TokenChild,881 PhantomType<(882 TokenData<T::CrossAccountId>,883 RpcCollection<T::AccountId>,884 // PoV Estimate Info885 PovInfo,886 )>,887 ),888 QueryKind = OptionQuery,889 >;890}891892impl<T: Config> Pallet<T> {893 /// Enshure that receiver address is correct.894 ///895 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.896 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {897 ensure!(898 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,899 <Error<T>>::AddressIsZero900 );901 Ok(())902 }903904 /// Get a vector of collection admins.905 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {906 <IsAdmin<T>>::iter_prefix((collection,))907 .map(|(a, _)| a)908 .collect()909 }910911 /// Get a vector of users allowed to mint tokens.912 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {913 <Allowlist<T>>::iter_prefix((collection,))914 .map(|(a, _)| a)915 .collect()916 }917918 /// Is `user` allowed to mint token in `collection`.919 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {920 <Allowlist<T>>::get((collection, user))921 }922923 /// Get statistics of collections.924 pub fn collection_stats() -> CollectionStats {925 let created = <CreatedCollectionCount<T>>::get();926 let destroyed = <DestroyedCollectionCount<T>>::get();927 CollectionStats {928 created: created.0,929 destroyed: destroyed.0,930 alive: created.0 - destroyed.0,931 }932 }933934 /// Get the effective limits for the collection.935 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {936 let collection = <CollectionById<T>>::get(collection)?;937 let limits = collection.limits;938 let effective_limits = CollectionLimits {939 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),940 sponsored_data_size: Some(limits.sponsored_data_size()),941 sponsored_data_rate_limit: Some(942 limits943 .sponsored_data_rate_limit944 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),945 ),946 token_limit: Some(limits.token_limit()),947 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(948 match collection.mode {949 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,950 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,951 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,952 },953 )),954 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),955 owner_can_transfer: Some(limits.owner_can_transfer()),956 owner_can_destroy: Some(limits.owner_can_destroy()),957 transfers_enabled: Some(limits.transfers_enabled()),958 };959960 Some(effective_limits)961 }962963 /// Returns information about the `collection` adapted for rpc.964 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {965 let Collection {966 name,967 description,968 owner,969 mode,970 token_prefix,971 sponsorship,972 limits,973 permissions,974 flags,975 } = <CollectionById<T>>::get(collection)?;976977 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)978 .into_iter()979 .map(|(key, permission)| PropertyKeyPermission { key, permission })980 .collect();981982 let properties = <CollectionProperties<T>>::get(collection)983 .into_iter()984 .map(|(key, value)| Property { key, value })985 .collect();986987 let permissions = CollectionPermissions {988 access: Some(permissions.access()),989 mint_mode: Some(permissions.mint_mode()),990 nesting: Some(permissions.nesting().clone()),991 };992993 Some(RpcCollection {994 name: name.into_inner(),995 description: description.into_inner(),996 owner,997 mode,998 token_prefix: token_prefix.into_inner(),999 sponsorship,1000 limits,1001 permissions,1002 token_property_permissions,1003 properties,1004 read_only: flags.external,10051006 flags: RpcCollectionFlags {1007 foreign: flags.foreign,1008 erc721metadata: flags.erc721metadata,1009 },1010 })1011 }1012}10131014macro_rules! limit_default {1015 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1016 $(1017 if let Some($new) = $new.$field {1018 let $old = $old.$field($($arg)?);1019 let _ = $new;1020 let _ = $old;1021 $check1022 } else {1023 $new.$field = $old.$field1024 }1025 )*1026 }};1027}1028macro_rules! limit_default_clone {1029 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1030 $(1031 if let Some($new) = $new.$field.clone() {1032 let $old = $old.$field($($arg)?);1033 let _ = $new;1034 let _ = $old;1035 $check1036 } else {1037 $new.$field = $old.$field.clone()1038 }1039 )*1040 }};1041}10421043impl<T: Config> Pallet<T> {1044 /// Create new collection.1045 ///1046 /// * `owner` - The owner of the collection.1047 /// * `data` - Description of the created collection.1048 /// * `flags` - Extra flags to store.1049 pub fn init_collection(1050 owner: T::CrossAccountId,1051 payer: T::CrossAccountId,1052 data: CreateCollectionData<T::AccountId>,1053 flags: CollectionFlags,1054 ) -> Result<CollectionId, DispatchError> {1055 {1056 ensure!(1057 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1058 Error::<T>::CollectionTokenPrefixLimitExceeded1059 );1060 }10611062 let created_count = <CreatedCollectionCount<T>>::get()1063 .01064 .checked_add(1)1065 .ok_or(ArithmeticError::Overflow)?;1066 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1067 let id = CollectionId(created_count);10681069 // bound Total number of collections1070 ensure!(1071 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1072 <Error<T>>::TotalCollectionsLimitExceeded1073 );10741075 // =========10761077 let collection = Collection {1078 owner: owner.as_sub().clone(),1079 name: data.name,1080 mode: data.mode.clone(),1081 description: data.description,1082 token_prefix: data.token_prefix,1083 sponsorship: data1084 .pending_sponsor1085 .map(SponsorshipState::Unconfirmed)1086 .unwrap_or_default(),1087 limits: data1088 .limits1089 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1090 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1091 permissions: data1092 .permissions1093 .map(|permissions| {1094 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1095 })1096 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1097 flags,1098 };10991100 let mut collection_properties = CollectionPropertiesT::new();1101 collection_properties1102 .try_set_from_iter(data.properties.into_iter())1103 .map_err(<Error<T>>::from)?;11041105 CollectionProperties::<T>::insert(id, collection_properties);11061107 let mut token_props_permissions = PropertiesPermissionMap::new();1108 token_props_permissions1109 .try_set_from_iter(data.token_property_permissions.into_iter())1110 .map_err(<Error<T>>::from)?;11111112 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11131114 // Take a (non-refundable) deposit of collection creation1115 {1116 let mut imbalance =1117 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1118 imbalance.subsume(1119 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1120 &T::TreasuryAccountId::get(),1121 T::CollectionCreationPrice::get(),1122 ),1123 );1124 <T as Config>::Currency::settle(1125 payer.as_sub(),1126 imbalance,1127 WithdrawReasons::TRANSFER,1128 ExistenceRequirement::KeepAlive,1129 )1130 .map_err(|_| Error::<T>::NotSufficientFounds)?;1131 }11321133 <CreatedCollectionCount<T>>::put(created_count);1134 <Pallet<T>>::deposit_event(Event::CollectionCreated(1135 id,1136 data.mode.id(),1137 owner.as_sub().clone(),1138 ));1139 <PalletEvm<T>>::deposit_log(1140 erc::CollectionHelpersEvents::CollectionCreated {1141 owner: *owner.as_eth(),1142 collection_id: eth::collection_id_to_address(id),1143 }1144 .to_log(T::ContractAddress::get()),1145 );1146 <CollectionById<T>>::insert(id, collection);1147 Ok(id)1148 }11491150 /// Destroy collection.1151 ///1152 /// * `collection` - Collection handler.1153 /// * `sender` - The owner or administrator of the collection.1154 pub fn destroy_collection(1155 collection: CollectionHandle<T>,1156 sender: &T::CrossAccountId,1157 ) -> DispatchResult {1158 ensure!(1159 collection.limits.owner_can_destroy(),1160 <Error<T>>::NoPermission,1161 );1162 collection.check_is_owner(sender)?;11631164 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1165 .01166 .checked_add(1)1167 .ok_or(ArithmeticError::Overflow)?;11681169 // =========11701171 <DestroyedCollectionCount<T>>::put(destroyed_collections);1172 <CollectionById<T>>::remove(collection.id);1173 <AdminAmount<T>>::remove(collection.id);1174 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1175 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1176 <CollectionProperties<T>>::remove(collection.id);11771178 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11791180 <PalletEvm<T>>::deposit_log(1181 erc::CollectionHelpersEvents::CollectionDestroyed {1182 collection_id: eth::collection_id_to_address(collection.id),1183 }1184 .to_log(T::ContractAddress::get()),1185 );1186 Ok(())1187 }11881189 /// This function sets or removes a collection properties according to1190 /// `properties_updates` contents:1191 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1192 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1193 ///1194 /// This function fires an event for each property change.1195 /// In case of an error, all the changes (including the events) will be reverted1196 /// since the function is transactional.1197 #[transactional]1198 fn modify_collection_properties(1199 collection: &CollectionHandle<T>,1200 sender: &T::CrossAccountId,1201 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1202 ) -> DispatchResult {1203 collection.check_is_owner_or_admin(sender)?;12041205 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12061207 for (key, value) in properties_updates {1208 match value {1209 Some(value) => {1210 stored_properties1211 .try_set(key.clone(), value)1212 .map_err(<Error<T>>::from)?;12131214 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1215 <PalletEvm<T>>::deposit_log(1216 erc::CollectionHelpersEvents::CollectionChanged {1217 collection_id: eth::collection_id_to_address(collection.id),1218 }1219 .to_log(T::ContractAddress::get()),1220 );1221 }1222 None => {1223 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12241225 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1226 <PalletEvm<T>>::deposit_log(1227 erc::CollectionHelpersEvents::CollectionChanged {1228 collection_id: eth::collection_id_to_address(collection.id),1229 }1230 .to_log(T::ContractAddress::get()),1231 );1232 }1233 }1234 }12351236 <CollectionProperties<T>>::set(collection.id, stored_properties);12371238 Ok(())1239 }12401241 /// A batch operation to add, edit or remove properties for a token.1242 /// It sets or removes a token's properties according to1243 /// `properties_updates` contents:1244 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1245 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1246 ///1247 /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.1248 /// - `is_token_create`: Indicates that method is called during token initialization.1249 /// Allows to bypass ownership check.1250 ///1251 /// All affected properties should have `mutable` permission1252 /// to be **deleted** or to be **set more than once**,1253 /// and the sender should have permission to edit those properties.1254 ///1255 /// This function fires an event for each property change.1256 /// In case of an error, all the changes (including the events) will be reverted1257 /// since the function is transactional.1258 pub fn modify_token_properties(1259 collection: &CollectionHandle<T>,1260 sender: &T::CrossAccountId,1261 token_id: TokenId,1262 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1263 is_token_create: bool,1264 mut stored_properties: TokenProperties,1265 is_token_owner: impl Fn() -> Result<bool, DispatchError>,1266 set_token_properties: impl FnOnce(TokenProperties),1267 log: evm_coder::ethereum::Log,1268 ) -> DispatchResult {1269 let is_collection_admin = collection.is_owner_or_admin(sender);1270 let permissions = Self::property_permissions(collection.id);12711272 let mut token_owner_result = None;1273 let mut is_token_owner = || -> Result<bool, DispatchError> {1274 *token_owner_result.get_or_insert_with(&is_token_owner)1275 };12761277 for (key, value) in properties_updates {1278 let permission = permissions1279 .get(&key)1280 .cloned()1281 .unwrap_or_else(PropertyPermission::none);12821283 let is_property_exists = stored_properties.get(&key).is_some();12841285 match permission {1286 PropertyPermission { mutable: false, .. } if is_property_exists => {1287 return Err(<Error<T>>::NoPermission.into());1288 }12891290 PropertyPermission {1291 collection_admin,1292 token_owner,1293 ..1294 } => {1295 //TODO: investigate threats during public minting.1296 let is_token_create =1297 is_token_create && (collection_admin || token_owner) && value.is_some();1298 if !(is_token_create1299 || (collection_admin && is_collection_admin)1300 || (token_owner && is_token_owner()?))1301 {1302 fail!(<Error<T>>::NoPermission);1303 }1304 }1305 }13061307 match value {1308 Some(value) => {1309 stored_properties1310 .try_set(key.clone(), value)1311 .map_err(<Error<T>>::from)?;13121313 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1314 }1315 None => {1316 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13171318 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1319 }1320 }13211322 <PalletEvm<T>>::deposit_log(log.clone());1323 }13241325 set_token_properties(stored_properties);13261327 Ok(())1328 }13291330 /// Sets or unsets the approval of a given operator.1331 ///1332 /// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1333 /// - `owner`: Token owner1334 /// - `operator`: Operator1335 /// - `approve`: Should operator status be granted or revoked?1336 pub fn set_allowance_for_all(1337 collection: &CollectionHandle<T>,1338 owner: &T::CrossAccountId,1339 operator: &T::CrossAccountId,1340 approve: bool,1341 set_allowance: impl FnOnce(),1342 log: evm_coder::ethereum::Log,1343 ) -> DispatchResult {1344 if collection.permissions.access() == AccessMode::AllowList {1345 collection.check_allowlist(owner)?;1346 collection.check_allowlist(operator)?;1347 }13481349 Self::ensure_correct_receiver(operator)?;13501351 set_allowance();13521353 <PalletEvm<T>>::deposit_log(log);1354 Self::deposit_event(Event::ApprovedForAll(1355 collection.id,1356 owner.clone(),1357 operator.clone(),1358 approve,1359 ));1360 Ok(())1361 }13621363 /// Set collection property.1364 ///1365 /// * `collection` - Collection handler.1366 /// * `sender` - The owner or administrator of the collection.1367 /// * `property` - The property to set.1368 pub fn set_collection_property(1369 collection: &CollectionHandle<T>,1370 sender: &T::CrossAccountId,1371 property: Property,1372 ) -> DispatchResult {1373 Self::set_collection_properties(collection, sender, [property].into_iter())1374 }13751376 /// Set a scoped collection property, where the scope is a special prefix1377 /// prohibiting a user access to change the property directly.1378 ///1379 /// * `collection_id` - ID of the collection for which the property is being set.1380 /// * `scope` - Property scope.1381 /// * `property` - The property to set.1382 pub fn set_scoped_collection_property(1383 collection_id: CollectionId,1384 scope: PropertyScope,1385 property: Property,1386 ) -> DispatchResult {1387 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1388 properties.try_scoped_set(scope, property.key, property.value)1389 })1390 .map_err(<Error<T>>::from)?;13911392 Ok(())1393 }13941395 /// Set scoped collection properties, where the scope is a special prefix1396 /// prohibiting a user access to change the properties directly.1397 ///1398 /// * `collection_id` - ID of the collection for which the properties is being set.1399 /// * `scope` - Property scope.1400 /// * `properties` - The properties to set.1401 pub fn set_scoped_collection_properties(1402 collection_id: CollectionId,1403 scope: PropertyScope,1404 properties: impl Iterator<Item = Property>,1405 ) -> DispatchResult {1406 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1407 stored_properties.try_scoped_set_from_iter(scope, properties)1408 })1409 .map_err(<Error<T>>::from)?;14101411 Ok(())1412 }14131414 /// Set collection properties.1415 ///1416 /// * `collection` - Collection handler.1417 /// * `sender` - The owner or administrator of the collection.1418 /// * `properties` - The properties to set.1419 pub fn set_collection_properties(1420 collection: &CollectionHandle<T>,1421 sender: &T::CrossAccountId,1422 properties: impl Iterator<Item = Property>,1423 ) -> DispatchResult {1424 Self::modify_collection_properties(1425 collection,1426 sender,1427 properties.map(|property| (property.key, Some(property.value))),1428 )1429 }14301431 /// Delete collection property.1432 ///1433 /// * `collection` - Collection handler.1434 /// * `sender` - The owner or administrator of the collection.1435 /// * `property` - The property to delete.1436 pub fn delete_collection_property(1437 collection: &CollectionHandle<T>,1438 sender: &T::CrossAccountId,1439 property_key: PropertyKey,1440 ) -> DispatchResult {1441 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1442 }14431444 /// Delete collection properties.1445 ///1446 /// * `collection` - Collection handler.1447 /// * `sender` - The owner or administrator of the collection.1448 /// * `properties` - The properties to delete.1449 pub fn delete_collection_properties(1450 collection: &CollectionHandle<T>,1451 sender: &T::CrossAccountId,1452 property_keys: impl Iterator<Item = PropertyKey>,1453 ) -> DispatchResult {1454 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1455 }14561457 /// Set collection propetry permission without any checks.1458 ///1459 /// Used for migrations.1460 ///1461 /// * `collection` - Collection handler.1462 /// * `property_permissions` - Property permissions.1463 pub fn set_property_permission_unchecked(1464 collection: CollectionId,1465 property_permission: PropertyKeyPermission,1466 ) -> DispatchResult {1467 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1468 permissions.try_set(property_permission.key, property_permission.permission)1469 })1470 .map_err(<Error<T>>::from)?;1471 Ok(())1472 }14731474 /// Set collection property permission.1475 ///1476 /// * `collection` - Collection handler.1477 /// * `sender` - The owner or administrator of the collection.1478 /// * `property_permission` - Property permission.1479 pub fn set_property_permission(1480 collection: &CollectionHandle<T>,1481 sender: &T::CrossAccountId,1482 property_permission: PropertyKeyPermission,1483 ) -> DispatchResult {1484 Self::set_scoped_property_permission(1485 collection,1486 sender,1487 PropertyScope::None,1488 property_permission,1489 )1490 }14911492 /// Set collection property permission with scope.1493 ///1494 /// * `collection` - Collection handler.1495 /// * `sender` - The owner or administrator of the collection.1496 /// * `scope` - Property scope.1497 /// * `property_permission` - Property permission.1498 pub fn set_scoped_property_permission(1499 collection: &CollectionHandle<T>,1500 sender: &T::CrossAccountId,1501 scope: PropertyScope,1502 property_permission: PropertyKeyPermission,1503 ) -> DispatchResult {1504 collection.check_is_owner_or_admin(sender)?;15051506 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1507 let current_permission = all_permissions.get(&property_permission.key);1508 if matches![1509 current_permission,1510 Some(PropertyPermission { mutable: false, .. })1511 ] {1512 return Err(<Error<T>>::NoPermission.into());1513 }15141515 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1516 let property_permission = property_permission.clone();1517 permissions.try_scoped_set(1518 scope,1519 property_permission.key,1520 property_permission.permission,1521 )1522 })1523 .map_err(<Error<T>>::from)?;15241525 Self::deposit_event(Event::PropertyPermissionSet(1526 collection.id,1527 property_permission.key,1528 ));1529 <PalletEvm<T>>::deposit_log(1530 erc::CollectionHelpersEvents::CollectionChanged {1531 collection_id: eth::collection_id_to_address(collection.id),1532 }1533 .to_log(T::ContractAddress::get()),1534 );15351536 Ok(())1537 }15381539 /// Set token property permission.1540 ///1541 /// * `collection` - Collection handler.1542 /// * `sender` - The owner or administrator of the collection.1543 /// * `property_permissions` - Property permissions.1544 #[transactional]1545 pub fn set_token_property_permissions(1546 collection: &CollectionHandle<T>,1547 sender: &T::CrossAccountId,1548 property_permissions: Vec<PropertyKeyPermission>,1549 ) -> DispatchResult {1550 Self::set_scoped_token_property_permissions(1551 collection,1552 sender,1553 PropertyScope::None,1554 property_permissions,1555 )1556 }15571558 /// Set token property permission with scope.1559 ///1560 /// * `collection` - Collection handler.1561 /// * `sender` - The owner or administrator of the collection.1562 /// * `scope` - Property scope.1563 /// * `property_permissions` - Property permissions.1564 #[transactional]1565 pub fn set_scoped_token_property_permissions(1566 collection: &CollectionHandle<T>,1567 sender: &T::CrossAccountId,1568 scope: PropertyScope,1569 property_permissions: Vec<PropertyKeyPermission>,1570 ) -> DispatchResult {1571 for prop_pemission in property_permissions {1572 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1573 }15741575 Ok(())1576 }15771578 /// Get collection property.1579 pub fn get_collection_property(1580 collection_id: CollectionId,1581 key: &PropertyKey,1582 ) -> Option<PropertyValue> {1583 Self::collection_properties(collection_id).get(key).cloned()1584 }15851586 /// Convert byte vector to property key vector.1587 pub fn bytes_keys_to_property_keys(1588 keys: Vec<Vec<u8>>,1589 ) -> Result<Vec<PropertyKey>, DispatchError> {1590 keys.into_iter()1591 .map(|key| -> Result<PropertyKey, DispatchError> {1592 key.try_into()1593 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1594 })1595 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1596 }15971598 /// Get properties according to given keys.1599 pub fn filter_collection_properties(1600 collection_id: CollectionId,1601 keys: Option<Vec<PropertyKey>>,1602 ) -> Result<Vec<Property>, DispatchError> {1603 let properties = Self::collection_properties(collection_id);16041605 let properties = keys1606 .map(|keys| {1607 keys.into_iter()1608 .filter_map(|key| {1609 properties.get(&key).map(|value| Property {1610 key,1611 value: value.clone(),1612 })1613 })1614 .collect()1615 })1616 .unwrap_or_else(|| {1617 properties1618 .into_iter()1619 .map(|(key, value)| Property { key, value })1620 .collect()1621 });16221623 Ok(properties)1624 }16251626 /// Get property permissions according to given keys.1627 pub fn filter_property_permissions(1628 collection_id: CollectionId,1629 keys: Option<Vec<PropertyKey>>,1630 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1631 let permissions = Self::property_permissions(collection_id);16321633 let key_permissions = keys1634 .map(|keys| {1635 keys.into_iter()1636 .filter_map(|key| {1637 permissions1638 .get(&key)1639 .map(|permission| PropertyKeyPermission {1640 key,1641 permission: permission.clone(),1642 })1643 })1644 .collect()1645 })1646 .unwrap_or_else(|| {1647 permissions1648 .into_iter()1649 .map(|(key, permission)| PropertyKeyPermission { key, permission })1650 .collect()1651 });16521653 Ok(key_permissions)1654 }16551656 /// Toggle `user` participation in the `collection`'s allow list.1657 /// #### Store read/writes1658 /// 1 writes1659 pub fn toggle_allowlist(1660 collection: &CollectionHandle<T>,1661 sender: &T::CrossAccountId,1662 user: &T::CrossAccountId,1663 allowed: bool,1664 ) -> DispatchResult {1665 collection.check_is_owner_or_admin(sender)?;16661667 // =========16681669 if allowed {1670 <Allowlist<T>>::insert((collection.id, user), true);1671 Self::deposit_event(Event::<T>::AllowListAddressAdded(1672 collection.id,1673 user.clone(),1674 ));1675 } else {1676 <Allowlist<T>>::remove((collection.id, user));1677 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1678 collection.id,1679 user.clone(),1680 ));1681 }16821683 <PalletEvm<T>>::deposit_log(1684 erc::CollectionHelpersEvents::CollectionChanged {1685 collection_id: eth::collection_id_to_address(collection.id),1686 }1687 .to_log(T::ContractAddress::get()),1688 );16891690 Ok(())1691 }16921693 /// Toggle `user` participation in the `collection`'s admin list.1694 /// #### Store read/writes1695 /// 2 reads, 2 writes1696 pub fn toggle_admin(1697 collection: &CollectionHandle<T>,1698 sender: &T::CrossAccountId,1699 user: &T::CrossAccountId,1700 admin: bool,1701 ) -> DispatchResult {1702 collection.check_is_internal()?;1703 collection.check_is_owner(sender)?;17041705 let is_admin = <IsAdmin<T>>::get((collection.id, user));1706 if is_admin == admin {1707 if admin {1708 return Ok(());1709 } else {1710 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1711 }1712 }1713 let amount = <AdminAmount<T>>::get(collection.id);17141715 // =========17161717 if admin {1718 let amount = amount1719 .checked_add(1)1720 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1721 ensure!(1722 amount <= Self::collection_admins_limit(),1723 <Error<T>>::CollectionAdminCountExceeded,1724 );17251726 <AdminAmount<T>>::insert(collection.id, amount);1727 <IsAdmin<T>>::insert((collection.id, user), true);17281729 Self::deposit_event(Event::<T>::CollectionAdminAdded(1730 collection.id,1731 user.clone(),1732 ));1733 } else {1734 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1735 <IsAdmin<T>>::remove((collection.id, user));17361737 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1738 collection.id,1739 user.clone(),1740 ));1741 }17421743 <PalletEvm<T>>::deposit_log(1744 erc::CollectionHelpersEvents::CollectionChanged {1745 collection_id: eth::collection_id_to_address(collection.id),1746 }1747 .to_log(T::ContractAddress::get()),1748 );17491750 Ok(())1751 }17521753 /// Update collection limits.1754 pub fn update_limits(1755 user: &T::CrossAccountId,1756 collection: &mut CollectionHandle<T>,1757 new_limit: CollectionLimits,1758 ) -> DispatchResult {1759 collection.check_is_internal()?;1760 collection.check_is_owner_or_admin(user)?;17611762 collection.limits =1763 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17641765 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1766 <PalletEvm<T>>::deposit_log(1767 erc::CollectionHelpersEvents::CollectionChanged {1768 collection_id: eth::collection_id_to_address(collection.id),1769 }1770 .to_log(T::ContractAddress::get()),1771 );17721773 collection.save()1774 }17751776 /// Merge set fields from `new_limit` to `old_limit`.1777 fn clamp_limits(1778 mode: CollectionMode,1779 old_limit: &CollectionLimits,1780 mut new_limit: CollectionLimits,1781 ) -> Result<CollectionLimits, DispatchError> {1782 let limits = old_limit;1783 limit_default!(old_limit, new_limit,1784 account_token_ownership_limit => ensure!(1785 new_limit <= MAX_TOKEN_OWNERSHIP,1786 <Error<T>>::CollectionLimitBoundsExceeded,1787 ),1788 sponsored_data_size => ensure!(1789 new_limit <= CUSTOM_DATA_LIMIT,1790 <Error<T>>::CollectionLimitBoundsExceeded,1791 ),17921793 sponsored_data_rate_limit => {},1794 token_limit => ensure!(1795 old_limit >= new_limit && new_limit > 0,1796 <Error<T>>::CollectionTokenLimitExceeded1797 ),17981799 sponsor_transfer_timeout(match mode {1800 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1801 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1802 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1803 }) => ensure!(1804 new_limit <= MAX_SPONSOR_TIMEOUT,1805 <Error<T>>::CollectionLimitBoundsExceeded,1806 ),1807 sponsor_approve_timeout => {},1808 owner_can_transfer => ensure!(1809 !limits.owner_can_transfer_instaled() ||1810 old_limit || !new_limit,1811 <Error<T>>::OwnerPermissionsCantBeReverted,1812 ),1813 owner_can_destroy => ensure!(1814 old_limit || !new_limit,1815 <Error<T>>::OwnerPermissionsCantBeReverted,1816 ),1817 transfers_enabled => {},1818 );1819 Ok(new_limit)1820 }18211822 /// Update collection permissions.1823 pub fn update_permissions(1824 user: &T::CrossAccountId,1825 collection: &mut CollectionHandle<T>,1826 new_permission: CollectionPermissions,1827 ) -> DispatchResult {1828 collection.check_is_internal()?;1829 collection.check_is_owner_or_admin(user)?;1830 collection.permissions = Self::clamp_permissions(1831 collection.mode.clone(),1832 &collection.permissions,1833 new_permission,1834 )?;18351836 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1837 <PalletEvm<T>>::deposit_log(1838 erc::CollectionHelpersEvents::CollectionChanged {1839 collection_id: eth::collection_id_to_address(collection.id),1840 }1841 .to_log(T::ContractAddress::get()),1842 );18431844 collection.save()1845 }18461847 /// Merge set fields from `new_permission` to `old_permission`.1848 fn clamp_permissions(1849 _mode: CollectionMode,1850 old_permission: &CollectionPermissions,1851 mut new_permission: CollectionPermissions,1852 ) -> Result<CollectionPermissions, DispatchError> {1853 limit_default_clone!(old_permission, new_permission,1854 access => {},1855 mint_mode => {},1856 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1857 );1858 Ok(new_permission)1859 }18601861 /// Repair possibly broken properties of a collection.1862 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1863 CollectionProperties::<T>::mutate(collection_id, |properties| {1864 properties.recompute_consumed_space();1865 });18661867 Ok(())1868 }1869}18701871/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1872#[macro_export]1873macro_rules! unsupported {1874 ($runtime:path) => {1875 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1876 };1877}18781879/// Return weights for various worst-case operations.1880pub trait CommonWeightInfo<CrossAccountId> {1881 /// Weight of item creation.1882 fn create_item(data: &CreateItemData) -> Weight {1883 Self::create_multiple_items(from_ref(data))1884 }18851886 /// Weight of items creation.1887 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18881889 /// Weight of items creation.1890 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18911892 /// The weight of the burning item.1893 fn burn_item() -> Weight;18941895 /// Property setting weight.1896 ///1897 /// * `amount`- The number of properties to set.1898 fn set_collection_properties(amount: u32) -> Weight;18991900 /// Collection property deletion weight.1901 ///1902 /// * `amount`- The number of properties to set.1903 fn delete_collection_properties(amount: u32) -> Weight;19041905 /// Token property setting weight.1906 ///1907 /// * `amount`- The number of properties to set.1908 fn set_token_properties(amount: u32) -> Weight;19091910 /// Token property deletion weight.1911 ///1912 /// * `amount`- The number of properties to delete.1913 fn delete_token_properties(amount: u32) -> Weight;19141915 /// Token property permissions set weight.1916 ///1917 /// * `amount`- The number of property permissions to set.1918 fn set_token_property_permissions(amount: u32) -> Weight;19191920 /// Transfer price of the token or its parts.1921 fn transfer() -> Weight;19221923 /// The price of setting the permission of the operation from another user.1924 fn approve() -> Weight;19251926 /// The price of setting the permission of the operation from another user for eth mirror.1927 fn approve_from() -> Weight;19281929 /// Transfer price from another user.1930 fn transfer_from() -> Weight;19311932 /// The price of burning a token from another user.1933 fn burn_from() -> Weight;19341935 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1936 /// whole users's balance.1937 ///1938 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1939 fn burn_recursively_self_raw() -> Weight;19401941 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1942 ///1943 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1944 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19451946 /// The price of recursive burning a token.1947 ///1948 /// `max_selfs` - The maximum burning weight of the token itself.1949 /// `max_breadth` - The maximum number of nested tokens to burn.1950 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1951 Self::burn_recursively_self_raw()1952 .saturating_mul(max_selfs.max(1) as u64)1953 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1954 }19551956 /// The price of retrieving token owner1957 fn token_owner() -> Weight;19581959 /// The price of setting approval for all1960 fn set_allowance_for_all() -> Weight;19611962 /// The price of repairing an item.1963 fn force_repair_item() -> Weight;1964}19651966/// Weight info extension trait for refungible pallet.1967pub trait RefungibleExtensionsWeightInfo {1968 /// Weight of token repartition.1969 fn repartition() -> Weight;1970}19711972/// Common collection operations.1973///1974/// It wraps methods in Fungible, Nonfungible and Refungible pallets1975/// and adds weight info.1976pub trait CommonCollectionOperations<T: Config> {1977 /// Create token.1978 ///1979 /// * `sender` - The user who mint the token and pays for the transaction.1980 /// * `to` - The user who will own the token.1981 /// * `data` - Token data.1982 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1983 fn create_item(1984 &self,1985 sender: T::CrossAccountId,1986 to: T::CrossAccountId,1987 data: CreateItemData,1988 nesting_budget: &dyn Budget,1989 ) -> DispatchResultWithPostInfo;19901991 /// Create multiple tokens.1992 ///1993 /// * `sender` - The user who mint the token and pays for the transaction.1994 /// * `to` - The user who will own the token.1995 /// * `data` - Token data.1996 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1997 fn create_multiple_items(1998 &self,1999 sender: T::CrossAccountId,2000 to: T::CrossAccountId,2001 data: Vec<CreateItemData>,2002 nesting_budget: &dyn Budget,2003 ) -> DispatchResultWithPostInfo;20042005 /// Create multiple tokens.2006 ///2007 /// * `sender` - The user who mint the token and pays for the transaction.2008 /// * `to` - The user who will own the token.2009 /// * `data` - Token data.2010 /// * `nesting_budget` - A budget that can be spent on nesting tokens.2011 fn create_multiple_items_ex(2012 &self,2013 sender: T::CrossAccountId,2014 data: CreateItemExData<T::CrossAccountId>,2015 nesting_budget: &dyn Budget,2016 ) -> DispatchResultWithPostInfo;20172018 /// Burn token.2019 ///2020 /// * `sender` - The user who owns the token.2021 /// * `token` - Token id that will burned.2022 /// * `amount` - The number of parts of the token that will be burned.2023 fn burn_item(2024 &self,2025 sender: T::CrossAccountId,2026 token: TokenId,2027 amount: u128,2028 ) -> DispatchResultWithPostInfo;20292030 /// Burn token and all nested tokens recursievly.2031 ///2032 /// * `sender` - The user who owns the token.2033 /// * `token` - Token id that will burned.2034 /// * `self_budget` - The budget that can be spent on burning tokens.2035 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.2036 fn burn_item_recursively(2037 &self,2038 sender: T::CrossAccountId,2039 token: TokenId,2040 self_budget: &dyn Budget,2041 breadth_budget: &dyn Budget,2042 ) -> DispatchResultWithPostInfo;20432044 /// Set collection properties.2045 ///2046 /// * `sender` - Must be either the owner of the collection or its admin.2047 /// * `properties` - Properties to be set.2048 fn set_collection_properties(2049 &self,2050 sender: T::CrossAccountId,2051 properties: Vec<Property>,2052 ) -> DispatchResultWithPostInfo;20532054 /// Delete collection properties.2055 ///2056 /// * `sender` - Must be either the owner of the collection or its admin.2057 /// * `properties` - The properties to be removed.2058 fn delete_collection_properties(2059 &self,2060 sender: &T::CrossAccountId,2061 property_keys: Vec<PropertyKey>,2062 ) -> DispatchResultWithPostInfo;20632064 /// Set token properties.2065 ///2066 /// The appropriate [`PropertyPermission`] for the token property2067 /// must be set with [`Self::set_token_property_permissions`].2068 ///2069 /// * `sender` - Must be either the owner of the token or its admin.2070 /// * `token_id` - The token for which the properties are being set.2071 /// * `properties` - Properties to be set.2072 /// * `budget` - Budget for setting properties.2073 fn set_token_properties(2074 &self,2075 sender: T::CrossAccountId,2076 token_id: TokenId,2077 properties: Vec<Property>,2078 budget: &dyn Budget,2079 ) -> DispatchResultWithPostInfo;20802081 /// Remove token properties.2082 ///2083 /// The appropriate [`PropertyPermission`] for the token property2084 /// must be set with [`Self::set_token_property_permissions`].2085 ///2086 /// * `sender` - Must be either the owner of the token or its admin.2087 /// * `token_id` - The token for which the properties are being remove.2088 /// * `property_keys` - Keys to remove corresponding properties.2089 /// * `budget` - Budget for removing properties.2090 fn delete_token_properties(2091 &self,2092 sender: T::CrossAccountId,2093 token_id: TokenId,2094 property_keys: Vec<PropertyKey>,2095 budget: &dyn Budget,2096 ) -> DispatchResultWithPostInfo;20972098 /// Set token property permissions.2099 ///2100 /// * `sender` - Must be either the owner of the token or its admin.2101 /// * `token_id` - The token for which the properties are being set.2102 /// * `property_permissions` - Property permissions to be set.2103 /// * `budget` - Budget for setting properties.2104 fn set_token_property_permissions(2105 &self,2106 sender: &T::CrossAccountId,2107 property_permissions: Vec<PropertyKeyPermission>,2108 ) -> DispatchResultWithPostInfo;21092110 /// Transfer amount of token pieces.2111 ///2112 /// * `sender` - Donor user.2113 /// * `to` - Recepient user.2114 /// * `token` - The token of which parts are being sent.2115 /// * `amount` - The number of parts of the token that will be transferred.2116 /// * `budget` - The maximum budget that can be spent on the transfer.2117 fn transfer(2118 &self,2119 sender: T::CrossAccountId,2120 to: T::CrossAccountId,2121 token: TokenId,2122 amount: u128,2123 budget: &dyn Budget,2124 ) -> DispatchResultWithPostInfo;21252126 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2127 ///2128 /// * `sender` - The user who grants access to the token.2129 /// * `spender` - The user to whom the rights are granted.2130 /// * `token` - The token to which access is granted.2131 /// * `amount` - The amount of pieces that another user can dispose of.2132 fn approve(2133 &self,2134 sender: T::CrossAccountId,2135 spender: T::CrossAccountId,2136 token: TokenId,2137 amount: u128,2138 ) -> DispatchResultWithPostInfo;21392140 /// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2141 ///2142 /// * `sender` - The user who grants access to the token.2143 /// * `from` - Spender's eth mirror.2144 /// * `to` - The user to whom the rights are granted.2145 /// * `token` - The token to which access is granted.2146 /// * `amount` - The amount of pieces that another user can dispose of.2147 fn approve_from(2148 &self,2149 sender: T::CrossAccountId,2150 from: T::CrossAccountId,2151 to: T::CrossAccountId,2152 token: TokenId,2153 amount: u128,2154 ) -> DispatchResultWithPostInfo;21552156 /// Send parts of a token owned by another user.2157 ///2158 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2159 ///2160 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2161 /// * `from` - The user who owns the token.2162 /// * `to` - Recepient user.2163 /// * `token` - The token of which parts are being sent.2164 /// * `amount` - The number of parts of the token that will be transferred.2165 /// * `budget` - The maximum budget that can be spent on the transfer.2166 fn transfer_from(2167 &self,2168 sender: T::CrossAccountId,2169 from: T::CrossAccountId,2170 to: T::CrossAccountId,2171 token: TokenId,2172 amount: u128,2173 budget: &dyn Budget,2174 ) -> DispatchResultWithPostInfo;21752176 /// Burn parts of a token owned by another user.2177 ///2178 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2179 ///2180 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2181 /// * `from` - The user who owns the token.2182 /// * `token` - The token of which parts are being sent.2183 /// * `amount` - The number of parts of the token that will be transferred.2184 /// * `budget` - The maximum budget that can be spent on the burn.2185 fn burn_from(2186 &self,2187 sender: T::CrossAccountId,2188 from: T::CrossAccountId,2189 token: TokenId,2190 amount: u128,2191 budget: &dyn Budget,2192 ) -> DispatchResultWithPostInfo;21932194 /// Check permission to nest token.2195 ///2196 /// * `sender` - The user who initiated the check.2197 /// * `from` - The token that is checked for embedding.2198 /// * `under` - Token under which to check.2199 /// * `budget` - The maximum budget that can be spent on the check.2200 fn check_nesting(2201 &self,2202 sender: T::CrossAccountId,2203 from: (CollectionId, TokenId),2204 under: TokenId,2205 budget: &dyn Budget,2206 ) -> DispatchResult;22072208 /// Nest one token into another.2209 ///2210 /// * `under` - Token holder.2211 /// * `to_nest` - Nested token.2212 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22132214 /// Unnest token.2215 ///2216 /// * `under` - Token holder.2217 /// * `to_nest` - Token to unnest.2218 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22192220 /// Get all user tokens.2221 ///2222 /// * `account` - Account for which you need to get tokens.2223 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22242225 /// Get all the tokens in the collection.2226 fn collection_tokens(&self) -> Vec<TokenId>;22272228 /// Check if the token exists.2229 ///2230 /// * `token` - Id token to check.2231 fn token_exists(&self, token: TokenId) -> bool;22322233 /// Get the id of the last minted token.2234 fn last_token_id(&self) -> TokenId;22352236 /// Get the owner of the token.2237 ///2238 /// * `token` - The token for which you need to find out the owner.2239 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22402241 /// Returns 10 tokens owners in no particular order.2242 ///2243 /// * `token` - The token for which you need to find out the owners.2244 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22452246 /// Get the value of the token property by key.2247 ///2248 /// * `token` - Token with the property to get.2249 /// * `key` - Property name.2250 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22512252 /// Get a set of token properties by key vector.2253 ///2254 /// * `token` - Token with the property to get.2255 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2256 /// then all properties are returned.2257 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22582259 /// Amount of unique collection tokens2260 fn total_supply(&self) -> u32;22612262 /// Amount of different tokens account has.2263 ///2264 /// * `account` - The account for which need to get the balance.2265 fn account_balance(&self, account: T::CrossAccountId) -> u32;22662267 /// Amount of specific token account have.2268 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22692270 /// Amount of token pieces2271 fn total_pieces(&self, token: TokenId) -> Option<u128>;22722273 /// Get the number of parts of the token that a trusted user can manage.2274 ///2275 /// * `sender` - Trusted user.2276 /// * `spender` - Owner of the token.2277 /// * `token` - The token for which to get the value.2278 fn allowance(2279 &self,2280 sender: T::CrossAccountId,2281 spender: T::CrossAccountId,2282 token: TokenId,2283 ) -> u128;22842285 /// Get extension for RFT collection.2286 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22872288 /// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2289 /// * `owner` - Token owner2290 /// * `operator` - Operator2291 /// * `approve` - Should operator status be granted or revoked?2292 fn set_allowance_for_all(2293 &self,2294 owner: T::CrossAccountId,2295 operator: T::CrossAccountId,2296 approve: bool,2297 ) -> DispatchResultWithPostInfo;22982299 /// Tells whether the given `owner` approves the `operator`.2300 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23012302 /// Repairs a possibly broken item.2303 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2304}23052306/// Extension for RFT collection.2307pub trait RefungibleExtensions<T>2308where2309 T: Config,2310{2311 /// Change the number of parts of the token.2312 ///2313 /// When the value changes down, this function is equivalent to burning parts of the token.2314 ///2315 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.2316 /// * `token` - The token for which you want to change the number of parts.2317 /// * `amount` - The new value of the parts of the token.2318 fn repartition(2319 &self,2320 sender: &T::CrossAccountId,2321 token: TokenId,2322 amount: u128,2323 ) -> DispatchResultWithPostInfo;2324}23252326/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2327///2328/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2329pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2330 let post_info = PostDispatchInfo {2331 actual_weight: Some(weight),2332 pays_fee: Pays::Yes,2333 };2334 match res {2335 Ok(()) => Ok(post_info),2336 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2337 }2338}23392340impl<T: Config> From<PropertiesError> for Error<T> {2341 fn from(error: PropertiesError) -> Self {2342 match error {2343 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2344 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2345 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2346 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2347 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2348 }2349 }2350}pallets/nonfungible/src/lib.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -281,6 +281,23 @@
Value = bool,
QueryKind = ValueQuery,
>;
+
+ #[pallet::genesis_config]
+ pub struct GenesisConfig<T>(PhantomData<T>);
+
+ #[cfg(feature = "std")]
+ impl<T: Config> Default for GenesisConfig<T> {
+ fn default() -> Self {
+ Self(Default::default())
+ }
+ }
+
+ #[pallet::genesis_build]
+ impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
+ fn build(&self) {
+ StorageVersion::new(1).put::<Pallet<T>>();
+ }
+ }
}
pub struct NonfungibleHandle<T: Config>(pallet_common::CollectionHandle<T>);