difftreelog
add contracts consts
in: master
7 files changed
pallets/common/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63 ensure,64 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65 dispatch::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 RpcCollectionFlags,75 CollectionId,76 CreateItemData,77 MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT,79 TokenId,80 TokenChild,81 CollectionStats,82 MAX_TOKEN_OWNERSHIP,83 CollectionMode,84 NFT_SPONSOR_TRANSFER_TIMEOUT,85 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87 MAX_SPONSOR_TIMEOUT,88 CUSTOM_DATA_LIMIT,89 CollectionLimits,90 CreateCollectionData,91 SponsorshipState,92 CreateItemExData,93 SponsoringRateLimit,94 budget::Budget,95 PhantomType,96 Property,97 Properties,98 PropertiesPermissionMap,99 PropertyKey,100 PropertyValue,101 PropertyPermission,102 PropertiesError,103 PropertyKeyPermission,104 TokenData,105 TrySetProperty,106 PropertyScope,107 // RMRK108 RmrkCollectionInfo,109 RmrkInstanceInfo,110 RmrkResourceInfo,111 RmrkPropertyInfo,112 RmrkBaseInfo,113 RmrkPartType,114 RmrkBoundedTheme,115 RmrkNftChild,116 CollectionPermissions,117};118119pub use pallet::*;120use sp_core::H160;121use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};122#[cfg(feature = "runtime-benchmarks")]123pub mod benchmarking;124pub mod dispatch;125pub mod erc;126pub mod eth;127pub mod weights;128129/// Weight info.130pub type SelfWeightOf<T> = <T as Config>::WeightInfo;131132/// Collection handle contains information about collection data and id.133/// Also provides functionality to count consumed gas.134///135/// CollectionHandle is used as a generic wrapper for collections of all types.136/// It allows to perform common operations and queries on any collection type,137/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].138#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]139pub struct CollectionHandle<T: Config> {140 /// Collection id141 pub id: CollectionId,142 collection: Collection<T::AccountId>,143 /// Substrate recorder for counting consumed gas144 pub recorder: SubstrateRecorder<T>,145}146147impl<T: Config> WithRecorder<T> for CollectionHandle<T> {148 fn recorder(&self) -> &SubstrateRecorder<T> {149 &self.recorder150 }151 fn into_recorder(self) -> SubstrateRecorder<T> {152 self.recorder153 }154}155156impl<T: Config> CollectionHandle<T> {157 /// Same as [CollectionHandle::new] but with an explicit gas limit.158 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {159 <CollectionById<T>>::get(id).map(|collection| Self {160 id,161 collection,162 recorder: SubstrateRecorder::new(gas_limit),163 })164 }165166 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].167 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {168 <CollectionById<T>>::get(id).map(|collection| Self {169 id,170 collection,171 recorder,172 })173 }174175 /// Retrives collection data from storage and creates collection handle with default parameters.176 /// If collection not found return `None`177 pub fn new(id: CollectionId) -> Option<Self> {178 Self::new_with_gas_limit(id, u64::MAX)179 }180181 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.182 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {183 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)184 }185186 /// Consume gas for reading.187 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {188 self.recorder189 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(190 <T as frame_system::Config>::DbWeight::get()191 .read192 .saturating_mul(reads),193 )))194 }195196 /// Consume gas for writing.197 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {198 self.recorder199 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(200 <T as frame_system::Config>::DbWeight::get()201 .write202 .saturating_mul(writes),203 )))204 }205206 /// Consume gas for reading and writing.207 pub fn consume_store_reads_and_writes(208 &self,209 reads: u64,210 writes: u64,211 ) -> evm_coder::execution::Result<()> {212 let weight = <T as frame_system::Config>::DbWeight::get();213 let reads = weight.read.saturating_mul(reads);214 let writes = weight.read.saturating_mul(writes);215 self.recorder216 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(217 reads.saturating_add(writes),218 )))219 }220221 /// Save collection to storage.222 pub fn save(&self) -> DispatchResult {223 <CollectionById<T>>::insert(self.id, &self.collection);224 Ok(())225 }226227 /// Set collection sponsor.228 ///229 /// Unique collections allows sponsoring for certain actions.230 /// This method allows you to set the sponsor of the collection.231 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].232 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {233 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);234 Ok(())235 }236237 /// Confirm sponsorship238 ///239 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.240 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].241 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {242 if self.collection.sponsorship.pending_sponsor() != Some(sender) {243 return Ok(false);244 }245246 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());247 Ok(true)248 }249250 /// Remove collection sponsor.251 pub fn remove_sponsor(&mut self) -> DispatchResult {252 self.collection.sponsorship = SponsorshipState::Disabled;253 Ok(())254 }255256 /// Checks that the collection was created with, and must be operated upon through **Unique API**.257 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.258 pub fn check_is_internal(&self) -> DispatchResult {259 if self.flags.external {260 return Err(<Error<T>>::CollectionIsExternal)?;261 }262263 Ok(())264 }265266 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.267 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.268 pub fn check_is_external(&self) -> DispatchResult {269 if !self.flags.external {270 return Err(<Error<T>>::CollectionIsInternal)?;271 }272273 Ok(())274 }275}276277impl<T: Config> Deref for CollectionHandle<T> {278 type Target = Collection<T::AccountId>;279280 fn deref(&self) -> &Self::Target {281 &self.collection282 }283}284285impl<T: Config> DerefMut for CollectionHandle<T> {286 fn deref_mut(&mut self) -> &mut Self::Target {287 &mut self.collection288 }289}290291impl<T: Config> CollectionHandle<T> {292 /// Checks if the `user` is the owner of the collection.293 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {294 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);295 Ok(())296 }297298 /// Returns **true** if the `user` is the owner or administrator of the collection.299 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {300 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))301 }302303 /// Checks if the `user` is the owner or administrator of the collection.304 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {305 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);306 Ok(())307 }308309 /// Return **true** if `user` was not allowed to have tokens, and he can ignore such restrictions.310 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {311 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)312 }313314 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.315 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {316 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)317 }318319 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.320 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {321 ensure!(322 <Allowlist<T>>::get((self.id, user)),323 <Error<T>>::AddressNotInAllowlist324 );325 Ok(())326 }327328 /// Changes collection owner to another account329 /// #### Store read/writes330 /// 1 writes331 fn set_owner_internal(332 &mut self,333 caller: T::CrossAccountId,334 new_owner: T::CrossAccountId,335 ) -> DispatchResult {336 self.check_is_owner(&caller)?;337 self.collection.owner = new_owner.as_sub().clone();338 self.save()339 }340}341342#[frame_support::pallet]343pub mod pallet {344 use super::*;345 use dispatch::CollectionDispatch;346 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};347 use frame_system::pallet_prelude::*;348 use frame_support::traits::Currency;349 use up_data_structs::{TokenId, mapping::TokenAddressMapping};350 use scale_info::TypeInfo;351 use weights::WeightInfo;352353 #[pallet::config]354 pub trait Config:355 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo356 {357 /// Weight information for functions of this pallet.358 type WeightInfo: WeightInfo;359360 /// Events compatible with [`frame_system::Config::Event`].361 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;362363 /// Handler of accounts and payment.364 type Currency: Currency<Self::AccountId>;365366 /// Set price to create a collection.367 #[pallet::constant]368 type CollectionCreationPrice: Get<369 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,370 >;371372 /// Dispatcher of operations on collections.373 type CollectionDispatch: CollectionDispatch<Self>;374375 /// Account which holds the chain's treasury.376 type TreasuryAccountId: Get<Self::AccountId>;377378 /// Address under which the CollectionHelper contract would be available.379 type ContractAddress: Get<H160>;380381 /// Mapper for token addresses to Ethereum addresses.382 type EvmTokenAddressMapping: TokenAddressMapping<H160>;383384 /// Mapper for token addresses to [`CrossAccountId`].385 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;386 }387388 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);389390 #[pallet::pallet]391 #[pallet::storage_version(STORAGE_VERSION)]392 #[pallet::generate_store(pub(super) trait Store)]393 pub struct Pallet<T>(_);394395 #[pallet::extra_constants]396 impl<T: Config> Pallet<T> {397 /// Maximum admins per collection.398 pub fn collection_admins_limit() -> u32 {399 COLLECTION_ADMINS_LIMIT400 }401 }402403 #[pallet::event]404 #[pallet::generate_deposit(pub fn deposit_event)]405 pub enum Event<T: Config> {406 /// New collection was created407 CollectionCreated(408 /// Globally unique identifier of newly created collection.409 CollectionId,410 /// [`CollectionMode`] converted into _u8_.411 u8,412 /// Collection owner.413 T::AccountId,414 ),415416 /// New collection was destroyed417 CollectionDestroyed(418 /// Globally unique identifier of collection.419 CollectionId,420 ),421422 /// New item was created.423 ItemCreated(424 /// Id of the collection where item was created.425 CollectionId,426 /// Id of an item. Unique within the collection.427 TokenId,428 /// Owner of newly created item429 T::CrossAccountId,430 /// Always 1 for NFT431 u128,432 ),433434 /// Collection item was burned.435 ItemDestroyed(436 /// Id of the collection where item was destroyed.437 CollectionId,438 /// Identifier of burned NFT.439 TokenId,440 /// Which user has destroyed its tokens.441 T::CrossAccountId,442 /// Amount of token pieces destroed. Always 1 for NFT.443 u128,444 ),445446 /// Item was transferred447 Transfer(448 /// Id of collection to which item is belong.449 CollectionId,450 /// Id of an item.451 TokenId,452 /// Original owner of item.453 T::CrossAccountId,454 /// New owner of item.455 T::CrossAccountId,456 /// Amount of token pieces transfered. Always 1 for NFT.457 u128,458 ),459460 /// Amount pieces of token owned by `sender` was approved for `spender`.461 Approved(462 /// Id of collection to which item is belong.463 CollectionId,464 /// Id of an item.465 TokenId,466 /// Original owner of item.467 T::CrossAccountId,468 /// Id for which the approval was granted.469 T::CrossAccountId,470 /// Amount of token pieces transfered. Always 1 for NFT.471 u128,472 ),473474 /// The colletion property has been added or edited.475 CollectionPropertySet(476 /// Id of collection to which property has been set.477 CollectionId,478 /// The property that was set.479 PropertyKey,480 ),481482 /// The property has been deleted.483 CollectionPropertyDeleted(484 /// Id of collection to which property has been deleted.485 CollectionId,486 /// The property that was deleted.487 PropertyKey,488 ),489490 /// The token property has been added or edited.491 TokenPropertySet(492 /// Identifier of the collection whose token has the property set.493 CollectionId,494 /// The token for which the property was set.495 TokenId,496 /// The property that was set.497 PropertyKey,498 ),499500 /// The token property has been deleted.501 TokenPropertyDeleted(502 /// Identifier of the collection whose token has the property deleted.503 CollectionId,504 /// The token for which the property was deleted.505 TokenId,506 /// The property that was deleted.507 PropertyKey,508 ),509510 /// The token property permission of a collection has been set.511 PropertyPermissionSet(512 /// ID of collection to which property permission has been set.513 CollectionId,514 /// The property permission that was set.515 PropertyKey,516 ),517 }518519 #[pallet::error]520 pub enum Error<T> {521 /// This collection does not exist.522 CollectionNotFound,523 /// Sender parameter and item owner must be equal.524 MustBeTokenOwner,525 /// No permission to perform action526 NoPermission,527 /// Destroying only empty collections is allowed528 CantDestroyNotEmptyCollection,529 /// Collection is not in mint mode.530 PublicMintingNotAllowed,531 /// Address is not in allow list.532 AddressNotInAllowlist,533534 /// Collection name can not be longer than 63 char.535 CollectionNameLimitExceeded,536 /// Collection description can not be longer than 255 char.537 CollectionDescriptionLimitExceeded,538 /// Token prefix can not be longer than 15 char.539 CollectionTokenPrefixLimitExceeded,540 /// Total collections bound exceeded.541 TotalCollectionsLimitExceeded,542 /// Exceeded max admin count543 CollectionAdminCountExceeded,544 /// Collection limit bounds per collection exceeded545 CollectionLimitBoundsExceeded,546 /// Tried to enable permissions which are only permitted to be disabled547 OwnerPermissionsCantBeReverted,548 /// Collection settings not allowing items transferring549 TransferNotAllowed,550 /// Account token limit exceeded per collection551 AccountTokenLimitExceeded,552 /// Collection token limit exceeded553 CollectionTokenLimitExceeded,554 /// Metadata flag frozen555 MetadataFlagFrozen,556557 /// Item does not exist558 TokenNotFound,559 /// Item is balance not enough560 TokenValueTooLow,561 /// Requested value is more than the approved562 ApprovedValueTooLow,563 /// Tried to approve more than owned564 CantApproveMoreThanOwned,565566 /// Can't transfer tokens to ethereum zero address567 AddressIsZero,568569 /// The operation is not supported570 UnsupportedOperation,571572 /// Insufficient funds to perform an action573 NotSufficientFounds,574575 /// User does not satisfy the nesting rule576 UserIsNotAllowedToNest,577 /// Only tokens from specific collections may nest tokens under this one578 SourceCollectionIsNotAllowedToNest,579580 /// Tried to store more data than allowed in collection field581 CollectionFieldSizeExceeded,582583 /// Tried to store more property data than allowed584 NoSpaceForProperty,585586 /// Tried to store more property keys than allowed587 PropertyLimitReached,588589 /// Property key is too long590 PropertyKeyIsTooLong,591592 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed593 InvalidCharacterInPropertyKey,594595 /// Empty property keys are forbidden596 EmptyPropertyKey,597598 /// Tried to access an external collection with an internal API599 CollectionIsExternal,600601 /// Tried to access an internal collection with an external API602 CollectionIsInternal,603 }604605 /// Storage of the count of created collections. Essentially contains the last collection ID.606 #[pallet::storage]607 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;608609 /// Storage of the count of deleted collections.610 #[pallet::storage]611 pub type DestroyedCollectionCount<T> =612 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;613614 /// Storage of collection info.615 #[pallet::storage]616 pub type CollectionById<T> = StorageMap<617 Hasher = Blake2_128Concat,618 Key = CollectionId,619 Value = Collection<<T as frame_system::Config>::AccountId>,620 QueryKind = OptionQuery,621 >;622623 /// Storage of collection properties.624 #[pallet::storage]625 #[pallet::getter(fn collection_properties)]626 pub type CollectionProperties<T> = StorageMap<627 Hasher = Blake2_128Concat,628 Key = CollectionId,629 Value = Properties,630 QueryKind = ValueQuery,631 OnEmpty = up_data_structs::CollectionProperties,632 >;633634 /// Storage of token property permissions of a collection.635 #[pallet::storage]636 #[pallet::getter(fn property_permissions)]637 pub type CollectionPropertyPermissions<T> = StorageMap<638 Hasher = Blake2_128Concat,639 Key = CollectionId,640 Value = PropertiesPermissionMap,641 QueryKind = ValueQuery,642 >;643644 /// Storage of the amount of collection admins.645 #[pallet::storage]646 pub type AdminAmount<T> = StorageMap<647 Hasher = Blake2_128Concat,648 Key = CollectionId,649 Value = u32,650 QueryKind = ValueQuery,651 >;652653 /// List of collection admins.654 #[pallet::storage]655 pub type IsAdmin<T: Config> = StorageNMap<656 Key = (657 Key<Blake2_128Concat, CollectionId>,658 Key<Blake2_128Concat, T::CrossAccountId>,659 ),660 Value = bool,661 QueryKind = ValueQuery,662 >;663664 /// Allowlisted collection users.665 #[pallet::storage]666 pub type Allowlist<T: Config> = StorageNMap<667 Key = (668 Key<Blake2_128Concat, CollectionId>,669 Key<Blake2_128Concat, T::CrossAccountId>,670 ),671 Value = bool,672 QueryKind = ValueQuery,673 >;674675 /// Not used by code, exists only to provide some types to metadata.676 #[pallet::storage]677 pub type DummyStorageValue<T: Config> = StorageValue<678 Value = (679 CollectionStats,680 CollectionId,681 TokenId,682 TokenChild,683 PhantomType<(684 TokenData<T::CrossAccountId>,685 RpcCollection<T::AccountId>,686 // RMRK687 RmrkCollectionInfo<T::AccountId>,688 RmrkInstanceInfo<T::AccountId>,689 RmrkResourceInfo,690 RmrkPropertyInfo,691 RmrkBaseInfo<T::AccountId>,692 RmrkPartType,693 RmrkBoundedTheme,694 RmrkNftChild,695 )>,696 ),697 QueryKind = OptionQuery,698 >;699700 #[pallet::hooks]701 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {702 fn on_runtime_upgrade() -> Weight {703 StorageVersion::new(1).put::<Pallet<T>>();704705 Weight::zero()706 }707 }708}709710impl<T: Config> Pallet<T> {711 /// Enshure that receiver address is correct.712 ///713 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.714 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {715 ensure!(716 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,717 <Error<T>>::AddressIsZero718 );719 Ok(())720 }721722 /// Get a vector of collection admins.723 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {724 <IsAdmin<T>>::iter_prefix((collection,))725 .map(|(a, _)| a)726 .collect()727 }728729 /// Get a vector of users allowed to mint tokens.730 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {731 <Allowlist<T>>::iter_prefix((collection,))732 .map(|(a, _)| a)733 .collect()734 }735736 /// Is `user` allowed to mint token in `collection`.737 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {738 <Allowlist<T>>::get((collection, user))739 }740741 /// Get statistics of collections.742 pub fn collection_stats() -> CollectionStats {743 let created = <CreatedCollectionCount<T>>::get();744 let destroyed = <DestroyedCollectionCount<T>>::get();745 CollectionStats {746 created: created.0,747 destroyed: destroyed.0,748 alive: created.0 - destroyed.0,749 }750 }751752 /// Get the effective limits for the collection.753 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {754 let collection = <CollectionById<T>>::get(collection)?;755 let limits = collection.limits;756 let effective_limits = CollectionLimits {757 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),758 sponsored_data_size: Some(limits.sponsored_data_size()),759 sponsored_data_rate_limit: Some(760 limits761 .sponsored_data_rate_limit762 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),763 ),764 token_limit: Some(limits.token_limit()),765 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(766 match collection.mode {767 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,768 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,769 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,770 },771 )),772 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),773 owner_can_transfer: Some(limits.owner_can_transfer()),774 owner_can_destroy: Some(limits.owner_can_destroy()),775 transfers_enabled: Some(limits.transfers_enabled()),776 };777778 Some(effective_limits)779 }780781 /// Returns information about the `collection` adapted for rpc.782 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {783 let Collection {784 name,785 description,786 owner,787 mode,788 token_prefix,789 sponsorship,790 limits,791 permissions,792 flags,793 } = <CollectionById<T>>::get(collection)?;794795 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)796 .into_iter()797 .map(|(key, permission)| PropertyKeyPermission { key, permission })798 .collect();799800 let properties = <CollectionProperties<T>>::get(collection)801 .into_iter()802 .map(|(key, value)| Property { key, value })803 .collect();804805 let permissions = CollectionPermissions {806 access: Some(permissions.access()),807 mint_mode: Some(permissions.mint_mode()),808 nesting: Some(permissions.nesting().clone()),809 };810811 Some(RpcCollection {812 name: name.into_inner(),813 description: description.into_inner(),814 owner,815 mode,816 token_prefix: token_prefix.into_inner(),817 sponsorship,818 limits,819 permissions,820 token_property_permissions,821 properties,822 read_only: flags.external,823824 flags: RpcCollectionFlags {825 foreign: flags.foreign,826 erc721metadata: flags.erc721metadata,827 },828 })829 }830}831832macro_rules! limit_default {833 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{834 $(835 if let Some($new) = $new.$field {836 let $old = $old.$field($($arg)?);837 let _ = $new;838 let _ = $old;839 $check840 } else {841 $new.$field = $old.$field842 }843 )*844 }};845}846macro_rules! limit_default_clone {847 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{848 $(849 if let Some($new) = $new.$field.clone() {850 let $old = $old.$field($($arg)?);851 let _ = $new;852 let _ = $old;853 $check854 } else {855 $new.$field = $old.$field.clone()856 }857 )*858 }};859}860861impl<T: Config> Pallet<T> {862 /// Create new collection.863 ///864 /// * `owner` - The owner of the collection.865 /// * `data` - Description of the created collection.866 /// * `flags` - Extra flags to store.867 pub fn init_collection(868 owner: T::CrossAccountId,869 payer: T::CrossAccountId,870 data: CreateCollectionData<T::AccountId>,871 flags: CollectionFlags,872 ) -> Result<CollectionId, DispatchError> {873 {874 ensure!(875 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,876 Error::<T>::CollectionTokenPrefixLimitExceeded877 );878 }879880 let created_count = <CreatedCollectionCount<T>>::get()881 .0882 .checked_add(1)883 .ok_or(ArithmeticError::Overflow)?;884 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;885 let id = CollectionId(created_count);886887 // bound Total number of collections888 ensure!(889 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,890 <Error<T>>::TotalCollectionsLimitExceeded891 );892893 // =========894895 let collection = Collection {896 owner: owner.as_sub().clone(),897 name: data.name,898 mode: data.mode.clone(),899 description: data.description,900 token_prefix: data.token_prefix,901 sponsorship: data902 .pending_sponsor903 .map(SponsorshipState::Unconfirmed)904 .unwrap_or_default(),905 limits: data906 .limits907 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))908 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,909 permissions: data910 .permissions911 .map(|permissions| {912 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)913 })914 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,915 flags,916 };917918 let mut collection_properties = up_data_structs::CollectionProperties::get();919 collection_properties920 .try_set_from_iter(data.properties.into_iter())921 .map_err(<Error<T>>::from)?;922923 CollectionProperties::<T>::insert(id, collection_properties);924925 let mut token_props_permissions = PropertiesPermissionMap::new();926 token_props_permissions927 .try_set_from_iter(data.token_property_permissions.into_iter())928 .map_err(<Error<T>>::from)?;929930 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);931932 // Take a (non-refundable) deposit of collection creation933 {934 let mut imbalance =935 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();936 imbalance.subsume(937 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(938 &T::TreasuryAccountId::get(),939 T::CollectionCreationPrice::get(),940 ),941 );942 <T as Config>::Currency::settle(943 payer.as_sub(),944 imbalance,945 WithdrawReasons::TRANSFER,946 ExistenceRequirement::KeepAlive,947 )948 .map_err(|_| Error::<T>::NotSufficientFounds)?;949 }950951 <CreatedCollectionCount<T>>::put(created_count);952 <Pallet<T>>::deposit_event(Event::CollectionCreated(953 id,954 data.mode.id(),955 owner.as_sub().clone(),956 ));957 <PalletEvm<T>>::deposit_log(958 erc::CollectionHelpersEvents::CollectionCreated {959 owner: *owner.as_eth(),960 collection_id: eth::collection_id_to_address(id),961 }962 .to_log(T::ContractAddress::get()),963 );964 <CollectionById<T>>::insert(id, collection);965 Ok(id)966 }967968 /// Destroy collection.969 ///970 /// * `collection` - Collection handler.971 /// * `sender` - The owner or administrator of the collection.972 pub fn destroy_collection(973 collection: CollectionHandle<T>,974 sender: &T::CrossAccountId,975 ) -> DispatchResult {976 ensure!(977 collection.limits.owner_can_destroy(),978 <Error<T>>::NoPermission,979 );980 collection.check_is_owner(sender)?;981982 let destroyed_collections = <DestroyedCollectionCount<T>>::get()983 .0984 .checked_add(1)985 .ok_or(ArithmeticError::Overflow)?;986987 // =========988989 <DestroyedCollectionCount<T>>::put(destroyed_collections);990 <CollectionById<T>>::remove(collection.id);991 <AdminAmount<T>>::remove(collection.id);992 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);993 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);994 <CollectionProperties<T>>::remove(collection.id);995996 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));997998 <PalletEvm<T>>::deposit_log(999 erc::CollectionHelpersEvents::CollectionDestroyed {1000 collection_id: eth::collection_id_to_address(collection.id),1001 }1002 .to_log(T::ContractAddress::get()),1003 );1004 Ok(())1005 }10061007 /// Set collection property.1008 ///1009 /// * `collection` - Collection handler.1010 /// * `sender` - The owner or administrator of the collection.1011 /// * `property` - The property to set.1012 pub fn set_collection_property(1013 collection: &CollectionHandle<T>,1014 sender: &T::CrossAccountId,1015 property: Property,1016 ) -> DispatchResult {1017 collection.check_is_owner_or_admin(sender)?;10181019 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1020 let property = property.clone();1021 properties.try_set(property.key, property.value)1022 })1023 .map_err(<Error<T>>::from)?;10241025 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10261027 Ok(())1028 }10291030 /// Set scouped collection property.1031 ///1032 /// * `collection_id` - ID of the collection for which the property is being set.1033 /// * `scope` - Property scope.1034 /// * `property` - The property to set.1035 pub fn set_scoped_collection_property(1036 collection_id: CollectionId,1037 scope: PropertyScope,1038 property: Property,1039 ) -> DispatchResult {1040 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1041 properties.try_scoped_set(scope, property.key, property.value)1042 })1043 .map_err(<Error<T>>::from)?;10441045 Ok(())1046 }10471048 /// Set scouped collection properties.1049 ///1050 /// * `collection_id` - ID of the collection for which the properties is being set.1051 /// * `scope` - Property scope.1052 /// * `properties` - The properties to set.1053 pub fn set_scoped_collection_properties(1054 collection_id: CollectionId,1055 scope: PropertyScope,1056 properties: impl Iterator<Item = Property>,1057 ) -> DispatchResult {1058 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1059 stored_properties.try_scoped_set_from_iter(scope, properties)1060 })1061 .map_err(<Error<T>>::from)?;10621063 Ok(())1064 }10651066 /// Set collection properties.1067 ///1068 /// * `collection` - Collection handler.1069 /// * `sender` - The owner or administrator of the collection.1070 /// * `properties` - The properties to set.1071 #[transactional]1072 pub fn set_collection_properties(1073 collection: &CollectionHandle<T>,1074 sender: &T::CrossAccountId,1075 properties: Vec<Property>,1076 ) -> DispatchResult {1077 for property in properties {1078 Self::set_collection_property(collection, sender, property)?;1079 }10801081 Ok(())1082 }10831084 /// Delete collection property.1085 ///1086 /// * `collection` - Collection handler.1087 /// * `sender` - The owner or administrator of the collection.1088 /// * `property` - The property to delete.1089 pub fn delete_collection_property(1090 collection: &CollectionHandle<T>,1091 sender: &T::CrossAccountId,1092 property_key: PropertyKey,1093 ) -> DispatchResult {1094 collection.check_is_owner_or_admin(sender)?;10951096 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1097 properties.remove(&property_key)1098 })1099 .map_err(<Error<T>>::from)?;11001101 Self::deposit_event(Event::CollectionPropertyDeleted(1102 collection.id,1103 property_key,1104 ));11051106 Ok(())1107 }11081109 /// Delete collection properties.1110 ///1111 /// * `collection` - Collection handler.1112 /// * `sender` - The owner or administrator of the collection.1113 /// * `properties` - The properties to delete.1114 #[transactional]1115 pub fn delete_collection_properties(1116 collection: &CollectionHandle<T>,1117 sender: &T::CrossAccountId,1118 property_keys: Vec<PropertyKey>,1119 ) -> DispatchResult {1120 for key in property_keys {1121 Self::delete_collection_property(collection, sender, key)?;1122 }11231124 Ok(())1125 }11261127 /// Set collection propetry permission without any checks.1128 ///1129 /// Used for migrations.1130 ///1131 /// * `collection` - Collection handler.1132 /// * `property_permissions` - Property permissions.1133 pub fn set_property_permission_unchecked(1134 collection: CollectionId,1135 property_permission: PropertyKeyPermission,1136 ) -> DispatchResult {1137 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1138 permissions.try_set(property_permission.key, property_permission.permission)1139 })1140 .map_err(<Error<T>>::from)?;1141 Ok(())1142 }11431144 /// Set collection property permission.1145 ///1146 /// * `collection` - Collection handler.1147 /// * `sender` - The owner or administrator of the collection.1148 /// * `property_permission` - Property permission.1149 pub fn set_property_permission(1150 collection: &CollectionHandle<T>,1151 sender: &T::CrossAccountId,1152 property_permission: PropertyKeyPermission,1153 ) -> DispatchResult {1154 Self::set_scoped_property_permission(1155 collection,1156 sender,1157 PropertyScope::None,1158 property_permission,1159 )1160 }11611162 /// Set collection property permission with scope.1163 ///1164 /// * `collection` - Collection handler.1165 /// * `sender` - The owner or administrator of the collection.1166 /// * `scope` - Property scope.1167 /// * `property_permission` - Property permission.1168 pub fn set_scoped_property_permission(1169 collection: &CollectionHandle<T>,1170 sender: &T::CrossAccountId,1171 scope: PropertyScope,1172 property_permission: PropertyKeyPermission,1173 ) -> DispatchResult {1174 collection.check_is_owner_or_admin(sender)?;11751176 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1177 let current_permission = all_permissions.get(&property_permission.key);1178 if matches![1179 current_permission,1180 Some(PropertyPermission { mutable: false, .. })1181 ] {1182 return Err(<Error<T>>::NoPermission.into());1183 }11841185 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1186 let property_permission = property_permission.clone();1187 permissions.try_scoped_set(1188 scope,1189 property_permission.key,1190 property_permission.permission,1191 )1192 })1193 .map_err(<Error<T>>::from)?;11941195 Self::deposit_event(Event::PropertyPermissionSet(1196 collection.id,1197 property_permission.key,1198 ));11991200 Ok(())1201 }12021203 /// Set token property permission.1204 ///1205 /// * `collection` - Collection handler.1206 /// * `sender` - The owner or administrator of the collection.1207 /// * `property_permissions` - Property permissions.1208 #[transactional]1209 pub fn set_token_property_permissions(1210 collection: &CollectionHandle<T>,1211 sender: &T::CrossAccountId,1212 property_permissions: Vec<PropertyKeyPermission>,1213 ) -> DispatchResult {1214 Self::set_scoped_token_property_permissions(1215 collection,1216 sender,1217 PropertyScope::None,1218 property_permissions,1219 )1220 }12211222 /// Set token property permission with scope.1223 ///1224 /// * `collection` - Collection handler.1225 /// * `sender` - The owner or administrator of the collection.1226 /// * `scope` - Property scope.1227 /// * `property_permissions` - Property permissions.1228 #[transactional]1229 pub fn set_scoped_token_property_permissions(1230 collection: &CollectionHandle<T>,1231 sender: &T::CrossAccountId,1232 scope: PropertyScope,1233 property_permissions: Vec<PropertyKeyPermission>,1234 ) -> DispatchResult {1235 for prop_pemission in property_permissions {1236 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1237 }12381239 Ok(())1240 }12411242 /// Get collection property.1243 pub fn get_collection_property(1244 collection_id: CollectionId,1245 key: &PropertyKey,1246 ) -> Option<PropertyValue> {1247 Self::collection_properties(collection_id).get(key).cloned()1248 }12491250 /// Convert byte vector to property key vector.1251 pub fn bytes_keys_to_property_keys(1252 keys: Vec<Vec<u8>>,1253 ) -> Result<Vec<PropertyKey>, DispatchError> {1254 keys.into_iter()1255 .map(|key| -> Result<PropertyKey, DispatchError> {1256 key.try_into()1257 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1258 })1259 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1260 }12611262 /// Get properties according to given keys.1263 pub fn filter_collection_properties(1264 collection_id: CollectionId,1265 keys: Option<Vec<PropertyKey>>,1266 ) -> Result<Vec<Property>, DispatchError> {1267 let properties = Self::collection_properties(collection_id);12681269 let properties = keys1270 .map(|keys| {1271 keys.into_iter()1272 .filter_map(|key| {1273 properties.get(&key).map(|value| Property {1274 key,1275 value: value.clone(),1276 })1277 })1278 .collect()1279 })1280 .unwrap_or_else(|| {1281 properties1282 .into_iter()1283 .map(|(key, value)| Property { key, value })1284 .collect()1285 });12861287 Ok(properties)1288 }12891290 /// Get property permissions according to given keys.1291 pub fn filter_property_permissions(1292 collection_id: CollectionId,1293 keys: Option<Vec<PropertyKey>>,1294 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1295 let permissions = Self::property_permissions(collection_id);12961297 let key_permissions = keys1298 .map(|keys| {1299 keys.into_iter()1300 .filter_map(|key| {1301 permissions1302 .get(&key)1303 .map(|permission| PropertyKeyPermission {1304 key,1305 permission: permission.clone(),1306 })1307 })1308 .collect()1309 })1310 .unwrap_or_else(|| {1311 permissions1312 .into_iter()1313 .map(|(key, permission)| PropertyKeyPermission { key, permission })1314 .collect()1315 });13161317 Ok(key_permissions)1318 }13191320 /// Toggle `user` participation in the `collection`'s allow list.1321 /// #### Store read/writes1322 /// 1 writes1323 pub fn toggle_allowlist(1324 collection: &CollectionHandle<T>,1325 sender: &T::CrossAccountId,1326 user: &T::CrossAccountId,1327 allowed: bool,1328 ) -> DispatchResult {1329 collection.check_is_owner_or_admin(sender)?;13301331 // =========13321333 if allowed {1334 <Allowlist<T>>::insert((collection.id, user), true);1335 } else {1336 <Allowlist<T>>::remove((collection.id, user));1337 }13381339 Ok(())1340 }13411342 /// Toggle `user` participation in the `collection`'s admin list.1343 /// #### Store read/writes1344 /// 2 writes1345 pub fn toggle_admin(1346 collection: &CollectionHandle<T>,1347 sender: &T::CrossAccountId,1348 user: &T::CrossAccountId,1349 admin: bool,1350 ) -> DispatchResult {1351 collection.check_is_owner(sender)?;13521353 let was_admin = <IsAdmin<T>>::get((collection.id, user));1354 if was_admin == admin {1355 return Ok(());1356 }1357 let amount = <AdminAmount<T>>::get(collection.id);13581359 if admin {1360 let amount = amount1361 .checked_add(1)1362 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1363 ensure!(1364 amount <= Self::collection_admins_limit(),1365 <Error<T>>::CollectionAdminCountExceeded,1366 );13671368 // =========13691370 <AdminAmount<T>>::insert(collection.id, amount);1371 <IsAdmin<T>>::insert((collection.id, user), true);1372 } else {1373 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1374 <IsAdmin<T>>::remove((collection.id, user));1375 }13761377 Ok(())1378 }13791380 /// Merge set fields from `new_limit` to `old_limit`.1381 pub fn clamp_limits(1382 mode: CollectionMode,1383 old_limit: &CollectionLimits,1384 mut new_limit: CollectionLimits,1385 ) -> Result<CollectionLimits, DispatchError> {1386 let limits = old_limit;1387 limit_default!(old_limit, new_limit,1388 account_token_ownership_limit => ensure!(1389 new_limit <= MAX_TOKEN_OWNERSHIP,1390 <Error<T>>::CollectionLimitBoundsExceeded,1391 ),1392 sponsored_data_size => ensure!(1393 new_limit <= CUSTOM_DATA_LIMIT,1394 <Error<T>>::CollectionLimitBoundsExceeded,1395 ),13961397 sponsored_data_rate_limit => {},1398 token_limit => ensure!(1399 old_limit >= new_limit && new_limit > 0,1400 <Error<T>>::CollectionTokenLimitExceeded1401 ),14021403 sponsor_transfer_timeout(match mode {1404 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1405 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1406 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1407 }) => ensure!(1408 new_limit <= MAX_SPONSOR_TIMEOUT,1409 <Error<T>>::CollectionLimitBoundsExceeded,1410 ),1411 sponsor_approve_timeout => {},1412 owner_can_transfer => ensure!(1413 !limits.owner_can_transfer_instaled() ||1414 old_limit || !new_limit,1415 <Error<T>>::OwnerPermissionsCantBeReverted,1416 ),1417 owner_can_destroy => ensure!(1418 old_limit || !new_limit,1419 <Error<T>>::OwnerPermissionsCantBeReverted,1420 ),1421 transfers_enabled => {},1422 );1423 Ok(new_limit)1424 }14251426 /// Merge set fields from `new_permission` to `old_permission`.1427 pub fn clamp_permissions(1428 _mode: CollectionMode,1429 old_permission: &CollectionPermissions,1430 mut new_permission: CollectionPermissions,1431 ) -> Result<CollectionPermissions, DispatchError> {1432 limit_default_clone!(old_permission, new_permission,1433 access => {},1434 mint_mode => {},1435 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1436 );1437 Ok(new_permission)1438 }1439}14401441/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1442#[macro_export]1443macro_rules! unsupported {1444 ($runtime:path) => {1445 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1446 };1447}14481449/// Return weights for various worst-case operations.1450pub trait CommonWeightInfo<CrossAccountId> {1451 /// Weight of item creation.1452 fn create_item() -> Weight;14531454 /// Weight of items creation.1455 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14561457 /// Weight of items creation.1458 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14591460 /// The weight of the burning item.1461 fn burn_item() -> Weight;14621463 /// Property setting weight.1464 ///1465 /// * `amount`- The number of properties to set.1466 fn set_collection_properties(amount: u32) -> Weight;14671468 /// Collection property deletion weight.1469 ///1470 /// * `amount`- The number of properties to set.1471 fn delete_collection_properties(amount: u32) -> Weight;14721473 /// Token property setting weight.1474 ///1475 /// * `amount`- The number of properties to set.1476 fn set_token_properties(amount: u32) -> Weight;14771478 /// Token property deletion weight.1479 ///1480 /// * `amount`- The number of properties to delete.1481 fn delete_token_properties(amount: u32) -> Weight;14821483 /// Token property permissions set weight.1484 ///1485 /// * `amount`- The number of property permissions to set.1486 fn set_token_property_permissions(amount: u32) -> Weight;14871488 /// Transfer price of the token or its parts.1489 fn transfer() -> Weight;14901491 /// The price of setting the permission of the operation from another user.1492 fn approve() -> Weight;14931494 /// Transfer price from another user.1495 fn transfer_from() -> Weight;14961497 /// The price of burning a token from another user.1498 fn burn_from() -> Weight;14991500 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1501 /// whole users's balance.1502 ///1503 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1504 fn burn_recursively_self_raw() -> Weight;15051506 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1507 ///1508 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1509 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15101511 /// The price of recursive burning a token.1512 ///1513 /// `max_selfs` - The maximum burning weight of the token itself.1514 /// `max_breadth` - The maximum number of nested tokens to burn.1515 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1516 Self::burn_recursively_self_raw()1517 .saturating_mul(max_selfs.max(1) as u64)1518 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1519 }15201521 /// The price of retrieving token owner1522 fn token_owner() -> Weight;1523}15241525/// Weight info extension trait for refungible pallet.1526pub trait RefungibleExtensionsWeightInfo {1527 /// Weight of token repartition.1528 fn repartition() -> Weight;1529}15301531/// Common collection operations.1532///1533/// It wraps methods in Fungible, Nonfungible and Refungible pallets1534/// and adds weight info.1535pub trait CommonCollectionOperations<T: Config> {1536 /// Create token.1537 ///1538 /// * `sender` - The user who mint the token and pays for the transaction.1539 /// * `to` - The user who will own the token.1540 /// * `data` - Token data.1541 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1542 fn create_item(1543 &self,1544 sender: T::CrossAccountId,1545 to: T::CrossAccountId,1546 data: CreateItemData,1547 nesting_budget: &dyn Budget,1548 ) -> DispatchResultWithPostInfo;15491550 /// Create multiple tokens.1551 ///1552 /// * `sender` - The user who mint the token and pays for the transaction.1553 /// * `to` - The user who will own the token.1554 /// * `data` - Token data.1555 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1556 fn create_multiple_items(1557 &self,1558 sender: T::CrossAccountId,1559 to: T::CrossAccountId,1560 data: Vec<CreateItemData>,1561 nesting_budget: &dyn Budget,1562 ) -> DispatchResultWithPostInfo;15631564 /// Create multiple tokens.1565 ///1566 /// * `sender` - The user who mint the token and pays for the transaction.1567 /// * `to` - The user who will own the token.1568 /// * `data` - Token data.1569 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1570 fn create_multiple_items_ex(1571 &self,1572 sender: T::CrossAccountId,1573 data: CreateItemExData<T::CrossAccountId>,1574 nesting_budget: &dyn Budget,1575 ) -> DispatchResultWithPostInfo;15761577 /// Burn token.1578 ///1579 /// * `sender` - The user who owns the token.1580 /// * `token` - Token id that will burned.1581 /// * `amount` - The number of parts of the token that will be burned.1582 fn burn_item(1583 &self,1584 sender: T::CrossAccountId,1585 token: TokenId,1586 amount: u128,1587 ) -> DispatchResultWithPostInfo;15881589 /// Burn token and all nested tokens recursievly.1590 ///1591 /// * `sender` - The user who owns the token.1592 /// * `token` - Token id that will burned.1593 /// * `self_budget` - The budget that can be spent on burning tokens.1594 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.1595 fn burn_item_recursively(1596 &self,1597 sender: T::CrossAccountId,1598 token: TokenId,1599 self_budget: &dyn Budget,1600 breadth_budget: &dyn Budget,1601 ) -> DispatchResultWithPostInfo;16021603 /// Set collection properties.1604 ///1605 /// * `sender` - Must be either the owner of the collection or its admin.1606 /// * `properties` - Properties to be set.1607 fn set_collection_properties(1608 &self,1609 sender: T::CrossAccountId,1610 properties: Vec<Property>,1611 ) -> DispatchResultWithPostInfo;16121613 /// Delete collection properties.1614 ///1615 /// * `sender` - Must be either the owner of the collection or its admin.1616 /// * `properties` - The properties to be removed.1617 fn delete_collection_properties(1618 &self,1619 sender: &T::CrossAccountId,1620 property_keys: Vec<PropertyKey>,1621 ) -> DispatchResultWithPostInfo;16221623 /// Set token properties.1624 ///1625 /// The appropriate [`PropertyPermission`] for the token property1626 /// must be set with [`Self::set_token_property_permissions`].1627 ///1628 /// * `sender` - Must be either the owner of the token or its admin.1629 /// * `token_id` - The token for which the properties are being set.1630 /// * `properties` - Properties to be set.1631 /// * `budget` - Budget for setting properties.1632 fn set_token_properties(1633 &self,1634 sender: T::CrossAccountId,1635 token_id: TokenId,1636 properties: Vec<Property>,1637 budget: &dyn Budget,1638 ) -> DispatchResultWithPostInfo;16391640 /// Remove token properties.1641 ///1642 /// The appropriate [`PropertyPermission`] for the token property1643 /// must be set with [`Self::set_token_property_permissions`].1644 ///1645 /// * `sender` - Must be either the owner of the token or its admin.1646 /// * `token_id` - The token for which the properties are being remove.1647 /// * `property_keys` - Keys to remove corresponding properties.1648 /// * `budget` - Budget for removing properties.1649 fn delete_token_properties(1650 &self,1651 sender: T::CrossAccountId,1652 token_id: TokenId,1653 property_keys: Vec<PropertyKey>,1654 budget: &dyn Budget,1655 ) -> DispatchResultWithPostInfo;16561657 /// Set token property permissions.1658 ///1659 /// * `sender` - Must be either the owner of the token or its admin.1660 /// * `token_id` - The token for which the properties are being set.1661 /// * `property_permissions` - Property permissions to be set.1662 /// * `budget` - Budget for setting properties.1663 fn set_token_property_permissions(1664 &self,1665 sender: &T::CrossAccountId,1666 property_permissions: Vec<PropertyKeyPermission>,1667 ) -> DispatchResultWithPostInfo;16681669 /// Transfer amount of token pieces.1670 ///1671 /// * `sender` - Donor user.1672 /// * `to` - Recepient user.1673 /// * `token` - The token of which parts are being sent.1674 /// * `amount` - The number of parts of the token that will be transferred.1675 /// * `budget` - The maximum budget that can be spent on the transfer.1676 fn transfer(1677 &self,1678 sender: T::CrossAccountId,1679 to: T::CrossAccountId,1680 token: TokenId,1681 amount: u128,1682 budget: &dyn Budget,1683 ) -> DispatchResultWithPostInfo;16841685 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].1686 ///1687 /// * `sender` - The user who grants access to the token.1688 /// * `spender` - The user to whom the rights are granted.1689 /// * `token` - The token to which access is granted.1690 /// * `amount` - The amount of pieces that another user can dispose of.1691 fn approve(1692 &self,1693 sender: T::CrossAccountId,1694 spender: T::CrossAccountId,1695 token: TokenId,1696 amount: u128,1697 ) -> DispatchResultWithPostInfo;16981699 /// Send parts of a token owned by another user.1700 ///1701 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1702 ///1703 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).1704 /// * `from` - The user who owns the token.1705 /// * `to` - Recepient user.1706 /// * `token` - The token of which parts are being sent.1707 /// * `amount` - The number of parts of the token that will be transferred.1708 /// * `budget` - The maximum budget that can be spent on the transfer.1709 fn transfer_from(1710 &self,1711 sender: T::CrossAccountId,1712 from: T::CrossAccountId,1713 to: T::CrossAccountId,1714 token: TokenId,1715 amount: u128,1716 budget: &dyn Budget,1717 ) -> DispatchResultWithPostInfo;17181719 /// Burn parts of a token owned by another user.1720 ///1721 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1722 ///1723 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).1724 /// * `from` - The user who owns the token.1725 /// * `token` - The token of which parts are being sent.1726 /// * `amount` - The number of parts of the token that will be transferred.1727 /// * `budget` - The maximum budget that can be spent on the burn.1728 fn burn_from(1729 &self,1730 sender: T::CrossAccountId,1731 from: T::CrossAccountId,1732 token: TokenId,1733 amount: u128,1734 budget: &dyn Budget,1735 ) -> DispatchResultWithPostInfo;17361737 /// Check permission to nest token.1738 ///1739 /// * `sender` - The user who initiated the check.1740 /// * `from` - The token that is checked for embedding.1741 /// * `under` - Token under which to check.1742 /// * `budget` - The maximum budget that can be spent on the check.1743 fn check_nesting(1744 &self,1745 sender: T::CrossAccountId,1746 from: (CollectionId, TokenId),1747 under: TokenId,1748 budget: &dyn Budget,1749 ) -> DispatchResult;17501751 /// Nest one token into another.1752 ///1753 /// * `under` - Token holder.1754 /// * `to_nest` - Nested token.1755 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17561757 /// Unnest token.1758 ///1759 /// * `under` - Token holder.1760 /// * `to_nest` - Token to unnest.1761 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17621763 /// Get all user tokens.1764 ///1765 /// * `account` - Account for which you need to get tokens.1766 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17671768 /// Get all the tokens in the collection.1769 fn collection_tokens(&self) -> Vec<TokenId>;17701771 /// Check if the token exists.1772 ///1773 /// * `token` - Id token to check.1774 fn token_exists(&self, token: TokenId) -> bool;17751776 /// Get the id of the last minted token.1777 fn last_token_id(&self) -> TokenId;17781779 /// Get the owner of the token.1780 ///1781 /// * `token` - The token for which you need to find out the owner.1782 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17831784 /// Returns 10 tokens owners in no particular order.1785 ///1786 /// * `token` - The token for which you need to find out the owners.1787 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17881789 /// Get the value of the token property by key.1790 ///1791 /// * `token` - Token with the property to get.1792 /// * `key` - Property name.1793 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17941795 /// Get a set of token properties by key vector.1796 ///1797 /// * `token` - Token with the property to get.1798 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),1799 /// then all properties are returned.1800 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18011802 /// Amount of unique collection tokens1803 fn total_supply(&self) -> u32;18041805 /// Amount of different tokens account has.1806 ///1807 /// * `account` - The account for which need to get the balance.1808 fn account_balance(&self, account: T::CrossAccountId) -> u32;18091810 /// Amount of specific token account have.1811 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18121813 /// Amount of token pieces1814 fn total_pieces(&self, token: TokenId) -> Option<u128>;18151816 /// Get the number of parts of the token that a trusted user can manage.1817 ///1818 /// * `sender` - Trusted user.1819 /// * `spender` - Owner of the token.1820 /// * `token` - The token for which to get the value.1821 fn allowance(1822 &self,1823 sender: T::CrossAccountId,1824 spender: T::CrossAccountId,1825 token: TokenId,1826 ) -> u128;18271828 /// Get extension for RFT collection.1829 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1830}18311832/// Extension for RFT collection.1833pub trait RefungibleExtensions<T>1834where1835 T: Config,1836{1837 /// Change the number of parts of the token.1838 ///1839 /// When the value changes down, this function is equivalent to burning parts of the token.1840 ///1841 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.1842 /// * `token` - The token for which you want to change the number of parts.1843 /// * `amount` - The new value of the parts of the token.1844 fn repartition(1845 &self,1846 sender: &T::CrossAccountId,1847 token: TokenId,1848 amount: u128,1849 ) -> DispatchResultWithPostInfo;1850}18511852/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].1853///1854/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.1855pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1856 let post_info = PostDispatchInfo {1857 actual_weight: Some(weight),1858 pays_fee: Pays::Yes,1859 };1860 match res {1861 Ok(()) => Ok(post_info),1862 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1863 }1864}18651866impl<T: Config> From<PropertiesError> for Error<T> {1867 fn from(error: PropertiesError) -> Self {1868 match error {1869 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1870 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1871 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1872 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1873 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1874 }1875 }1876}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63 ensure,64 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65 dispatch::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 RpcCollectionFlags,75 CollectionId,76 CreateItemData,77 MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT,79 TokenId,80 TokenChild,81 CollectionStats,82 MAX_TOKEN_OWNERSHIP,83 CollectionMode,84 NFT_SPONSOR_TRANSFER_TIMEOUT,85 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87 MAX_SPONSOR_TIMEOUT,88 CUSTOM_DATA_LIMIT,89 CollectionLimits,90 CreateCollectionData,91 SponsorshipState,92 CreateItemExData,93 SponsoringRateLimit,94 budget::Budget,95 PhantomType,96 Property,97 Properties,98 PropertiesPermissionMap,99 PropertyKey,100 PropertyValue,101 PropertyPermission,102 PropertiesError,103 PropertyKeyPermission,104 TokenData,105 TrySetProperty,106 PropertyScope,107 // RMRK108 RmrkCollectionInfo,109 RmrkInstanceInfo,110 RmrkResourceInfo,111 RmrkPropertyInfo,112 RmrkBaseInfo,113 RmrkPartType,114 RmrkBoundedTheme,115 RmrkNftChild,116 CollectionPermissions,117};118119pub use pallet::*;120use sp_core::H160;121use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};122#[cfg(feature = "runtime-benchmarks")]123pub mod benchmarking;124pub mod dispatch;125pub mod erc;126pub mod eth;127pub mod weights;128129/// Weight info.130pub type SelfWeightOf<T> = <T as Config>::WeightInfo;131132/// Collection handle contains information about collection data and id.133/// Also provides functionality to count consumed gas.134///135/// CollectionHandle is used as a generic wrapper for collections of all types.136/// It allows to perform common operations and queries on any collection type,137/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].138#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]139pub struct CollectionHandle<T: Config> {140 /// Collection id141 pub id: CollectionId,142 collection: Collection<T::AccountId>,143 /// Substrate recorder for counting consumed gas144 pub recorder: SubstrateRecorder<T>,145}146147impl<T: Config> WithRecorder<T> for CollectionHandle<T> {148 fn recorder(&self) -> &SubstrateRecorder<T> {149 &self.recorder150 }151 fn into_recorder(self) -> SubstrateRecorder<T> {152 self.recorder153 }154}155156impl<T: Config> CollectionHandle<T> {157 /// Same as [CollectionHandle::new] but with an explicit gas limit.158 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {159 <CollectionById<T>>::get(id).map(|collection| Self {160 id,161 collection,162 recorder: SubstrateRecorder::new(gas_limit),163 })164 }165166 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].167 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {168 <CollectionById<T>>::get(id).map(|collection| Self {169 id,170 collection,171 recorder,172 })173 }174175 /// Retrives collection data from storage and creates collection handle with default parameters.176 /// If collection not found return `None`177 pub fn new(id: CollectionId) -> Option<Self> {178 Self::new_with_gas_limit(id, u64::MAX)179 }180181 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.182 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {183 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)184 }185186 /// Consume gas for reading.187 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {188 self.recorder189 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(190 <T as frame_system::Config>::DbWeight::get()191 .read192 .saturating_mul(reads),193 )))194 }195196 /// Consume gas for writing.197 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {198 self.recorder199 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(200 <T as frame_system::Config>::DbWeight::get()201 .write202 .saturating_mul(writes),203 )))204 }205206 /// Consume gas for reading and writing.207 pub fn consume_store_reads_and_writes(208 &self,209 reads: u64,210 writes: u64,211 ) -> evm_coder::execution::Result<()> {212 let weight = <T as frame_system::Config>::DbWeight::get();213 let reads = weight.read.saturating_mul(reads);214 let writes = weight.read.saturating_mul(writes);215 self.recorder216 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(217 reads.saturating_add(writes),218 )))219 }220221 /// Save collection to storage.222 pub fn save(&self) -> DispatchResult {223 <CollectionById<T>>::insert(self.id, &self.collection);224 Ok(())225 }226227 /// Set collection sponsor.228 ///229 /// Unique collections allows sponsoring for certain actions.230 /// This method allows you to set the sponsor of the collection.231 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].232 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {233 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);234 Ok(())235 }236237 /// Confirm sponsorship238 ///239 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.240 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].241 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {242 if self.collection.sponsorship.pending_sponsor() != Some(sender) {243 return Ok(false);244 }245246 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());247 Ok(true)248 }249250 /// Remove collection sponsor.251 pub fn remove_sponsor(&mut self) -> DispatchResult {252 self.collection.sponsorship = SponsorshipState::Disabled;253 Ok(())254 }255256 /// Checks that the collection was created with, and must be operated upon through **Unique API**.257 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.258 pub fn check_is_internal(&self) -> DispatchResult {259 if self.flags.external {260 return Err(<Error<T>>::CollectionIsExternal)?;261 }262263 Ok(())264 }265266 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.267 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.268 pub fn check_is_external(&self) -> DispatchResult {269 if !self.flags.external {270 return Err(<Error<T>>::CollectionIsInternal)?;271 }272273 Ok(())274 }275}276277impl<T: Config> Deref for CollectionHandle<T> {278 type Target = Collection<T::AccountId>;279280 fn deref(&self) -> &Self::Target {281 &self.collection282 }283}284285impl<T: Config> DerefMut for CollectionHandle<T> {286 fn deref_mut(&mut self) -> &mut Self::Target {287 &mut self.collection288 }289}290291impl<T: Config> CollectionHandle<T> {292 /// Checks if the `user` is the owner of the collection.293 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {294 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);295 Ok(())296 }297298 /// Returns **true** if the `user` is the owner or administrator of the collection.299 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {300 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))301 }302303 /// Checks if the `user` is the owner or administrator of the collection.304 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {305 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);306 Ok(())307 }308309 /// Return **true** if `user` was not allowed to have tokens, and he can ignore such restrictions.310 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {311 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)312 }313314 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.315 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {316 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)317 }318319 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.320 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {321 ensure!(322 <Allowlist<T>>::get((self.id, user)),323 <Error<T>>::AddressNotInAllowlist324 );325 Ok(())326 }327328 /// Changes collection owner to another account329 /// #### Store read/writes330 /// 1 writes331 fn set_owner_internal(332 &mut self,333 caller: T::CrossAccountId,334 new_owner: T::CrossAccountId,335 ) -> DispatchResult {336 self.check_is_owner(&caller)?;337 self.collection.owner = new_owner.as_sub().clone();338 self.save()339 }340}341342#[frame_support::pallet]343pub mod pallet {344 use super::*;345 use dispatch::CollectionDispatch;346 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};347 use frame_system::pallet_prelude::*;348 use frame_support::traits::Currency;349 use up_data_structs::{TokenId, mapping::TokenAddressMapping};350 use scale_info::TypeInfo;351 use weights::WeightInfo;352353 #[pallet::config]354 pub trait Config:355 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo356 {357 /// Weight information for functions of this pallet.358 type WeightInfo: WeightInfo;359360 /// Events compatible with [`frame_system::Config::Event`].361 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;362363 /// Handler of accounts and payment.364 type Currency: Currency<Self::AccountId>;365366 /// Set price to create a collection.367 #[pallet::constant]368 type CollectionCreationPrice: Get<369 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,370 >;371372 /// Dispatcher of operations on collections.373 type CollectionDispatch: CollectionDispatch<Self>;374375 /// Account which holds the chain's treasury.376 type TreasuryAccountId: Get<Self::AccountId>;377378 /// Address under which the CollectionHelper contract would be available.379 #[pallet::constant]380 type ContractAddress: Get<H160>;381382 /// Mapper for token addresses to Ethereum addresses.383 type EvmTokenAddressMapping: TokenAddressMapping<H160>;384385 /// Mapper for token addresses to [`CrossAccountId`].386 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;387 }388389 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);390391 #[pallet::pallet]392 #[pallet::storage_version(STORAGE_VERSION)]393 #[pallet::generate_store(pub(super) trait Store)]394 pub struct Pallet<T>(_);395396 #[pallet::extra_constants]397 impl<T: Config> Pallet<T> {398 /// Maximum admins per collection.399 pub fn collection_admins_limit() -> u32 {400 COLLECTION_ADMINS_LIMIT401 }402 }403404 #[pallet::event]405 #[pallet::generate_deposit(pub fn deposit_event)]406 pub enum Event<T: Config> {407 /// New collection was created408 CollectionCreated(409 /// Globally unique identifier of newly created collection.410 CollectionId,411 /// [`CollectionMode`] converted into _u8_.412 u8,413 /// Collection owner.414 T::AccountId,415 ),416417 /// New collection was destroyed418 CollectionDestroyed(419 /// Globally unique identifier of collection.420 CollectionId,421 ),422423 /// New item was created.424 ItemCreated(425 /// Id of the collection where item was created.426 CollectionId,427 /// Id of an item. Unique within the collection.428 TokenId,429 /// Owner of newly created item430 T::CrossAccountId,431 /// Always 1 for NFT432 u128,433 ),434435 /// Collection item was burned.436 ItemDestroyed(437 /// Id of the collection where item was destroyed.438 CollectionId,439 /// Identifier of burned NFT.440 TokenId,441 /// Which user has destroyed its tokens.442 T::CrossAccountId,443 /// Amount of token pieces destroed. Always 1 for NFT.444 u128,445 ),446447 /// Item was transferred448 Transfer(449 /// Id of collection to which item is belong.450 CollectionId,451 /// Id of an item.452 TokenId,453 /// Original owner of item.454 T::CrossAccountId,455 /// New owner of item.456 T::CrossAccountId,457 /// Amount of token pieces transfered. Always 1 for NFT.458 u128,459 ),460461 /// Amount pieces of token owned by `sender` was approved for `spender`.462 Approved(463 /// Id of collection to which item is belong.464 CollectionId,465 /// Id of an item.466 TokenId,467 /// Original owner of item.468 T::CrossAccountId,469 /// Id for which the approval was granted.470 T::CrossAccountId,471 /// Amount of token pieces transfered. Always 1 for NFT.472 u128,473 ),474475 /// The colletion property has been added or edited.476 CollectionPropertySet(477 /// Id of collection to which property has been set.478 CollectionId,479 /// The property that was set.480 PropertyKey,481 ),482483 /// The property has been deleted.484 CollectionPropertyDeleted(485 /// Id of collection to which property has been deleted.486 CollectionId,487 /// The property that was deleted.488 PropertyKey,489 ),490491 /// The token property has been added or edited.492 TokenPropertySet(493 /// Identifier of the collection whose token has the property set.494 CollectionId,495 /// The token for which the property was set.496 TokenId,497 /// The property that was set.498 PropertyKey,499 ),500501 /// The token property has been deleted.502 TokenPropertyDeleted(503 /// Identifier of the collection whose token has the property deleted.504 CollectionId,505 /// The token for which the property was deleted.506 TokenId,507 /// The property that was deleted.508 PropertyKey,509 ),510511 /// The token property permission of a collection has been set.512 PropertyPermissionSet(513 /// ID of collection to which property permission has been set.514 CollectionId,515 /// The property permission that was set.516 PropertyKey,517 ),518 }519520 #[pallet::error]521 pub enum Error<T> {522 /// This collection does not exist.523 CollectionNotFound,524 /// Sender parameter and item owner must be equal.525 MustBeTokenOwner,526 /// No permission to perform action527 NoPermission,528 /// Destroying only empty collections is allowed529 CantDestroyNotEmptyCollection,530 /// Collection is not in mint mode.531 PublicMintingNotAllowed,532 /// Address is not in allow list.533 AddressNotInAllowlist,534535 /// Collection name can not be longer than 63 char.536 CollectionNameLimitExceeded,537 /// Collection description can not be longer than 255 char.538 CollectionDescriptionLimitExceeded,539 /// Token prefix can not be longer than 15 char.540 CollectionTokenPrefixLimitExceeded,541 /// Total collections bound exceeded.542 TotalCollectionsLimitExceeded,543 /// Exceeded max admin count544 CollectionAdminCountExceeded,545 /// Collection limit bounds per collection exceeded546 CollectionLimitBoundsExceeded,547 /// Tried to enable permissions which are only permitted to be disabled548 OwnerPermissionsCantBeReverted,549 /// Collection settings not allowing items transferring550 TransferNotAllowed,551 /// Account token limit exceeded per collection552 AccountTokenLimitExceeded,553 /// Collection token limit exceeded554 CollectionTokenLimitExceeded,555 /// Metadata flag frozen556 MetadataFlagFrozen,557558 /// Item does not exist559 TokenNotFound,560 /// Item is balance not enough561 TokenValueTooLow,562 /// Requested value is more than the approved563 ApprovedValueTooLow,564 /// Tried to approve more than owned565 CantApproveMoreThanOwned,566567 /// Can't transfer tokens to ethereum zero address568 AddressIsZero,569570 /// The operation is not supported571 UnsupportedOperation,572573 /// Insufficient funds to perform an action574 NotSufficientFounds,575576 /// User does not satisfy the nesting rule577 UserIsNotAllowedToNest,578 /// Only tokens from specific collections may nest tokens under this one579 SourceCollectionIsNotAllowedToNest,580581 /// Tried to store more data than allowed in collection field582 CollectionFieldSizeExceeded,583584 /// Tried to store more property data than allowed585 NoSpaceForProperty,586587 /// Tried to store more property keys than allowed588 PropertyLimitReached,589590 /// Property key is too long591 PropertyKeyIsTooLong,592593 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed594 InvalidCharacterInPropertyKey,595596 /// Empty property keys are forbidden597 EmptyPropertyKey,598599 /// Tried to access an external collection with an internal API600 CollectionIsExternal,601602 /// Tried to access an internal collection with an external API603 CollectionIsInternal,604 }605606 /// Storage of the count of created collections. Essentially contains the last collection ID.607 #[pallet::storage]608 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;609610 /// Storage of the count of deleted collections.611 #[pallet::storage]612 pub type DestroyedCollectionCount<T> =613 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;614615 /// Storage of collection info.616 #[pallet::storage]617 pub type CollectionById<T> = StorageMap<618 Hasher = Blake2_128Concat,619 Key = CollectionId,620 Value = Collection<<T as frame_system::Config>::AccountId>,621 QueryKind = OptionQuery,622 >;623624 /// Storage of collection properties.625 #[pallet::storage]626 #[pallet::getter(fn collection_properties)]627 pub type CollectionProperties<T> = StorageMap<628 Hasher = Blake2_128Concat,629 Key = CollectionId,630 Value = Properties,631 QueryKind = ValueQuery,632 OnEmpty = up_data_structs::CollectionProperties,633 >;634635 /// Storage of token property permissions of a collection.636 #[pallet::storage]637 #[pallet::getter(fn property_permissions)]638 pub type CollectionPropertyPermissions<T> = StorageMap<639 Hasher = Blake2_128Concat,640 Key = CollectionId,641 Value = PropertiesPermissionMap,642 QueryKind = ValueQuery,643 >;644645 /// Storage of the amount of collection admins.646 #[pallet::storage]647 pub type AdminAmount<T> = StorageMap<648 Hasher = Blake2_128Concat,649 Key = CollectionId,650 Value = u32,651 QueryKind = ValueQuery,652 >;653654 /// List of collection admins.655 #[pallet::storage]656 pub type IsAdmin<T: Config> = StorageNMap<657 Key = (658 Key<Blake2_128Concat, CollectionId>,659 Key<Blake2_128Concat, T::CrossAccountId>,660 ),661 Value = bool,662 QueryKind = ValueQuery,663 >;664665 /// Allowlisted collection users.666 #[pallet::storage]667 pub type Allowlist<T: Config> = StorageNMap<668 Key = (669 Key<Blake2_128Concat, CollectionId>,670 Key<Blake2_128Concat, T::CrossAccountId>,671 ),672 Value = bool,673 QueryKind = ValueQuery,674 >;675676 /// Not used by code, exists only to provide some types to metadata.677 #[pallet::storage]678 pub type DummyStorageValue<T: Config> = StorageValue<679 Value = (680 CollectionStats,681 CollectionId,682 TokenId,683 TokenChild,684 PhantomType<(685 TokenData<T::CrossAccountId>,686 RpcCollection<T::AccountId>,687 // RMRK688 RmrkCollectionInfo<T::AccountId>,689 RmrkInstanceInfo<T::AccountId>,690 RmrkResourceInfo,691 RmrkPropertyInfo,692 RmrkBaseInfo<T::AccountId>,693 RmrkPartType,694 RmrkBoundedTheme,695 RmrkNftChild,696 )>,697 ),698 QueryKind = OptionQuery,699 >;700701 #[pallet::hooks]702 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {703 fn on_runtime_upgrade() -> Weight {704 StorageVersion::new(1).put::<Pallet<T>>();705706 Weight::zero()707 }708 }709}710711impl<T: Config> Pallet<T> {712 /// Enshure that receiver address is correct.713 ///714 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.715 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {716 ensure!(717 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,718 <Error<T>>::AddressIsZero719 );720 Ok(())721 }722723 /// Get a vector of collection admins.724 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {725 <IsAdmin<T>>::iter_prefix((collection,))726 .map(|(a, _)| a)727 .collect()728 }729730 /// Get a vector of users allowed to mint tokens.731 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {732 <Allowlist<T>>::iter_prefix((collection,))733 .map(|(a, _)| a)734 .collect()735 }736737 /// Is `user` allowed to mint token in `collection`.738 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {739 <Allowlist<T>>::get((collection, user))740 }741742 /// Get statistics of collections.743 pub fn collection_stats() -> CollectionStats {744 let created = <CreatedCollectionCount<T>>::get();745 let destroyed = <DestroyedCollectionCount<T>>::get();746 CollectionStats {747 created: created.0,748 destroyed: destroyed.0,749 alive: created.0 - destroyed.0,750 }751 }752753 /// Get the effective limits for the collection.754 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {755 let collection = <CollectionById<T>>::get(collection)?;756 let limits = collection.limits;757 let effective_limits = CollectionLimits {758 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),759 sponsored_data_size: Some(limits.sponsored_data_size()),760 sponsored_data_rate_limit: Some(761 limits762 .sponsored_data_rate_limit763 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),764 ),765 token_limit: Some(limits.token_limit()),766 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(767 match collection.mode {768 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,769 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,770 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,771 },772 )),773 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),774 owner_can_transfer: Some(limits.owner_can_transfer()),775 owner_can_destroy: Some(limits.owner_can_destroy()),776 transfers_enabled: Some(limits.transfers_enabled()),777 };778779 Some(effective_limits)780 }781782 /// Returns information about the `collection` adapted for rpc.783 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {784 let Collection {785 name,786 description,787 owner,788 mode,789 token_prefix,790 sponsorship,791 limits,792 permissions,793 flags,794 } = <CollectionById<T>>::get(collection)?;795796 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)797 .into_iter()798 .map(|(key, permission)| PropertyKeyPermission { key, permission })799 .collect();800801 let properties = <CollectionProperties<T>>::get(collection)802 .into_iter()803 .map(|(key, value)| Property { key, value })804 .collect();805806 let permissions = CollectionPermissions {807 access: Some(permissions.access()),808 mint_mode: Some(permissions.mint_mode()),809 nesting: Some(permissions.nesting().clone()),810 };811812 Some(RpcCollection {813 name: name.into_inner(),814 description: description.into_inner(),815 owner,816 mode,817 token_prefix: token_prefix.into_inner(),818 sponsorship,819 limits,820 permissions,821 token_property_permissions,822 properties,823 read_only: flags.external,824825 flags: RpcCollectionFlags {826 foreign: flags.foreign,827 erc721metadata: flags.erc721metadata,828 },829 })830 }831}832833macro_rules! limit_default {834 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{835 $(836 if let Some($new) = $new.$field {837 let $old = $old.$field($($arg)?);838 let _ = $new;839 let _ = $old;840 $check841 } else {842 $new.$field = $old.$field843 }844 )*845 }};846}847macro_rules! limit_default_clone {848 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{849 $(850 if let Some($new) = $new.$field.clone() {851 let $old = $old.$field($($arg)?);852 let _ = $new;853 let _ = $old;854 $check855 } else {856 $new.$field = $old.$field.clone()857 }858 )*859 }};860}861862impl<T: Config> Pallet<T> {863 /// Create new collection.864 ///865 /// * `owner` - The owner of the collection.866 /// * `data` - Description of the created collection.867 /// * `flags` - Extra flags to store.868 pub fn init_collection(869 owner: T::CrossAccountId,870 payer: T::CrossAccountId,871 data: CreateCollectionData<T::AccountId>,872 flags: CollectionFlags,873 ) -> Result<CollectionId, DispatchError> {874 {875 ensure!(876 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,877 Error::<T>::CollectionTokenPrefixLimitExceeded878 );879 }880881 let created_count = <CreatedCollectionCount<T>>::get()882 .0883 .checked_add(1)884 .ok_or(ArithmeticError::Overflow)?;885 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;886 let id = CollectionId(created_count);887888 // bound Total number of collections889 ensure!(890 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,891 <Error<T>>::TotalCollectionsLimitExceeded892 );893894 // =========895896 let collection = Collection {897 owner: owner.as_sub().clone(),898 name: data.name,899 mode: data.mode.clone(),900 description: data.description,901 token_prefix: data.token_prefix,902 sponsorship: data903 .pending_sponsor904 .map(SponsorshipState::Unconfirmed)905 .unwrap_or_default(),906 limits: data907 .limits908 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))909 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,910 permissions: data911 .permissions912 .map(|permissions| {913 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)914 })915 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,916 flags,917 };918919 let mut collection_properties = up_data_structs::CollectionProperties::get();920 collection_properties921 .try_set_from_iter(data.properties.into_iter())922 .map_err(<Error<T>>::from)?;923924 CollectionProperties::<T>::insert(id, collection_properties);925926 let mut token_props_permissions = PropertiesPermissionMap::new();927 token_props_permissions928 .try_set_from_iter(data.token_property_permissions.into_iter())929 .map_err(<Error<T>>::from)?;930931 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);932933 // Take a (non-refundable) deposit of collection creation934 {935 let mut imbalance =936 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();937 imbalance.subsume(938 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(939 &T::TreasuryAccountId::get(),940 T::CollectionCreationPrice::get(),941 ),942 );943 <T as Config>::Currency::settle(944 payer.as_sub(),945 imbalance,946 WithdrawReasons::TRANSFER,947 ExistenceRequirement::KeepAlive,948 )949 .map_err(|_| Error::<T>::NotSufficientFounds)?;950 }951952 <CreatedCollectionCount<T>>::put(created_count);953 <Pallet<T>>::deposit_event(Event::CollectionCreated(954 id,955 data.mode.id(),956 owner.as_sub().clone(),957 ));958 <PalletEvm<T>>::deposit_log(959 erc::CollectionHelpersEvents::CollectionCreated {960 owner: *owner.as_eth(),961 collection_id: eth::collection_id_to_address(id),962 }963 .to_log(T::ContractAddress::get()),964 );965 <CollectionById<T>>::insert(id, collection);966 Ok(id)967 }968969 /// Destroy collection.970 ///971 /// * `collection` - Collection handler.972 /// * `sender` - The owner or administrator of the collection.973 pub fn destroy_collection(974 collection: CollectionHandle<T>,975 sender: &T::CrossAccountId,976 ) -> DispatchResult {977 ensure!(978 collection.limits.owner_can_destroy(),979 <Error<T>>::NoPermission,980 );981 collection.check_is_owner(sender)?;982983 let destroyed_collections = <DestroyedCollectionCount<T>>::get()984 .0985 .checked_add(1)986 .ok_or(ArithmeticError::Overflow)?;987988 // =========989990 <DestroyedCollectionCount<T>>::put(destroyed_collections);991 <CollectionById<T>>::remove(collection.id);992 <AdminAmount<T>>::remove(collection.id);993 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);994 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);995 <CollectionProperties<T>>::remove(collection.id);996997 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));998999 <PalletEvm<T>>::deposit_log(1000 erc::CollectionHelpersEvents::CollectionDestroyed {1001 collection_id: eth::collection_id_to_address(collection.id),1002 }1003 .to_log(T::ContractAddress::get()),1004 );1005 Ok(())1006 }10071008 /// Set collection property.1009 ///1010 /// * `collection` - Collection handler.1011 /// * `sender` - The owner or administrator of the collection.1012 /// * `property` - The property to set.1013 pub fn set_collection_property(1014 collection: &CollectionHandle<T>,1015 sender: &T::CrossAccountId,1016 property: Property,1017 ) -> DispatchResult {1018 collection.check_is_owner_or_admin(sender)?;10191020 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1021 let property = property.clone();1022 properties.try_set(property.key, property.value)1023 })1024 .map_err(<Error<T>>::from)?;10251026 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10271028 Ok(())1029 }10301031 /// Set scouped collection property.1032 ///1033 /// * `collection_id` - ID of the collection for which the property is being set.1034 /// * `scope` - Property scope.1035 /// * `property` - The property to set.1036 pub fn set_scoped_collection_property(1037 collection_id: CollectionId,1038 scope: PropertyScope,1039 property: Property,1040 ) -> DispatchResult {1041 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1042 properties.try_scoped_set(scope, property.key, property.value)1043 })1044 .map_err(<Error<T>>::from)?;10451046 Ok(())1047 }10481049 /// Set scouped collection properties.1050 ///1051 /// * `collection_id` - ID of the collection for which the properties is being set.1052 /// * `scope` - Property scope.1053 /// * `properties` - The properties to set.1054 pub fn set_scoped_collection_properties(1055 collection_id: CollectionId,1056 scope: PropertyScope,1057 properties: impl Iterator<Item = Property>,1058 ) -> DispatchResult {1059 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1060 stored_properties.try_scoped_set_from_iter(scope, properties)1061 })1062 .map_err(<Error<T>>::from)?;10631064 Ok(())1065 }10661067 /// Set collection properties.1068 ///1069 /// * `collection` - Collection handler.1070 /// * `sender` - The owner or administrator of the collection.1071 /// * `properties` - The properties to set.1072 #[transactional]1073 pub fn set_collection_properties(1074 collection: &CollectionHandle<T>,1075 sender: &T::CrossAccountId,1076 properties: Vec<Property>,1077 ) -> DispatchResult {1078 for property in properties {1079 Self::set_collection_property(collection, sender, property)?;1080 }10811082 Ok(())1083 }10841085 /// Delete collection property.1086 ///1087 /// * `collection` - Collection handler.1088 /// * `sender` - The owner or administrator of the collection.1089 /// * `property` - The property to delete.1090 pub fn delete_collection_property(1091 collection: &CollectionHandle<T>,1092 sender: &T::CrossAccountId,1093 property_key: PropertyKey,1094 ) -> DispatchResult {1095 collection.check_is_owner_or_admin(sender)?;10961097 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1098 properties.remove(&property_key)1099 })1100 .map_err(<Error<T>>::from)?;11011102 Self::deposit_event(Event::CollectionPropertyDeleted(1103 collection.id,1104 property_key,1105 ));11061107 Ok(())1108 }11091110 /// Delete collection properties.1111 ///1112 /// * `collection` - Collection handler.1113 /// * `sender` - The owner or administrator of the collection.1114 /// * `properties` - The properties to delete.1115 #[transactional]1116 pub fn delete_collection_properties(1117 collection: &CollectionHandle<T>,1118 sender: &T::CrossAccountId,1119 property_keys: Vec<PropertyKey>,1120 ) -> DispatchResult {1121 for key in property_keys {1122 Self::delete_collection_property(collection, sender, key)?;1123 }11241125 Ok(())1126 }11271128 /// Set collection propetry permission without any checks.1129 ///1130 /// Used for migrations.1131 ///1132 /// * `collection` - Collection handler.1133 /// * `property_permissions` - Property permissions.1134 pub fn set_property_permission_unchecked(1135 collection: CollectionId,1136 property_permission: PropertyKeyPermission,1137 ) -> DispatchResult {1138 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1139 permissions.try_set(property_permission.key, property_permission.permission)1140 })1141 .map_err(<Error<T>>::from)?;1142 Ok(())1143 }11441145 /// Set collection property permission.1146 ///1147 /// * `collection` - Collection handler.1148 /// * `sender` - The owner or administrator of the collection.1149 /// * `property_permission` - Property permission.1150 pub fn set_property_permission(1151 collection: &CollectionHandle<T>,1152 sender: &T::CrossAccountId,1153 property_permission: PropertyKeyPermission,1154 ) -> DispatchResult {1155 Self::set_scoped_property_permission(1156 collection,1157 sender,1158 PropertyScope::None,1159 property_permission,1160 )1161 }11621163 /// Set collection property permission with scope.1164 ///1165 /// * `collection` - Collection handler.1166 /// * `sender` - The owner or administrator of the collection.1167 /// * `scope` - Property scope.1168 /// * `property_permission` - Property permission.1169 pub fn set_scoped_property_permission(1170 collection: &CollectionHandle<T>,1171 sender: &T::CrossAccountId,1172 scope: PropertyScope,1173 property_permission: PropertyKeyPermission,1174 ) -> DispatchResult {1175 collection.check_is_owner_or_admin(sender)?;11761177 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1178 let current_permission = all_permissions.get(&property_permission.key);1179 if matches![1180 current_permission,1181 Some(PropertyPermission { mutable: false, .. })1182 ] {1183 return Err(<Error<T>>::NoPermission.into());1184 }11851186 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1187 let property_permission = property_permission.clone();1188 permissions.try_scoped_set(1189 scope,1190 property_permission.key,1191 property_permission.permission,1192 )1193 })1194 .map_err(<Error<T>>::from)?;11951196 Self::deposit_event(Event::PropertyPermissionSet(1197 collection.id,1198 property_permission.key,1199 ));12001201 Ok(())1202 }12031204 /// Set token property permission.1205 ///1206 /// * `collection` - Collection handler.1207 /// * `sender` - The owner or administrator of the collection.1208 /// * `property_permissions` - Property permissions.1209 #[transactional]1210 pub fn set_token_property_permissions(1211 collection: &CollectionHandle<T>,1212 sender: &T::CrossAccountId,1213 property_permissions: Vec<PropertyKeyPermission>,1214 ) -> DispatchResult {1215 Self::set_scoped_token_property_permissions(1216 collection,1217 sender,1218 PropertyScope::None,1219 property_permissions,1220 )1221 }12221223 /// Set token property permission with scope.1224 ///1225 /// * `collection` - Collection handler.1226 /// * `sender` - The owner or administrator of the collection.1227 /// * `scope` - Property scope.1228 /// * `property_permissions` - Property permissions.1229 #[transactional]1230 pub fn set_scoped_token_property_permissions(1231 collection: &CollectionHandle<T>,1232 sender: &T::CrossAccountId,1233 scope: PropertyScope,1234 property_permissions: Vec<PropertyKeyPermission>,1235 ) -> DispatchResult {1236 for prop_pemission in property_permissions {1237 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1238 }12391240 Ok(())1241 }12421243 /// Get collection property.1244 pub fn get_collection_property(1245 collection_id: CollectionId,1246 key: &PropertyKey,1247 ) -> Option<PropertyValue> {1248 Self::collection_properties(collection_id).get(key).cloned()1249 }12501251 /// Convert byte vector to property key vector.1252 pub fn bytes_keys_to_property_keys(1253 keys: Vec<Vec<u8>>,1254 ) -> Result<Vec<PropertyKey>, DispatchError> {1255 keys.into_iter()1256 .map(|key| -> Result<PropertyKey, DispatchError> {1257 key.try_into()1258 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1259 })1260 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1261 }12621263 /// Get properties according to given keys.1264 pub fn filter_collection_properties(1265 collection_id: CollectionId,1266 keys: Option<Vec<PropertyKey>>,1267 ) -> Result<Vec<Property>, DispatchError> {1268 let properties = Self::collection_properties(collection_id);12691270 let properties = keys1271 .map(|keys| {1272 keys.into_iter()1273 .filter_map(|key| {1274 properties.get(&key).map(|value| Property {1275 key,1276 value: value.clone(),1277 })1278 })1279 .collect()1280 })1281 .unwrap_or_else(|| {1282 properties1283 .into_iter()1284 .map(|(key, value)| Property { key, value })1285 .collect()1286 });12871288 Ok(properties)1289 }12901291 /// Get property permissions according to given keys.1292 pub fn filter_property_permissions(1293 collection_id: CollectionId,1294 keys: Option<Vec<PropertyKey>>,1295 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1296 let permissions = Self::property_permissions(collection_id);12971298 let key_permissions = keys1299 .map(|keys| {1300 keys.into_iter()1301 .filter_map(|key| {1302 permissions1303 .get(&key)1304 .map(|permission| PropertyKeyPermission {1305 key,1306 permission: permission.clone(),1307 })1308 })1309 .collect()1310 })1311 .unwrap_or_else(|| {1312 permissions1313 .into_iter()1314 .map(|(key, permission)| PropertyKeyPermission { key, permission })1315 .collect()1316 });13171318 Ok(key_permissions)1319 }13201321 /// Toggle `user` participation in the `collection`'s allow list.1322 /// #### Store read/writes1323 /// 1 writes1324 pub fn toggle_allowlist(1325 collection: &CollectionHandle<T>,1326 sender: &T::CrossAccountId,1327 user: &T::CrossAccountId,1328 allowed: bool,1329 ) -> DispatchResult {1330 collection.check_is_owner_or_admin(sender)?;13311332 // =========13331334 if allowed {1335 <Allowlist<T>>::insert((collection.id, user), true);1336 } else {1337 <Allowlist<T>>::remove((collection.id, user));1338 }13391340 Ok(())1341 }13421343 /// Toggle `user` participation in the `collection`'s admin list.1344 /// #### Store read/writes1345 /// 2 writes1346 pub fn toggle_admin(1347 collection: &CollectionHandle<T>,1348 sender: &T::CrossAccountId,1349 user: &T::CrossAccountId,1350 admin: bool,1351 ) -> DispatchResult {1352 collection.check_is_owner(sender)?;13531354 let was_admin = <IsAdmin<T>>::get((collection.id, user));1355 if was_admin == admin {1356 return Ok(());1357 }1358 let amount = <AdminAmount<T>>::get(collection.id);13591360 if admin {1361 let amount = amount1362 .checked_add(1)1363 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1364 ensure!(1365 amount <= Self::collection_admins_limit(),1366 <Error<T>>::CollectionAdminCountExceeded,1367 );13681369 // =========13701371 <AdminAmount<T>>::insert(collection.id, amount);1372 <IsAdmin<T>>::insert((collection.id, user), true);1373 } else {1374 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1375 <IsAdmin<T>>::remove((collection.id, user));1376 }13771378 Ok(())1379 }13801381 /// Merge set fields from `new_limit` to `old_limit`.1382 pub fn clamp_limits(1383 mode: CollectionMode,1384 old_limit: &CollectionLimits,1385 mut new_limit: CollectionLimits,1386 ) -> Result<CollectionLimits, DispatchError> {1387 let limits = old_limit;1388 limit_default!(old_limit, new_limit,1389 account_token_ownership_limit => ensure!(1390 new_limit <= MAX_TOKEN_OWNERSHIP,1391 <Error<T>>::CollectionLimitBoundsExceeded,1392 ),1393 sponsored_data_size => ensure!(1394 new_limit <= CUSTOM_DATA_LIMIT,1395 <Error<T>>::CollectionLimitBoundsExceeded,1396 ),13971398 sponsored_data_rate_limit => {},1399 token_limit => ensure!(1400 old_limit >= new_limit && new_limit > 0,1401 <Error<T>>::CollectionTokenLimitExceeded1402 ),14031404 sponsor_transfer_timeout(match mode {1405 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1406 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1407 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1408 }) => ensure!(1409 new_limit <= MAX_SPONSOR_TIMEOUT,1410 <Error<T>>::CollectionLimitBoundsExceeded,1411 ),1412 sponsor_approve_timeout => {},1413 owner_can_transfer => ensure!(1414 !limits.owner_can_transfer_instaled() ||1415 old_limit || !new_limit,1416 <Error<T>>::OwnerPermissionsCantBeReverted,1417 ),1418 owner_can_destroy => ensure!(1419 old_limit || !new_limit,1420 <Error<T>>::OwnerPermissionsCantBeReverted,1421 ),1422 transfers_enabled => {},1423 );1424 Ok(new_limit)1425 }14261427 /// Merge set fields from `new_permission` to `old_permission`.1428 pub fn clamp_permissions(1429 _mode: CollectionMode,1430 old_permission: &CollectionPermissions,1431 mut new_permission: CollectionPermissions,1432 ) -> Result<CollectionPermissions, DispatchError> {1433 limit_default_clone!(old_permission, new_permission,1434 access => {},1435 mint_mode => {},1436 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1437 );1438 Ok(new_permission)1439 }1440}14411442/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1443#[macro_export]1444macro_rules! unsupported {1445 ($runtime:path) => {1446 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1447 };1448}14491450/// Return weights for various worst-case operations.1451pub trait CommonWeightInfo<CrossAccountId> {1452 /// Weight of item creation.1453 fn create_item() -> Weight;14541455 /// Weight of items creation.1456 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14571458 /// Weight of items creation.1459 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14601461 /// The weight of the burning item.1462 fn burn_item() -> Weight;14631464 /// Property setting weight.1465 ///1466 /// * `amount`- The number of properties to set.1467 fn set_collection_properties(amount: u32) -> Weight;14681469 /// Collection property deletion weight.1470 ///1471 /// * `amount`- The number of properties to set.1472 fn delete_collection_properties(amount: u32) -> Weight;14731474 /// Token property setting weight.1475 ///1476 /// * `amount`- The number of properties to set.1477 fn set_token_properties(amount: u32) -> Weight;14781479 /// Token property deletion weight.1480 ///1481 /// * `amount`- The number of properties to delete.1482 fn delete_token_properties(amount: u32) -> Weight;14831484 /// Token property permissions set weight.1485 ///1486 /// * `amount`- The number of property permissions to set.1487 fn set_token_property_permissions(amount: u32) -> Weight;14881489 /// Transfer price of the token or its parts.1490 fn transfer() -> Weight;14911492 /// The price of setting the permission of the operation from another user.1493 fn approve() -> Weight;14941495 /// Transfer price from another user.1496 fn transfer_from() -> Weight;14971498 /// The price of burning a token from another user.1499 fn burn_from() -> Weight;15001501 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1502 /// whole users's balance.1503 ///1504 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1505 fn burn_recursively_self_raw() -> Weight;15061507 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1508 ///1509 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1510 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15111512 /// The price of recursive burning a token.1513 ///1514 /// `max_selfs` - The maximum burning weight of the token itself.1515 /// `max_breadth` - The maximum number of nested tokens to burn.1516 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1517 Self::burn_recursively_self_raw()1518 .saturating_mul(max_selfs.max(1) as u64)1519 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1520 }15211522 /// The price of retrieving token owner1523 fn token_owner() -> Weight;1524}15251526/// Weight info extension trait for refungible pallet.1527pub trait RefungibleExtensionsWeightInfo {1528 /// Weight of token repartition.1529 fn repartition() -> Weight;1530}15311532/// Common collection operations.1533///1534/// It wraps methods in Fungible, Nonfungible and Refungible pallets1535/// and adds weight info.1536pub trait CommonCollectionOperations<T: Config> {1537 /// Create token.1538 ///1539 /// * `sender` - The user who mint the token and pays for the transaction.1540 /// * `to` - The user who will own the token.1541 /// * `data` - Token data.1542 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1543 fn create_item(1544 &self,1545 sender: T::CrossAccountId,1546 to: T::CrossAccountId,1547 data: CreateItemData,1548 nesting_budget: &dyn Budget,1549 ) -> DispatchResultWithPostInfo;15501551 /// Create multiple tokens.1552 ///1553 /// * `sender` - The user who mint the token and pays for the transaction.1554 /// * `to` - The user who will own the token.1555 /// * `data` - Token data.1556 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1557 fn create_multiple_items(1558 &self,1559 sender: T::CrossAccountId,1560 to: T::CrossAccountId,1561 data: Vec<CreateItemData>,1562 nesting_budget: &dyn Budget,1563 ) -> DispatchResultWithPostInfo;15641565 /// Create multiple tokens.1566 ///1567 /// * `sender` - The user who mint the token and pays for the transaction.1568 /// * `to` - The user who will own the token.1569 /// * `data` - Token data.1570 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1571 fn create_multiple_items_ex(1572 &self,1573 sender: T::CrossAccountId,1574 data: CreateItemExData<T::CrossAccountId>,1575 nesting_budget: &dyn Budget,1576 ) -> DispatchResultWithPostInfo;15771578 /// Burn token.1579 ///1580 /// * `sender` - The user who owns the token.1581 /// * `token` - Token id that will burned.1582 /// * `amount` - The number of parts of the token that will be burned.1583 fn burn_item(1584 &self,1585 sender: T::CrossAccountId,1586 token: TokenId,1587 amount: u128,1588 ) -> DispatchResultWithPostInfo;15891590 /// Burn token and all nested tokens recursievly.1591 ///1592 /// * `sender` - The user who owns the token.1593 /// * `token` - Token id that will burned.1594 /// * `self_budget` - The budget that can be spent on burning tokens.1595 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.1596 fn burn_item_recursively(1597 &self,1598 sender: T::CrossAccountId,1599 token: TokenId,1600 self_budget: &dyn Budget,1601 breadth_budget: &dyn Budget,1602 ) -> DispatchResultWithPostInfo;16031604 /// Set collection properties.1605 ///1606 /// * `sender` - Must be either the owner of the collection or its admin.1607 /// * `properties` - Properties to be set.1608 fn set_collection_properties(1609 &self,1610 sender: T::CrossAccountId,1611 properties: Vec<Property>,1612 ) -> DispatchResultWithPostInfo;16131614 /// Delete collection properties.1615 ///1616 /// * `sender` - Must be either the owner of the collection or its admin.1617 /// * `properties` - The properties to be removed.1618 fn delete_collection_properties(1619 &self,1620 sender: &T::CrossAccountId,1621 property_keys: Vec<PropertyKey>,1622 ) -> DispatchResultWithPostInfo;16231624 /// Set token properties.1625 ///1626 /// The appropriate [`PropertyPermission`] for the token property1627 /// must be set with [`Self::set_token_property_permissions`].1628 ///1629 /// * `sender` - Must be either the owner of the token or its admin.1630 /// * `token_id` - The token for which the properties are being set.1631 /// * `properties` - Properties to be set.1632 /// * `budget` - Budget for setting properties.1633 fn set_token_properties(1634 &self,1635 sender: T::CrossAccountId,1636 token_id: TokenId,1637 properties: Vec<Property>,1638 budget: &dyn Budget,1639 ) -> DispatchResultWithPostInfo;16401641 /// Remove token properties.1642 ///1643 /// The appropriate [`PropertyPermission`] for the token property1644 /// must be set with [`Self::set_token_property_permissions`].1645 ///1646 /// * `sender` - Must be either the owner of the token or its admin.1647 /// * `token_id` - The token for which the properties are being remove.1648 /// * `property_keys` - Keys to remove corresponding properties.1649 /// * `budget` - Budget for removing properties.1650 fn delete_token_properties(1651 &self,1652 sender: T::CrossAccountId,1653 token_id: TokenId,1654 property_keys: Vec<PropertyKey>,1655 budget: &dyn Budget,1656 ) -> DispatchResultWithPostInfo;16571658 /// Set token property permissions.1659 ///1660 /// * `sender` - Must be either the owner of the token or its admin.1661 /// * `token_id` - The token for which the properties are being set.1662 /// * `property_permissions` - Property permissions to be set.1663 /// * `budget` - Budget for setting properties.1664 fn set_token_property_permissions(1665 &self,1666 sender: &T::CrossAccountId,1667 property_permissions: Vec<PropertyKeyPermission>,1668 ) -> DispatchResultWithPostInfo;16691670 /// Transfer amount of token pieces.1671 ///1672 /// * `sender` - Donor user.1673 /// * `to` - Recepient user.1674 /// * `token` - The token of which parts are being sent.1675 /// * `amount` - The number of parts of the token that will be transferred.1676 /// * `budget` - The maximum budget that can be spent on the transfer.1677 fn transfer(1678 &self,1679 sender: T::CrossAccountId,1680 to: T::CrossAccountId,1681 token: TokenId,1682 amount: u128,1683 budget: &dyn Budget,1684 ) -> DispatchResultWithPostInfo;16851686 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].1687 ///1688 /// * `sender` - The user who grants access to the token.1689 /// * `spender` - The user to whom the rights are granted.1690 /// * `token` - The token to which access is granted.1691 /// * `amount` - The amount of pieces that another user can dispose of.1692 fn approve(1693 &self,1694 sender: T::CrossAccountId,1695 spender: T::CrossAccountId,1696 token: TokenId,1697 amount: u128,1698 ) -> DispatchResultWithPostInfo;16991700 /// Send parts of a token owned by another user.1701 ///1702 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1703 ///1704 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).1705 /// * `from` - The user who owns the token.1706 /// * `to` - Recepient user.1707 /// * `token` - The token of which parts are being sent.1708 /// * `amount` - The number of parts of the token that will be transferred.1709 /// * `budget` - The maximum budget that can be spent on the transfer.1710 fn transfer_from(1711 &self,1712 sender: T::CrossAccountId,1713 from: T::CrossAccountId,1714 to: T::CrossAccountId,1715 token: TokenId,1716 amount: u128,1717 budget: &dyn Budget,1718 ) -> DispatchResultWithPostInfo;17191720 /// Burn parts of a token owned by another user.1721 ///1722 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].1723 ///1724 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).1725 /// * `from` - The user who owns the token.1726 /// * `token` - The token of which parts are being sent.1727 /// * `amount` - The number of parts of the token that will be transferred.1728 /// * `budget` - The maximum budget that can be spent on the burn.1729 fn burn_from(1730 &self,1731 sender: T::CrossAccountId,1732 from: T::CrossAccountId,1733 token: TokenId,1734 amount: u128,1735 budget: &dyn Budget,1736 ) -> DispatchResultWithPostInfo;17371738 /// Check permission to nest token.1739 ///1740 /// * `sender` - The user who initiated the check.1741 /// * `from` - The token that is checked for embedding.1742 /// * `under` - Token under which to check.1743 /// * `budget` - The maximum budget that can be spent on the check.1744 fn check_nesting(1745 &self,1746 sender: T::CrossAccountId,1747 from: (CollectionId, TokenId),1748 under: TokenId,1749 budget: &dyn Budget,1750 ) -> DispatchResult;17511752 /// Nest one token into another.1753 ///1754 /// * `under` - Token holder.1755 /// * `to_nest` - Nested token.1756 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17571758 /// Unnest token.1759 ///1760 /// * `under` - Token holder.1761 /// * `to_nest` - Token to unnest.1762 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17631764 /// Get all user tokens.1765 ///1766 /// * `account` - Account for which you need to get tokens.1767 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17681769 /// Get all the tokens in the collection.1770 fn collection_tokens(&self) -> Vec<TokenId>;17711772 /// Check if the token exists.1773 ///1774 /// * `token` - Id token to check.1775 fn token_exists(&self, token: TokenId) -> bool;17761777 /// Get the id of the last minted token.1778 fn last_token_id(&self) -> TokenId;17791780 /// Get the owner of the token.1781 ///1782 /// * `token` - The token for which you need to find out the owner.1783 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17841785 /// Returns 10 tokens owners in no particular order.1786 ///1787 /// * `token` - The token for which you need to find out the owners.1788 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17891790 /// Get the value of the token property by key.1791 ///1792 /// * `token` - Token with the property to get.1793 /// * `key` - Property name.1794 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17951796 /// Get a set of token properties by key vector.1797 ///1798 /// * `token` - Token with the property to get.1799 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),1800 /// then all properties are returned.1801 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18021803 /// Amount of unique collection tokens1804 fn total_supply(&self) -> u32;18051806 /// Amount of different tokens account has.1807 ///1808 /// * `account` - The account for which need to get the balance.1809 fn account_balance(&self, account: T::CrossAccountId) -> u32;18101811 /// Amount of specific token account have.1812 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18131814 /// Amount of token pieces1815 fn total_pieces(&self, token: TokenId) -> Option<u128>;18161817 /// Get the number of parts of the token that a trusted user can manage.1818 ///1819 /// * `sender` - Trusted user.1820 /// * `spender` - Owner of the token.1821 /// * `token` - The token for which to get the value.1822 fn allowance(1823 &self,1824 sender: T::CrossAccountId,1825 spender: T::CrossAccountId,1826 token: TokenId,1827 ) -> u128;18281829 /// Get extension for RFT collection.1830 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1831}18321833/// Extension for RFT collection.1834pub trait RefungibleExtensions<T>1835where1836 T: Config,1837{1838 /// Change the number of parts of the token.1839 ///1840 /// When the value changes down, this function is equivalent to burning parts of the token.1841 ///1842 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.1843 /// * `token` - The token for which you want to change the number of parts.1844 /// * `amount` - The new value of the parts of the token.1845 fn repartition(1846 &self,1847 sender: &T::CrossAccountId,1848 token: TokenId,1849 amount: u128,1850 ) -> DispatchResultWithPostInfo;1851}18521853/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].1854///1855/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.1856pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1857 let post_info = PostDispatchInfo {1858 actual_weight: Some(weight),1859 pays_fee: Pays::Yes,1860 };1861 match res {1862 Ok(()) => Ok(post_info),1863 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1864 }1865}18661867impl<T: Config> From<PropertiesError> for Error<T> {1868 fn from(error: PropertiesError) -> Self {1869 match error {1870 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1871 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1872 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1873 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1874 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1875 }1876 }1877}pallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -47,6 +47,7 @@
type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;
/// Address, under which magic contract will be available
+ #[pallet::constant]
type ContractAddress: Get<H160>;
/// In case of enabled sponsoring, but no sponsoring rate limit set,
tests/src/apiConsts.test.tsdiffbeforeafterboth--- a/tests/src/apiConsts.test.ts
+++ b/tests/src/apiConsts.test.ts
@@ -15,6 +15,7 @@
// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
import {ApiPromise} from '@polkadot/api';
+import {ApiBase} from '@polkadot/api/base';
import {usingPlaygrounds, itSub, expect} from './util';
@@ -41,6 +42,9 @@
transfersEnabled: true,
};
+const EVM_COLLECTION_HELPERS_ADDRESS = '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f';
+const HELPERS_CONTRACT_ADDRESS = '0x842899ECF380553E8a4de75bF534cdf6fBF64049';
+
describe('integration test: API UNIQUE consts', () => {
let api: ApiPromise;
@@ -101,6 +105,14 @@
itSub('COLLECTION_ADMINS_LIMIT', () => {
checkConst(api.consts.unique.collectionAdminsLimit, COLLECTION_ADMINS_LIMIT);
});
+
+ itSub('HELPERS_CONTRACT_ADDRESS', () => {
+ expect(api.consts.evmContractHelpers.contractAddress.toString().toLowerCase()).to.be.equal(HELPERS_CONTRACT_ADDRESS.toLowerCase());
+ });
+
+ itSub('EVM_COLLECTION_HELPERS_ADDRESS', () => {
+ expect(api.consts.common.contractAddress.toString().toLowerCase()).to.be.equal(EVM_COLLECTION_HELPERS_ADDRESS.toLowerCase());
+ });
});
function checkConst<T>(constValue: any, expectedValue: T) {
tests/src/eth/util/playgrounds/unique.dev.tsdiffbeforeafterboth--- a/tests/src/eth/util/playgrounds/unique.dev.ts
+++ b/tests/src/eth/util/playgrounds/unique.dev.ts
@@ -103,12 +103,12 @@
contractHelpers(caller: string): Contract {
const web3 = this.helper.getWeb3();
- return new web3.eth.Contract(contractHelpersAbi as any, '0x842899ECF380553E8a4de75bF534cdf6fBF64049', {from: caller, gas: this.helper.eth.DEFAULT_GAS});
+ return new web3.eth.Contract(contractHelpersAbi as any, this.helper.getApi().consts.evmContractHelpers.contractAddress.toString(), {from: caller, gas: this.helper.eth.DEFAULT_GAS});
}
collectionHelpers(caller: string) {
const web3 = this.helper.getWeb3();
- return new web3.eth.Contract(collectionHelpersAbi as any, '0x6c4e9fe1ae37a41e93cee429e8e1881abdcbb54f', {from: caller, gas: this.helper.eth.DEFAULT_GAS});
+ return new web3.eth.Contract(collectionHelpersAbi as any, this.helper.getApi().consts.common.contractAddress.toString(), {from: caller, gas: this.helper.eth.DEFAULT_GAS});
}
collection(address: string, mode: TCollectionMode, caller?: string, mergeDeprecated = false): Contract {
tests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth--- a/tests/src/interfaces/augment-api-consts.ts
+++ b/tests/src/interfaces/augment-api-consts.ts
@@ -8,7 +8,7 @@
import type { ApiTypes, AugmentedConst } from '@polkadot/api-base/types';
import type { Option, u128, u16, u32, u64, u8 } from '@polkadot/types-codec';
import type { Codec } from '@polkadot/types-codec/types';
-import type { Perbill, Permill, Weight } from '@polkadot/types/interfaces/runtime';
+import type { H160, Perbill, Permill, Weight } from '@polkadot/types/interfaces/runtime';
import type { FrameSupportPalletId, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, SpVersionRuntimeVersion, SpWeightsRuntimeDbWeight, UpDataStructsCollectionLimits, XcmV1MultiLocation } from '@polkadot/types/lookup';
export type __AugmentedConst<ApiType extends ApiTypes> = AugmentedConst<ApiType>;
@@ -70,6 +70,10 @@
**/
collectionCreationPrice: u128 & AugmentedConst<ApiType>;
/**
+ * Address under which the CollectionHelper contract would be available.
+ **/
+ contractAddress: H160 & AugmentedConst<ApiType>;
+ /**
* Generic const
**/
[key: string]: Codec;
@@ -82,6 +86,16 @@
**/
[key: string]: Codec;
};
+ evmContractHelpers: {
+ /**
+ * Address, under which magic contract will be available
+ **/
+ contractAddress: H160 & AugmentedConst<ApiType>;
+ /**
+ * Generic const
+ **/
+ [key: string]: Codec;
+ };
inflation: {
/**
* Number of blocks that pass between treasury balance updates due to inflation
tests/src/interfaces/lookup.tsdiffbeforeafterboth--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -2936,13 +2936,13 @@
}
},
/**
- * Lookup340: pallet_maintenance::pallet::Call<T>
+ * Lookup338: pallet_maintenance::pallet::Call<T>
**/
PalletMaintenanceCall: {
_enum: ['enable', 'disable']
},
/**
- * Lookup341: pallet_test_utils::pallet::Call<T>
+ * Lookup339: pallet_test_utils::pallet::Call<T>
**/
PalletTestUtilsCall: {
_enum: {
tests/src/interfaces/types-lookup.tsdiffbeforeafterboth--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -3175,14 +3175,14 @@
readonly type: 'Begin' | 'SetData' | 'Finish' | 'InsertEthLogs' | 'InsertEvents';
}
- /** @name PalletMaintenanceCall (340) */
+ /** @name PalletMaintenanceCall (338) */
interface PalletMaintenanceCall extends Enum {
readonly isEnable: boolean;
readonly isDisable: boolean;
readonly type: 'Enable' | 'Disable';
}
- /** @name PalletTestUtilsCall (341) */
+ /** @name PalletTestUtilsCall (339) */
interface PalletTestUtilsCall extends Enum {
readonly isEnable: boolean;
readonly isSetTestValue: boolean;