difftreelog
fix find_parent
in: master
14 files changed
pallets/common/src/erc.rsdiffbeforeafterboth--- a/pallets/common/src/erc.rs
+++ b/pallets/common/src/erc.rs
@@ -28,8 +28,8 @@
use sp_std::{vec, vec::Vec};
use sp_core::U256;
use up_data_structs::{
- AccessMode, CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property,
- SponsoringRateLimit, SponsorshipState,
+ CollectionMode, CollectionPermissions, OwnerRestrictedSet, Property, SponsoringRateLimit,
+ SponsorshipState,
};
use crate::{
pallets/common/src/eth.rsdiffbeforeafterboth--- a/pallets/common/src/eth.rs
+++ b/pallets/common/src/eth.rs
@@ -80,10 +80,7 @@
if cross_account_id.is_canonical_substrate() {
Self::from_sub::<T>(cross_account_id.as_sub())
} else {
- Self {
- eth: *cross_account_id.as_eth(),
- sub: Default::default(),
- }
+ Self::from_eth(*cross_account_id.as_eth())
}
}
/// Creates [`CrossAddress`] from Substrate account.
@@ -97,6 +94,13 @@
sub: U256::from_big_endian(account_id.as_ref()),
}
}
+ /// Creates [`CrossAddress`] from Ethereum account.
+ pub fn from_eth(address: Address) -> Self {
+ Self {
+ eth: address,
+ sub: Default::default(),
+ }
+ }
/// Converts [`CrossAddress`] to `CrossAccountId`.
pub fn into_sub_cross_account<T>(&self) -> evm_coder::execution::Result<T::CrossAccountId>
where
pallets/common/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63 ensure,64 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65 dispatch::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 RpcCollectionFlags,75 CollectionId,76 CreateItemData,77 MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT,79 TokenId,80 TokenChild,81 CollectionStats,82 MAX_TOKEN_OWNERSHIP,83 CollectionMode,84 NFT_SPONSOR_TRANSFER_TIMEOUT,85 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87 MAX_SPONSOR_TIMEOUT,88 CUSTOM_DATA_LIMIT,89 CollectionLimits,90 CreateCollectionData,91 SponsorshipState,92 CreateItemExData,93 SponsoringRateLimit,94 budget::Budget,95 PhantomType,96 Property,97 Properties,98 PropertiesPermissionMap,99 PropertyKey,100 PropertyValue,101 PropertyPermission,102 PropertiesError,103 PropertyKeyPermission,104 TokenData,105 TrySetProperty,106 PropertyScope,107 // RMRK108 RmrkCollectionInfo,109 RmrkInstanceInfo,110 RmrkResourceInfo,111 RmrkPropertyInfo,112 RmrkBaseInfo,113 RmrkPartType,114 RmrkBoundedTheme,115 RmrkNftChild,116 CollectionPermissions,117};118use up_pov_estimate_rpc::PovInfo;119120pub use pallet::*;121use sp_core::H160;122use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};123#[cfg(feature = "runtime-benchmarks")]124pub mod benchmarking;125pub mod dispatch;126pub mod erc;127pub mod eth;128pub mod weights;129130/// Weight info.131pub type SelfWeightOf<T> = <T as Config>::WeightInfo;132133/// Collection handle contains information about collection data and id.134/// Also provides functionality to count consumed gas.135///136/// CollectionHandle is used as a generic wrapper for collections of all types.137/// It allows to perform common operations and queries on any collection type,138/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].139#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]140pub struct CollectionHandle<T: Config> {141 /// Collection id142 pub id: CollectionId,143 collection: Collection<T::AccountId>,144 /// Substrate recorder for counting consumed gas145 pub recorder: SubstrateRecorder<T>,146}147148impl<T: Config> WithRecorder<T> for CollectionHandle<T> {149 fn recorder(&self) -> &SubstrateRecorder<T> {150 &self.recorder151 }152 fn into_recorder(self) -> SubstrateRecorder<T> {153 self.recorder154 }155}156157impl<T: Config> CollectionHandle<T> {158 /// Same as [CollectionHandle::new] but with an explicit gas limit.159 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {160 <CollectionById<T>>::get(id).map(|collection| Self {161 id,162 collection,163 recorder: SubstrateRecorder::new(gas_limit),164 })165 }166167 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].168 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {169 <CollectionById<T>>::get(id).map(|collection| Self {170 id,171 collection,172 recorder,173 })174 }175176 /// Retrives collection data from storage and creates collection handle with default parameters.177 /// If collection not found return `None`178 pub fn new(id: CollectionId) -> Option<Self> {179 Self::new_with_gas_limit(id, u64::MAX)180 }181182 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.183 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {184 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)185 }186187 /// Consume gas for reading.188 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {189 self.recorder190 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(191 <T as frame_system::Config>::DbWeight::get()192 .read193 .saturating_mul(reads),194 )))195 }196197 /// Consume gas for writing.198 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {199 self.recorder200 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(201 <T as frame_system::Config>::DbWeight::get()202 .write203 .saturating_mul(writes),204 )))205 }206207 /// Consume gas for reading and writing.208 pub fn consume_store_reads_and_writes(209 &self,210 reads: u64,211 writes: u64,212 ) -> evm_coder::execution::Result<()> {213 let weight = <T as frame_system::Config>::DbWeight::get();214 let reads = weight.read.saturating_mul(reads);215 let writes = weight.read.saturating_mul(writes);216 self.recorder217 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(218 reads.saturating_add(writes),219 )))220 }221222 /// Save collection to storage.223 pub fn save(&self) -> DispatchResult {224 <CollectionById<T>>::insert(self.id, &self.collection);225 Ok(())226 }227228 /// Set collection sponsor.229 ///230 /// Unique collections allows sponsoring for certain actions.231 /// This method allows you to set the sponsor of the collection.232 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].233 pub fn set_sponsor(234 &mut self,235 sender: &T::CrossAccountId,236 sponsor: T::AccountId,237 ) -> DispatchResult {238 self.check_is_internal()?;239 self.check_is_owner_or_admin(sender)?;240241 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());242243 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));244 <PalletEvm<T>>::deposit_log(245 erc::CollectionHelpersEvents::CollectionChanged {246 collection_id: eth::collection_id_to_address(self.id),247 }248 .to_log(T::ContractAddress::get()),249 );250251 self.save()252 }253254 /// Force set `sponsor`.255 ///256 /// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation257 /// from the `sponsor` is not required.258 ///259 /// # Arguments260 ///261 /// * `sender`: Caller's account.262 /// * `sponsor`: ID of the account of the sponsor-to-be.263 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {264 self.check_is_internal()?;265266 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());267268 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));269 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));270 <PalletEvm<T>>::deposit_log(271 erc::CollectionHelpersEvents::CollectionChanged {272 collection_id: eth::collection_id_to_address(self.id),273 }274 .to_log(T::ContractAddress::get()),275 );276277 self.save()278 }279280 /// Confirm sponsorship281 ///282 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.283 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].284 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {285 self.check_is_internal()?;286 ensure!(287 self.collection.sponsorship.pending_sponsor() == Some(sender),288 Error::<T>::ConfirmSponsorshipFail289 );290291 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());292293 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));294 <PalletEvm<T>>::deposit_log(295 erc::CollectionHelpersEvents::CollectionChanged {296 collection_id: eth::collection_id_to_address(self.id),297 }298 .to_log(T::ContractAddress::get()),299 );300301 self.save()302 }303304 /// Remove collection sponsor.305 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {306 self.check_is_internal()?;307 self.check_is_owner_or_admin(sender)?;308309 self.collection.sponsorship = SponsorshipState::Disabled;310311 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));312 <PalletEvm<T>>::deposit_log(313 erc::CollectionHelpersEvents::CollectionChanged {314 collection_id: eth::collection_id_to_address(self.id),315 }316 .to_log(T::ContractAddress::get()),317 );318 self.save()319 }320321 /// Force remove `sponsor`.322 ///323 /// Differs from `remove_sponsor` in that324 /// it doesn't require consent from the `owner` of the collection.325 pub fn force_remove_sponsor(&mut self) -> DispatchResult {326 self.check_is_internal()?;327328 self.collection.sponsorship = SponsorshipState::Disabled;329330 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));331 <PalletEvm<T>>::deposit_log(332 erc::CollectionHelpersEvents::CollectionChanged {333 collection_id: eth::collection_id_to_address(self.id),334 }335 .to_log(T::ContractAddress::get()),336 );337 self.save()338 }339340 /// Checks that the collection was created with, and must be operated upon through **Unique API**.341 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.342 pub fn check_is_internal(&self) -> DispatchResult {343 if self.flags.external {344 return Err(<Error<T>>::CollectionIsExternal)?;345 }346347 Ok(())348 }349350 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.351 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.352 pub fn check_is_external(&self) -> DispatchResult {353 if !self.flags.external {354 return Err(<Error<T>>::CollectionIsInternal)?;355 }356357 Ok(())358 }359}360361impl<T: Config> Deref for CollectionHandle<T> {362 type Target = Collection<T::AccountId>;363364 fn deref(&self) -> &Self::Target {365 &self.collection366 }367}368369impl<T: Config> DerefMut for CollectionHandle<T> {370 fn deref_mut(&mut self) -> &mut Self::Target {371 &mut self.collection372 }373}374375impl<T: Config> CollectionHandle<T> {376 /// Checks if the `user` is the owner of the collection.377 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {378 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);379 Ok(())380 }381382 /// Returns **true** if the `user` is the owner or administrator of the collection.383 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {384 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))385 }386387 /// Checks if the `user` is the owner or administrator of the collection.388 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {389 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);390 Ok(())391 }392393 /// Returns **true** if394 /// * the `user`is a collection owner or admin395 /// * the collection limits allow the owner/admins to transfer/burn any collection token396 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {397 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)398 }399400 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.401 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {402 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)403 }404405 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.406 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {407 ensure!(408 <Allowlist<T>>::get((self.id, user)),409 <Error<T>>::AddressNotInAllowlist410 );411 Ok(())412 }413414 /// Changes collection owner to another account415 /// #### Store read/writes416 /// 1 writes417 pub fn change_owner(418 &mut self,419 caller: T::CrossAccountId,420 new_owner: T::CrossAccountId,421 ) -> DispatchResult {422 self.check_is_internal()?;423 self.check_is_owner(&caller)?;424 self.collection.owner = new_owner.as_sub().clone();425426 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(427 self.id,428 new_owner.as_sub().clone(),429 ));430 <PalletEvm<T>>::deposit_log(431 erc::CollectionHelpersEvents::CollectionChanged {432 collection_id: eth::collection_id_to_address(self.id),433 }434 .to_log(T::ContractAddress::get()),435 );436437 self.save()438 }439}440441#[frame_support::pallet]442pub mod pallet {443 use super::*;444 use dispatch::CollectionDispatch;445 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};446 use frame_system::pallet_prelude::*;447 use frame_support::traits::Currency;448 use up_data_structs::{TokenId, mapping::TokenAddressMapping};449 use scale_info::TypeInfo;450 use weights::WeightInfo;451452 #[pallet::config]453 pub trait Config:454 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo455 {456 /// Weight information for functions of this pallet.457 type WeightInfo: WeightInfo;458459 /// Events compatible with [`frame_system::Config::Event`].460 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;461462 /// Handler of accounts and payment.463 type Currency: Currency<Self::AccountId>;464465 /// Set price to create a collection.466 #[pallet::constant]467 type CollectionCreationPrice: Get<468 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,469 >;470471 /// Dispatcher of operations on collections.472 type CollectionDispatch: CollectionDispatch<Self>;473474 /// Account which holds the chain's treasury.475 type TreasuryAccountId: Get<Self::AccountId>;476477 /// Address under which the CollectionHelper contract would be available.478 #[pallet::constant]479 type ContractAddress: Get<H160>;480481 /// Mapper for token addresses to Ethereum addresses.482 type EvmTokenAddressMapping: TokenAddressMapping<H160>;483484 /// Mapper for token addresses to [`CrossAccountId`].485 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;486 }487488 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);489490 #[pallet::pallet]491 #[pallet::storage_version(STORAGE_VERSION)]492 #[pallet::generate_store(pub(super) trait Store)]493 pub struct Pallet<T>(_);494495 #[pallet::extra_constants]496 impl<T: Config> Pallet<T> {497 /// Maximum admins per collection.498 pub fn collection_admins_limit() -> u32 {499 COLLECTION_ADMINS_LIMIT500 }501 }502503 impl<T: Config> Pallet<T> {504 /// Helper function that handles deposit events505 pub fn deposit_event(event: Event<T>) {506 let event = <T as Config>::RuntimeEvent::from(event);507 let event = event.into();508 <frame_system::Pallet<T>>::deposit_event(event)509 }510 }511512 #[pallet::event]513 pub enum Event<T: Config> {514 /// New collection was created515 CollectionCreated(516 /// Globally unique identifier of newly created collection.517 CollectionId,518 /// [`CollectionMode`] converted into _u8_.519 u8,520 /// Collection owner.521 T::AccountId,522 ),523524 /// New collection was destroyed525 CollectionDestroyed(526 /// Globally unique identifier of collection.527 CollectionId,528 ),529530 /// New item was created.531 ItemCreated(532 /// Id of the collection where item was created.533 CollectionId,534 /// Id of an item. Unique within the collection.535 TokenId,536 /// Owner of newly created item537 T::CrossAccountId,538 /// Always 1 for NFT539 u128,540 ),541542 /// Collection item was burned.543 ItemDestroyed(544 /// Id of the collection where item was destroyed.545 CollectionId,546 /// Identifier of burned NFT.547 TokenId,548 /// Which user has destroyed its tokens.549 T::CrossAccountId,550 /// Amount of token pieces destroed. Always 1 for NFT.551 u128,552 ),553554 /// Item was transferred555 Transfer(556 /// Id of collection to which item is belong.557 CollectionId,558 /// Id of an item.559 TokenId,560 /// Original owner of item.561 T::CrossAccountId,562 /// New owner of item.563 T::CrossAccountId,564 /// Amount of token pieces transfered. Always 1 for NFT.565 u128,566 ),567568 /// Amount pieces of token owned by `sender` was approved for `spender`.569 Approved(570 /// Id of collection to which item is belong.571 CollectionId,572 /// Id of an item.573 TokenId,574 /// Original owner of item.575 T::CrossAccountId,576 /// Id for which the approval was granted.577 T::CrossAccountId,578 /// Amount of token pieces transfered. Always 1 for NFT.579 u128,580 ),581582 /// A `sender` approves operations on all owned tokens for `spender`.583 ApprovedForAll(584 /// Id of collection to which item is belong.585 CollectionId,586 /// Owner of a wallet.587 T::CrossAccountId,588 /// Id for which operator status was granted or rewoked.589 T::CrossAccountId,590 /// Is operator status granted or revoked?591 bool,592 ),593594 /// The colletion property has been added or edited.595 CollectionPropertySet(596 /// Id of collection to which property has been set.597 CollectionId,598 /// The property that was set.599 PropertyKey,600 ),601602 /// The property has been deleted.603 CollectionPropertyDeleted(604 /// Id of collection to which property has been deleted.605 CollectionId,606 /// The property that was deleted.607 PropertyKey,608 ),609610 /// The token property has been added or edited.611 TokenPropertySet(612 /// Identifier of the collection whose token has the property set.613 CollectionId,614 /// The token for which the property was set.615 TokenId,616 /// The property that was set.617 PropertyKey,618 ),619620 /// The token property has been deleted.621 TokenPropertyDeleted(622 /// Identifier of the collection whose token has the property deleted.623 CollectionId,624 /// The token for which the property was deleted.625 TokenId,626 /// The property that was deleted.627 PropertyKey,628 ),629630 /// The token property permission of a collection has been set.631 PropertyPermissionSet(632 /// ID of collection to which property permission has been set.633 CollectionId,634 /// The property permission that was set.635 PropertyKey,636 ),637638 /// Address was added to the allow list.639 AllowListAddressAdded(640 /// ID of the affected collection.641 CollectionId,642 /// Address of the added account.643 T::CrossAccountId,644 ),645646 /// Address was removed from the allow list.647 AllowListAddressRemoved(648 /// ID of the affected collection.649 CollectionId,650 /// Address of the removed account.651 T::CrossAccountId,652 ),653654 /// Collection admin was added.655 CollectionAdminAdded(656 /// ID of the affected collection.657 CollectionId,658 /// Admin address.659 T::CrossAccountId,660 ),661662 /// Collection admin was removed.663 CollectionAdminRemoved(664 /// ID of the affected collection.665 CollectionId,666 /// Removed admin address.667 T::CrossAccountId,668 ),669670 /// Collection limits were set.671 CollectionLimitSet(672 /// ID of the affected collection.673 CollectionId,674 ),675676 /// Collection owned was changed.677 CollectionOwnerChanged(678 /// ID of the affected collection.679 CollectionId,680 /// New owner address.681 T::AccountId,682 ),683684 /// Collection permissions were set.685 CollectionPermissionSet(686 /// ID of the affected collection.687 CollectionId,688 ),689690 /// Collection sponsor was set.691 CollectionSponsorSet(692 /// ID of the affected collection.693 CollectionId,694 /// New sponsor address.695 T::AccountId,696 ),697698 /// New sponsor was confirm.699 SponsorshipConfirmed(700 /// ID of the affected collection.701 CollectionId,702 /// New sponsor address.703 T::AccountId,704 ),705706 /// Collection sponsor was removed.707 CollectionSponsorRemoved(708 /// ID of the affected collection.709 CollectionId,710 ),711 }712713 #[pallet::error]714 pub enum Error<T> {715 /// This collection does not exist.716 CollectionNotFound,717 /// Sender parameter and item owner must be equal.718 MustBeTokenOwner,719 /// No permission to perform action720 NoPermission,721 /// Destroying only empty collections is allowed722 CantDestroyNotEmptyCollection,723 /// Collection is not in mint mode.724 PublicMintingNotAllowed,725 /// Address is not in allow list.726 AddressNotInAllowlist,727728 /// Collection name can not be longer than 63 char.729 CollectionNameLimitExceeded,730 /// Collection description can not be longer than 255 char.731 CollectionDescriptionLimitExceeded,732 /// Token prefix can not be longer than 15 char.733 CollectionTokenPrefixLimitExceeded,734 /// Total collections bound exceeded.735 TotalCollectionsLimitExceeded,736 /// Exceeded max admin count737 CollectionAdminCountExceeded,738 /// Collection limit bounds per collection exceeded739 CollectionLimitBoundsExceeded,740 /// Tried to enable permissions which are only permitted to be disabled741 OwnerPermissionsCantBeReverted,742 /// Collection settings not allowing items transferring743 TransferNotAllowed,744 /// Account token limit exceeded per collection745 AccountTokenLimitExceeded,746 /// Collection token limit exceeded747 CollectionTokenLimitExceeded,748 /// Metadata flag frozen749 MetadataFlagFrozen,750751 /// Item does not exist752 TokenNotFound,753 /// Item is balance not enough754 TokenValueTooLow,755 /// Requested value is more than the approved756 ApprovedValueTooLow,757 /// Tried to approve more than owned758 CantApproveMoreThanOwned,759 /// Only spending from eth mirror could be approved760 AddressIsNotEthMirror,761762 /// Can't transfer tokens to ethereum zero address763 AddressIsZero,764765 /// The operation is not supported766 UnsupportedOperation,767768 /// Insufficient funds to perform an action769 NotSufficientFounds,770771 /// User does not satisfy the nesting rule772 UserIsNotAllowedToNest,773 /// Only tokens from specific collections may nest tokens under this one774 SourceCollectionIsNotAllowedToNest,775776 /// Tried to store more data than allowed in collection field777 CollectionFieldSizeExceeded,778779 /// Tried to store more property data than allowed780 NoSpaceForProperty,781782 /// Tried to store more property keys than allowed783 PropertyLimitReached,784785 /// Property key is too long786 PropertyKeyIsTooLong,787788 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed789 InvalidCharacterInPropertyKey,790791 /// Empty property keys are forbidden792 EmptyPropertyKey,793794 /// Tried to access an external collection with an internal API795 CollectionIsExternal,796797 /// Tried to access an internal collection with an external API798 CollectionIsInternal,799800 /// This address is not set as sponsor, use setCollectionSponsor first.801 ConfirmSponsorshipFail,802803 /// The user is not an administrator.804 UserIsNotCollectionAdmin,805 }806807 /// Storage of the count of created collections. Essentially contains the last collection ID.808 #[pallet::storage]809 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;810811 /// Storage of the count of deleted collections.812 #[pallet::storage]813 pub type DestroyedCollectionCount<T> =814 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;815816 /// Storage of collection info.817 #[pallet::storage]818 pub type CollectionById<T> = StorageMap<819 Hasher = Blake2_128Concat,820 Key = CollectionId,821 Value = Collection<<T as frame_system::Config>::AccountId>,822 QueryKind = OptionQuery,823 >;824825 /// Storage of collection properties.826 #[pallet::storage]827 #[pallet::getter(fn collection_properties)]828 pub type CollectionProperties<T> = StorageMap<829 Hasher = Blake2_128Concat,830 Key = CollectionId,831 Value = Properties,832 QueryKind = ValueQuery,833 OnEmpty = up_data_structs::CollectionProperties,834 >;835836 /// Storage of token property permissions of a collection.837 #[pallet::storage]838 #[pallet::getter(fn property_permissions)]839 pub type CollectionPropertyPermissions<T> = StorageMap<840 Hasher = Blake2_128Concat,841 Key = CollectionId,842 Value = PropertiesPermissionMap,843 QueryKind = ValueQuery,844 >;845846 /// Storage of the amount of collection admins.847 #[pallet::storage]848 pub type AdminAmount<T> = StorageMap<849 Hasher = Blake2_128Concat,850 Key = CollectionId,851 Value = u32,852 QueryKind = ValueQuery,853 >;854855 /// List of collection admins.856 #[pallet::storage]857 pub type IsAdmin<T: Config> = StorageNMap<858 Key = (859 Key<Blake2_128Concat, CollectionId>,860 Key<Blake2_128Concat, T::CrossAccountId>,861 ),862 Value = bool,863 QueryKind = ValueQuery,864 >;865866 /// Allowlisted collection users.867 #[pallet::storage]868 pub type Allowlist<T: Config> = StorageNMap<869 Key = (870 Key<Blake2_128Concat, CollectionId>,871 Key<Blake2_128Concat, T::CrossAccountId>,872 ),873 Value = bool,874 QueryKind = ValueQuery,875 >;876877 /// Not used by code, exists only to provide some types to metadata.878 #[pallet::storage]879 pub type DummyStorageValue<T: Config> = StorageValue<880 Value = (881 CollectionStats,882 CollectionId,883 TokenId,884 TokenChild,885 PhantomType<(886 TokenData<T::CrossAccountId>,887 RpcCollection<T::AccountId>,888 // RMRK889 RmrkCollectionInfo<T::AccountId>,890 RmrkInstanceInfo<T::AccountId>,891 RmrkResourceInfo,892 RmrkPropertyInfo,893 RmrkBaseInfo<T::AccountId>,894 RmrkPartType,895 RmrkBoundedTheme,896 RmrkNftChild,897 // PoV Estimate Info898 PovInfo,899 )>,900 ),901 QueryKind = OptionQuery,902 >;903904 #[pallet::hooks]905 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {906 fn on_runtime_upgrade() -> Weight {907 StorageVersion::new(1).put::<Pallet<T>>();908909 Weight::zero()910 }911 }912}913914impl<T: Config> Pallet<T> {915 /// Enshure that receiver address is correct.916 ///917 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.918 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {919 ensure!(920 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,921 <Error<T>>::AddressIsZero922 );923 Ok(())924 }925926 /// Get a vector of collection admins.927 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {928 <IsAdmin<T>>::iter_prefix((collection,))929 .map(|(a, _)| a)930 .collect()931 }932933 /// Get a vector of users allowed to mint tokens.934 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {935 <Allowlist<T>>::iter_prefix((collection,))936 .map(|(a, _)| a)937 .collect()938 }939940 /// Is `user` allowed to mint token in `collection`.941 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {942 <Allowlist<T>>::get((collection, user))943 }944945 /// Get statistics of collections.946 pub fn collection_stats() -> CollectionStats {947 let created = <CreatedCollectionCount<T>>::get();948 let destroyed = <DestroyedCollectionCount<T>>::get();949 CollectionStats {950 created: created.0,951 destroyed: destroyed.0,952 alive: created.0 - destroyed.0,953 }954 }955956 /// Get the effective limits for the collection.957 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {958 let collection = <CollectionById<T>>::get(collection)?;959 let limits = collection.limits;960 let effective_limits = CollectionLimits {961 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),962 sponsored_data_size: Some(limits.sponsored_data_size()),963 sponsored_data_rate_limit: Some(964 limits965 .sponsored_data_rate_limit966 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),967 ),968 token_limit: Some(limits.token_limit()),969 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(970 match collection.mode {971 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,972 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,973 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,974 },975 )),976 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),977 owner_can_transfer: Some(limits.owner_can_transfer()),978 owner_can_destroy: Some(limits.owner_can_destroy()),979 transfers_enabled: Some(limits.transfers_enabled()),980 };981982 Some(effective_limits)983 }984985 /// Returns information about the `collection` adapted for rpc.986 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {987 let Collection {988 name,989 description,990 owner,991 mode,992 token_prefix,993 sponsorship,994 limits,995 permissions,996 flags,997 } = <CollectionById<T>>::get(collection)?;998999 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1000 .into_iter()1001 .map(|(key, permission)| PropertyKeyPermission { key, permission })1002 .collect();10031004 let properties = <CollectionProperties<T>>::get(collection)1005 .into_iter()1006 .map(|(key, value)| Property { key, value })1007 .collect();10081009 let permissions = CollectionPermissions {1010 access: Some(permissions.access()),1011 mint_mode: Some(permissions.mint_mode()),1012 nesting: Some(permissions.nesting().clone()),1013 };10141015 Some(RpcCollection {1016 name: name.into_inner(),1017 description: description.into_inner(),1018 owner,1019 mode,1020 token_prefix: token_prefix.into_inner(),1021 sponsorship,1022 limits,1023 permissions,1024 token_property_permissions,1025 properties,1026 read_only: flags.external,10271028 flags: RpcCollectionFlags {1029 foreign: flags.foreign,1030 erc721metadata: flags.erc721metadata,1031 },1032 })1033 }1034}10351036macro_rules! limit_default {1037 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1038 $(1039 if let Some($new) = $new.$field {1040 let $old = $old.$field($($arg)?);1041 let _ = $new;1042 let _ = $old;1043 $check1044 } else {1045 $new.$field = $old.$field1046 }1047 )*1048 }};1049}1050macro_rules! limit_default_clone {1051 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1052 $(1053 if let Some($new) = $new.$field.clone() {1054 let $old = $old.$field($($arg)?);1055 let _ = $new;1056 let _ = $old;1057 $check1058 } else {1059 $new.$field = $old.$field.clone()1060 }1061 )*1062 }};1063}10641065impl<T: Config> Pallet<T> {1066 /// Create new collection.1067 ///1068 /// * `owner` - The owner of the collection.1069 /// * `data` - Description of the created collection.1070 /// * `flags` - Extra flags to store.1071 pub fn init_collection(1072 owner: T::CrossAccountId,1073 payer: T::CrossAccountId,1074 data: CreateCollectionData<T::AccountId>,1075 flags: CollectionFlags,1076 ) -> Result<CollectionId, DispatchError> {1077 {1078 ensure!(1079 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1080 Error::<T>::CollectionTokenPrefixLimitExceeded1081 );1082 }10831084 let created_count = <CreatedCollectionCount<T>>::get()1085 .01086 .checked_add(1)1087 .ok_or(ArithmeticError::Overflow)?;1088 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1089 let id = CollectionId(created_count);10901091 // bound Total number of collections1092 ensure!(1093 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1094 <Error<T>>::TotalCollectionsLimitExceeded1095 );10961097 // =========10981099 let collection = Collection {1100 owner: owner.as_sub().clone(),1101 name: data.name,1102 mode: data.mode.clone(),1103 description: data.description,1104 token_prefix: data.token_prefix,1105 sponsorship: data1106 .pending_sponsor1107 .map(SponsorshipState::Unconfirmed)1108 .unwrap_or_default(),1109 limits: data1110 .limits1111 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1112 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1113 permissions: data1114 .permissions1115 .map(|permissions| {1116 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1117 })1118 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1119 flags,1120 };11211122 let mut collection_properties = up_data_structs::CollectionProperties::get();1123 collection_properties1124 .try_set_from_iter(data.properties.into_iter())1125 .map_err(<Error<T>>::from)?;11261127 CollectionProperties::<T>::insert(id, collection_properties);11281129 let mut token_props_permissions = PropertiesPermissionMap::new();1130 token_props_permissions1131 .try_set_from_iter(data.token_property_permissions.into_iter())1132 .map_err(<Error<T>>::from)?;11331134 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11351136 // Take a (non-refundable) deposit of collection creation1137 {1138 let mut imbalance =1139 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1140 imbalance.subsume(1141 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1142 &T::TreasuryAccountId::get(),1143 T::CollectionCreationPrice::get(),1144 ),1145 );1146 <T as Config>::Currency::settle(1147 payer.as_sub(),1148 imbalance,1149 WithdrawReasons::TRANSFER,1150 ExistenceRequirement::KeepAlive,1151 )1152 .map_err(|_| Error::<T>::NotSufficientFounds)?;1153 }11541155 <CreatedCollectionCount<T>>::put(created_count);1156 <Pallet<T>>::deposit_event(Event::CollectionCreated(1157 id,1158 data.mode.id(),1159 owner.as_sub().clone(),1160 ));1161 <PalletEvm<T>>::deposit_log(1162 erc::CollectionHelpersEvents::CollectionCreated {1163 owner: *owner.as_eth(),1164 collection_id: eth::collection_id_to_address(id),1165 }1166 .to_log(T::ContractAddress::get()),1167 );1168 <CollectionById<T>>::insert(id, collection);1169 Ok(id)1170 }11711172 /// Destroy collection.1173 ///1174 /// * `collection` - Collection handler.1175 /// * `sender` - The owner or administrator of the collection.1176 pub fn destroy_collection(1177 collection: CollectionHandle<T>,1178 sender: &T::CrossAccountId,1179 ) -> DispatchResult {1180 ensure!(1181 collection.limits.owner_can_destroy(),1182 <Error<T>>::NoPermission,1183 );1184 collection.check_is_owner(sender)?;11851186 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1187 .01188 .checked_add(1)1189 .ok_or(ArithmeticError::Overflow)?;11901191 // =========11921193 <DestroyedCollectionCount<T>>::put(destroyed_collections);1194 <CollectionById<T>>::remove(collection.id);1195 <AdminAmount<T>>::remove(collection.id);1196 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1197 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1198 <CollectionProperties<T>>::remove(collection.id);11991200 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12011202 <PalletEvm<T>>::deposit_log(1203 erc::CollectionHelpersEvents::CollectionDestroyed {1204 collection_id: eth::collection_id_to_address(collection.id),1205 }1206 .to_log(T::ContractAddress::get()),1207 );1208 Ok(())1209 }12101211 /// This function sets or removes a collection properties according to1212 /// `properties_updates` contents:1213 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1214 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1215 ///1216 /// This function fires an event for each property change.1217 /// In case of an error, all the changes (including the events) will be reverted1218 /// since the function is transactional.1219 #[transactional]1220 fn modify_collection_properties(1221 collection: &CollectionHandle<T>,1222 sender: &T::CrossAccountId,1223 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1224 ) -> DispatchResult {1225 collection.check_is_owner_or_admin(sender)?;12261227 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12281229 for (key, value) in properties_updates {1230 match value {1231 Some(value) => {1232 stored_properties1233 .try_set(key.clone(), value)1234 .map_err(<Error<T>>::from)?;12351236 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1237 <PalletEvm<T>>::deposit_log(1238 erc::CollectionHelpersEvents::CollectionChanged {1239 collection_id: eth::collection_id_to_address(collection.id),1240 }1241 .to_log(T::ContractAddress::get()),1242 );1243 }1244 None => {1245 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12461247 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1248 <PalletEvm<T>>::deposit_log(1249 erc::CollectionHelpersEvents::CollectionChanged {1250 collection_id: eth::collection_id_to_address(collection.id),1251 }1252 .to_log(T::ContractAddress::get()),1253 );1254 }1255 }1256 }12571258 <CollectionProperties<T>>::set(collection.id, stored_properties);12591260 Ok(())1261 }12621263 /// Set collection property.1264 ///1265 /// * `collection` - Collection handler.1266 /// * `sender` - The owner or administrator of the collection.1267 /// * `property` - The property to set.1268 pub fn set_collection_property(1269 collection: &CollectionHandle<T>,1270 sender: &T::CrossAccountId,1271 property: Property,1272 ) -> DispatchResult {1273 Self::set_collection_properties(collection, sender, [property].into_iter())1274 }12751276 /// Set a scoped collection property, where the scope is a special prefix1277 /// prohibiting a user access to change the property directly.1278 ///1279 /// * `collection_id` - ID of the collection for which the property is being set.1280 /// * `scope` - Property scope.1281 /// * `property` - The property to set.1282 pub fn set_scoped_collection_property(1283 collection_id: CollectionId,1284 scope: PropertyScope,1285 property: Property,1286 ) -> DispatchResult {1287 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1288 properties.try_scoped_set(scope, property.key, property.value)1289 })1290 .map_err(<Error<T>>::from)?;12911292 Ok(())1293 }12941295 /// Set scoped collection properties, where the scope is a special prefix1296 /// prohibiting a user access to change the properties directly.1297 ///1298 /// * `collection_id` - ID of the collection for which the properties is being set.1299 /// * `scope` - Property scope.1300 /// * `properties` - The properties to set.1301 pub fn set_scoped_collection_properties(1302 collection_id: CollectionId,1303 scope: PropertyScope,1304 properties: impl Iterator<Item = Property>,1305 ) -> DispatchResult {1306 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1307 stored_properties.try_scoped_set_from_iter(scope, properties)1308 })1309 .map_err(<Error<T>>::from)?;13101311 Ok(())1312 }13131314 /// Set collection properties.1315 ///1316 /// * `collection` - Collection handler.1317 /// * `sender` - The owner or administrator of the collection.1318 /// * `properties` - The properties to set.1319 pub fn set_collection_properties(1320 collection: &CollectionHandle<T>,1321 sender: &T::CrossAccountId,1322 properties: impl Iterator<Item = Property>,1323 ) -> DispatchResult {1324 Self::modify_collection_properties(1325 collection,1326 sender,1327 properties.map(|property| (property.key, Some(property.value))),1328 )1329 }13301331 /// Delete collection property.1332 ///1333 /// * `collection` - Collection handler.1334 /// * `sender` - The owner or administrator of the collection.1335 /// * `property` - The property to delete.1336 pub fn delete_collection_property(1337 collection: &CollectionHandle<T>,1338 sender: &T::CrossAccountId,1339 property_key: PropertyKey,1340 ) -> DispatchResult {1341 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1342 }13431344 /// Delete collection properties.1345 ///1346 /// * `collection` - Collection handler.1347 /// * `sender` - The owner or administrator of the collection.1348 /// * `properties` - The properties to delete.1349 pub fn delete_collection_properties(1350 collection: &CollectionHandle<T>,1351 sender: &T::CrossAccountId,1352 property_keys: impl Iterator<Item = PropertyKey>,1353 ) -> DispatchResult {1354 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1355 }13561357 /// Set collection propetry permission without any checks.1358 ///1359 /// Used for migrations.1360 ///1361 /// * `collection` - Collection handler.1362 /// * `property_permissions` - Property permissions.1363 pub fn set_property_permission_unchecked(1364 collection: CollectionId,1365 property_permission: PropertyKeyPermission,1366 ) -> DispatchResult {1367 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1368 permissions.try_set(property_permission.key, property_permission.permission)1369 })1370 .map_err(<Error<T>>::from)?;1371 Ok(())1372 }13731374 /// Set collection property permission.1375 ///1376 /// * `collection` - Collection handler.1377 /// * `sender` - The owner or administrator of the collection.1378 /// * `property_permission` - Property permission.1379 pub fn set_property_permission(1380 collection: &CollectionHandle<T>,1381 sender: &T::CrossAccountId,1382 property_permission: PropertyKeyPermission,1383 ) -> DispatchResult {1384 Self::set_scoped_property_permission(1385 collection,1386 sender,1387 PropertyScope::None,1388 property_permission,1389 )1390 }13911392 /// Set collection property permission with scope.1393 ///1394 /// * `collection` - Collection handler.1395 /// * `sender` - The owner or administrator of the collection.1396 /// * `scope` - Property scope.1397 /// * `property_permission` - Property permission.1398 pub fn set_scoped_property_permission(1399 collection: &CollectionHandle<T>,1400 sender: &T::CrossAccountId,1401 scope: PropertyScope,1402 property_permission: PropertyKeyPermission,1403 ) -> DispatchResult {1404 collection.check_is_owner_or_admin(sender)?;14051406 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1407 let current_permission = all_permissions.get(&property_permission.key);1408 if matches![1409 current_permission,1410 Some(PropertyPermission { mutable: false, .. })1411 ] {1412 return Err(<Error<T>>::NoPermission.into());1413 }14141415 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1416 let property_permission = property_permission.clone();1417 permissions.try_scoped_set(1418 scope,1419 property_permission.key,1420 property_permission.permission,1421 )1422 })1423 .map_err(<Error<T>>::from)?;14241425 Self::deposit_event(Event::PropertyPermissionSet(1426 collection.id,1427 property_permission.key,1428 ));1429 <PalletEvm<T>>::deposit_log(1430 erc::CollectionHelpersEvents::CollectionChanged {1431 collection_id: eth::collection_id_to_address(collection.id),1432 }1433 .to_log(T::ContractAddress::get()),1434 );14351436 Ok(())1437 }14381439 /// Set token property permission.1440 ///1441 /// * `collection` - Collection handler.1442 /// * `sender` - The owner or administrator of the collection.1443 /// * `property_permissions` - Property permissions.1444 #[transactional]1445 pub fn set_token_property_permissions(1446 collection: &CollectionHandle<T>,1447 sender: &T::CrossAccountId,1448 property_permissions: Vec<PropertyKeyPermission>,1449 ) -> DispatchResult {1450 Self::set_scoped_token_property_permissions(1451 collection,1452 sender,1453 PropertyScope::None,1454 property_permissions,1455 )1456 }14571458 /// Set token property permission with scope.1459 ///1460 /// * `collection` - Collection handler.1461 /// * `sender` - The owner or administrator of the collection.1462 /// * `scope` - Property scope.1463 /// * `property_permissions` - Property permissions.1464 #[transactional]1465 pub fn set_scoped_token_property_permissions(1466 collection: &CollectionHandle<T>,1467 sender: &T::CrossAccountId,1468 scope: PropertyScope,1469 property_permissions: Vec<PropertyKeyPermission>,1470 ) -> DispatchResult {1471 for prop_pemission in property_permissions {1472 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1473 }14741475 Ok(())1476 }14771478 /// Get collection property.1479 pub fn get_collection_property(1480 collection_id: CollectionId,1481 key: &PropertyKey,1482 ) -> Option<PropertyValue> {1483 Self::collection_properties(collection_id).get(key).cloned()1484 }14851486 /// Convert byte vector to property key vector.1487 pub fn bytes_keys_to_property_keys(1488 keys: Vec<Vec<u8>>,1489 ) -> Result<Vec<PropertyKey>, DispatchError> {1490 keys.into_iter()1491 .map(|key| -> Result<PropertyKey, DispatchError> {1492 key.try_into()1493 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1494 })1495 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1496 }14971498 /// Get properties according to given keys.1499 pub fn filter_collection_properties(1500 collection_id: CollectionId,1501 keys: Option<Vec<PropertyKey>>,1502 ) -> Result<Vec<Property>, DispatchError> {1503 let properties = Self::collection_properties(collection_id);15041505 let properties = keys1506 .map(|keys| {1507 keys.into_iter()1508 .filter_map(|key| {1509 properties.get(&key).map(|value| Property {1510 key,1511 value: value.clone(),1512 })1513 })1514 .collect()1515 })1516 .unwrap_or_else(|| {1517 properties1518 .into_iter()1519 .map(|(key, value)| Property { key, value })1520 .collect()1521 });15221523 Ok(properties)1524 }15251526 /// Get property permissions according to given keys.1527 pub fn filter_property_permissions(1528 collection_id: CollectionId,1529 keys: Option<Vec<PropertyKey>>,1530 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1531 let permissions = Self::property_permissions(collection_id);15321533 let key_permissions = keys1534 .map(|keys| {1535 keys.into_iter()1536 .filter_map(|key| {1537 permissions1538 .get(&key)1539 .map(|permission| PropertyKeyPermission {1540 key,1541 permission: permission.clone(),1542 })1543 })1544 .collect()1545 })1546 .unwrap_or_else(|| {1547 permissions1548 .into_iter()1549 .map(|(key, permission)| PropertyKeyPermission { key, permission })1550 .collect()1551 });15521553 Ok(key_permissions)1554 }15551556 /// Toggle `user` participation in the `collection`'s allow list.1557 /// #### Store read/writes1558 /// 1 writes1559 pub fn toggle_allowlist(1560 collection: &CollectionHandle<T>,1561 sender: &T::CrossAccountId,1562 user: &T::CrossAccountId,1563 allowed: bool,1564 ) -> DispatchResult {1565 collection.check_is_owner_or_admin(sender)?;15661567 // =========15681569 if allowed {1570 <Allowlist<T>>::insert((collection.id, user), true);1571 Self::deposit_event(Event::<T>::AllowListAddressAdded(1572 collection.id,1573 user.clone(),1574 ));1575 } else {1576 <Allowlist<T>>::remove((collection.id, user));1577 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1578 collection.id,1579 user.clone(),1580 ));1581 }15821583 <PalletEvm<T>>::deposit_log(1584 erc::CollectionHelpersEvents::CollectionChanged {1585 collection_id: eth::collection_id_to_address(collection.id),1586 }1587 .to_log(T::ContractAddress::get()),1588 );15891590 Ok(())1591 }15921593 /// Toggle `user` participation in the `collection`'s admin list.1594 /// #### Store read/writes1595 /// 2 reads, 2 writes1596 pub fn toggle_admin(1597 collection: &CollectionHandle<T>,1598 sender: &T::CrossAccountId,1599 user: &T::CrossAccountId,1600 admin: bool,1601 ) -> DispatchResult {1602 collection.check_is_internal()?;1603 collection.check_is_owner(sender)?;16041605 let is_admin = <IsAdmin<T>>::get((collection.id, user));1606 if is_admin == admin {1607 if admin {1608 return Ok(());1609 } else {1610 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1611 }1612 }1613 let amount = <AdminAmount<T>>::get(collection.id);16141615 // =========16161617 if admin {1618 let amount = amount1619 .checked_add(1)1620 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1621 ensure!(1622 amount <= Self::collection_admins_limit(),1623 <Error<T>>::CollectionAdminCountExceeded,1624 );16251626 <AdminAmount<T>>::insert(collection.id, amount);1627 <IsAdmin<T>>::insert((collection.id, user), true);16281629 Self::deposit_event(Event::<T>::CollectionAdminAdded(1630 collection.id,1631 user.clone(),1632 ));1633 } else {1634 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1635 <IsAdmin<T>>::remove((collection.id, user));16361637 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1638 collection.id,1639 user.clone(),1640 ));1641 }16421643 <PalletEvm<T>>::deposit_log(1644 erc::CollectionHelpersEvents::CollectionChanged {1645 collection_id: eth::collection_id_to_address(collection.id),1646 }1647 .to_log(T::ContractAddress::get()),1648 );16491650 Ok(())1651 }16521653 /// Update collection limits.1654 pub fn update_limits(1655 user: &T::CrossAccountId,1656 collection: &mut CollectionHandle<T>,1657 new_limit: CollectionLimits,1658 ) -> DispatchResult {1659 collection.check_is_internal()?;1660 collection.check_is_owner_or_admin(user)?;16611662 collection.limits =1663 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;16641665 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1666 <PalletEvm<T>>::deposit_log(1667 erc::CollectionHelpersEvents::CollectionChanged {1668 collection_id: eth::collection_id_to_address(collection.id),1669 }1670 .to_log(T::ContractAddress::get()),1671 );16721673 collection.save()1674 }16751676 /// Merge set fields from `new_limit` to `old_limit`.1677 fn clamp_limits(1678 mode: CollectionMode,1679 old_limit: &CollectionLimits,1680 mut new_limit: CollectionLimits,1681 ) -> Result<CollectionLimits, DispatchError> {1682 let limits = old_limit;1683 limit_default!(old_limit, new_limit,1684 account_token_ownership_limit => ensure!(1685 new_limit <= MAX_TOKEN_OWNERSHIP,1686 <Error<T>>::CollectionLimitBoundsExceeded,1687 ),1688 sponsored_data_size => ensure!(1689 new_limit <= CUSTOM_DATA_LIMIT,1690 <Error<T>>::CollectionLimitBoundsExceeded,1691 ),16921693 sponsored_data_rate_limit => {},1694 token_limit => ensure!(1695 old_limit >= new_limit && new_limit > 0,1696 <Error<T>>::CollectionTokenLimitExceeded1697 ),16981699 sponsor_transfer_timeout(match mode {1700 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1701 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1702 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1703 }) => ensure!(1704 new_limit <= MAX_SPONSOR_TIMEOUT,1705 <Error<T>>::CollectionLimitBoundsExceeded,1706 ),1707 sponsor_approve_timeout => {},1708 owner_can_transfer => ensure!(1709 !limits.owner_can_transfer_instaled() ||1710 old_limit || !new_limit,1711 <Error<T>>::OwnerPermissionsCantBeReverted,1712 ),1713 owner_can_destroy => ensure!(1714 old_limit || !new_limit,1715 <Error<T>>::OwnerPermissionsCantBeReverted,1716 ),1717 transfers_enabled => {},1718 );1719 Ok(new_limit)1720 }17211722 /// Update collection permissions.1723 pub fn update_permissions(1724 user: &T::CrossAccountId,1725 collection: &mut CollectionHandle<T>,1726 new_permission: CollectionPermissions,1727 ) -> DispatchResult {1728 collection.check_is_internal()?;1729 collection.check_is_owner_or_admin(user)?;1730 collection.permissions = Self::clamp_permissions(1731 collection.mode.clone(),1732 &collection.permissions,1733 new_permission,1734 )?;17351736 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1737 <PalletEvm<T>>::deposit_log(1738 erc::CollectionHelpersEvents::CollectionChanged {1739 collection_id: eth::collection_id_to_address(collection.id),1740 }1741 .to_log(T::ContractAddress::get()),1742 );17431744 collection.save()1745 }17461747 /// Merge set fields from `new_permission` to `old_permission`.1748 fn clamp_permissions(1749 _mode: CollectionMode,1750 old_permission: &CollectionPermissions,1751 mut new_permission: CollectionPermissions,1752 ) -> Result<CollectionPermissions, DispatchError> {1753 limit_default_clone!(old_permission, new_permission,1754 access => {},1755 mint_mode => {},1756 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1757 );1758 Ok(new_permission)1759 }17601761 /// Repair possibly broken properties of a collection.1762 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1763 CollectionProperties::<T>::mutate(collection_id, |properties| {1764 properties.recompute_consumed_space();1765 });17661767 Ok(())1768 }1769}17701771/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1772#[macro_export]1773macro_rules! unsupported {1774 ($runtime:path) => {1775 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1776 };1777}17781779/// Return weights for various worst-case operations.1780pub trait CommonWeightInfo<CrossAccountId> {1781 /// Weight of item creation.1782 fn create_item() -> Weight;17831784 /// Weight of items creation.1785 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;17861787 /// Weight of items creation.1788 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;17891790 /// The weight of the burning item.1791 fn burn_item() -> Weight;17921793 /// Property setting weight.1794 ///1795 /// * `amount`- The number of properties to set.1796 fn set_collection_properties(amount: u32) -> Weight;17971798 /// Collection property deletion weight.1799 ///1800 /// * `amount`- The number of properties to set.1801 fn delete_collection_properties(amount: u32) -> Weight;18021803 /// Token property setting weight.1804 ///1805 /// * `amount`- The number of properties to set.1806 fn set_token_properties(amount: u32) -> Weight;18071808 /// Token property deletion weight.1809 ///1810 /// * `amount`- The number of properties to delete.1811 fn delete_token_properties(amount: u32) -> Weight;18121813 /// Token property permissions set weight.1814 ///1815 /// * `amount`- The number of property permissions to set.1816 fn set_token_property_permissions(amount: u32) -> Weight;18171818 /// Transfer price of the token or its parts.1819 fn transfer() -> Weight;18201821 /// The price of setting the permission of the operation from another user.1822 fn approve() -> Weight;18231824 /// The price of setting the permission of the operation from another user for eth mirror.1825 fn approve_from() -> Weight;18261827 /// Transfer price from another user.1828 fn transfer_from() -> Weight;18291830 /// The price of burning a token from another user.1831 fn burn_from() -> Weight;18321833 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1834 /// whole users's balance.1835 ///1836 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1837 fn burn_recursively_self_raw() -> Weight;18381839 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1840 ///1841 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1842 fn burn_recursively_breadth_raw(amount: u32) -> Weight;18431844 /// The price of recursive burning a token.1845 ///1846 /// `max_selfs` - The maximum burning weight of the token itself.1847 /// `max_breadth` - The maximum number of nested tokens to burn.1848 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1849 Self::burn_recursively_self_raw()1850 .saturating_mul(max_selfs.max(1) as u64)1851 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1852 }18531854 /// The price of retrieving token owner1855 fn token_owner() -> Weight;18561857 /// The price of setting approval for all1858 fn set_allowance_for_all() -> Weight;18591860 /// The price of repairing an item.1861 fn force_repair_item() -> Weight;1862}18631864/// Weight info extension trait for refungible pallet.1865pub trait RefungibleExtensionsWeightInfo {1866 /// Weight of token repartition.1867 fn repartition() -> Weight;1868}18691870/// Common collection operations.1871///1872/// It wraps methods in Fungible, Nonfungible and Refungible pallets1873/// and adds weight info.1874pub trait CommonCollectionOperations<T: Config> {1875 /// Create token.1876 ///1877 /// * `sender` - The user who mint the token and pays for the transaction.1878 /// * `to` - The user who will own the token.1879 /// * `data` - Token data.1880 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1881 fn create_item(1882 &self,1883 sender: T::CrossAccountId,1884 to: T::CrossAccountId,1885 data: CreateItemData,1886 nesting_budget: &dyn Budget,1887 ) -> DispatchResultWithPostInfo;18881889 /// Create multiple tokens.1890 ///1891 /// * `sender` - The user who mint the token and pays for the transaction.1892 /// * `to` - The user who will own the token.1893 /// * `data` - Token data.1894 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1895 fn create_multiple_items(1896 &self,1897 sender: T::CrossAccountId,1898 to: T::CrossAccountId,1899 data: Vec<CreateItemData>,1900 nesting_budget: &dyn Budget,1901 ) -> DispatchResultWithPostInfo;19021903 /// Create multiple tokens.1904 ///1905 /// * `sender` - The user who mint the token and pays for the transaction.1906 /// * `to` - The user who will own the token.1907 /// * `data` - Token data.1908 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1909 fn create_multiple_items_ex(1910 &self,1911 sender: T::CrossAccountId,1912 data: CreateItemExData<T::CrossAccountId>,1913 nesting_budget: &dyn Budget,1914 ) -> DispatchResultWithPostInfo;19151916 /// Burn token.1917 ///1918 /// * `sender` - The user who owns the token.1919 /// * `token` - Token id that will burned.1920 /// * `amount` - The number of parts of the token that will be burned.1921 fn burn_item(1922 &self,1923 sender: T::CrossAccountId,1924 token: TokenId,1925 amount: u128,1926 ) -> DispatchResultWithPostInfo;19271928 /// Burn token and all nested tokens recursievly.1929 ///1930 /// * `sender` - The user who owns the token.1931 /// * `token` - Token id that will burned.1932 /// * `self_budget` - The budget that can be spent on burning tokens.1933 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.1934 fn burn_item_recursively(1935 &self,1936 sender: T::CrossAccountId,1937 token: TokenId,1938 self_budget: &dyn Budget,1939 breadth_budget: &dyn Budget,1940 ) -> DispatchResultWithPostInfo;19411942 /// Set collection properties.1943 ///1944 /// * `sender` - Must be either the owner of the collection or its admin.1945 /// * `properties` - Properties to be set.1946 fn set_collection_properties(1947 &self,1948 sender: T::CrossAccountId,1949 properties: Vec<Property>,1950 ) -> DispatchResultWithPostInfo;19511952 /// Delete collection properties.1953 ///1954 /// * `sender` - Must be either the owner of the collection or its admin.1955 /// * `properties` - The properties to be removed.1956 fn delete_collection_properties(1957 &self,1958 sender: &T::CrossAccountId,1959 property_keys: Vec<PropertyKey>,1960 ) -> DispatchResultWithPostInfo;19611962 /// Set token properties.1963 ///1964 /// The appropriate [`PropertyPermission`] for the token property1965 /// must be set with [`Self::set_token_property_permissions`].1966 ///1967 /// * `sender` - Must be either the owner of the token or its admin.1968 /// * `token_id` - The token for which the properties are being set.1969 /// * `properties` - Properties to be set.1970 /// * `budget` - Budget for setting properties.1971 fn set_token_properties(1972 &self,1973 sender: T::CrossAccountId,1974 token_id: TokenId,1975 properties: Vec<Property>,1976 budget: &dyn Budget,1977 ) -> DispatchResultWithPostInfo;19781979 /// Remove token properties.1980 ///1981 /// The appropriate [`PropertyPermission`] for the token property1982 /// must be set with [`Self::set_token_property_permissions`].1983 ///1984 /// * `sender` - Must be either the owner of the token or its admin.1985 /// * `token_id` - The token for which the properties are being remove.1986 /// * `property_keys` - Keys to remove corresponding properties.1987 /// * `budget` - Budget for removing properties.1988 fn delete_token_properties(1989 &self,1990 sender: T::CrossAccountId,1991 token_id: TokenId,1992 property_keys: Vec<PropertyKey>,1993 budget: &dyn Budget,1994 ) -> DispatchResultWithPostInfo;19951996 /// Set token property permissions.1997 ///1998 /// * `sender` - Must be either the owner of the token or its admin.1999 /// * `token_id` - The token for which the properties are being set.2000 /// * `property_permissions` - Property permissions to be set.2001 /// * `budget` - Budget for setting properties.2002 fn set_token_property_permissions(2003 &self,2004 sender: &T::CrossAccountId,2005 property_permissions: Vec<PropertyKeyPermission>,2006 ) -> DispatchResultWithPostInfo;20072008 /// Transfer amount of token pieces.2009 ///2010 /// * `sender` - Donor user.2011 /// * `to` - Recepient user.2012 /// * `token` - The token of which parts are being sent.2013 /// * `amount` - The number of parts of the token that will be transferred.2014 /// * `budget` - The maximum budget that can be spent on the transfer.2015 fn transfer(2016 &self,2017 sender: T::CrossAccountId,2018 to: T::CrossAccountId,2019 token: TokenId,2020 amount: u128,2021 budget: &dyn Budget,2022 ) -> DispatchResultWithPostInfo;20232024 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2025 ///2026 /// * `sender` - The user who grants access to the token.2027 /// * `spender` - The user to whom the rights are granted.2028 /// * `token` - The token to which access is granted.2029 /// * `amount` - The amount of pieces that another user can dispose of.2030 fn approve(2031 &self,2032 sender: T::CrossAccountId,2033 spender: T::CrossAccountId,2034 token: TokenId,2035 amount: u128,2036 ) -> DispatchResultWithPostInfo;20372038 /// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2039 ///2040 /// * `sender` - The user who grants access to the token.2041 /// * `from` - Spender's eth mirror.2042 /// * `to` - The user to whom the rights are granted.2043 /// * `token` - The token to which access is granted.2044 /// * `amount` - The amount of pieces that another user can dispose of.2045 fn approve_from(2046 &self,2047 sender: T::CrossAccountId,2048 from: T::CrossAccountId,2049 to: T::CrossAccountId,2050 token: TokenId,2051 amount: u128,2052 ) -> DispatchResultWithPostInfo;20532054 /// Send parts of a token owned by another user.2055 ///2056 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2057 ///2058 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2059 /// * `from` - The user who owns the token.2060 /// * `to` - Recepient user.2061 /// * `token` - The token of which parts are being sent.2062 /// * `amount` - The number of parts of the token that will be transferred.2063 /// * `budget` - The maximum budget that can be spent on the transfer.2064 fn transfer_from(2065 &self,2066 sender: T::CrossAccountId,2067 from: T::CrossAccountId,2068 to: T::CrossAccountId,2069 token: TokenId,2070 amount: u128,2071 budget: &dyn Budget,2072 ) -> DispatchResultWithPostInfo;20732074 /// Burn parts of a token owned by another user.2075 ///2076 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2077 ///2078 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2079 /// * `from` - The user who owns the token.2080 /// * `token` - The token of which parts are being sent.2081 /// * `amount` - The number of parts of the token that will be transferred.2082 /// * `budget` - The maximum budget that can be spent on the burn.2083 fn burn_from(2084 &self,2085 sender: T::CrossAccountId,2086 from: T::CrossAccountId,2087 token: TokenId,2088 amount: u128,2089 budget: &dyn Budget,2090 ) -> DispatchResultWithPostInfo;20912092 /// Check permission to nest token.2093 ///2094 /// * `sender` - The user who initiated the check.2095 /// * `from` - The token that is checked for embedding.2096 /// * `under` - Token under which to check.2097 /// * `budget` - The maximum budget that can be spent on the check.2098 fn check_nesting(2099 &self,2100 sender: T::CrossAccountId,2101 from: (CollectionId, TokenId),2102 under: TokenId,2103 budget: &dyn Budget,2104 ) -> DispatchResult;21052106 /// Nest one token into another.2107 ///2108 /// * `under` - Token holder.2109 /// * `to_nest` - Nested token.2110 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21112112 /// Unnest token.2113 ///2114 /// * `under` - Token holder.2115 /// * `to_nest` - Token to unnest.2116 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21172118 /// Get all user tokens.2119 ///2120 /// * `account` - Account for which you need to get tokens.2121 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;21222123 /// Get all the tokens in the collection.2124 fn collection_tokens(&self) -> Vec<TokenId>;21252126 /// Check if the token exists.2127 ///2128 /// * `token` - Id token to check.2129 fn token_exists(&self, token: TokenId) -> bool;21302131 /// Get the id of the last minted token.2132 fn last_token_id(&self) -> TokenId;21332134 /// Get the owner of the token.2135 ///2136 /// * `token` - The token for which you need to find out the owner.2137 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;21382139 /// Returns 10 tokens owners in no particular order.2140 ///2141 /// * `token` - The token for which you need to find out the owners.2142 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;21432144 /// Get the value of the token property by key.2145 ///2146 /// * `token` - Token with the property to get.2147 /// * `key` - Property name.2148 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;21492150 /// Get a set of token properties by key vector.2151 ///2152 /// * `token` - Token with the property to get.2153 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2154 /// then all properties are returned.2155 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;21562157 /// Amount of unique collection tokens2158 fn total_supply(&self) -> u32;21592160 /// Amount of different tokens account has.2161 ///2162 /// * `account` - The account for which need to get the balance.2163 fn account_balance(&self, account: T::CrossAccountId) -> u32;21642165 /// Amount of specific token account have.2166 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;21672168 /// Amount of token pieces2169 fn total_pieces(&self, token: TokenId) -> Option<u128>;21702171 /// Get the number of parts of the token that a trusted user can manage.2172 ///2173 /// * `sender` - Trusted user.2174 /// * `spender` - Owner of the token.2175 /// * `token` - The token for which to get the value.2176 fn allowance(2177 &self,2178 sender: T::CrossAccountId,2179 spender: T::CrossAccountId,2180 token: TokenId,2181 ) -> u128;21822183 /// Get extension for RFT collection.2184 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;21852186 /// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2187 /// * `owner` - Token owner2188 /// * `operator` - Operator2189 /// * `approve` - Should operator status be granted or revoked?2190 fn set_allowance_for_all(2191 &self,2192 owner: T::CrossAccountId,2193 operator: T::CrossAccountId,2194 approve: bool,2195 ) -> DispatchResultWithPostInfo;21962197 /// Tells whether the given `owner` approves the `operator`.2198 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;21992200 /// Repairs a possibly broken item.2201 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2202}22032204/// Extension for RFT collection.2205pub trait RefungibleExtensions<T>2206where2207 T: Config,2208{2209 /// Change the number of parts of the token.2210 ///2211 /// When the value changes down, this function is equivalent to burning parts of the token.2212 ///2213 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.2214 /// * `token` - The token for which you want to change the number of parts.2215 /// * `amount` - The new value of the parts of the token.2216 fn repartition(2217 &self,2218 sender: &T::CrossAccountId,2219 token: TokenId,2220 amount: u128,2221 ) -> DispatchResultWithPostInfo;2222}22232224/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2225///2226/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2227pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2228 let post_info = PostDispatchInfo {2229 actual_weight: Some(weight),2230 pays_fee: Pays::Yes,2231 };2232 match res {2233 Ok(()) => Ok(post_info),2234 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2235 }2236}22372238impl<T: Config> From<PropertiesError> for Error<T> {2239 fn from(error: PropertiesError) -> Self {2240 match error {2241 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2242 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2243 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2244 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2245 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2246 }2247 }2248}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63 ensure,64 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65 dispatch::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 RpcCollectionFlags,75 CollectionId,76 CreateItemData,77 MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT,79 TokenId,80 TokenChild,81 CollectionStats,82 MAX_TOKEN_OWNERSHIP,83 CollectionMode,84 NFT_SPONSOR_TRANSFER_TIMEOUT,85 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87 MAX_SPONSOR_TIMEOUT,88 CUSTOM_DATA_LIMIT,89 CollectionLimits,90 CreateCollectionData,91 SponsorshipState,92 CreateItemExData,93 SponsoringRateLimit,94 budget::Budget,95 PhantomType,96 Property,97 Properties,98 PropertiesPermissionMap,99 PropertyKey,100 PropertyValue,101 PropertyPermission,102 PropertiesError,103 TokenOwnerError,104 PropertyKeyPermission,105 TokenData,106 TrySetProperty,107 PropertyScope,108 // RMRK109 RmrkCollectionInfo,110 RmrkInstanceInfo,111 RmrkResourceInfo,112 RmrkPropertyInfo,113 RmrkBaseInfo,114 RmrkPartType,115 RmrkBoundedTheme,116 RmrkNftChild,117 CollectionPermissions,118};119use up_pov_estimate_rpc::PovInfo;120121pub use pallet::*;122use sp_core::H160;123use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};124#[cfg(feature = "runtime-benchmarks")]125pub mod benchmarking;126pub mod dispatch;127pub mod erc;128pub mod eth;129pub mod weights;130131/// Weight info.132pub type SelfWeightOf<T> = <T as Config>::WeightInfo;133134/// Collection handle contains information about collection data and id.135/// Also provides functionality to count consumed gas.136///137/// CollectionHandle is used as a generic wrapper for collections of all types.138/// It allows to perform common operations and queries on any collection type,139/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].140#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]141pub struct CollectionHandle<T: Config> {142 /// Collection id143 pub id: CollectionId,144 collection: Collection<T::AccountId>,145 /// Substrate recorder for counting consumed gas146 pub recorder: SubstrateRecorder<T>,147}148149impl<T: Config> WithRecorder<T> for CollectionHandle<T> {150 fn recorder(&self) -> &SubstrateRecorder<T> {151 &self.recorder152 }153 fn into_recorder(self) -> SubstrateRecorder<T> {154 self.recorder155 }156}157158impl<T: Config> CollectionHandle<T> {159 /// Same as [CollectionHandle::new] but with an explicit gas limit.160 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {161 <CollectionById<T>>::get(id).map(|collection| Self {162 id,163 collection,164 recorder: SubstrateRecorder::new(gas_limit),165 })166 }167168 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].169 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {170 <CollectionById<T>>::get(id).map(|collection| Self {171 id,172 collection,173 recorder,174 })175 }176177 /// Retrives collection data from storage and creates collection handle with default parameters.178 /// If collection not found return `None`179 pub fn new(id: CollectionId) -> Option<Self> {180 Self::new_with_gas_limit(id, u64::MAX)181 }182183 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.184 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {185 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)186 }187188 /// Consume gas for reading.189 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {190 self.recorder191 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(192 <T as frame_system::Config>::DbWeight::get()193 .read194 .saturating_mul(reads),195 )))196 }197198 /// Consume gas for writing.199 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {200 self.recorder201 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(202 <T as frame_system::Config>::DbWeight::get()203 .write204 .saturating_mul(writes),205 )))206 }207208 /// Consume gas for reading and writing.209 pub fn consume_store_reads_and_writes(210 &self,211 reads: u64,212 writes: u64,213 ) -> evm_coder::execution::Result<()> {214 let weight = <T as frame_system::Config>::DbWeight::get();215 let reads = weight.read.saturating_mul(reads);216 let writes = weight.read.saturating_mul(writes);217 self.recorder218 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(219 reads.saturating_add(writes),220 )))221 }222223 /// Save collection to storage.224 pub fn save(&self) -> DispatchResult {225 <CollectionById<T>>::insert(self.id, &self.collection);226 Ok(())227 }228229 /// Set collection sponsor.230 ///231 /// Unique collections allows sponsoring for certain actions.232 /// This method allows you to set the sponsor of the collection.233 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].234 pub fn set_sponsor(235 &mut self,236 sender: &T::CrossAccountId,237 sponsor: T::AccountId,238 ) -> DispatchResult {239 self.check_is_internal()?;240 self.check_is_owner_or_admin(sender)?;241242 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());243244 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));245 <PalletEvm<T>>::deposit_log(246 erc::CollectionHelpersEvents::CollectionChanged {247 collection_id: eth::collection_id_to_address(self.id),248 }249 .to_log(T::ContractAddress::get()),250 );251252 self.save()253 }254255 /// Force set `sponsor`.256 ///257 /// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation258 /// from the `sponsor` is not required.259 ///260 /// # Arguments261 ///262 /// * `sender`: Caller's account.263 /// * `sponsor`: ID of the account of the sponsor-to-be.264 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {265 self.check_is_internal()?;266267 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());268269 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));270 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));271 <PalletEvm<T>>::deposit_log(272 erc::CollectionHelpersEvents::CollectionChanged {273 collection_id: eth::collection_id_to_address(self.id),274 }275 .to_log(T::ContractAddress::get()),276 );277278 self.save()279 }280281 /// Confirm sponsorship282 ///283 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.284 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].285 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {286 self.check_is_internal()?;287 ensure!(288 self.collection.sponsorship.pending_sponsor() == Some(sender),289 Error::<T>::ConfirmSponsorshipFail290 );291292 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());293294 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));295 <PalletEvm<T>>::deposit_log(296 erc::CollectionHelpersEvents::CollectionChanged {297 collection_id: eth::collection_id_to_address(self.id),298 }299 .to_log(T::ContractAddress::get()),300 );301302 self.save()303 }304305 /// Remove collection sponsor.306 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {307 self.check_is_internal()?;308 self.check_is_owner_or_admin(sender)?;309310 self.collection.sponsorship = SponsorshipState::Disabled;311312 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));313 <PalletEvm<T>>::deposit_log(314 erc::CollectionHelpersEvents::CollectionChanged {315 collection_id: eth::collection_id_to_address(self.id),316 }317 .to_log(T::ContractAddress::get()),318 );319 self.save()320 }321322 /// Force remove `sponsor`.323 ///324 /// Differs from `remove_sponsor` in that325 /// it doesn't require consent from the `owner` of the collection.326 pub fn force_remove_sponsor(&mut self) -> DispatchResult {327 self.check_is_internal()?;328329 self.collection.sponsorship = SponsorshipState::Disabled;330331 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));332 <PalletEvm<T>>::deposit_log(333 erc::CollectionHelpersEvents::CollectionChanged {334 collection_id: eth::collection_id_to_address(self.id),335 }336 .to_log(T::ContractAddress::get()),337 );338 self.save()339 }340341 /// Checks that the collection was created with, and must be operated upon through **Unique API**.342 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.343 pub fn check_is_internal(&self) -> DispatchResult {344 if self.flags.external {345 return Err(<Error<T>>::CollectionIsExternal)?;346 }347348 Ok(())349 }350351 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.352 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.353 pub fn check_is_external(&self) -> DispatchResult {354 if !self.flags.external {355 return Err(<Error<T>>::CollectionIsInternal)?;356 }357358 Ok(())359 }360}361362impl<T: Config> Deref for CollectionHandle<T> {363 type Target = Collection<T::AccountId>;364365 fn deref(&self) -> &Self::Target {366 &self.collection367 }368}369370impl<T: Config> DerefMut for CollectionHandle<T> {371 fn deref_mut(&mut self) -> &mut Self::Target {372 &mut self.collection373 }374}375376impl<T: Config> CollectionHandle<T> {377 /// Checks if the `user` is the owner of the collection.378 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {379 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);380 Ok(())381 }382383 /// Returns **true** if the `user` is the owner or administrator of the collection.384 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {385 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))386 }387388 /// Checks if the `user` is the owner or administrator of the collection.389 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {390 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);391 Ok(())392 }393394 /// Returns **true** if395 /// * the `user`is a collection owner or admin396 /// * the collection limits allow the owner/admins to transfer/burn any collection token397 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {398 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)399 }400401 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.402 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {403 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)404 }405406 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.407 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {408 ensure!(409 <Allowlist<T>>::get((self.id, user)),410 <Error<T>>::AddressNotInAllowlist411 );412 Ok(())413 }414415 /// Changes collection owner to another account416 /// #### Store read/writes417 /// 1 writes418 pub fn change_owner(419 &mut self,420 caller: T::CrossAccountId,421 new_owner: T::CrossAccountId,422 ) -> DispatchResult {423 self.check_is_internal()?;424 self.check_is_owner(&caller)?;425 self.collection.owner = new_owner.as_sub().clone();426427 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(428 self.id,429 new_owner.as_sub().clone(),430 ));431 <PalletEvm<T>>::deposit_log(432 erc::CollectionHelpersEvents::CollectionChanged {433 collection_id: eth::collection_id_to_address(self.id),434 }435 .to_log(T::ContractAddress::get()),436 );437438 self.save()439 }440}441442#[frame_support::pallet]443pub mod pallet {444 use super::*;445 use dispatch::CollectionDispatch;446 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};447 use frame_system::pallet_prelude::*;448 use frame_support::traits::Currency;449 use up_data_structs::{TokenId, mapping::TokenAddressMapping};450 use scale_info::TypeInfo;451 use weights::WeightInfo;452453 #[pallet::config]454 pub trait Config:455 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo456 {457 /// Weight information for functions of this pallet.458 type WeightInfo: WeightInfo;459460 /// Events compatible with [`frame_system::Config::Event`].461 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;462463 /// Handler of accounts and payment.464 type Currency: Currency<Self::AccountId>;465466 /// Set price to create a collection.467 #[pallet::constant]468 type CollectionCreationPrice: Get<469 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,470 >;471472 /// Dispatcher of operations on collections.473 type CollectionDispatch: CollectionDispatch<Self>;474475 /// Account which holds the chain's treasury.476 type TreasuryAccountId: Get<Self::AccountId>;477478 /// Address under which the CollectionHelper contract would be available.479 #[pallet::constant]480 type ContractAddress: Get<H160>;481482 /// Mapper for token addresses to Ethereum addresses.483 type EvmTokenAddressMapping: TokenAddressMapping<H160>;484485 /// Mapper for token addresses to [`CrossAccountId`].486 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;487 }488489 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);490491 #[pallet::pallet]492 #[pallet::storage_version(STORAGE_VERSION)]493 #[pallet::generate_store(pub(super) trait Store)]494 pub struct Pallet<T>(_);495496 #[pallet::extra_constants]497 impl<T: Config> Pallet<T> {498 /// Maximum admins per collection.499 pub fn collection_admins_limit() -> u32 {500 COLLECTION_ADMINS_LIMIT501 }502 }503504 impl<T: Config> Pallet<T> {505 /// Helper function that handles deposit events506 pub fn deposit_event(event: Event<T>) {507 let event = <T as Config>::RuntimeEvent::from(event);508 let event = event.into();509 <frame_system::Pallet<T>>::deposit_event(event)510 }511 }512513 #[pallet::event]514 pub enum Event<T: Config> {515 /// New collection was created516 CollectionCreated(517 /// Globally unique identifier of newly created collection.518 CollectionId,519 /// [`CollectionMode`] converted into _u8_.520 u8,521 /// Collection owner.522 T::AccountId,523 ),524525 /// New collection was destroyed526 CollectionDestroyed(527 /// Globally unique identifier of collection.528 CollectionId,529 ),530531 /// New item was created.532 ItemCreated(533 /// Id of the collection where item was created.534 CollectionId,535 /// Id of an item. Unique within the collection.536 TokenId,537 /// Owner of newly created item538 T::CrossAccountId,539 /// Always 1 for NFT540 u128,541 ),542543 /// Collection item was burned.544 ItemDestroyed(545 /// Id of the collection where item was destroyed.546 CollectionId,547 /// Identifier of burned NFT.548 TokenId,549 /// Which user has destroyed its tokens.550 T::CrossAccountId,551 /// Amount of token pieces destroed. Always 1 for NFT.552 u128,553 ),554555 /// Item was transferred556 Transfer(557 /// Id of collection to which item is belong.558 CollectionId,559 /// Id of an item.560 TokenId,561 /// Original owner of item.562 T::CrossAccountId,563 /// New owner of item.564 T::CrossAccountId,565 /// Amount of token pieces transfered. Always 1 for NFT.566 u128,567 ),568569 /// Amount pieces of token owned by `sender` was approved for `spender`.570 Approved(571 /// Id of collection to which item is belong.572 CollectionId,573 /// Id of an item.574 TokenId,575 /// Original owner of item.576 T::CrossAccountId,577 /// Id for which the approval was granted.578 T::CrossAccountId,579 /// Amount of token pieces transfered. Always 1 for NFT.580 u128,581 ),582583 /// A `sender` approves operations on all owned tokens for `spender`.584 ApprovedForAll(585 /// Id of collection to which item is belong.586 CollectionId,587 /// Owner of a wallet.588 T::CrossAccountId,589 /// Id for which operator status was granted or rewoked.590 T::CrossAccountId,591 /// Is operator status granted or revoked?592 bool,593 ),594595 /// The colletion property has been added or edited.596 CollectionPropertySet(597 /// Id of collection to which property has been set.598 CollectionId,599 /// The property that was set.600 PropertyKey,601 ),602603 /// The property has been deleted.604 CollectionPropertyDeleted(605 /// Id of collection to which property has been deleted.606 CollectionId,607 /// The property that was deleted.608 PropertyKey,609 ),610611 /// The token property has been added or edited.612 TokenPropertySet(613 /// Identifier of the collection whose token has the property set.614 CollectionId,615 /// The token for which the property was set.616 TokenId,617 /// The property that was set.618 PropertyKey,619 ),620621 /// The token property has been deleted.622 TokenPropertyDeleted(623 /// Identifier of the collection whose token has the property deleted.624 CollectionId,625 /// The token for which the property was deleted.626 TokenId,627 /// The property that was deleted.628 PropertyKey,629 ),630631 /// The token property permission of a collection has been set.632 PropertyPermissionSet(633 /// ID of collection to which property permission has been set.634 CollectionId,635 /// The property permission that was set.636 PropertyKey,637 ),638639 /// Address was added to the allow list.640 AllowListAddressAdded(641 /// ID of the affected collection.642 CollectionId,643 /// Address of the added account.644 T::CrossAccountId,645 ),646647 /// Address was removed from the allow list.648 AllowListAddressRemoved(649 /// ID of the affected collection.650 CollectionId,651 /// Address of the removed account.652 T::CrossAccountId,653 ),654655 /// Collection admin was added.656 CollectionAdminAdded(657 /// ID of the affected collection.658 CollectionId,659 /// Admin address.660 T::CrossAccountId,661 ),662663 /// Collection admin was removed.664 CollectionAdminRemoved(665 /// ID of the affected collection.666 CollectionId,667 /// Removed admin address.668 T::CrossAccountId,669 ),670671 /// Collection limits were set.672 CollectionLimitSet(673 /// ID of the affected collection.674 CollectionId,675 ),676677 /// Collection owned was changed.678 CollectionOwnerChanged(679 /// ID of the affected collection.680 CollectionId,681 /// New owner address.682 T::AccountId,683 ),684685 /// Collection permissions were set.686 CollectionPermissionSet(687 /// ID of the affected collection.688 CollectionId,689 ),690691 /// Collection sponsor was set.692 CollectionSponsorSet(693 /// ID of the affected collection.694 CollectionId,695 /// New sponsor address.696 T::AccountId,697 ),698699 /// New sponsor was confirm.700 SponsorshipConfirmed(701 /// ID of the affected collection.702 CollectionId,703 /// New sponsor address.704 T::AccountId,705 ),706707 /// Collection sponsor was removed.708 CollectionSponsorRemoved(709 /// ID of the affected collection.710 CollectionId,711 ),712 }713714 #[pallet::error]715 pub enum Error<T> {716 /// This collection does not exist.717 CollectionNotFound,718 /// Sender parameter and item owner must be equal.719 MustBeTokenOwner,720 /// No permission to perform action721 NoPermission,722 /// Destroying only empty collections is allowed723 CantDestroyNotEmptyCollection,724 /// Collection is not in mint mode.725 PublicMintingNotAllowed,726 /// Address is not in allow list.727 AddressNotInAllowlist,728729 /// Collection name can not be longer than 63 char.730 CollectionNameLimitExceeded,731 /// Collection description can not be longer than 255 char.732 CollectionDescriptionLimitExceeded,733 /// Token prefix can not be longer than 15 char.734 CollectionTokenPrefixLimitExceeded,735 /// Total collections bound exceeded.736 TotalCollectionsLimitExceeded,737 /// Exceeded max admin count738 CollectionAdminCountExceeded,739 /// Collection limit bounds per collection exceeded740 CollectionLimitBoundsExceeded,741 /// Tried to enable permissions which are only permitted to be disabled742 OwnerPermissionsCantBeReverted,743 /// Collection settings not allowing items transferring744 TransferNotAllowed,745 /// Account token limit exceeded per collection746 AccountTokenLimitExceeded,747 /// Collection token limit exceeded748 CollectionTokenLimitExceeded,749 /// Metadata flag frozen750 MetadataFlagFrozen,751752 /// Item does not exist753 TokenNotFound,754 /// Item is balance not enough755 TokenValueTooLow,756 /// Requested value is more than the approved757 ApprovedValueTooLow,758 /// Tried to approve more than owned759 CantApproveMoreThanOwned,760 /// Only spending from eth mirror could be approved761 AddressIsNotEthMirror,762763 /// Can't transfer tokens to ethereum zero address764 AddressIsZero,765766 /// The operation is not supported767 UnsupportedOperation,768769 /// Insufficient funds to perform an action770 NotSufficientFounds,771772 /// User does not satisfy the nesting rule773 UserIsNotAllowedToNest,774 /// Only tokens from specific collections may nest tokens under this one775 SourceCollectionIsNotAllowedToNest,776777 /// Tried to store more data than allowed in collection field778 CollectionFieldSizeExceeded,779780 /// Tried to store more property data than allowed781 NoSpaceForProperty,782783 /// Tried to store more property keys than allowed784 PropertyLimitReached,785786 /// Property key is too long787 PropertyKeyIsTooLong,788789 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed790 InvalidCharacterInPropertyKey,791792 /// Empty property keys are forbidden793 EmptyPropertyKey,794795 /// Tried to access an external collection with an internal API796 CollectionIsExternal,797798 /// Tried to access an internal collection with an external API799 CollectionIsInternal,800801 /// This address is not set as sponsor, use setCollectionSponsor first.802 ConfirmSponsorshipFail,803804 /// The user is not an administrator.805 UserIsNotCollectionAdmin,806 }807808 /// Storage of the count of created collections. Essentially contains the last collection ID.809 #[pallet::storage]810 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;811812 /// Storage of the count of deleted collections.813 #[pallet::storage]814 pub type DestroyedCollectionCount<T> =815 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;816817 /// Storage of collection info.818 #[pallet::storage]819 pub type CollectionById<T> = StorageMap<820 Hasher = Blake2_128Concat,821 Key = CollectionId,822 Value = Collection<<T as frame_system::Config>::AccountId>,823 QueryKind = OptionQuery,824 >;825826 /// Storage of collection properties.827 #[pallet::storage]828 #[pallet::getter(fn collection_properties)]829 pub type CollectionProperties<T> = StorageMap<830 Hasher = Blake2_128Concat,831 Key = CollectionId,832 Value = Properties,833 QueryKind = ValueQuery,834 OnEmpty = up_data_structs::CollectionProperties,835 >;836837 /// Storage of token property permissions of a collection.838 #[pallet::storage]839 #[pallet::getter(fn property_permissions)]840 pub type CollectionPropertyPermissions<T> = StorageMap<841 Hasher = Blake2_128Concat,842 Key = CollectionId,843 Value = PropertiesPermissionMap,844 QueryKind = ValueQuery,845 >;846847 /// Storage of the amount of collection admins.848 #[pallet::storage]849 pub type AdminAmount<T> = StorageMap<850 Hasher = Blake2_128Concat,851 Key = CollectionId,852 Value = u32,853 QueryKind = ValueQuery,854 >;855856 /// List of collection admins.857 #[pallet::storage]858 pub type IsAdmin<T: Config> = StorageNMap<859 Key = (860 Key<Blake2_128Concat, CollectionId>,861 Key<Blake2_128Concat, T::CrossAccountId>,862 ),863 Value = bool,864 QueryKind = ValueQuery,865 >;866867 /// Allowlisted collection users.868 #[pallet::storage]869 pub type Allowlist<T: Config> = StorageNMap<870 Key = (871 Key<Blake2_128Concat, CollectionId>,872 Key<Blake2_128Concat, T::CrossAccountId>,873 ),874 Value = bool,875 QueryKind = ValueQuery,876 >;877878 /// Not used by code, exists only to provide some types to metadata.879 #[pallet::storage]880 pub type DummyStorageValue<T: Config> = StorageValue<881 Value = (882 CollectionStats,883 CollectionId,884 TokenId,885 TokenChild,886 PhantomType<(887 TokenData<T::CrossAccountId>,888 RpcCollection<T::AccountId>,889 // RMRK890 RmrkCollectionInfo<T::AccountId>,891 RmrkInstanceInfo<T::AccountId>,892 RmrkResourceInfo,893 RmrkPropertyInfo,894 RmrkBaseInfo<T::AccountId>,895 RmrkPartType,896 RmrkBoundedTheme,897 RmrkNftChild,898 // PoV Estimate Info899 PovInfo,900 )>,901 ),902 QueryKind = OptionQuery,903 >;904905 #[pallet::hooks]906 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {907 fn on_runtime_upgrade() -> Weight {908 StorageVersion::new(1).put::<Pallet<T>>();909910 Weight::zero()911 }912 }913}914915impl<T: Config> Pallet<T> {916 /// Enshure that receiver address is correct.917 ///918 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.919 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {920 ensure!(921 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,922 <Error<T>>::AddressIsZero923 );924 Ok(())925 }926927 /// Get a vector of collection admins.928 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {929 <IsAdmin<T>>::iter_prefix((collection,))930 .map(|(a, _)| a)931 .collect()932 }933934 /// Get a vector of users allowed to mint tokens.935 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {936 <Allowlist<T>>::iter_prefix((collection,))937 .map(|(a, _)| a)938 .collect()939 }940941 /// Is `user` allowed to mint token in `collection`.942 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {943 <Allowlist<T>>::get((collection, user))944 }945946 /// Get statistics of collections.947 pub fn collection_stats() -> CollectionStats {948 let created = <CreatedCollectionCount<T>>::get();949 let destroyed = <DestroyedCollectionCount<T>>::get();950 CollectionStats {951 created: created.0,952 destroyed: destroyed.0,953 alive: created.0 - destroyed.0,954 }955 }956957 /// Get the effective limits for the collection.958 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {959 let collection = <CollectionById<T>>::get(collection)?;960 let limits = collection.limits;961 let effective_limits = CollectionLimits {962 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),963 sponsored_data_size: Some(limits.sponsored_data_size()),964 sponsored_data_rate_limit: Some(965 limits966 .sponsored_data_rate_limit967 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),968 ),969 token_limit: Some(limits.token_limit()),970 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(971 match collection.mode {972 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,973 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,974 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,975 },976 )),977 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),978 owner_can_transfer: Some(limits.owner_can_transfer()),979 owner_can_destroy: Some(limits.owner_can_destroy()),980 transfers_enabled: Some(limits.transfers_enabled()),981 };982983 Some(effective_limits)984 }985986 /// Returns information about the `collection` adapted for rpc.987 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {988 let Collection {989 name,990 description,991 owner,992 mode,993 token_prefix,994 sponsorship,995 limits,996 permissions,997 flags,998 } = <CollectionById<T>>::get(collection)?;9991000 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1001 .into_iter()1002 .map(|(key, permission)| PropertyKeyPermission { key, permission })1003 .collect();10041005 let properties = <CollectionProperties<T>>::get(collection)1006 .into_iter()1007 .map(|(key, value)| Property { key, value })1008 .collect();10091010 let permissions = CollectionPermissions {1011 access: Some(permissions.access()),1012 mint_mode: Some(permissions.mint_mode()),1013 nesting: Some(permissions.nesting().clone()),1014 };10151016 Some(RpcCollection {1017 name: name.into_inner(),1018 description: description.into_inner(),1019 owner,1020 mode,1021 token_prefix: token_prefix.into_inner(),1022 sponsorship,1023 limits,1024 permissions,1025 token_property_permissions,1026 properties,1027 read_only: flags.external,10281029 flags: RpcCollectionFlags {1030 foreign: flags.foreign,1031 erc721metadata: flags.erc721metadata,1032 },1033 })1034 }1035}10361037macro_rules! limit_default {1038 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1039 $(1040 if let Some($new) = $new.$field {1041 let $old = $old.$field($($arg)?);1042 let _ = $new;1043 let _ = $old;1044 $check1045 } else {1046 $new.$field = $old.$field1047 }1048 )*1049 }};1050}1051macro_rules! limit_default_clone {1052 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1053 $(1054 if let Some($new) = $new.$field.clone() {1055 let $old = $old.$field($($arg)?);1056 let _ = $new;1057 let _ = $old;1058 $check1059 } else {1060 $new.$field = $old.$field.clone()1061 }1062 )*1063 }};1064}10651066impl<T: Config> Pallet<T> {1067 /// Create new collection.1068 ///1069 /// * `owner` - The owner of the collection.1070 /// * `data` - Description of the created collection.1071 /// * `flags` - Extra flags to store.1072 pub fn init_collection(1073 owner: T::CrossAccountId,1074 payer: T::CrossAccountId,1075 data: CreateCollectionData<T::AccountId>,1076 flags: CollectionFlags,1077 ) -> Result<CollectionId, DispatchError> {1078 {1079 ensure!(1080 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1081 Error::<T>::CollectionTokenPrefixLimitExceeded1082 );1083 }10841085 let created_count = <CreatedCollectionCount<T>>::get()1086 .01087 .checked_add(1)1088 .ok_or(ArithmeticError::Overflow)?;1089 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1090 let id = CollectionId(created_count);10911092 // bound Total number of collections1093 ensure!(1094 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1095 <Error<T>>::TotalCollectionsLimitExceeded1096 );10971098 // =========10991100 let collection = Collection {1101 owner: owner.as_sub().clone(),1102 name: data.name,1103 mode: data.mode.clone(),1104 description: data.description,1105 token_prefix: data.token_prefix,1106 sponsorship: data1107 .pending_sponsor1108 .map(SponsorshipState::Unconfirmed)1109 .unwrap_or_default(),1110 limits: data1111 .limits1112 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1113 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1114 permissions: data1115 .permissions1116 .map(|permissions| {1117 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1118 })1119 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1120 flags,1121 };11221123 let mut collection_properties = up_data_structs::CollectionProperties::get();1124 collection_properties1125 .try_set_from_iter(data.properties.into_iter())1126 .map_err(<Error<T>>::from)?;11271128 CollectionProperties::<T>::insert(id, collection_properties);11291130 let mut token_props_permissions = PropertiesPermissionMap::new();1131 token_props_permissions1132 .try_set_from_iter(data.token_property_permissions.into_iter())1133 .map_err(<Error<T>>::from)?;11341135 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11361137 // Take a (non-refundable) deposit of collection creation1138 {1139 let mut imbalance =1140 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1141 imbalance.subsume(1142 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1143 &T::TreasuryAccountId::get(),1144 T::CollectionCreationPrice::get(),1145 ),1146 );1147 <T as Config>::Currency::settle(1148 payer.as_sub(),1149 imbalance,1150 WithdrawReasons::TRANSFER,1151 ExistenceRequirement::KeepAlive,1152 )1153 .map_err(|_| Error::<T>::NotSufficientFounds)?;1154 }11551156 <CreatedCollectionCount<T>>::put(created_count);1157 <Pallet<T>>::deposit_event(Event::CollectionCreated(1158 id,1159 data.mode.id(),1160 owner.as_sub().clone(),1161 ));1162 <PalletEvm<T>>::deposit_log(1163 erc::CollectionHelpersEvents::CollectionCreated {1164 owner: *owner.as_eth(),1165 collection_id: eth::collection_id_to_address(id),1166 }1167 .to_log(T::ContractAddress::get()),1168 );1169 <CollectionById<T>>::insert(id, collection);1170 Ok(id)1171 }11721173 /// Destroy collection.1174 ///1175 /// * `collection` - Collection handler.1176 /// * `sender` - The owner or administrator of the collection.1177 pub fn destroy_collection(1178 collection: CollectionHandle<T>,1179 sender: &T::CrossAccountId,1180 ) -> DispatchResult {1181 ensure!(1182 collection.limits.owner_can_destroy(),1183 <Error<T>>::NoPermission,1184 );1185 collection.check_is_owner(sender)?;11861187 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1188 .01189 .checked_add(1)1190 .ok_or(ArithmeticError::Overflow)?;11911192 // =========11931194 <DestroyedCollectionCount<T>>::put(destroyed_collections);1195 <CollectionById<T>>::remove(collection.id);1196 <AdminAmount<T>>::remove(collection.id);1197 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1198 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1199 <CollectionProperties<T>>::remove(collection.id);12001201 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12021203 <PalletEvm<T>>::deposit_log(1204 erc::CollectionHelpersEvents::CollectionDestroyed {1205 collection_id: eth::collection_id_to_address(collection.id),1206 }1207 .to_log(T::ContractAddress::get()),1208 );1209 Ok(())1210 }12111212 /// This function sets or removes a collection properties according to1213 /// `properties_updates` contents:1214 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1215 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1216 ///1217 /// This function fires an event for each property change.1218 /// In case of an error, all the changes (including the events) will be reverted1219 /// since the function is transactional.1220 #[transactional]1221 fn modify_collection_properties(1222 collection: &CollectionHandle<T>,1223 sender: &T::CrossAccountId,1224 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1225 ) -> DispatchResult {1226 collection.check_is_owner_or_admin(sender)?;12271228 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12291230 for (key, value) in properties_updates {1231 match value {1232 Some(value) => {1233 stored_properties1234 .try_set(key.clone(), value)1235 .map_err(<Error<T>>::from)?;12361237 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1238 <PalletEvm<T>>::deposit_log(1239 erc::CollectionHelpersEvents::CollectionChanged {1240 collection_id: eth::collection_id_to_address(collection.id),1241 }1242 .to_log(T::ContractAddress::get()),1243 );1244 }1245 None => {1246 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12471248 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1249 <PalletEvm<T>>::deposit_log(1250 erc::CollectionHelpersEvents::CollectionChanged {1251 collection_id: eth::collection_id_to_address(collection.id),1252 }1253 .to_log(T::ContractAddress::get()),1254 );1255 }1256 }1257 }12581259 <CollectionProperties<T>>::set(collection.id, stored_properties);12601261 Ok(())1262 }12631264 /// Set collection property.1265 ///1266 /// * `collection` - Collection handler.1267 /// * `sender` - The owner or administrator of the collection.1268 /// * `property` - The property to set.1269 pub fn set_collection_property(1270 collection: &CollectionHandle<T>,1271 sender: &T::CrossAccountId,1272 property: Property,1273 ) -> DispatchResult {1274 Self::set_collection_properties(collection, sender, [property].into_iter())1275 }12761277 /// Set a scoped collection property, where the scope is a special prefix1278 /// prohibiting a user access to change the property directly.1279 ///1280 /// * `collection_id` - ID of the collection for which the property is being set.1281 /// * `scope` - Property scope.1282 /// * `property` - The property to set.1283 pub fn set_scoped_collection_property(1284 collection_id: CollectionId,1285 scope: PropertyScope,1286 property: Property,1287 ) -> DispatchResult {1288 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1289 properties.try_scoped_set(scope, property.key, property.value)1290 })1291 .map_err(<Error<T>>::from)?;12921293 Ok(())1294 }12951296 /// Set scoped collection properties, where the scope is a special prefix1297 /// prohibiting a user access to change the properties directly.1298 ///1299 /// * `collection_id` - ID of the collection for which the properties is being set.1300 /// * `scope` - Property scope.1301 /// * `properties` - The properties to set.1302 pub fn set_scoped_collection_properties(1303 collection_id: CollectionId,1304 scope: PropertyScope,1305 properties: impl Iterator<Item = Property>,1306 ) -> DispatchResult {1307 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1308 stored_properties.try_scoped_set_from_iter(scope, properties)1309 })1310 .map_err(<Error<T>>::from)?;13111312 Ok(())1313 }13141315 /// Set collection properties.1316 ///1317 /// * `collection` - Collection handler.1318 /// * `sender` - The owner or administrator of the collection.1319 /// * `properties` - The properties to set.1320 pub fn set_collection_properties(1321 collection: &CollectionHandle<T>,1322 sender: &T::CrossAccountId,1323 properties: impl Iterator<Item = Property>,1324 ) -> DispatchResult {1325 Self::modify_collection_properties(1326 collection,1327 sender,1328 properties.map(|property| (property.key, Some(property.value))),1329 )1330 }13311332 /// Delete collection property.1333 ///1334 /// * `collection` - Collection handler.1335 /// * `sender` - The owner or administrator of the collection.1336 /// * `property` - The property to delete.1337 pub fn delete_collection_property(1338 collection: &CollectionHandle<T>,1339 sender: &T::CrossAccountId,1340 property_key: PropertyKey,1341 ) -> DispatchResult {1342 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1343 }13441345 /// Delete collection properties.1346 ///1347 /// * `collection` - Collection handler.1348 /// * `sender` - The owner or administrator of the collection.1349 /// * `properties` - The properties to delete.1350 pub fn delete_collection_properties(1351 collection: &CollectionHandle<T>,1352 sender: &T::CrossAccountId,1353 property_keys: impl Iterator<Item = PropertyKey>,1354 ) -> DispatchResult {1355 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1356 }13571358 /// Set collection propetry permission without any checks.1359 ///1360 /// Used for migrations.1361 ///1362 /// * `collection` - Collection handler.1363 /// * `property_permissions` - Property permissions.1364 pub fn set_property_permission_unchecked(1365 collection: CollectionId,1366 property_permission: PropertyKeyPermission,1367 ) -> DispatchResult {1368 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1369 permissions.try_set(property_permission.key, property_permission.permission)1370 })1371 .map_err(<Error<T>>::from)?;1372 Ok(())1373 }13741375 /// Set collection property permission.1376 ///1377 /// * `collection` - Collection handler.1378 /// * `sender` - The owner or administrator of the collection.1379 /// * `property_permission` - Property permission.1380 pub fn set_property_permission(1381 collection: &CollectionHandle<T>,1382 sender: &T::CrossAccountId,1383 property_permission: PropertyKeyPermission,1384 ) -> DispatchResult {1385 Self::set_scoped_property_permission(1386 collection,1387 sender,1388 PropertyScope::None,1389 property_permission,1390 )1391 }13921393 /// Set collection property permission with scope.1394 ///1395 /// * `collection` - Collection handler.1396 /// * `sender` - The owner or administrator of the collection.1397 /// * `scope` - Property scope.1398 /// * `property_permission` - Property permission.1399 pub fn set_scoped_property_permission(1400 collection: &CollectionHandle<T>,1401 sender: &T::CrossAccountId,1402 scope: PropertyScope,1403 property_permission: PropertyKeyPermission,1404 ) -> DispatchResult {1405 collection.check_is_owner_or_admin(sender)?;14061407 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1408 let current_permission = all_permissions.get(&property_permission.key);1409 if matches![1410 current_permission,1411 Some(PropertyPermission { mutable: false, .. })1412 ] {1413 return Err(<Error<T>>::NoPermission.into());1414 }14151416 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1417 let property_permission = property_permission.clone();1418 permissions.try_scoped_set(1419 scope,1420 property_permission.key,1421 property_permission.permission,1422 )1423 })1424 .map_err(<Error<T>>::from)?;14251426 Self::deposit_event(Event::PropertyPermissionSet(1427 collection.id,1428 property_permission.key,1429 ));1430 <PalletEvm<T>>::deposit_log(1431 erc::CollectionHelpersEvents::CollectionChanged {1432 collection_id: eth::collection_id_to_address(collection.id),1433 }1434 .to_log(T::ContractAddress::get()),1435 );14361437 Ok(())1438 }14391440 /// Set token property permission.1441 ///1442 /// * `collection` - Collection handler.1443 /// * `sender` - The owner or administrator of the collection.1444 /// * `property_permissions` - Property permissions.1445 #[transactional]1446 pub fn set_token_property_permissions(1447 collection: &CollectionHandle<T>,1448 sender: &T::CrossAccountId,1449 property_permissions: Vec<PropertyKeyPermission>,1450 ) -> DispatchResult {1451 Self::set_scoped_token_property_permissions(1452 collection,1453 sender,1454 PropertyScope::None,1455 property_permissions,1456 )1457 }14581459 /// Set token property permission with scope.1460 ///1461 /// * `collection` - Collection handler.1462 /// * `sender` - The owner or administrator of the collection.1463 /// * `scope` - Property scope.1464 /// * `property_permissions` - Property permissions.1465 #[transactional]1466 pub fn set_scoped_token_property_permissions(1467 collection: &CollectionHandle<T>,1468 sender: &T::CrossAccountId,1469 scope: PropertyScope,1470 property_permissions: Vec<PropertyKeyPermission>,1471 ) -> DispatchResult {1472 for prop_pemission in property_permissions {1473 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1474 }14751476 Ok(())1477 }14781479 /// Get collection property.1480 pub fn get_collection_property(1481 collection_id: CollectionId,1482 key: &PropertyKey,1483 ) -> Option<PropertyValue> {1484 Self::collection_properties(collection_id).get(key).cloned()1485 }14861487 /// Convert byte vector to property key vector.1488 pub fn bytes_keys_to_property_keys(1489 keys: Vec<Vec<u8>>,1490 ) -> Result<Vec<PropertyKey>, DispatchError> {1491 keys.into_iter()1492 .map(|key| -> Result<PropertyKey, DispatchError> {1493 key.try_into()1494 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1495 })1496 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1497 }14981499 /// Get properties according to given keys.1500 pub fn filter_collection_properties(1501 collection_id: CollectionId,1502 keys: Option<Vec<PropertyKey>>,1503 ) -> Result<Vec<Property>, DispatchError> {1504 let properties = Self::collection_properties(collection_id);15051506 let properties = keys1507 .map(|keys| {1508 keys.into_iter()1509 .filter_map(|key| {1510 properties.get(&key).map(|value| Property {1511 key,1512 value: value.clone(),1513 })1514 })1515 .collect()1516 })1517 .unwrap_or_else(|| {1518 properties1519 .into_iter()1520 .map(|(key, value)| Property { key, value })1521 .collect()1522 });15231524 Ok(properties)1525 }15261527 /// Get property permissions according to given keys.1528 pub fn filter_property_permissions(1529 collection_id: CollectionId,1530 keys: Option<Vec<PropertyKey>>,1531 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1532 let permissions = Self::property_permissions(collection_id);15331534 let key_permissions = keys1535 .map(|keys| {1536 keys.into_iter()1537 .filter_map(|key| {1538 permissions1539 .get(&key)1540 .map(|permission| PropertyKeyPermission {1541 key,1542 permission: permission.clone(),1543 })1544 })1545 .collect()1546 })1547 .unwrap_or_else(|| {1548 permissions1549 .into_iter()1550 .map(|(key, permission)| PropertyKeyPermission { key, permission })1551 .collect()1552 });15531554 Ok(key_permissions)1555 }15561557 /// Toggle `user` participation in the `collection`'s allow list.1558 /// #### Store read/writes1559 /// 1 writes1560 pub fn toggle_allowlist(1561 collection: &CollectionHandle<T>,1562 sender: &T::CrossAccountId,1563 user: &T::CrossAccountId,1564 allowed: bool,1565 ) -> DispatchResult {1566 collection.check_is_owner_or_admin(sender)?;15671568 // =========15691570 if allowed {1571 <Allowlist<T>>::insert((collection.id, user), true);1572 Self::deposit_event(Event::<T>::AllowListAddressAdded(1573 collection.id,1574 user.clone(),1575 ));1576 } else {1577 <Allowlist<T>>::remove((collection.id, user));1578 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1579 collection.id,1580 user.clone(),1581 ));1582 }15831584 <PalletEvm<T>>::deposit_log(1585 erc::CollectionHelpersEvents::CollectionChanged {1586 collection_id: eth::collection_id_to_address(collection.id),1587 }1588 .to_log(T::ContractAddress::get()),1589 );15901591 Ok(())1592 }15931594 /// Toggle `user` participation in the `collection`'s admin list.1595 /// #### Store read/writes1596 /// 2 reads, 2 writes1597 pub fn toggle_admin(1598 collection: &CollectionHandle<T>,1599 sender: &T::CrossAccountId,1600 user: &T::CrossAccountId,1601 admin: bool,1602 ) -> DispatchResult {1603 collection.check_is_internal()?;1604 collection.check_is_owner(sender)?;16051606 let is_admin = <IsAdmin<T>>::get((collection.id, user));1607 if is_admin == admin {1608 if admin {1609 return Ok(());1610 } else {1611 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1612 }1613 }1614 let amount = <AdminAmount<T>>::get(collection.id);16151616 // =========16171618 if admin {1619 let amount = amount1620 .checked_add(1)1621 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1622 ensure!(1623 amount <= Self::collection_admins_limit(),1624 <Error<T>>::CollectionAdminCountExceeded,1625 );16261627 <AdminAmount<T>>::insert(collection.id, amount);1628 <IsAdmin<T>>::insert((collection.id, user), true);16291630 Self::deposit_event(Event::<T>::CollectionAdminAdded(1631 collection.id,1632 user.clone(),1633 ));1634 } else {1635 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1636 <IsAdmin<T>>::remove((collection.id, user));16371638 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1639 collection.id,1640 user.clone(),1641 ));1642 }16431644 <PalletEvm<T>>::deposit_log(1645 erc::CollectionHelpersEvents::CollectionChanged {1646 collection_id: eth::collection_id_to_address(collection.id),1647 }1648 .to_log(T::ContractAddress::get()),1649 );16501651 Ok(())1652 }16531654 /// Update collection limits.1655 pub fn update_limits(1656 user: &T::CrossAccountId,1657 collection: &mut CollectionHandle<T>,1658 new_limit: CollectionLimits,1659 ) -> DispatchResult {1660 collection.check_is_internal()?;1661 collection.check_is_owner_or_admin(user)?;16621663 collection.limits =1664 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;16651666 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1667 <PalletEvm<T>>::deposit_log(1668 erc::CollectionHelpersEvents::CollectionChanged {1669 collection_id: eth::collection_id_to_address(collection.id),1670 }1671 .to_log(T::ContractAddress::get()),1672 );16731674 collection.save()1675 }16761677 /// Merge set fields from `new_limit` to `old_limit`.1678 fn clamp_limits(1679 mode: CollectionMode,1680 old_limit: &CollectionLimits,1681 mut new_limit: CollectionLimits,1682 ) -> Result<CollectionLimits, DispatchError> {1683 let limits = old_limit;1684 limit_default!(old_limit, new_limit,1685 account_token_ownership_limit => ensure!(1686 new_limit <= MAX_TOKEN_OWNERSHIP,1687 <Error<T>>::CollectionLimitBoundsExceeded,1688 ),1689 sponsored_data_size => ensure!(1690 new_limit <= CUSTOM_DATA_LIMIT,1691 <Error<T>>::CollectionLimitBoundsExceeded,1692 ),16931694 sponsored_data_rate_limit => {},1695 token_limit => ensure!(1696 old_limit >= new_limit && new_limit > 0,1697 <Error<T>>::CollectionTokenLimitExceeded1698 ),16991700 sponsor_transfer_timeout(match mode {1701 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1702 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1703 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1704 }) => ensure!(1705 new_limit <= MAX_SPONSOR_TIMEOUT,1706 <Error<T>>::CollectionLimitBoundsExceeded,1707 ),1708 sponsor_approve_timeout => {},1709 owner_can_transfer => ensure!(1710 !limits.owner_can_transfer_instaled() ||1711 old_limit || !new_limit,1712 <Error<T>>::OwnerPermissionsCantBeReverted,1713 ),1714 owner_can_destroy => ensure!(1715 old_limit || !new_limit,1716 <Error<T>>::OwnerPermissionsCantBeReverted,1717 ),1718 transfers_enabled => {},1719 );1720 Ok(new_limit)1721 }17221723 /// Update collection permissions.1724 pub fn update_permissions(1725 user: &T::CrossAccountId,1726 collection: &mut CollectionHandle<T>,1727 new_permission: CollectionPermissions,1728 ) -> DispatchResult {1729 collection.check_is_internal()?;1730 collection.check_is_owner_or_admin(user)?;1731 collection.permissions = Self::clamp_permissions(1732 collection.mode.clone(),1733 &collection.permissions,1734 new_permission,1735 )?;17361737 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1738 <PalletEvm<T>>::deposit_log(1739 erc::CollectionHelpersEvents::CollectionChanged {1740 collection_id: eth::collection_id_to_address(collection.id),1741 }1742 .to_log(T::ContractAddress::get()),1743 );17441745 collection.save()1746 }17471748 /// Merge set fields from `new_permission` to `old_permission`.1749 fn clamp_permissions(1750 _mode: CollectionMode,1751 old_permission: &CollectionPermissions,1752 mut new_permission: CollectionPermissions,1753 ) -> Result<CollectionPermissions, DispatchError> {1754 limit_default_clone!(old_permission, new_permission,1755 access => {},1756 mint_mode => {},1757 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1758 );1759 Ok(new_permission)1760 }17611762 /// Repair possibly broken properties of a collection.1763 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1764 CollectionProperties::<T>::mutate(collection_id, |properties| {1765 properties.recompute_consumed_space();1766 });17671768 Ok(())1769 }1770}17711772/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1773#[macro_export]1774macro_rules! unsupported {1775 ($runtime:path) => {1776 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1777 };1778}17791780/// Return weights for various worst-case operations.1781pub trait CommonWeightInfo<CrossAccountId> {1782 /// Weight of item creation.1783 fn create_item() -> Weight;17841785 /// Weight of items creation.1786 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;17871788 /// Weight of items creation.1789 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;17901791 /// The weight of the burning item.1792 fn burn_item() -> Weight;17931794 /// Property setting weight.1795 ///1796 /// * `amount`- The number of properties to set.1797 fn set_collection_properties(amount: u32) -> Weight;17981799 /// Collection property deletion weight.1800 ///1801 /// * `amount`- The number of properties to set.1802 fn delete_collection_properties(amount: u32) -> Weight;18031804 /// Token property setting weight.1805 ///1806 /// * `amount`- The number of properties to set.1807 fn set_token_properties(amount: u32) -> Weight;18081809 /// Token property deletion weight.1810 ///1811 /// * `amount`- The number of properties to delete.1812 fn delete_token_properties(amount: u32) -> Weight;18131814 /// Token property permissions set weight.1815 ///1816 /// * `amount`- The number of property permissions to set.1817 fn set_token_property_permissions(amount: u32) -> Weight;18181819 /// Transfer price of the token or its parts.1820 fn transfer() -> Weight;18211822 /// The price of setting the permission of the operation from another user.1823 fn approve() -> Weight;18241825 /// The price of setting the permission of the operation from another user for eth mirror.1826 fn approve_from() -> Weight;18271828 /// Transfer price from another user.1829 fn transfer_from() -> Weight;18301831 /// The price of burning a token from another user.1832 fn burn_from() -> Weight;18331834 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1835 /// whole users's balance.1836 ///1837 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1838 fn burn_recursively_self_raw() -> Weight;18391840 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1841 ///1842 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1843 fn burn_recursively_breadth_raw(amount: u32) -> Weight;18441845 /// The price of recursive burning a token.1846 ///1847 /// `max_selfs` - The maximum burning weight of the token itself.1848 /// `max_breadth` - The maximum number of nested tokens to burn.1849 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1850 Self::burn_recursively_self_raw()1851 .saturating_mul(max_selfs.max(1) as u64)1852 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1853 }18541855 /// The price of retrieving token owner1856 fn token_owner() -> Weight;18571858 /// The price of setting approval for all1859 fn set_allowance_for_all() -> Weight;18601861 /// The price of repairing an item.1862 fn force_repair_item() -> Weight;1863}18641865/// Weight info extension trait for refungible pallet.1866pub trait RefungibleExtensionsWeightInfo {1867 /// Weight of token repartition.1868 fn repartition() -> Weight;1869}18701871/// Common collection operations.1872///1873/// It wraps methods in Fungible, Nonfungible and Refungible pallets1874/// and adds weight info.1875pub trait CommonCollectionOperations<T: Config> {1876 /// Create token.1877 ///1878 /// * `sender` - The user who mint the token and pays for the transaction.1879 /// * `to` - The user who will own the token.1880 /// * `data` - Token data.1881 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1882 fn create_item(1883 &self,1884 sender: T::CrossAccountId,1885 to: T::CrossAccountId,1886 data: CreateItemData,1887 nesting_budget: &dyn Budget,1888 ) -> DispatchResultWithPostInfo;18891890 /// Create multiple tokens.1891 ///1892 /// * `sender` - The user who mint the token and pays for the transaction.1893 /// * `to` - The user who will own the token.1894 /// * `data` - Token data.1895 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1896 fn create_multiple_items(1897 &self,1898 sender: T::CrossAccountId,1899 to: T::CrossAccountId,1900 data: Vec<CreateItemData>,1901 nesting_budget: &dyn Budget,1902 ) -> DispatchResultWithPostInfo;19031904 /// Create multiple tokens.1905 ///1906 /// * `sender` - The user who mint the token and pays for the transaction.1907 /// * `to` - The user who will own the token.1908 /// * `data` - Token data.1909 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1910 fn create_multiple_items_ex(1911 &self,1912 sender: T::CrossAccountId,1913 data: CreateItemExData<T::CrossAccountId>,1914 nesting_budget: &dyn Budget,1915 ) -> DispatchResultWithPostInfo;19161917 /// Burn token.1918 ///1919 /// * `sender` - The user who owns the token.1920 /// * `token` - Token id that will burned.1921 /// * `amount` - The number of parts of the token that will be burned.1922 fn burn_item(1923 &self,1924 sender: T::CrossAccountId,1925 token: TokenId,1926 amount: u128,1927 ) -> DispatchResultWithPostInfo;19281929 /// Burn token and all nested tokens recursievly.1930 ///1931 /// * `sender` - The user who owns the token.1932 /// * `token` - Token id that will burned.1933 /// * `self_budget` - The budget that can be spent on burning tokens.1934 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.1935 fn burn_item_recursively(1936 &self,1937 sender: T::CrossAccountId,1938 token: TokenId,1939 self_budget: &dyn Budget,1940 breadth_budget: &dyn Budget,1941 ) -> DispatchResultWithPostInfo;19421943 /// Set collection properties.1944 ///1945 /// * `sender` - Must be either the owner of the collection or its admin.1946 /// * `properties` - Properties to be set.1947 fn set_collection_properties(1948 &self,1949 sender: T::CrossAccountId,1950 properties: Vec<Property>,1951 ) -> DispatchResultWithPostInfo;19521953 /// Delete collection properties.1954 ///1955 /// * `sender` - Must be either the owner of the collection or its admin.1956 /// * `properties` - The properties to be removed.1957 fn delete_collection_properties(1958 &self,1959 sender: &T::CrossAccountId,1960 property_keys: Vec<PropertyKey>,1961 ) -> DispatchResultWithPostInfo;19621963 /// Set token properties.1964 ///1965 /// The appropriate [`PropertyPermission`] for the token property1966 /// must be set with [`Self::set_token_property_permissions`].1967 ///1968 /// * `sender` - Must be either the owner of the token or its admin.1969 /// * `token_id` - The token for which the properties are being set.1970 /// * `properties` - Properties to be set.1971 /// * `budget` - Budget for setting properties.1972 fn set_token_properties(1973 &self,1974 sender: T::CrossAccountId,1975 token_id: TokenId,1976 properties: Vec<Property>,1977 budget: &dyn Budget,1978 ) -> DispatchResultWithPostInfo;19791980 /// Remove token properties.1981 ///1982 /// The appropriate [`PropertyPermission`] for the token property1983 /// must be set with [`Self::set_token_property_permissions`].1984 ///1985 /// * `sender` - Must be either the owner of the token or its admin.1986 /// * `token_id` - The token for which the properties are being remove.1987 /// * `property_keys` - Keys to remove corresponding properties.1988 /// * `budget` - Budget for removing properties.1989 fn delete_token_properties(1990 &self,1991 sender: T::CrossAccountId,1992 token_id: TokenId,1993 property_keys: Vec<PropertyKey>,1994 budget: &dyn Budget,1995 ) -> DispatchResultWithPostInfo;19961997 /// Set token property permissions.1998 ///1999 /// * `sender` - Must be either the owner of the token or its admin.2000 /// * `token_id` - The token for which the properties are being set.2001 /// * `property_permissions` - Property permissions to be set.2002 /// * `budget` - Budget for setting properties.2003 fn set_token_property_permissions(2004 &self,2005 sender: &T::CrossAccountId,2006 property_permissions: Vec<PropertyKeyPermission>,2007 ) -> DispatchResultWithPostInfo;20082009 /// Transfer amount of token pieces.2010 ///2011 /// * `sender` - Donor user.2012 /// * `to` - Recepient user.2013 /// * `token` - The token of which parts are being sent.2014 /// * `amount` - The number of parts of the token that will be transferred.2015 /// * `budget` - The maximum budget that can be spent on the transfer.2016 fn transfer(2017 &self,2018 sender: T::CrossAccountId,2019 to: T::CrossAccountId,2020 token: TokenId,2021 amount: u128,2022 budget: &dyn Budget,2023 ) -> DispatchResultWithPostInfo;20242025 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2026 ///2027 /// * `sender` - The user who grants access to the token.2028 /// * `spender` - The user to whom the rights are granted.2029 /// * `token` - The token to which access is granted.2030 /// * `amount` - The amount of pieces that another user can dispose of.2031 fn approve(2032 &self,2033 sender: T::CrossAccountId,2034 spender: T::CrossAccountId,2035 token: TokenId,2036 amount: u128,2037 ) -> DispatchResultWithPostInfo;20382039 /// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2040 ///2041 /// * `sender` - The user who grants access to the token.2042 /// * `from` - Spender's eth mirror.2043 /// * `to` - The user to whom the rights are granted.2044 /// * `token` - The token to which access is granted.2045 /// * `amount` - The amount of pieces that another user can dispose of.2046 fn approve_from(2047 &self,2048 sender: T::CrossAccountId,2049 from: T::CrossAccountId,2050 to: T::CrossAccountId,2051 token: TokenId,2052 amount: u128,2053 ) -> DispatchResultWithPostInfo;20542055 /// Send parts of a token owned by another user.2056 ///2057 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2058 ///2059 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2060 /// * `from` - The user who owns the token.2061 /// * `to` - Recepient user.2062 /// * `token` - The token of which parts are being sent.2063 /// * `amount` - The number of parts of the token that will be transferred.2064 /// * `budget` - The maximum budget that can be spent on the transfer.2065 fn transfer_from(2066 &self,2067 sender: T::CrossAccountId,2068 from: T::CrossAccountId,2069 to: T::CrossAccountId,2070 token: TokenId,2071 amount: u128,2072 budget: &dyn Budget,2073 ) -> DispatchResultWithPostInfo;20742075 /// Burn parts of a token owned by another user.2076 ///2077 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2078 ///2079 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2080 /// * `from` - The user who owns the token.2081 /// * `token` - The token of which parts are being sent.2082 /// * `amount` - The number of parts of the token that will be transferred.2083 /// * `budget` - The maximum budget that can be spent on the burn.2084 fn burn_from(2085 &self,2086 sender: T::CrossAccountId,2087 from: T::CrossAccountId,2088 token: TokenId,2089 amount: u128,2090 budget: &dyn Budget,2091 ) -> DispatchResultWithPostInfo;20922093 /// Check permission to nest token.2094 ///2095 /// * `sender` - The user who initiated the check.2096 /// * `from` - The token that is checked for embedding.2097 /// * `under` - Token under which to check.2098 /// * `budget` - The maximum budget that can be spent on the check.2099 fn check_nesting(2100 &self,2101 sender: T::CrossAccountId,2102 from: (CollectionId, TokenId),2103 under: TokenId,2104 budget: &dyn Budget,2105 ) -> DispatchResult;21062107 /// Nest one token into another.2108 ///2109 /// * `under` - Token holder.2110 /// * `to_nest` - Nested token.2111 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21122113 /// Unnest token.2114 ///2115 /// * `under` - Token holder.2116 /// * `to_nest` - Token to unnest.2117 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21182119 /// Get all user tokens.2120 ///2121 /// * `account` - Account for which you need to get tokens.2122 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;21232124 /// Get all the tokens in the collection.2125 fn collection_tokens(&self) -> Vec<TokenId>;21262127 /// Check if the token exists.2128 ///2129 /// * `token` - Id token to check.2130 fn token_exists(&self, token: TokenId) -> bool;21312132 /// Get the id of the last minted token.2133 fn last_token_id(&self) -> TokenId;21342135 /// Get the owner of the token.2136 ///2137 /// * `token` - The token for which you need to find out the owner.2138 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;21392140 /// Returns 10 tokens owners in no particular order.2141 ///2142 /// * `token` - The token for which you need to find out the owners.2143 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;21442145 /// Get the value of the token property by key.2146 ///2147 /// * `token` - Token with the property to get.2148 /// * `key` - Property name.2149 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;21502151 /// Get a set of token properties by key vector.2152 ///2153 /// * `token` - Token with the property to get.2154 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2155 /// then all properties are returned.2156 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;21572158 /// Amount of unique collection tokens2159 fn total_supply(&self) -> u32;21602161 /// Amount of different tokens account has.2162 ///2163 /// * `account` - The account for which need to get the balance.2164 fn account_balance(&self, account: T::CrossAccountId) -> u32;21652166 /// Amount of specific token account have.2167 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;21682169 /// Amount of token pieces2170 fn total_pieces(&self, token: TokenId) -> Option<u128>;21712172 /// Get the number of parts of the token that a trusted user can manage.2173 ///2174 /// * `sender` - Trusted user.2175 /// * `spender` - Owner of the token.2176 /// * `token` - The token for which to get the value.2177 fn allowance(2178 &self,2179 sender: T::CrossAccountId,2180 spender: T::CrossAccountId,2181 token: TokenId,2182 ) -> u128;21832184 /// Get extension for RFT collection.2185 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;21862187 /// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2188 /// * `owner` - Token owner2189 /// * `operator` - Operator2190 /// * `approve` - Should operator status be granted or revoked?2191 fn set_allowance_for_all(2192 &self,2193 owner: T::CrossAccountId,2194 operator: T::CrossAccountId,2195 approve: bool,2196 ) -> DispatchResultWithPostInfo;21972198 /// Tells whether the given `owner` approves the `operator`.2199 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;22002201 /// Repairs a possibly broken item.2202 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2203}22042205/// Extension for RFT collection.2206pub trait RefungibleExtensions<T>2207where2208 T: Config,2209{2210 /// Change the number of parts of the token.2211 ///2212 /// When the value changes down, this function is equivalent to burning parts of the token.2213 ///2214 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.2215 /// * `token` - The token for which you want to change the number of parts.2216 /// * `amount` - The new value of the parts of the token.2217 fn repartition(2218 &self,2219 sender: &T::CrossAccountId,2220 token: TokenId,2221 amount: u128,2222 ) -> DispatchResultWithPostInfo;2223}22242225/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2226///2227/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2228pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2229 let post_info = PostDispatchInfo {2230 actual_weight: Some(weight),2231 pays_fee: Pays::Yes,2232 };2233 match res {2234 Ok(()) => Ok(post_info),2235 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2236 }2237}22382239impl<T: Config> From<PropertiesError> for Error<T> {2240 fn from(error: PropertiesError) -> Self {2241 match error {2242 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2243 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2244 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2245 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2246 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2247 }2248 }2249}pallets/fungible/src/common.rsdiffbeforeafterboth--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -17,7 +17,9 @@
use core::marker::PhantomData;
use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight, traits::Get};
-use up_data_structs::{TokenId, CollectionId, CreateItemExData, budget::Budget, CreateItemData};
+use up_data_structs::{
+ TokenId, CollectionId, CreateItemExData, budget::Budget, CreateItemData, TokenOwnerError,
+};
use pallet_common::{
CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,
weights::WeightInfo as _,
@@ -404,8 +406,8 @@
TokenId::default()
}
- fn token_owner(&self, _token: TokenId) -> Option<T::CrossAccountId> {
- None
+ fn token_owner(&self, _token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError> {
+ Err(TokenOwnerError::MultipleOwners)
}
/// Returns 10 tokens owners in no particular order.
pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -19,7 +19,7 @@
use frame_support::{dispatch::DispatchResultWithPostInfo, ensure, fail, weights::Weight};
use up_data_structs::{
TokenId, CreateItemExData, CollectionId, budget::Budget, Property, PropertyKey,
- PropertyKeyPermission, PropertyValue,
+ PropertyKeyPermission, PropertyValue, TokenOwnerError,
};
use pallet_common::{
CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,
@@ -460,13 +460,15 @@
TokenId(<TokensMinted<T>>::get(self.id))
}
- fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId> {
- <TokenData<T>>::get((self.id, token)).map(|t| t.owner)
+ fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError> {
+ <TokenData<T>>::get((self.id, token))
+ .map(|t| t.owner)
+ .ok_or(TokenOwnerError::NotFound)
}
/// Returns token owners.
fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
- self.token_owner(token).map_or_else(|| vec![], |t| vec![t])
+ self.token_owner(token).map_or_else(|_| vec![], |t| vec![t])
}
fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue> {
pallets/nonfungible/src/erc.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/erc.rs
+++ b/pallets/nonfungible/src/erc.rs
@@ -728,7 +728,7 @@
fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {
Self::token_owner(&self, token_id.try_into()?)
.map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))
- .ok_or(Error::Revert("key too large".into()))
+ .map_err(|_| Error::Revert("token not found".into()))
}
/// Returns the token properties.
pallets/proxy-rmrk-core/src/lib.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/lib.rs
+++ b/pallets/proxy-rmrk-core/src/lib.rs
@@ -741,7 +741,8 @@
Some((collection_id, nft_id)),
&target_nft_budget,
)
- .map_err(Self::map_unique_err_to_proxy)?;
+ .map_err(Self::map_unique_err_to_proxy)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
approval_required = cross_sender != target_nft_owner;
@@ -989,7 +990,8 @@
let nft_owner =
<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)
- .map_err(|_| <Error<T>>::ResourceDoesntExist)?;
+ .map_err(|_| <Error<T>>::ResourceDoesntExist)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
Self::try_mutate_resource_info(collection_id, nft_id, resource_id, |res| {
ensure!(res.pending, <Error<T>>::ResourceNotPending);
@@ -1044,7 +1046,8 @@
let nft_owner =
<PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)
- .map_err(|_| <Error<T>>::ResourceDoesntExist)?;
+ .map_err(|_| <Error<T>>::ResourceDoesntExist)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
ensure!(cross_sender == nft_owner, <Error<T>>::NoPermission);
@@ -1666,7 +1669,8 @@
let budget = budget::Value::new(NESTING_BUDGET);
let nft_owner = <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)
- .map_err(Self::map_unique_err_to_proxy)?;
+ .map_err(Self::map_unique_err_to_proxy)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
let pending = sender != nft_owner;
@@ -1720,7 +1724,8 @@
let budget = up_data_structs::budget::Value::new(NESTING_BUDGET);
let topmost_owner =
- <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?;
+ <PalletStructure<T>>::find_topmost_owner(collection_id, nft_id, &budget)?
+ .ok_or::<DispatchError>(<Error<T>>::NoPermission.into())?;
let sender = T::CrossAccountId::from_sub(sender);
if topmost_owner == sender {
pallets/proxy-rmrk-core/src/rpc.rsdiffbeforeafterboth--- a/pallets/proxy-rmrk-core/src/rpc.rs
+++ b/pallets/proxy-rmrk-core/src/rpc.rs
@@ -68,7 +68,7 @@
}
let owner = match collection.token_owner(nft_id) {
- Some(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {
+ Ok(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {
Some((col, tok)) => {
let rmrk_collection = <Pallet<T>>::rmrk_collection_id(col)?;
@@ -76,7 +76,7 @@
}
None => RmrkAccountIdOrCollectionNftTuple::AccountId(owner.as_sub().clone()),
},
- None => return Ok(None),
+ _ => return Ok(None),
};
Ok(Some(RmrkInstanceInfo {
pallets/refungible/src/common.rsdiffbeforeafterboth--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -21,7 +21,7 @@
use up_data_structs::{
CollectionId, TokenId, CreateItemExData, budget::Budget, Property, PropertyKey, PropertyValue,
PropertyKeyPermission, CollectionPropertiesVec, CreateRefungibleExMultipleOwners,
- CreateRefungibleExSingleOwner,
+ CreateRefungibleExSingleOwner, TokenOwnerError,
};
use pallet_common::{
CommonCollectionOperations, CommonWeightInfo, RefungibleExtensions, with_weight,
@@ -478,7 +478,7 @@
TokenId(<TokensMinted<T>>::get(self.id))
}
- fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId> {
+ fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError> {
<Pallet<T>>::token_owner(self.id, token)
}
pallets/refungible/src/erc.rsdiffbeforeafterboth--- a/pallets/refungible/src/erc.rs
+++ b/pallets/refungible/src/erc.rs
@@ -43,7 +43,7 @@
use sp_std::{collections::btree_map::BTreeMap, vec::Vec, vec};
use up_data_structs::{
CollectionId, CollectionPropertiesVec, mapping::TokenAddressMapping, Property, PropertyKey,
- PropertyKeyPermission, PropertyPermission, TokenId,
+ PropertyKeyPermission, PropertyPermission, TokenId, TokenOwnerError,
};
use crate::{
@@ -411,9 +411,12 @@
self.consume_store_reads(2)?;
let token = token_id.try_into()?;
let owner = <Pallet<T>>::token_owner(self.id, token);
- Ok(owner
+ owner
.map(|address| *address.as_eth())
- .unwrap_or_else(|| ADDRESS_FOR_PARTIALLY_OWNED_TOKENS))
+ .or_else(|err| match err {
+ TokenOwnerError::NotFound => Err(Error::Revert("token not found".into())),
+ TokenOwnerError::MultipleOwners => Ok(ADDRESS_FOR_PARTIALLY_OWNED_TOKENS),
+ })
}
/// @dev Not implemented
@@ -766,7 +769,12 @@
fn cross_owner_of(&self, token_id: U256) -> Result<eth::CrossAddress> {
Self::token_owner(&self, token_id.try_into()?)
.map(|o| eth::CrossAddress::from_sub_cross_account::<T>(&o))
- .ok_or(Error::Revert("key too large".into()))
+ .or_else(|err| match err {
+ TokenOwnerError::NotFound => Err(Error::Revert("token not found".into())),
+ TokenOwnerError::MultipleOwners => Ok(eth::CrossAddress::from_eth(
+ ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,
+ )),
+ })
}
/// Returns the token properties.
pallets/refungible/src/lib.rsdiffbeforeafterboth--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -107,7 +107,7 @@
AccessMode, budget::Budget, CollectionId, CollectionFlags, CreateCollectionData,
mapping::TokenAddressMapping, MAX_REFUNGIBLE_PIECES, Property, PropertyKey,
PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue, TokenId,
- TrySetProperty, PropertiesPermissionMap, CreateRefungibleExMultipleOwners,
+ TrySetProperty, PropertiesPermissionMap, CreateRefungibleExMultipleOwners, TokenOwnerError,
};
pub use pallet::*;
@@ -480,7 +480,7 @@
<Balance<T>>::remove((collection.id, token, owner));
<AccountBalance<T>>::insert((collection.id, owner), account_balance);
- if let Some(user) = Self::token_owner(collection.id, token) {
+ if let Ok(user) = Self::token_owner(collection.id, token) {
<PalletEvm<T>>::deposit_log(
ERC721Events::Transfer {
from: erc::ADDRESS_FOR_PARTIALLY_OWNED_TOKENS,
@@ -1365,17 +1365,20 @@
Ok(())
}
- fn token_owner(collection_id: CollectionId, token_id: TokenId) -> Option<T::CrossAccountId> {
+ fn token_owner(
+ collection_id: CollectionId,
+ token_id: TokenId,
+ ) -> Result<T::CrossAccountId, TokenOwnerError> {
let mut owner = None;
let mut count = 0;
for key in Balance::<T>::iter_key_prefix((collection_id, token_id)) {
count += 1;
if count > 1 {
- return None;
+ return Err(TokenOwnerError::MultipleOwners);
}
owner = Some(key);
}
- owner
+ owner.ok_or(TokenOwnerError::NotFound)
}
fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128> {
pallets/structure/src/lib.rsdiffbeforeafterboth--- a/pallets/structure/src/lib.rs
+++ b/pallets/structure/src/lib.rs
@@ -61,7 +61,9 @@
use frame_support::fail;
pub use pallet::*;
use pallet_common::{dispatch::CollectionDispatch, CollectionHandle};
-use up_data_structs::{CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget};
+use up_data_structs::{
+ CollectionId, TokenId, mapping::TokenAddressMapping, budget::Budget, TokenOwnerError,
+};
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
@@ -135,6 +137,8 @@
User(CrossAccountId),
/// Could not find the token provided as the owner.
TokenNotFound,
+ /// Nested token has multiple owners.
+ MultipleOwners,
/// Token owner is another token (still, the target token may not exist).
Token(CollectionId, TokenId),
}
@@ -159,11 +163,12 @@
let handle = handle.as_dyn();
Ok(match handle.token_owner(token) {
- Some(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {
+ Ok(owner) => match T::CrossTokenAddressMapping::address_to_token(&owner) {
Some((collection, token)) => Parent::Token(collection, token),
None => Parent::User(owner),
},
- None => Parent::TokenNotFound,
+ Err(TokenOwnerError::MultipleOwners) => Parent::MultipleOwners,
+ Err(TokenOwnerError::NotFound) => Parent::TokenNotFound,
})
}
@@ -203,19 +208,27 @@
///
/// May return token address if parent token not yet exists
///
+ /// Returns `None` if the token has multiple owners.
+ ///
/// - `budget`: Limit for searching parents in depth.
pub fn find_topmost_owner(
collection: CollectionId,
token: TokenId,
budget: &dyn Budget,
- ) -> Result<T::CrossAccountId, DispatchError> {
+ ) -> Result<Option<T::CrossAccountId>, DispatchError> {
let owner = Self::parent_chain(collection, token)
.take_while(|_| budget.consume())
- .find(|p| matches!(p, Ok(Parent::User(_) | Parent::TokenNotFound)))
+ .find(|p| {
+ matches!(
+ p,
+ Ok(Parent::User(_) | Parent::TokenNotFound | Parent::MultipleOwners)
+ )
+ })
.ok_or(<Error<T>>::DepthLimit)??;
Ok(match owner {
- Parent::User(v) => v,
+ Parent::User(v) => Some(v),
+ Parent::MultipleOwners => None,
_ => fail!(<Error<T>>::TokenNotFound),
})
}
@@ -223,13 +236,15 @@
/// Find the topmost parent and check that assigning `for_nest` token as a child for
/// `token` wouldn't create a cycle.
///
+ /// Returns `None` if the token has multiple owners.
+ ///
/// - `budget`: Limit for searching parents in depth.
pub fn get_checked_topmost_owner(
collection: CollectionId,
token: TokenId,
for_nest: Option<(CollectionId, TokenId)>,
budget: &dyn Budget,
- ) -> Result<T::CrossAccountId, DispatchError> {
+ ) -> Result<Option<T::CrossAccountId>, DispatchError> {
// Tried to nest token in itself
if Some((collection, token)) == for_nest {
return Err(<Error<T>>::OuroborosDetected.into());
@@ -242,8 +257,9 @@
return Err(<Error<T>>::OuroborosDetected.into())
}
// Token is owned by other user
- Parent::User(user) => return Ok(user),
+ Parent::User(user) => return Ok(Some(user)),
Parent::TokenNotFound => return Err(<Error<T>>::TokenNotFound.into()),
+ Parent::MultipleOwners => return Ok(None),
// Continue parent chain
Parent::Token(_, _) => {}
}
@@ -284,12 +300,17 @@
budget: &dyn Budget,
) -> Result<bool, DispatchError> {
let target_parent = match T::CrossTokenAddressMapping::address_to_token(&user) {
- Some((collection, token)) => Self::find_topmost_owner(collection, token, budget)?,
+ Some((collection, token)) => match Self::find_topmost_owner(collection, token, budget)?
+ {
+ Some(topmost_owner) => topmost_owner,
+ None => return Ok(false),
+ },
None => user,
};
- Self::get_checked_topmost_owner(collection, token, for_nest, budget)
- .map(|indirect_owner| indirect_owner == target_parent)
+ Self::get_checked_topmost_owner(collection, token, for_nest, budget).map(|indirect_owner| {
+ indirect_owner.map_or(false, |indirect_owner| indirect_owner == target_parent)
+ })
}
/// Checks that `under` is valid token and that `token_id` could be nested under it
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -1099,6 +1099,13 @@
EmptyPropertyKey,
}
+/// Token owner error: it could be either `NotFound` ot `MultipleOwners`.
+#[derive(Debug)]
+pub enum TokenOwnerError {
+ NotFound,
+ MultipleOwners,
+}
+
/// Marker for scope of property.
///
/// Scoped property can't be changed by user. Used for external collections.
runtime/common/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/runtime_apis.rs
+++ b/runtime/common/runtime_apis.rs
@@ -16,11 +16,11 @@
#[macro_export]
macro_rules! dispatch_unique_runtime {
- ($collection:ident.$method:ident($($name:ident),*)) => {{
+ ($collection:ident.$method:ident($($name:ident),*) $($rest:tt)*) => {{
let collection = <Runtime as pallet_common::Config>::CollectionDispatch::dispatch(<pallet_common::CollectionHandle<Runtime>>::try_get($collection)?);
let dispatch = collection.as_dyn();
- Ok::<_, DispatchError>(dispatch.$method($($name),*))
+ Ok::<_, DispatchError>(dispatch.$method($($name),*) $($rest)*)
}};
}
@@ -73,7 +73,7 @@
}
fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
- dispatch_unique_runtime!(collection.token_owner(token))
+ dispatch_unique_runtime!(collection.token_owner(token).ok())
}
fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec::<CrossAccountId>, DispatchError> {
@@ -83,7 +83,7 @@
fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
let budget = up_data_structs::budget::Value::new(10);
- Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))
+ Ok(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?)
}
fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>, DispatchError> {
Ok(<pallet_nonfungible::Pallet<Runtime>>::token_children_ids(collection, token))