difftreelog
fix eth weights
in: master
3 files changed
pallets/balances-adapter/src/erc.rsdiffbeforeafterboth--- a/pallets/balances-adapter/src/erc.rs
+++ b/pallets/balances-adapter/src/erc.rs
@@ -3,6 +3,7 @@
use frame_support::traits::{Currency, ExistenceRequirement};
use pallet_balances::WeightInfo;
use pallet_common::{
+ consume_store_reads,
erc::{CommonEvmHandler, CrossAccountId, PrecompileHandle, PrecompileResult},
eth::CrossAddress,
};
@@ -43,7 +44,7 @@
}
fn balance_of(&self, owner: Address) -> Result<U256> {
- // self.consume_store_reads(1)?;
+ consume_store_reads(self, 1)?;
let owner = T::CrossAccountId::from_eth(owner);
let balance = <T as Config>::Currency::free_balance(owner.as_sub());
Ok(balance.into())
@@ -62,7 +63,7 @@
}
fn total_supply(&self) -> Result<U256> {
- // self.consume_store_reads(1)?;
+ consume_store_reads(self, 1)?;
let total = <T as Config>::Currency::total_issuance();
Ok(total.into())
}
@@ -126,7 +127,7 @@
T::AccountId: From<[u8; 32]>,
{
fn balance_of_cross(&self, owner: CrossAddress) -> Result<U256> {
- // self.consume_store_reads(1)?;
+ consume_store_reads(self, 1)?;
let owner = owner.into_sub_cross_account::<T>()?;
let balance = <T as Config>::Currency::free_balance(owner.as_sub());
Ok(balance.into())
pallets/balances-adapter/src/lib.rsdiffbeforeafterboth--- a/pallets/balances-adapter/src/lib.rs
+++ b/pallets/balances-adapter/src/lib.rs
@@ -2,6 +2,8 @@
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
+use core::ops::Deref;
+
use frame_support::sp_runtime::DispatchResult;
pub use pallet::*;
use pallet_evm_coder_substrate::{WithRecorder, SubstrateRecorder};
@@ -33,6 +35,14 @@
self.0
}
}
+
+impl<T: Config> Deref for NativeFungibleHandle<T> {
+ type Target = SubstrateRecorder<T>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
#[frame_support::pallet]
pub mod pallet {
use alloc::string::String;
pallets/common/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::{57 ops::{Deref, DerefMut},58 slice::from_ref,59};60use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};61use sp_std::vec::Vec;62use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};63use evm_coder::ToLog;64use frame_support::{65 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},66 ensure,67 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},68 dispatch::Pays,69 transactional, fail,70};71use pallet_evm::GasWeightMapping;72use up_data_structs::{73 AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,74 RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,75 COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP,76 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,77 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,78 CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,79 PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties,80 PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError,81 TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope,82 CollectionPermissions,83};84use up_pov_estimate_rpc::PovInfo;8586pub use pallet::*;87use sp_core::H160;88use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};8990#[cfg(feature = "runtime-benchmarks")]91pub mod benchmarking;92pub mod dispatch;93pub mod erc;94pub mod eth;95pub mod helpers;96#[allow(missing_docs)]97pub mod weights;98/// Weight info.99pub type SelfWeightOf<T> = <T as Config>::WeightInfo;100101/// Collection handle contains information about collection data and id.102/// Also provides functionality to count consumed gas.103///104/// CollectionHandle is used as a generic wrapper for collections of all types.105/// It allows to perform common operations and queries on any collection type,106/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].107#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]108pub struct CollectionHandle<T: Config> {109 /// Collection id110 pub id: CollectionId,111 collection: Collection<T::AccountId>,112 /// Substrate recorder for counting consumed gas113 pub recorder: SubstrateRecorder<T>,114}115116impl<T: Config> WithRecorder<T> for CollectionHandle<T> {117 fn recorder(&self) -> &SubstrateRecorder<T> {118 &self.recorder119 }120 fn into_recorder(self) -> SubstrateRecorder<T> {121 self.recorder122 }123}124125impl<T: Config> CollectionHandle<T> {126 /// Same as [CollectionHandle::new] but with an explicit gas limit.127 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {128 Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))129 }130131 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].132 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {133 <CollectionById<T>>::get(id).map(|collection| Self {134 id,135 collection,136 recorder,137 })138 }139140 /// Retrives collection data from storage and creates collection handle with default parameters.141 /// If collection not found return `None`142 pub fn new(id: CollectionId) -> Option<Self> {143 Self::new_with_gas_limit(id, u64::MAX)144 }145146 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.147 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {148 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)149 }150151 /// Consume gas for reading.152 pub fn consume_store_reads(153 &self,154 reads: u64,155 ) -> pallet_evm_coder_substrate::execution::Result<()> {156 self.recorder157 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(158 <T as frame_system::Config>::DbWeight::get()159 .read160 .saturating_mul(reads),161 // TODO: measure proof162 0,163 )))164 }165166 /// Consume gas for writing.167 pub fn consume_store_writes(168 &self,169 writes: u64,170 ) -> pallet_evm_coder_substrate::execution::Result<()> {171 self.recorder172 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(173 <T as frame_system::Config>::DbWeight::get()174 .write175 .saturating_mul(writes),176 // TODO: measure proof177 0,178 )))179 }180181 /// Consume gas for reading and writing.182 pub fn consume_store_reads_and_writes(183 &self,184 reads: u64,185 writes: u64,186 ) -> pallet_evm_coder_substrate::execution::Result<()> {187 let weight = <T as frame_system::Config>::DbWeight::get();188 let reads = weight.read.saturating_mul(reads);189 let writes = weight.read.saturating_mul(writes);190 self.recorder191 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(192 reads.saturating_add(writes),193 // TODO: measure proof194 0,195 )))196 }197198 /// Save collection to storage.199 pub fn save(&self) -> DispatchResult {200 <CollectionById<T>>::insert(self.id, &self.collection);201 Ok(())202 }203204 /// Set collection sponsor.205 ///206 /// Unique collections allows sponsoring for certain actions.207 /// This method allows you to set the sponsor of the collection.208 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].209 pub fn set_sponsor(210 &mut self,211 sender: &T::CrossAccountId,212 sponsor: T::AccountId,213 ) -> DispatchResult {214 self.check_is_internal()?;215 self.check_is_owner_or_admin(sender)?;216217 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());218219 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));220 <PalletEvm<T>>::deposit_log(221 erc::CollectionHelpersEvents::CollectionChanged {222 collection_id: eth::collection_id_to_address(self.id),223 }224 .to_log(T::ContractAddress::get()),225 );226227 self.save()228 }229230 /// Force set `sponsor`.231 ///232 /// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation233 /// from the `sponsor` is not required.234 ///235 /// # Arguments236 ///237 /// * `sender`: Caller's account.238 /// * `sponsor`: ID of the account of the sponsor-to-be.239 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {240 self.check_is_internal()?;241242 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());243244 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));245 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));246 <PalletEvm<T>>::deposit_log(247 erc::CollectionHelpersEvents::CollectionChanged {248 collection_id: eth::collection_id_to_address(self.id),249 }250 .to_log(T::ContractAddress::get()),251 );252253 self.save()254 }255256 /// Confirm sponsorship257 ///258 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.259 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].260 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {261 self.check_is_internal()?;262 ensure!(263 self.collection.sponsorship.pending_sponsor() == Some(sender),264 Error::<T>::ConfirmSponsorshipFail265 );266267 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());268269 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));270 <PalletEvm<T>>::deposit_log(271 erc::CollectionHelpersEvents::CollectionChanged {272 collection_id: eth::collection_id_to_address(self.id),273 }274 .to_log(T::ContractAddress::get()),275 );276277 self.save()278 }279280 /// Remove collection sponsor.281 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {282 self.check_is_internal()?;283 self.check_is_owner_or_admin(sender)?;284285 self.collection.sponsorship = SponsorshipState::Disabled;286287 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));288 <PalletEvm<T>>::deposit_log(289 erc::CollectionHelpersEvents::CollectionChanged {290 collection_id: eth::collection_id_to_address(self.id),291 }292 .to_log(T::ContractAddress::get()),293 );294 self.save()295 }296297 /// Force remove `sponsor`.298 ///299 /// Differs from `remove_sponsor` in that300 /// it doesn't require consent from the `owner` of the collection.301 pub fn force_remove_sponsor(&mut self) -> DispatchResult {302 self.check_is_internal()?;303304 self.collection.sponsorship = SponsorshipState::Disabled;305306 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));307 <PalletEvm<T>>::deposit_log(308 erc::CollectionHelpersEvents::CollectionChanged {309 collection_id: eth::collection_id_to_address(self.id),310 }311 .to_log(T::ContractAddress::get()),312 );313 self.save()314 }315316 /// Checks that the collection was created with, and must be operated upon through **Unique API**.317 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.318 pub fn check_is_internal(&self) -> DispatchResult {319 if self.flags.external {320 return Err(<Error<T>>::CollectionIsExternal)?;321 }322323 Ok(())324 }325326 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.327 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.328 pub fn check_is_external(&self) -> DispatchResult {329 if !self.flags.external {330 return Err(<Error<T>>::CollectionIsInternal)?;331 }332333 Ok(())334 }335}336337impl<T: Config> Deref for CollectionHandle<T> {338 type Target = Collection<T::AccountId>;339340 fn deref(&self) -> &Self::Target {341 &self.collection342 }343}344345impl<T: Config> DerefMut for CollectionHandle<T> {346 fn deref_mut(&mut self) -> &mut Self::Target {347 &mut self.collection348 }349}350351impl<T: Config> CollectionHandle<T> {352 /// Checks if the `user` is the owner of the collection.353 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {354 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);355 Ok(())356 }357358 /// Returns **true** if the `user` is the owner or administrator of the collection.359 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {360 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))361 }362363 /// Checks if the `user` is the owner or administrator of the collection.364 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {365 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);366 Ok(())367 }368369 /// Returns **true** if370 /// * the `user`is a collection owner or admin371 /// * the collection limits allow the owner/admins to transfer/burn any collection token372 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {373 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)374 }375376 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.377 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {378 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)379 }380381 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.382 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {383 ensure!(384 <Allowlist<T>>::get((self.id, user)),385 <Error<T>>::AddressNotInAllowlist386 );387 Ok(())388 }389390 /// Changes collection owner to another account391 /// #### Store read/writes392 /// 1 writes393 pub fn change_owner(394 &mut self,395 caller: T::CrossAccountId,396 new_owner: T::CrossAccountId,397 ) -> DispatchResult {398 self.check_is_internal()?;399 self.check_is_owner(&caller)?;400 self.collection.owner = new_owner.as_sub().clone();401402 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(403 self.id,404 new_owner.as_sub().clone(),405 ));406 <PalletEvm<T>>::deposit_log(407 erc::CollectionHelpersEvents::CollectionChanged {408 collection_id: eth::collection_id_to_address(self.id),409 }410 .to_log(T::ContractAddress::get()),411 );412413 self.save()414 }415}416417#[frame_support::pallet]418pub mod pallet {419420 use super::*;421 use dispatch::CollectionDispatch;422 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};423 use frame_support::traits::Currency;424 use up_data_structs::{TokenId, mapping::TokenAddressMapping};425 use scale_info::TypeInfo;426 use weights::WeightInfo;427428 #[pallet::config]429 pub trait Config:430 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo431 {432 /// Weight information for functions of this pallet.433 type WeightInfo: WeightInfo;434435 /// Events compatible with [`frame_system::Config::Event`].436 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;437438 /// Handler of accounts and payment.439 type Currency: Currency<Self::AccountId>;440441 /// Set price to create a collection.442 #[pallet::constant]443 type CollectionCreationPrice: Get<444 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,445 >;446447 /// Dispatcher of operations on collections.448 type CollectionDispatch: CollectionDispatch<Self>;449450 /// Account which holds the chain's treasury.451 type TreasuryAccountId: Get<Self::AccountId>;452453 /// Address under which the CollectionHelper contract would be available.454 #[pallet::constant]455 type ContractAddress: Get<H160>;456457 /// Mapper for token addresses to Ethereum addresses.458 type EvmTokenAddressMapping: TokenAddressMapping<H160>;459460 /// Mapper for token addresses to [`CrossAccountId`].461 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;462 }463464 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);465 /// Collection id for native fungible collction.466 pub const NATIVE_FINGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);467468 #[pallet::pallet]469 #[pallet::storage_version(STORAGE_VERSION)]470 pub struct Pallet<T>(_);471472 #[pallet::extra_constants]473 impl<T: Config> Pallet<T> {474 /// Maximum admins per collection.475 pub fn collection_admins_limit() -> u32 {476 COLLECTION_ADMINS_LIMIT477 }478 }479480 #[pallet::genesis_config]481 pub struct GenesisConfig<T>(PhantomData<T>);482483 #[cfg(feature = "std")]484 impl<T: Config> Default for GenesisConfig<T> {485 fn default() -> Self {486 Self(Default::default())487 }488 }489490 #[pallet::genesis_build]491 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {492 fn build(&self) {493 StorageVersion::new(1).put::<Pallet<T>>();494 }495 }496497 impl<T: Config> Pallet<T> {498 /// Helper function that handles deposit events499 pub fn deposit_event(event: Event<T>) {500 let event = <T as Config>::RuntimeEvent::from(event);501 let event = event.into();502 <frame_system::Pallet<T>>::deposit_event(event)503 }504 }505506 #[pallet::event]507 pub enum Event<T: Config> {508 /// New collection was created509 CollectionCreated(510 /// Globally unique identifier of newly created collection.511 CollectionId,512 /// [`CollectionMode`] converted into _u8_.513 u8,514 /// Collection owner.515 T::AccountId,516 ),517518 /// New collection was destroyed519 CollectionDestroyed(520 /// Globally unique identifier of collection.521 CollectionId,522 ),523524 /// New item was created.525 ItemCreated(526 /// Id of the collection where item was created.527 CollectionId,528 /// Id of an item. Unique within the collection.529 TokenId,530 /// Owner of newly created item531 T::CrossAccountId,532 /// Always 1 for NFT533 u128,534 ),535536 /// Collection item was burned.537 ItemDestroyed(538 /// Id of the collection where item was destroyed.539 CollectionId,540 /// Identifier of burned NFT.541 TokenId,542 /// Which user has destroyed its tokens.543 T::CrossAccountId,544 /// Amount of token pieces destroed. Always 1 for NFT.545 u128,546 ),547548 /// Item was transferred549 Transfer(550 /// Id of collection to which item is belong.551 CollectionId,552 /// Id of an item.553 TokenId,554 /// Original owner of item.555 T::CrossAccountId,556 /// New owner of item.557 T::CrossAccountId,558 /// Amount of token pieces transfered. Always 1 for NFT.559 u128,560 ),561562 /// Amount pieces of token owned by `sender` was approved for `spender`.563 Approved(564 /// Id of collection to which item is belong.565 CollectionId,566 /// Id of an item.567 TokenId,568 /// Original owner of item.569 T::CrossAccountId,570 /// Id for which the approval was granted.571 T::CrossAccountId,572 /// Amount of token pieces transfered. Always 1 for NFT.573 u128,574 ),575576 /// A `sender` approves operations on all owned tokens for `spender`.577 ApprovedForAll(578 /// Id of collection to which item is belong.579 CollectionId,580 /// Owner of a wallet.581 T::CrossAccountId,582 /// Id for which operator status was granted or rewoked.583 T::CrossAccountId,584 /// Is operator status granted or revoked?585 bool,586 ),587588 /// The colletion property has been added or edited.589 CollectionPropertySet(590 /// Id of collection to which property has been set.591 CollectionId,592 /// The property that was set.593 PropertyKey,594 ),595596 /// The property has been deleted.597 CollectionPropertyDeleted(598 /// Id of collection to which property has been deleted.599 CollectionId,600 /// The property that was deleted.601 PropertyKey,602 ),603604 /// The token property has been added or edited.605 TokenPropertySet(606 /// Identifier of the collection whose token has the property set.607 CollectionId,608 /// The token for which the property was set.609 TokenId,610 /// The property that was set.611 PropertyKey,612 ),613614 /// The token property has been deleted.615 TokenPropertyDeleted(616 /// Identifier of the collection whose token has the property deleted.617 CollectionId,618 /// The token for which the property was deleted.619 TokenId,620 /// The property that was deleted.621 PropertyKey,622 ),623624 /// The token property permission of a collection has been set.625 PropertyPermissionSet(626 /// ID of collection to which property permission has been set.627 CollectionId,628 /// The property permission that was set.629 PropertyKey,630 ),631632 /// Address was added to the allow list.633 AllowListAddressAdded(634 /// ID of the affected collection.635 CollectionId,636 /// Address of the added account.637 T::CrossAccountId,638 ),639640 /// Address was removed from the allow list.641 AllowListAddressRemoved(642 /// ID of the affected collection.643 CollectionId,644 /// Address of the removed account.645 T::CrossAccountId,646 ),647648 /// Collection admin was added.649 CollectionAdminAdded(650 /// ID of the affected collection.651 CollectionId,652 /// Admin address.653 T::CrossAccountId,654 ),655656 /// Collection admin was removed.657 CollectionAdminRemoved(658 /// ID of the affected collection.659 CollectionId,660 /// Removed admin address.661 T::CrossAccountId,662 ),663664 /// Collection limits were set.665 CollectionLimitSet(666 /// ID of the affected collection.667 CollectionId,668 ),669670 /// Collection owned was changed.671 CollectionOwnerChanged(672 /// ID of the affected collection.673 CollectionId,674 /// New owner address.675 T::AccountId,676 ),677678 /// Collection permissions were set.679 CollectionPermissionSet(680 /// ID of the affected collection.681 CollectionId,682 ),683684 /// Collection sponsor was set.685 CollectionSponsorSet(686 /// ID of the affected collection.687 CollectionId,688 /// New sponsor address.689 T::AccountId,690 ),691692 /// New sponsor was confirm.693 SponsorshipConfirmed(694 /// ID of the affected collection.695 CollectionId,696 /// New sponsor address.697 T::AccountId,698 ),699700 /// Collection sponsor was removed.701 CollectionSponsorRemoved(702 /// ID of the affected collection.703 CollectionId,704 ),705 }706707 #[pallet::error]708 pub enum Error<T> {709 /// This collection does not exist.710 CollectionNotFound,711 /// Sender parameter and item owner must be equal.712 MustBeTokenOwner,713 /// No permission to perform action714 NoPermission,715 /// Destroying only empty collections is allowed716 CantDestroyNotEmptyCollection,717 /// Collection is not in mint mode.718 PublicMintingNotAllowed,719 /// Address is not in allow list.720 AddressNotInAllowlist,721722 /// Collection name can not be longer than 63 char.723 CollectionNameLimitExceeded,724 /// Collection description can not be longer than 255 char.725 CollectionDescriptionLimitExceeded,726 /// Token prefix can not be longer than 15 char.727 CollectionTokenPrefixLimitExceeded,728 /// Total collections bound exceeded.729 TotalCollectionsLimitExceeded,730 /// Exceeded max admin count731 CollectionAdminCountExceeded,732 /// Collection limit bounds per collection exceeded733 CollectionLimitBoundsExceeded,734 /// Tried to enable permissions which are only permitted to be disabled735 OwnerPermissionsCantBeReverted,736 /// Collection settings not allowing items transferring737 TransferNotAllowed,738 /// Account token limit exceeded per collection739 AccountTokenLimitExceeded,740 /// Collection token limit exceeded741 CollectionTokenLimitExceeded,742 /// Metadata flag frozen743 MetadataFlagFrozen,744745 /// Item does not exist746 TokenNotFound,747 /// Item is balance not enough748 TokenValueTooLow,749 /// Requested value is more than the approved750 ApprovedValueTooLow,751 /// Tried to approve more than owned752 CantApproveMoreThanOwned,753 /// Only spending from eth mirror could be approved754 AddressIsNotEthMirror,755756 /// Can't transfer tokens to ethereum zero address757 AddressIsZero,758759 /// The operation is not supported760 UnsupportedOperation,761762 /// Insufficient funds to perform an action763 NotSufficientFounds,764765 /// User does not satisfy the nesting rule766 UserIsNotAllowedToNest,767 /// Only tokens from specific collections may nest tokens under this one768 SourceCollectionIsNotAllowedToNest,769770 /// Tried to store more data than allowed in collection field771 CollectionFieldSizeExceeded,772773 /// Tried to store more property data than allowed774 NoSpaceForProperty,775776 /// Tried to store more property keys than allowed777 PropertyLimitReached,778779 /// Property key is too long780 PropertyKeyIsTooLong,781782 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed783 InvalidCharacterInPropertyKey,784785 /// Empty property keys are forbidden786 EmptyPropertyKey,787788 /// Tried to access an external collection with an internal API789 CollectionIsExternal,790791 /// Tried to access an internal collection with an external API792 CollectionIsInternal,793794 /// This address is not set as sponsor, use setCollectionSponsor first.795 ConfirmSponsorshipFail,796797 /// The user is not an administrator.798 UserIsNotCollectionAdmin,799 }800801 /// Storage of the count of created collections. Essentially contains the last collection ID.802 #[pallet::storage]803 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;804805 /// Storage of the count of deleted collections.806 #[pallet::storage]807 pub type DestroyedCollectionCount<T> =808 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;809810 /// Storage of collection info.811 #[pallet::storage]812 pub type CollectionById<T> = StorageMap<813 Hasher = Blake2_128Concat,814 Key = CollectionId,815 Value = Collection<<T as frame_system::Config>::AccountId>,816 QueryKind = OptionQuery,817 >;818819 /// Storage of collection properties.820 #[pallet::storage]821 #[pallet::getter(fn collection_properties)]822 pub type CollectionProperties<T> = StorageMap<823 Hasher = Blake2_128Concat,824 Key = CollectionId,825 Value = CollectionPropertiesT,826 QueryKind = ValueQuery,827 >;828829 /// Storage of token property permissions of a collection.830 #[pallet::storage]831 #[pallet::getter(fn property_permissions)]832 pub type CollectionPropertyPermissions<T> = StorageMap<833 Hasher = Blake2_128Concat,834 Key = CollectionId,835 Value = PropertiesPermissionMap,836 QueryKind = ValueQuery,837 >;838839 /// Storage of the amount of collection admins.840 #[pallet::storage]841 pub type AdminAmount<T> = StorageMap<842 Hasher = Blake2_128Concat,843 Key = CollectionId,844 Value = u32,845 QueryKind = ValueQuery,846 >;847848 /// List of collection admins.849 #[pallet::storage]850 pub type IsAdmin<T: Config> = StorageNMap<851 Key = (852 Key<Blake2_128Concat, CollectionId>,853 Key<Blake2_128Concat, T::CrossAccountId>,854 ),855 Value = bool,856 QueryKind = ValueQuery,857 >;858859 /// Allowlisted collection users.860 #[pallet::storage]861 pub type Allowlist<T: Config> = StorageNMap<862 Key = (863 Key<Blake2_128Concat, CollectionId>,864 Key<Blake2_128Concat, T::CrossAccountId>,865 ),866 Value = bool,867 QueryKind = ValueQuery,868 >;869870 /// Not used by code, exists only to provide some types to metadata.871 #[pallet::storage]872 pub type DummyStorageValue<T: Config> = StorageValue<873 Value = (874 CollectionStats,875 CollectionId,876 TokenId,877 TokenChild,878 PhantomType<(879 TokenData<T::CrossAccountId>,880 RpcCollection<T::AccountId>,881 // PoV Estimate Info882 PovInfo,883 )>,884 ),885 QueryKind = OptionQuery,886 >;887}888889impl<T: Config> Pallet<T> {890 /// Enshure that receiver address is correct.891 ///892 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.893 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {894 ensure!(895 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,896 <Error<T>>::AddressIsZero897 );898 Ok(())899 }900901 /// Get a vector of collection admins.902 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {903 <IsAdmin<T>>::iter_prefix((collection,))904 .map(|(a, _)| a)905 .collect()906 }907908 /// Get a vector of users allowed to mint tokens.909 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {910 <Allowlist<T>>::iter_prefix((collection,))911 .map(|(a, _)| a)912 .collect()913 }914915 /// Is `user` allowed to mint token in `collection`.916 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {917 <Allowlist<T>>::get((collection, user))918 }919920 /// Get statistics of collections.921 pub fn collection_stats() -> CollectionStats {922 let created = <CreatedCollectionCount<T>>::get();923 let destroyed = <DestroyedCollectionCount<T>>::get();924 CollectionStats {925 created: created.0,926 destroyed: destroyed.0,927 alive: created.0 - destroyed.0,928 }929 }930931 /// Get the effective limits for the collection.932 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {933 let collection = <CollectionById<T>>::get(collection)?;934 let limits = collection.limits;935 let effective_limits = CollectionLimits {936 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),937 sponsored_data_size: Some(limits.sponsored_data_size()),938 sponsored_data_rate_limit: Some(939 limits940 .sponsored_data_rate_limit941 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),942 ),943 token_limit: Some(limits.token_limit()),944 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(945 match collection.mode {946 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,947 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,948 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,949 },950 )),951 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),952 owner_can_transfer: Some(limits.owner_can_transfer()),953 owner_can_destroy: Some(limits.owner_can_destroy()),954 transfers_enabled: Some(limits.transfers_enabled()),955 };956957 Some(effective_limits)958 }959960 /// Returns information about the `collection` adapted for rpc.961 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {962 let Collection {963 name,964 description,965 owner,966 mode,967 token_prefix,968 sponsorship,969 limits,970 permissions,971 flags,972 } = <CollectionById<T>>::get(collection)?;973974 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)975 .into_iter()976 .map(|(key, permission)| PropertyKeyPermission { key, permission })977 .collect();978979 let properties = <CollectionProperties<T>>::get(collection)980 .into_iter()981 .map(|(key, value)| Property { key, value })982 .collect();983984 let permissions = CollectionPermissions {985 access: Some(permissions.access()),986 mint_mode: Some(permissions.mint_mode()),987 nesting: Some(permissions.nesting().clone()),988 };989990 Some(RpcCollection {991 name: name.into_inner(),992 description: description.into_inner(),993 owner,994 mode,995 token_prefix: token_prefix.into_inner(),996 sponsorship,997 limits,998 permissions,999 token_property_permissions,1000 properties,1001 read_only: flags.external,10021003 flags: RpcCollectionFlags {1004 foreign: flags.foreign,1005 erc721metadata: flags.erc721metadata,1006 },1007 })1008 }1009}10101011macro_rules! limit_default {1012 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1013 $(1014 if let Some($new) = $new.$field {1015 let $old = $old.$field($($arg)?);1016 let _ = $new;1017 let _ = $old;1018 $check1019 } else {1020 $new.$field = $old.$field1021 }1022 )*1023 }};1024}1025macro_rules! limit_default_clone {1026 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1027 $(1028 if let Some($new) = $new.$field.clone() {1029 let $old = $old.$field($($arg)?);1030 let _ = $new;1031 let _ = $old;1032 $check1033 } else {1034 $new.$field = $old.$field.clone()1035 }1036 )*1037 }};1038}10391040impl<T: Config> Pallet<T> {1041 /// Create new collection.1042 ///1043 /// * `owner` - The owner of the collection.1044 /// * `data` - Description of the created collection.1045 /// * `flags` - Extra flags to store.1046 pub fn init_collection(1047 owner: T::CrossAccountId,1048 payer: T::CrossAccountId,1049 data: CreateCollectionData<T::AccountId>,1050 flags: CollectionFlags,1051 ) -> Result<CollectionId, DispatchError> {1052 {1053 ensure!(1054 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1055 Error::<T>::CollectionTokenPrefixLimitExceeded1056 );1057 }10581059 let created_count = <CreatedCollectionCount<T>>::get()1060 .01061 .checked_add(1)1062 .ok_or(ArithmeticError::Overflow)?;1063 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1064 let id = CollectionId(created_count);10651066 // bound Total number of collections1067 ensure!(1068 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1069 <Error<T>>::TotalCollectionsLimitExceeded1070 );10711072 // =========10731074 let collection = Collection {1075 owner: owner.as_sub().clone(),1076 name: data.name,1077 mode: data.mode.clone(),1078 description: data.description,1079 token_prefix: data.token_prefix,1080 sponsorship: data1081 .pending_sponsor1082 .map(SponsorshipState::Unconfirmed)1083 .unwrap_or_default(),1084 limits: data1085 .limits1086 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1087 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1088 permissions: data1089 .permissions1090 .map(|permissions| {1091 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1092 })1093 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1094 flags,1095 };10961097 let mut collection_properties = CollectionPropertiesT::new();1098 collection_properties1099 .try_set_from_iter(data.properties.into_iter())1100 .map_err(<Error<T>>::from)?;11011102 CollectionProperties::<T>::insert(id, collection_properties);11031104 let mut token_props_permissions = PropertiesPermissionMap::new();1105 token_props_permissions1106 .try_set_from_iter(data.token_property_permissions.into_iter())1107 .map_err(<Error<T>>::from)?;11081109 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11101111 // Take a (non-refundable) deposit of collection creation1112 {1113 let mut imbalance =1114 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1115 imbalance.subsume(1116 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1117 &T::TreasuryAccountId::get(),1118 T::CollectionCreationPrice::get(),1119 ),1120 );1121 <T as Config>::Currency::settle(1122 payer.as_sub(),1123 imbalance,1124 WithdrawReasons::TRANSFER,1125 ExistenceRequirement::KeepAlive,1126 )1127 .map_err(|_| Error::<T>::NotSufficientFounds)?;1128 }11291130 <CreatedCollectionCount<T>>::put(created_count);1131 <Pallet<T>>::deposit_event(Event::CollectionCreated(1132 id,1133 data.mode.id(),1134 owner.as_sub().clone(),1135 ));1136 <PalletEvm<T>>::deposit_log(1137 erc::CollectionHelpersEvents::CollectionCreated {1138 owner: *owner.as_eth(),1139 collection_id: eth::collection_id_to_address(id),1140 }1141 .to_log(T::ContractAddress::get()),1142 );1143 <CollectionById<T>>::insert(id, collection);1144 Ok(id)1145 }11461147 /// Destroy collection.1148 ///1149 /// * `collection` - Collection handler.1150 /// * `sender` - The owner or administrator of the collection.1151 pub fn destroy_collection(1152 collection: CollectionHandle<T>,1153 sender: &T::CrossAccountId,1154 ) -> DispatchResult {1155 ensure!(1156 collection.limits.owner_can_destroy(),1157 <Error<T>>::NoPermission,1158 );1159 collection.check_is_owner(sender)?;11601161 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1162 .01163 .checked_add(1)1164 .ok_or(ArithmeticError::Overflow)?;11651166 // =========11671168 <DestroyedCollectionCount<T>>::put(destroyed_collections);1169 <CollectionById<T>>::remove(collection.id);1170 <AdminAmount<T>>::remove(collection.id);1171 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1172 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1173 <CollectionProperties<T>>::remove(collection.id);11741175 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11761177 <PalletEvm<T>>::deposit_log(1178 erc::CollectionHelpersEvents::CollectionDestroyed {1179 collection_id: eth::collection_id_to_address(collection.id),1180 }1181 .to_log(T::ContractAddress::get()),1182 );1183 Ok(())1184 }11851186 /// This function sets or removes a collection properties according to1187 /// `properties_updates` contents:1188 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1189 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1190 ///1191 /// This function fires an event for each property change.1192 /// In case of an error, all the changes (including the events) will be reverted1193 /// since the function is transactional.1194 #[transactional]1195 fn modify_collection_properties(1196 collection: &CollectionHandle<T>,1197 sender: &T::CrossAccountId,1198 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1199 ) -> DispatchResult {1200 collection.check_is_owner_or_admin(sender)?;12011202 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12031204 for (key, value) in properties_updates {1205 match value {1206 Some(value) => {1207 stored_properties1208 .try_set(key.clone(), value)1209 .map_err(<Error<T>>::from)?;12101211 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1212 <PalletEvm<T>>::deposit_log(1213 erc::CollectionHelpersEvents::CollectionChanged {1214 collection_id: eth::collection_id_to_address(collection.id),1215 }1216 .to_log(T::ContractAddress::get()),1217 );1218 }1219 None => {1220 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12211222 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1223 <PalletEvm<T>>::deposit_log(1224 erc::CollectionHelpersEvents::CollectionChanged {1225 collection_id: eth::collection_id_to_address(collection.id),1226 }1227 .to_log(T::ContractAddress::get()),1228 );1229 }1230 }1231 }12321233 <CollectionProperties<T>>::set(collection.id, stored_properties);12341235 Ok(())1236 }12371238 /// A batch operation to add, edit or remove properties for a token.1239 /// It sets or removes a token's properties according to1240 /// `properties_updates` contents:1241 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1242 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1243 ///1244 /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.1245 /// - `is_token_create`: Indicates that method is called during token initialization.1246 /// Allows to bypass ownership check.1247 ///1248 /// All affected properties should have `mutable` permission1249 /// to be **deleted** or to be **set more than once**,1250 /// and the sender should have permission to edit those properties.1251 ///1252 /// This function fires an event for each property change.1253 /// In case of an error, all the changes (including the events) will be reverted1254 /// since the function is transactional.1255 pub fn modify_token_properties(1256 collection: &CollectionHandle<T>,1257 sender: &T::CrossAccountId,1258 token_id: TokenId,1259 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1260 is_token_create: bool,1261 mut stored_properties: TokenProperties,1262 is_token_owner: impl Fn() -> Result<bool, DispatchError>,1263 set_token_properties: impl FnOnce(TokenProperties),1264 log: evm_coder::ethereum::Log,1265 ) -> DispatchResult {1266 let is_collection_admin = collection.is_owner_or_admin(sender);1267 let permissions = Self::property_permissions(collection.id);12681269 let mut token_owner_result = None;1270 let mut is_token_owner = || -> Result<bool, DispatchError> {1271 *token_owner_result.get_or_insert_with(&is_token_owner)1272 };12731274 for (key, value) in properties_updates {1275 let permission = permissions1276 .get(&key)1277 .cloned()1278 .unwrap_or_else(PropertyPermission::none);12791280 let is_property_exists = stored_properties.get(&key).is_some();12811282 match permission {1283 PropertyPermission { mutable: false, .. } if is_property_exists => {1284 return Err(<Error<T>>::NoPermission.into());1285 }12861287 PropertyPermission {1288 collection_admin,1289 token_owner,1290 ..1291 } => {1292 //TODO: investigate threats during public minting.1293 let is_token_create =1294 is_token_create && (collection_admin || token_owner) && value.is_some();1295 if !(is_token_create1296 || (collection_admin && is_collection_admin)1297 || (token_owner && is_token_owner()?))1298 {1299 fail!(<Error<T>>::NoPermission);1300 }1301 }1302 }13031304 match value {1305 Some(value) => {1306 stored_properties1307 .try_set(key.clone(), value)1308 .map_err(<Error<T>>::from)?;13091310 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1311 }1312 None => {1313 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13141315 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1316 }1317 }13181319 <PalletEvm<T>>::deposit_log(log.clone());1320 }13211322 set_token_properties(stored_properties);13231324 Ok(())1325 }13261327 /// Sets or unsets the approval of a given operator.1328 ///1329 /// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1330 /// - `owner`: Token owner1331 /// - `operator`: Operator1332 /// - `approve`: Should operator status be granted or revoked?1333 pub fn set_allowance_for_all(1334 collection: &CollectionHandle<T>,1335 owner: &T::CrossAccountId,1336 operator: &T::CrossAccountId,1337 approve: bool,1338 set_allowance: impl FnOnce(),1339 log: evm_coder::ethereum::Log,1340 ) -> DispatchResult {1341 if collection.permissions.access() == AccessMode::AllowList {1342 collection.check_allowlist(owner)?;1343 collection.check_allowlist(operator)?;1344 }13451346 Self::ensure_correct_receiver(operator)?;13471348 set_allowance();13491350 <PalletEvm<T>>::deposit_log(log);1351 Self::deposit_event(Event::ApprovedForAll(1352 collection.id,1353 owner.clone(),1354 operator.clone(),1355 approve,1356 ));1357 Ok(())1358 }13591360 /// Set collection property.1361 ///1362 /// * `collection` - Collection handler.1363 /// * `sender` - The owner or administrator of the collection.1364 /// * `property` - The property to set.1365 pub fn set_collection_property(1366 collection: &CollectionHandle<T>,1367 sender: &T::CrossAccountId,1368 property: Property,1369 ) -> DispatchResult {1370 Self::set_collection_properties(collection, sender, [property].into_iter())1371 }13721373 /// Set a scoped collection property, where the scope is a special prefix1374 /// prohibiting a user access to change the property directly.1375 ///1376 /// * `collection_id` - ID of the collection for which the property is being set.1377 /// * `scope` - Property scope.1378 /// * `property` - The property to set.1379 pub fn set_scoped_collection_property(1380 collection_id: CollectionId,1381 scope: PropertyScope,1382 property: Property,1383 ) -> DispatchResult {1384 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1385 properties.try_scoped_set(scope, property.key, property.value)1386 })1387 .map_err(<Error<T>>::from)?;13881389 Ok(())1390 }13911392 /// Set scoped collection properties, where the scope is a special prefix1393 /// prohibiting a user access to change the properties directly.1394 ///1395 /// * `collection_id` - ID of the collection for which the properties is being set.1396 /// * `scope` - Property scope.1397 /// * `properties` - The properties to set.1398 pub fn set_scoped_collection_properties(1399 collection_id: CollectionId,1400 scope: PropertyScope,1401 properties: impl Iterator<Item = Property>,1402 ) -> DispatchResult {1403 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1404 stored_properties.try_scoped_set_from_iter(scope, properties)1405 })1406 .map_err(<Error<T>>::from)?;14071408 Ok(())1409 }14101411 /// Set collection properties.1412 ///1413 /// * `collection` - Collection handler.1414 /// * `sender` - The owner or administrator of the collection.1415 /// * `properties` - The properties to set.1416 pub fn set_collection_properties(1417 collection: &CollectionHandle<T>,1418 sender: &T::CrossAccountId,1419 properties: impl Iterator<Item = Property>,1420 ) -> DispatchResult {1421 Self::modify_collection_properties(1422 collection,1423 sender,1424 properties.map(|property| (property.key, Some(property.value))),1425 )1426 }14271428 /// Delete collection property.1429 ///1430 /// * `collection` - Collection handler.1431 /// * `sender` - The owner or administrator of the collection.1432 /// * `property` - The property to delete.1433 pub fn delete_collection_property(1434 collection: &CollectionHandle<T>,1435 sender: &T::CrossAccountId,1436 property_key: PropertyKey,1437 ) -> DispatchResult {1438 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1439 }14401441 /// Delete collection properties.1442 ///1443 /// * `collection` - Collection handler.1444 /// * `sender` - The owner or administrator of the collection.1445 /// * `properties` - The properties to delete.1446 pub fn delete_collection_properties(1447 collection: &CollectionHandle<T>,1448 sender: &T::CrossAccountId,1449 property_keys: impl Iterator<Item = PropertyKey>,1450 ) -> DispatchResult {1451 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1452 }14531454 /// Set collection propetry permission without any checks.1455 ///1456 /// Used for migrations.1457 ///1458 /// * `collection` - Collection handler.1459 /// * `property_permissions` - Property permissions.1460 pub fn set_property_permission_unchecked(1461 collection: CollectionId,1462 property_permission: PropertyKeyPermission,1463 ) -> DispatchResult {1464 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1465 permissions.try_set(property_permission.key, property_permission.permission)1466 })1467 .map_err(<Error<T>>::from)?;1468 Ok(())1469 }14701471 /// Set collection property permission.1472 ///1473 /// * `collection` - Collection handler.1474 /// * `sender` - The owner or administrator of the collection.1475 /// * `property_permission` - Property permission.1476 pub fn set_property_permission(1477 collection: &CollectionHandle<T>,1478 sender: &T::CrossAccountId,1479 property_permission: PropertyKeyPermission,1480 ) -> DispatchResult {1481 Self::set_scoped_property_permission(1482 collection,1483 sender,1484 PropertyScope::None,1485 property_permission,1486 )1487 }14881489 /// Set collection property permission with scope.1490 ///1491 /// * `collection` - Collection handler.1492 /// * `sender` - The owner or administrator of the collection.1493 /// * `scope` - Property scope.1494 /// * `property_permission` - Property permission.1495 pub fn set_scoped_property_permission(1496 collection: &CollectionHandle<T>,1497 sender: &T::CrossAccountId,1498 scope: PropertyScope,1499 property_permission: PropertyKeyPermission,1500 ) -> DispatchResult {1501 collection.check_is_owner_or_admin(sender)?;15021503 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1504 let current_permission = all_permissions.get(&property_permission.key);1505 if matches![1506 current_permission,1507 Some(PropertyPermission { mutable: false, .. })1508 ] {1509 return Err(<Error<T>>::NoPermission.into());1510 }15111512 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1513 let property_permission = property_permission.clone();1514 permissions.try_scoped_set(1515 scope,1516 property_permission.key,1517 property_permission.permission,1518 )1519 })1520 .map_err(<Error<T>>::from)?;15211522 Self::deposit_event(Event::PropertyPermissionSet(1523 collection.id,1524 property_permission.key,1525 ));1526 <PalletEvm<T>>::deposit_log(1527 erc::CollectionHelpersEvents::CollectionChanged {1528 collection_id: eth::collection_id_to_address(collection.id),1529 }1530 .to_log(T::ContractAddress::get()),1531 );15321533 Ok(())1534 }15351536 /// Set token property permission.1537 ///1538 /// * `collection` - Collection handler.1539 /// * `sender` - The owner or administrator of the collection.1540 /// * `property_permissions` - Property permissions.1541 #[transactional]1542 pub fn set_token_property_permissions(1543 collection: &CollectionHandle<T>,1544 sender: &T::CrossAccountId,1545 property_permissions: Vec<PropertyKeyPermission>,1546 ) -> DispatchResult {1547 Self::set_scoped_token_property_permissions(1548 collection,1549 sender,1550 PropertyScope::None,1551 property_permissions,1552 )1553 }15541555 /// Set token property permission with scope.1556 ///1557 /// * `collection` - Collection handler.1558 /// * `sender` - The owner or administrator of the collection.1559 /// * `scope` - Property scope.1560 /// * `property_permissions` - Property permissions.1561 #[transactional]1562 pub fn set_scoped_token_property_permissions(1563 collection: &CollectionHandle<T>,1564 sender: &T::CrossAccountId,1565 scope: PropertyScope,1566 property_permissions: Vec<PropertyKeyPermission>,1567 ) -> DispatchResult {1568 for prop_pemission in property_permissions {1569 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1570 }15711572 Ok(())1573 }15741575 /// Get collection property.1576 pub fn get_collection_property(1577 collection_id: CollectionId,1578 key: &PropertyKey,1579 ) -> Option<PropertyValue> {1580 Self::collection_properties(collection_id).get(key).cloned()1581 }15821583 /// Convert byte vector to property key vector.1584 pub fn bytes_keys_to_property_keys(1585 keys: Vec<Vec<u8>>,1586 ) -> Result<Vec<PropertyKey>, DispatchError> {1587 keys.into_iter()1588 .map(|key| -> Result<PropertyKey, DispatchError> {1589 key.try_into()1590 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1591 })1592 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1593 }15941595 /// Get properties according to given keys.1596 pub fn filter_collection_properties(1597 collection_id: CollectionId,1598 keys: Option<Vec<PropertyKey>>,1599 ) -> Result<Vec<Property>, DispatchError> {1600 let properties = Self::collection_properties(collection_id);16011602 let properties = keys1603 .map(|keys| {1604 keys.into_iter()1605 .filter_map(|key| {1606 properties.get(&key).map(|value| Property {1607 key,1608 value: value.clone(),1609 })1610 })1611 .collect()1612 })1613 .unwrap_or_else(|| {1614 properties1615 .into_iter()1616 .map(|(key, value)| Property { key, value })1617 .collect()1618 });16191620 Ok(properties)1621 }16221623 /// Get property permissions according to given keys.1624 pub fn filter_property_permissions(1625 collection_id: CollectionId,1626 keys: Option<Vec<PropertyKey>>,1627 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1628 let permissions = Self::property_permissions(collection_id);16291630 let key_permissions = keys1631 .map(|keys| {1632 keys.into_iter()1633 .filter_map(|key| {1634 permissions1635 .get(&key)1636 .map(|permission| PropertyKeyPermission {1637 key,1638 permission: permission.clone(),1639 })1640 })1641 .collect()1642 })1643 .unwrap_or_else(|| {1644 permissions1645 .into_iter()1646 .map(|(key, permission)| PropertyKeyPermission { key, permission })1647 .collect()1648 });16491650 Ok(key_permissions)1651 }16521653 /// Toggle `user` participation in the `collection`'s allow list.1654 /// #### Store read/writes1655 /// 1 writes1656 pub fn toggle_allowlist(1657 collection: &CollectionHandle<T>,1658 sender: &T::CrossAccountId,1659 user: &T::CrossAccountId,1660 allowed: bool,1661 ) -> DispatchResult {1662 collection.check_is_owner_or_admin(sender)?;16631664 // =========16651666 if allowed {1667 <Allowlist<T>>::insert((collection.id, user), true);1668 Self::deposit_event(Event::<T>::AllowListAddressAdded(1669 collection.id,1670 user.clone(),1671 ));1672 } else {1673 <Allowlist<T>>::remove((collection.id, user));1674 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1675 collection.id,1676 user.clone(),1677 ));1678 }16791680 <PalletEvm<T>>::deposit_log(1681 erc::CollectionHelpersEvents::CollectionChanged {1682 collection_id: eth::collection_id_to_address(collection.id),1683 }1684 .to_log(T::ContractAddress::get()),1685 );16861687 Ok(())1688 }16891690 /// Toggle `user` participation in the `collection`'s admin list.1691 /// #### Store read/writes1692 /// 2 reads, 2 writes1693 pub fn toggle_admin(1694 collection: &CollectionHandle<T>,1695 sender: &T::CrossAccountId,1696 user: &T::CrossAccountId,1697 admin: bool,1698 ) -> DispatchResult {1699 collection.check_is_internal()?;1700 collection.check_is_owner(sender)?;17011702 let is_admin = <IsAdmin<T>>::get((collection.id, user));1703 if is_admin == admin {1704 if admin {1705 return Ok(());1706 } else {1707 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1708 }1709 }1710 let amount = <AdminAmount<T>>::get(collection.id);17111712 // =========17131714 if admin {1715 let amount = amount1716 .checked_add(1)1717 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1718 ensure!(1719 amount <= Self::collection_admins_limit(),1720 <Error<T>>::CollectionAdminCountExceeded,1721 );17221723 <AdminAmount<T>>::insert(collection.id, amount);1724 <IsAdmin<T>>::insert((collection.id, user), true);17251726 Self::deposit_event(Event::<T>::CollectionAdminAdded(1727 collection.id,1728 user.clone(),1729 ));1730 } else {1731 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1732 <IsAdmin<T>>::remove((collection.id, user));17331734 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1735 collection.id,1736 user.clone(),1737 ));1738 }17391740 <PalletEvm<T>>::deposit_log(1741 erc::CollectionHelpersEvents::CollectionChanged {1742 collection_id: eth::collection_id_to_address(collection.id),1743 }1744 .to_log(T::ContractAddress::get()),1745 );17461747 Ok(())1748 }17491750 /// Update collection limits.1751 pub fn update_limits(1752 user: &T::CrossAccountId,1753 collection: &mut CollectionHandle<T>,1754 new_limit: CollectionLimits,1755 ) -> DispatchResult {1756 collection.check_is_internal()?;1757 collection.check_is_owner_or_admin(user)?;17581759 collection.limits =1760 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17611762 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1763 <PalletEvm<T>>::deposit_log(1764 erc::CollectionHelpersEvents::CollectionChanged {1765 collection_id: eth::collection_id_to_address(collection.id),1766 }1767 .to_log(T::ContractAddress::get()),1768 );17691770 collection.save()1771 }17721773 /// Merge set fields from `new_limit` to `old_limit`.1774 fn clamp_limits(1775 mode: CollectionMode,1776 old_limit: &CollectionLimits,1777 mut new_limit: CollectionLimits,1778 ) -> Result<CollectionLimits, DispatchError> {1779 let limits = old_limit;1780 limit_default!(old_limit, new_limit,1781 account_token_ownership_limit => ensure!(1782 new_limit <= MAX_TOKEN_OWNERSHIP,1783 <Error<T>>::CollectionLimitBoundsExceeded,1784 ),1785 sponsored_data_size => ensure!(1786 new_limit <= CUSTOM_DATA_LIMIT,1787 <Error<T>>::CollectionLimitBoundsExceeded,1788 ),17891790 sponsored_data_rate_limit => {},1791 token_limit => ensure!(1792 old_limit >= new_limit && new_limit > 0,1793 <Error<T>>::CollectionTokenLimitExceeded1794 ),17951796 sponsor_transfer_timeout(match mode {1797 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1798 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1799 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1800 }) => ensure!(1801 new_limit <= MAX_SPONSOR_TIMEOUT,1802 <Error<T>>::CollectionLimitBoundsExceeded,1803 ),1804 sponsor_approve_timeout => {},1805 owner_can_transfer => ensure!(1806 !limits.owner_can_transfer_instaled() ||1807 old_limit || !new_limit,1808 <Error<T>>::OwnerPermissionsCantBeReverted,1809 ),1810 owner_can_destroy => ensure!(1811 old_limit || !new_limit,1812 <Error<T>>::OwnerPermissionsCantBeReverted,1813 ),1814 transfers_enabled => {},1815 );1816 Ok(new_limit)1817 }18181819 /// Update collection permissions.1820 pub fn update_permissions(1821 user: &T::CrossAccountId,1822 collection: &mut CollectionHandle<T>,1823 new_permission: CollectionPermissions,1824 ) -> DispatchResult {1825 collection.check_is_internal()?;1826 collection.check_is_owner_or_admin(user)?;1827 collection.permissions = Self::clamp_permissions(1828 collection.mode.clone(),1829 &collection.permissions,1830 new_permission,1831 )?;18321833 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1834 <PalletEvm<T>>::deposit_log(1835 erc::CollectionHelpersEvents::CollectionChanged {1836 collection_id: eth::collection_id_to_address(collection.id),1837 }1838 .to_log(T::ContractAddress::get()),1839 );18401841 collection.save()1842 }18431844 /// Merge set fields from `new_permission` to `old_permission`.1845 fn clamp_permissions(1846 _mode: CollectionMode,1847 old_permission: &CollectionPermissions,1848 mut new_permission: CollectionPermissions,1849 ) -> Result<CollectionPermissions, DispatchError> {1850 limit_default_clone!(old_permission, new_permission,1851 access => {},1852 mint_mode => {},1853 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1854 );1855 Ok(new_permission)1856 }18571858 /// Repair possibly broken properties of a collection.1859 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1860 CollectionProperties::<T>::mutate(collection_id, |properties| {1861 properties.recompute_consumed_space();1862 });18631864 Ok(())1865 }1866}18671868/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1869#[macro_export]1870macro_rules! unsupported {1871 ($runtime:path) => {1872 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1873 };1874}18751876/// Return weights for various worst-case operations.1877pub trait CommonWeightInfo<CrossAccountId> {1878 /// Weight of item creation.1879 fn create_item(data: &CreateItemData) -> Weight {1880 Self::create_multiple_items(from_ref(data))1881 }18821883 /// Weight of items creation.1884 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18851886 /// Weight of items creation.1887 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18881889 /// The weight of the burning item.1890 fn burn_item() -> Weight;18911892 /// Property setting weight.1893 ///1894 /// * `amount`- The number of properties to set.1895 fn set_collection_properties(amount: u32) -> Weight;18961897 /// Collection property deletion weight.1898 ///1899 /// * `amount`- The number of properties to set.1900 fn delete_collection_properties(amount: u32) -> Weight;19011902 /// Token property setting weight.1903 ///1904 /// * `amount`- The number of properties to set.1905 fn set_token_properties(amount: u32) -> Weight;19061907 /// Token property deletion weight.1908 ///1909 /// * `amount`- The number of properties to delete.1910 fn delete_token_properties(amount: u32) -> Weight;19111912 /// Token property permissions set weight.1913 ///1914 /// * `amount`- The number of property permissions to set.1915 fn set_token_property_permissions(amount: u32) -> Weight;19161917 /// Transfer price of the token or its parts.1918 fn transfer() -> Weight;19191920 /// The price of setting the permission of the operation from another user.1921 fn approve() -> Weight;19221923 /// The price of setting the permission of the operation from another user for eth mirror.1924 fn approve_from() -> Weight;19251926 /// Transfer price from another user.1927 fn transfer_from() -> Weight;19281929 /// The price of burning a token from another user.1930 fn burn_from() -> Weight;19311932 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1933 /// whole users's balance.1934 ///1935 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1936 fn burn_recursively_self_raw() -> Weight;19371938 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1939 ///1940 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1941 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19421943 /// The price of recursive burning a token.1944 ///1945 /// `max_selfs` - The maximum burning weight of the token itself.1946 /// `max_breadth` - The maximum number of nested tokens to burn.1947 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1948 Self::burn_recursively_self_raw()1949 .saturating_mul(max_selfs.max(1) as u64)1950 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1951 }19521953 /// The price of retrieving token owner1954 fn token_owner() -> Weight;19551956 /// The price of setting approval for all1957 fn set_allowance_for_all() -> Weight;19581959 /// The price of repairing an item.1960 fn force_repair_item() -> Weight;1961}19621963/// Weight info extension trait for refungible pallet.1964pub trait RefungibleExtensionsWeightInfo {1965 /// Weight of token repartition.1966 fn repartition() -> Weight;1967}19681969/// Common collection operations.1970///1971/// It wraps methods in Fungible, Nonfungible and Refungible pallets1972/// and adds weight info.1973pub trait CommonCollectionOperations<T: Config> {1974 /// Create token.1975 ///1976 /// * `sender` - The user who mint the token and pays for the transaction.1977 /// * `to` - The user who will own the token.1978 /// * `data` - Token data.1979 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1980 fn create_item(1981 &self,1982 sender: T::CrossAccountId,1983 to: T::CrossAccountId,1984 data: CreateItemData,1985 nesting_budget: &dyn Budget,1986 ) -> DispatchResultWithPostInfo;19871988 /// Create multiple tokens.1989 ///1990 /// * `sender` - The user who mint the token and pays for the transaction.1991 /// * `to` - The user who will own the token.1992 /// * `data` - Token data.1993 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1994 fn create_multiple_items(1995 &self,1996 sender: T::CrossAccountId,1997 to: T::CrossAccountId,1998 data: Vec<CreateItemData>,1999 nesting_budget: &dyn Budget,2000 ) -> DispatchResultWithPostInfo;20012002 /// Create multiple tokens.2003 ///2004 /// * `sender` - The user who mint the token and pays for the transaction.2005 /// * `to` - The user who will own the token.2006 /// * `data` - Token data.2007 /// * `nesting_budget` - A budget that can be spent on nesting tokens.2008 fn create_multiple_items_ex(2009 &self,2010 sender: T::CrossAccountId,2011 data: CreateItemExData<T::CrossAccountId>,2012 nesting_budget: &dyn Budget,2013 ) -> DispatchResultWithPostInfo;20142015 /// Burn token.2016 ///2017 /// * `sender` - The user who owns the token.2018 /// * `token` - Token id that will burned.2019 /// * `amount` - The number of parts of the token that will be burned.2020 fn burn_item(2021 &self,2022 sender: T::CrossAccountId,2023 token: TokenId,2024 amount: u128,2025 ) -> DispatchResultWithPostInfo;20262027 /// Burn token and all nested tokens recursievly.2028 ///2029 /// * `sender` - The user who owns the token.2030 /// * `token` - Token id that will burned.2031 /// * `self_budget` - The budget that can be spent on burning tokens.2032 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.2033 fn burn_item_recursively(2034 &self,2035 sender: T::CrossAccountId,2036 token: TokenId,2037 self_budget: &dyn Budget,2038 breadth_budget: &dyn Budget,2039 ) -> DispatchResultWithPostInfo;20402041 /// Set collection properties.2042 ///2043 /// * `sender` - Must be either the owner of the collection or its admin.2044 /// * `properties` - Properties to be set.2045 fn set_collection_properties(2046 &self,2047 sender: T::CrossAccountId,2048 properties: Vec<Property>,2049 ) -> DispatchResultWithPostInfo;20502051 /// Delete collection properties.2052 ///2053 /// * `sender` - Must be either the owner of the collection or its admin.2054 /// * `properties` - The properties to be removed.2055 fn delete_collection_properties(2056 &self,2057 sender: &T::CrossAccountId,2058 property_keys: Vec<PropertyKey>,2059 ) -> DispatchResultWithPostInfo;20602061 /// Set token properties.2062 ///2063 /// The appropriate [`PropertyPermission`] for the token property2064 /// must be set with [`Self::set_token_property_permissions`].2065 ///2066 /// * `sender` - Must be either the owner of the token or its admin.2067 /// * `token_id` - The token for which the properties are being set.2068 /// * `properties` - Properties to be set.2069 /// * `budget` - Budget for setting properties.2070 fn set_token_properties(2071 &self,2072 sender: T::CrossAccountId,2073 token_id: TokenId,2074 properties: Vec<Property>,2075 budget: &dyn Budget,2076 ) -> DispatchResultWithPostInfo;20772078 /// Remove token properties.2079 ///2080 /// The appropriate [`PropertyPermission`] for the token property2081 /// must be set with [`Self::set_token_property_permissions`].2082 ///2083 /// * `sender` - Must be either the owner of the token or its admin.2084 /// * `token_id` - The token for which the properties are being remove.2085 /// * `property_keys` - Keys to remove corresponding properties.2086 /// * `budget` - Budget for removing properties.2087 fn delete_token_properties(2088 &self,2089 sender: T::CrossAccountId,2090 token_id: TokenId,2091 property_keys: Vec<PropertyKey>,2092 budget: &dyn Budget,2093 ) -> DispatchResultWithPostInfo;20942095 /// Set token property permissions.2096 ///2097 /// * `sender` - Must be either the owner of the token or its admin.2098 /// * `token_id` - The token for which the properties are being set.2099 /// * `property_permissions` - Property permissions to be set.2100 /// * `budget` - Budget for setting properties.2101 fn set_token_property_permissions(2102 &self,2103 sender: &T::CrossAccountId,2104 property_permissions: Vec<PropertyKeyPermission>,2105 ) -> DispatchResultWithPostInfo;21062107 /// Transfer amount of token pieces.2108 ///2109 /// * `sender` - Donor user.2110 /// * `to` - Recepient user.2111 /// * `token` - The token of which parts are being sent.2112 /// * `amount` - The number of parts of the token that will be transferred.2113 /// * `budget` - The maximum budget that can be spent on the transfer.2114 fn transfer(2115 &self,2116 sender: T::CrossAccountId,2117 to: T::CrossAccountId,2118 token: TokenId,2119 amount: u128,2120 budget: &dyn Budget,2121 ) -> DispatchResultWithPostInfo;21222123 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2124 ///2125 /// * `sender` - The user who grants access to the token.2126 /// * `spender` - The user to whom the rights are granted.2127 /// * `token` - The token to which access is granted.2128 /// * `amount` - The amount of pieces that another user can dispose of.2129 fn approve(2130 &self,2131 sender: T::CrossAccountId,2132 spender: T::CrossAccountId,2133 token: TokenId,2134 amount: u128,2135 ) -> DispatchResultWithPostInfo;21362137 /// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2138 ///2139 /// * `sender` - The user who grants access to the token.2140 /// * `from` - Spender's eth mirror.2141 /// * `to` - The user to whom the rights are granted.2142 /// * `token` - The token to which access is granted.2143 /// * `amount` - The amount of pieces that another user can dispose of.2144 fn approve_from(2145 &self,2146 sender: T::CrossAccountId,2147 from: T::CrossAccountId,2148 to: T::CrossAccountId,2149 token: TokenId,2150 amount: u128,2151 ) -> DispatchResultWithPostInfo;21522153 /// Send parts of a token owned by another user.2154 ///2155 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2156 ///2157 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2158 /// * `from` - The user who owns the token.2159 /// * `to` - Recepient user.2160 /// * `token` - The token of which parts are being sent.2161 /// * `amount` - The number of parts of the token that will be transferred.2162 /// * `budget` - The maximum budget that can be spent on the transfer.2163 fn transfer_from(2164 &self,2165 sender: T::CrossAccountId,2166 from: T::CrossAccountId,2167 to: T::CrossAccountId,2168 token: TokenId,2169 amount: u128,2170 budget: &dyn Budget,2171 ) -> DispatchResultWithPostInfo;21722173 /// Burn parts of a token owned by another user.2174 ///2175 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2176 ///2177 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2178 /// * `from` - The user who owns the token.2179 /// * `token` - The token of which parts are being sent.2180 /// * `amount` - The number of parts of the token that will be transferred.2181 /// * `budget` - The maximum budget that can be spent on the burn.2182 fn burn_from(2183 &self,2184 sender: T::CrossAccountId,2185 from: T::CrossAccountId,2186 token: TokenId,2187 amount: u128,2188 budget: &dyn Budget,2189 ) -> DispatchResultWithPostInfo;21902191 /// Check permission to nest token.2192 ///2193 /// * `sender` - The user who initiated the check.2194 /// * `from` - The token that is checked for embedding.2195 /// * `under` - Token under which to check.2196 /// * `budget` - The maximum budget that can be spent on the check.2197 fn check_nesting(2198 &self,2199 sender: T::CrossAccountId,2200 from: (CollectionId, TokenId),2201 under: TokenId,2202 budget: &dyn Budget,2203 ) -> DispatchResult;22042205 /// Nest one token into another.2206 ///2207 /// * `under` - Token holder.2208 /// * `to_nest` - Nested token.2209 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22102211 /// Unnest token.2212 ///2213 /// * `under` - Token holder.2214 /// * `to_nest` - Token to unnest.2215 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22162217 /// Get all user tokens.2218 ///2219 /// * `account` - Account for which you need to get tokens.2220 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22212222 /// Get all the tokens in the collection.2223 fn collection_tokens(&self) -> Vec<TokenId>;22242225 /// Check if the token exists.2226 ///2227 /// * `token` - Id token to check.2228 fn token_exists(&self, token: TokenId) -> bool;22292230 /// Get the id of the last minted token.2231 fn last_token_id(&self) -> TokenId;22322233 /// Get the owner of the token.2234 ///2235 /// * `token` - The token for which you need to find out the owner.2236 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22372238 /// Returns 10 tokens owners in no particular order.2239 ///2240 /// * `token` - The token for which you need to find out the owners.2241 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22422243 /// Get the value of the token property by key.2244 ///2245 /// * `token` - Token with the property to get.2246 /// * `key` - Property name.2247 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22482249 /// Get a set of token properties by key vector.2250 ///2251 /// * `token` - Token with the property to get.2252 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2253 /// then all properties are returned.2254 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22552256 /// Amount of unique collection tokens2257 fn total_supply(&self) -> u32;22582259 /// Amount of different tokens account has.2260 ///2261 /// * `account` - The account for which need to get the balance.2262 fn account_balance(&self, account: T::CrossAccountId) -> u32;22632264 /// Amount of specific token account have.2265 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22662267 /// Amount of token pieces2268 fn total_pieces(&self, token: TokenId) -> Option<u128>;22692270 /// Get the number of parts of the token that a trusted user can manage.2271 ///2272 /// * `sender` - Trusted user.2273 /// * `spender` - Owner of the token.2274 /// * `token` - The token for which to get the value.2275 fn allowance(2276 &self,2277 sender: T::CrossAccountId,2278 spender: T::CrossAccountId,2279 token: TokenId,2280 ) -> u128;22812282 /// Get extension for RFT collection.2283 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22842285 /// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2286 /// * `owner` - Token owner2287 /// * `operator` - Operator2288 /// * `approve` - Should operator status be granted or revoked?2289 fn set_allowance_for_all(2290 &self,2291 owner: T::CrossAccountId,2292 operator: T::CrossAccountId,2293 approve: bool,2294 ) -> DispatchResultWithPostInfo;22952296 /// Tells whether the given `owner` approves the `operator`.2297 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;22982299 /// Repairs a possibly broken item.2300 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2301}23022303/// Extension for RFT collection.2304pub trait RefungibleExtensions<T>2305where2306 T: Config,2307{2308 /// Change the number of parts of the token.2309 ///2310 /// When the value changes down, this function is equivalent to burning parts of the token.2311 ///2312 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.2313 /// * `token` - The token for which you want to change the number of parts.2314 /// * `amount` - The new value of the parts of the token.2315 fn repartition(2316 &self,2317 sender: &T::CrossAccountId,2318 token: TokenId,2319 amount: u128,2320 ) -> DispatchResultWithPostInfo;2321}23222323/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2324///2325/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2326pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2327 let post_info = PostDispatchInfo {2328 actual_weight: Some(weight),2329 pays_fee: Pays::Yes,2330 };2331 match res {2332 Ok(()) => Ok(post_info),2333 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2334 }2335}23362337impl<T: Config> From<PropertiesError> for Error<T> {2338 fn from(error: PropertiesError) -> Self {2339 match error {2340 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2341 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2342 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2343 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2344 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2345 }2346 }2347}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617//! # Common pallet18//!19//! The Common pallet provides functionality for handling collections.20//!21//! ## Overview22//!23//! The Common pallet provides an interface for common collection operations for different collection types24//! (see [CommonCollectionOperations]), as well as a generic dispatcher for these, see [dispatch] module.25//! It also provides this functionality to EVM, see [erc] and [eth] modules.26//!27//! The Common pallet provides functions for:28//!29//! - Setting and approving collection sponsor.30//! - Get\set\delete allow list.31//! - Get\set\delete collection properties.32//! - Get\set\delete collection property permissions.33//! - Get\set\delete token property permissions.34//! - Get\set\delete collection administrators.35//! - Checking access permissions.36//!37//! ### Terminology38//! **Collection sponsor** - For the collection, you can set a sponsor, at whose expense it will39//! be possible to mint tokens.40//!41//! **Allow list** - List of users who have the right to minting tokens.42//!43//! **Collection properties** - Collection properties are simply key-value stores where various44//! metadata can be placed.45//!46//! **Permissions on token properties** - For each property in the token can be set permission47//! to change, see [`PropertyPermission`].48//!49//! **Collection administrator** - For a collection, you can set administrators who have the right50//! to most actions on the collection.5152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::{57 ops::{Deref, DerefMut},58 slice::from_ref,59};60use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};61use sp_std::vec::Vec;62use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};63use evm_coder::ToLog;64use frame_support::{65 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},66 ensure,67 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},68 dispatch::Pays,69 transactional, fail,70};71use pallet_evm::GasWeightMapping;72use up_data_structs::{73 AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,74 RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,75 COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP,76 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,77 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,78 CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,79 PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties,80 PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError,81 TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope,82 CollectionPermissions,83};84use up_pov_estimate_rpc::PovInfo;8586pub use pallet::*;87use sp_core::H160;88use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};8990#[cfg(feature = "runtime-benchmarks")]91pub mod benchmarking;92pub mod dispatch;93pub mod erc;94pub mod eth;95pub mod helpers;96#[allow(missing_docs)]97pub mod weights;98/// Weight info.99pub type SelfWeightOf<T> = <T as Config>::WeightInfo;100101/// Collection handle contains information about collection data and id.102/// Also provides functionality to count consumed gas.103///104/// CollectionHandle is used as a generic wrapper for collections of all types.105/// It allows to perform common operations and queries on any collection type,106/// both completely general for all, as well as their respective implementations of [`CommonCollectionOperations`].107#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]108pub struct CollectionHandle<T: Config> {109 /// Collection id110 pub id: CollectionId,111 collection: Collection<T::AccountId>,112 /// Substrate recorder for counting consumed gas113 pub recorder: SubstrateRecorder<T>,114}115116impl<T: Config> WithRecorder<T> for CollectionHandle<T> {117 fn recorder(&self) -> &SubstrateRecorder<T> {118 &self.recorder119 }120 fn into_recorder(self) -> SubstrateRecorder<T> {121 self.recorder122 }123}124125impl<T: Config> CollectionHandle<T> {126 /// Same as [CollectionHandle::new] but with an explicit gas limit.127 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {128 Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))129 }130131 /// Same as [CollectionHandle::new] but with an existed [`SubstrateRecorder`].132 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {133 <CollectionById<T>>::get(id).map(|collection| Self {134 id,135 collection,136 recorder,137 })138 }139140 /// Retrives collection data from storage and creates collection handle with default parameters.141 /// If collection not found return `None`142 pub fn new(id: CollectionId) -> Option<Self> {143 Self::new_with_gas_limit(id, u64::MAX)144 }145146 /// Same as [`CollectionHandle::new`] but if collection not found [CollectionNotFound](Error::CollectionNotFound) returned.147 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {148 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)149 }150151 /// Consume gas for reading.152 pub fn consume_store_reads(153 &self,154 reads: u64,155 ) -> pallet_evm_coder_substrate::execution::Result<()> {156 consume_store_reads(self.recorder(), reads)157 }158159 /// Consume gas for writing.160 pub fn consume_store_writes(161 &self,162 writes: u64,163 ) -> pallet_evm_coder_substrate::execution::Result<()> {164 consume_store_writes(self.recorder(), writes)165 }166167 /// Consume gas for reading and writing.168 pub fn consume_store_reads_and_writes(169 &self,170 reads: u64,171 writes: u64,172 ) -> pallet_evm_coder_substrate::execution::Result<()> {173 consume_store_reads_and_writes(self.recorder(), reads, writes)174 }175176 /// Save collection to storage.177 pub fn save(&self) -> DispatchResult {178 <CollectionById<T>>::insert(self.id, &self.collection);179 Ok(())180 }181182 /// Set collection sponsor.183 ///184 /// Unique collections allows sponsoring for certain actions.185 /// This method allows you to set the sponsor of the collection.186 /// In order for sponsorship to become active, it must be confirmed through [`Self::confirm_sponsorship`].187 pub fn set_sponsor(188 &mut self,189 sender: &T::CrossAccountId,190 sponsor: T::AccountId,191 ) -> DispatchResult {192 self.check_is_internal()?;193 self.check_is_owner_or_admin(sender)?;194195 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());196197 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));198 <PalletEvm<T>>::deposit_log(199 erc::CollectionHelpersEvents::CollectionChanged {200 collection_id: eth::collection_id_to_address(self.id),201 }202 .to_log(T::ContractAddress::get()),203 );204205 self.save()206 }207208 /// Force set `sponsor`.209 ///210 /// Differs from [`set_sponsor`][`Self::set_sponsor`] in that confirmation211 /// from the `sponsor` is not required.212 ///213 /// # Arguments214 ///215 /// * `sender`: Caller's account.216 /// * `sponsor`: ID of the account of the sponsor-to-be.217 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {218 self.check_is_internal()?;219220 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());221222 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));223 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));224 <PalletEvm<T>>::deposit_log(225 erc::CollectionHelpersEvents::CollectionChanged {226 collection_id: eth::collection_id_to_address(self.id),227 }228 .to_log(T::ContractAddress::get()),229 );230231 self.save()232 }233234 /// Confirm sponsorship235 ///236 /// In order for the sponsorship to become active, the user set as the sponsor must confirm their participation.237 /// Before confirming sponsorship, the user must be specified as the sponsor of the collection via [`Self::set_sponsor`].238 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {239 self.check_is_internal()?;240 ensure!(241 self.collection.sponsorship.pending_sponsor() == Some(sender),242 Error::<T>::ConfirmSponsorshipFail243 );244245 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());246247 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));248 <PalletEvm<T>>::deposit_log(249 erc::CollectionHelpersEvents::CollectionChanged {250 collection_id: eth::collection_id_to_address(self.id),251 }252 .to_log(T::ContractAddress::get()),253 );254255 self.save()256 }257258 /// Remove collection sponsor.259 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {260 self.check_is_internal()?;261 self.check_is_owner_or_admin(sender)?;262263 self.collection.sponsorship = SponsorshipState::Disabled;264265 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));266 <PalletEvm<T>>::deposit_log(267 erc::CollectionHelpersEvents::CollectionChanged {268 collection_id: eth::collection_id_to_address(self.id),269 }270 .to_log(T::ContractAddress::get()),271 );272 self.save()273 }274275 /// Force remove `sponsor`.276 ///277 /// Differs from `remove_sponsor` in that278 /// it doesn't require consent from the `owner` of the collection.279 pub fn force_remove_sponsor(&mut self) -> DispatchResult {280 self.check_is_internal()?;281282 self.collection.sponsorship = SponsorshipState::Disabled;283284 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));285 <PalletEvm<T>>::deposit_log(286 erc::CollectionHelpersEvents::CollectionChanged {287 collection_id: eth::collection_id_to_address(self.id),288 }289 .to_log(T::ContractAddress::get()),290 );291 self.save()292 }293294 /// Checks that the collection was created with, and must be operated upon through **Unique API**.295 /// Now check only the `external` flag and if it's **true**, then return [`Error::CollectionIsExternal`] error.296 pub fn check_is_internal(&self) -> DispatchResult {297 if self.flags.external {298 return Err(<Error<T>>::CollectionIsExternal)?;299 }300301 Ok(())302 }303304 /// Checks that the collection was created with, and must be operated upon through an **assimilated API**.305 /// Now check only the `external` flag and if it's **false**, then return [`Error::CollectionIsInternal`] error.306 pub fn check_is_external(&self) -> DispatchResult {307 if !self.flags.external {308 return Err(<Error<T>>::CollectionIsInternal)?;309 }310311 Ok(())312 }313}314315impl<T: Config> Deref for CollectionHandle<T> {316 type Target = Collection<T::AccountId>;317318 fn deref(&self) -> &Self::Target {319 &self.collection320 }321}322323impl<T: Config> DerefMut for CollectionHandle<T> {324 fn deref_mut(&mut self) -> &mut Self::Target {325 &mut self.collection326 }327}328329impl<T: Config> CollectionHandle<T> {330 /// Checks if the `user` is the owner of the collection.331 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {332 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);333 Ok(())334 }335336 /// Returns **true** if the `user` is the owner or administrator of the collection.337 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {338 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))339 }340341 /// Checks if the `user` is the owner or administrator of the collection.342 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {343 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);344 Ok(())345 }346347 /// Returns **true** if348 /// * the `user`is a collection owner or admin349 /// * the collection limits allow the owner/admins to transfer/burn any collection token350 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {351 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)352 }353354 /// Return **true** if `user` does not have enough token parts, and he can ignore such restrictions.355 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {356 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)357 }358359 /// Checks if the user is in the allow list. If not [Error::AddressNotInAllowlist] returns.360 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {361 ensure!(362 <Allowlist<T>>::get((self.id, user)),363 <Error<T>>::AddressNotInAllowlist364 );365 Ok(())366 }367368 /// Changes collection owner to another account369 /// #### Store read/writes370 /// 1 writes371 pub fn change_owner(372 &mut self,373 caller: T::CrossAccountId,374 new_owner: T::CrossAccountId,375 ) -> DispatchResult {376 self.check_is_internal()?;377 self.check_is_owner(&caller)?;378 self.collection.owner = new_owner.as_sub().clone();379380 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(381 self.id,382 new_owner.as_sub().clone(),383 ));384 <PalletEvm<T>>::deposit_log(385 erc::CollectionHelpersEvents::CollectionChanged {386 collection_id: eth::collection_id_to_address(self.id),387 }388 .to_log(T::ContractAddress::get()),389 );390391 self.save()392 }393}394395#[frame_support::pallet]396pub mod pallet {397398 use super::*;399 use dispatch::CollectionDispatch;400 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};401 use frame_support::traits::Currency;402 use up_data_structs::{TokenId, mapping::TokenAddressMapping};403 use scale_info::TypeInfo;404 use weights::WeightInfo;405406 #[pallet::config]407 pub trait Config:408 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo409 {410 /// Weight information for functions of this pallet.411 type WeightInfo: WeightInfo;412413 /// Events compatible with [`frame_system::Config::Event`].414 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;415416 /// Handler of accounts and payment.417 type Currency: Currency<Self::AccountId>;418419 /// Set price to create a collection.420 #[pallet::constant]421 type CollectionCreationPrice: Get<422 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,423 >;424425 /// Dispatcher of operations on collections.426 type CollectionDispatch: CollectionDispatch<Self>;427428 /// Account which holds the chain's treasury.429 type TreasuryAccountId: Get<Self::AccountId>;430431 /// Address under which the CollectionHelper contract would be available.432 #[pallet::constant]433 type ContractAddress: Get<H160>;434435 /// Mapper for token addresses to Ethereum addresses.436 type EvmTokenAddressMapping: TokenAddressMapping<H160>;437438 /// Mapper for token addresses to [`CrossAccountId`].439 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;440 }441442 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);443 /// Collection id for native fungible collction.444 pub const NATIVE_FINGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);445446 #[pallet::pallet]447 #[pallet::storage_version(STORAGE_VERSION)]448 pub struct Pallet<T>(_);449450 #[pallet::extra_constants]451 impl<T: Config> Pallet<T> {452 /// Maximum admins per collection.453 pub fn collection_admins_limit() -> u32 {454 COLLECTION_ADMINS_LIMIT455 }456 }457458 #[pallet::genesis_config]459 pub struct GenesisConfig<T>(PhantomData<T>);460461 #[cfg(feature = "std")]462 impl<T: Config> Default for GenesisConfig<T> {463 fn default() -> Self {464 Self(Default::default())465 }466 }467468 #[pallet::genesis_build]469 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {470 fn build(&self) {471 StorageVersion::new(1).put::<Pallet<T>>();472 }473 }474475 impl<T: Config> Pallet<T> {476 /// Helper function that handles deposit events477 pub fn deposit_event(event: Event<T>) {478 let event = <T as Config>::RuntimeEvent::from(event);479 let event = event.into();480 <frame_system::Pallet<T>>::deposit_event(event)481 }482 }483484 #[pallet::event]485 pub enum Event<T: Config> {486 /// New collection was created487 CollectionCreated(488 /// Globally unique identifier of newly created collection.489 CollectionId,490 /// [`CollectionMode`] converted into _u8_.491 u8,492 /// Collection owner.493 T::AccountId,494 ),495496 /// New collection was destroyed497 CollectionDestroyed(498 /// Globally unique identifier of collection.499 CollectionId,500 ),501502 /// New item was created.503 ItemCreated(504 /// Id of the collection where item was created.505 CollectionId,506 /// Id of an item. Unique within the collection.507 TokenId,508 /// Owner of newly created item509 T::CrossAccountId,510 /// Always 1 for NFT511 u128,512 ),513514 /// Collection item was burned.515 ItemDestroyed(516 /// Id of the collection where item was destroyed.517 CollectionId,518 /// Identifier of burned NFT.519 TokenId,520 /// Which user has destroyed its tokens.521 T::CrossAccountId,522 /// Amount of token pieces destroed. Always 1 for NFT.523 u128,524 ),525526 /// Item was transferred527 Transfer(528 /// Id of collection to which item is belong.529 CollectionId,530 /// Id of an item.531 TokenId,532 /// Original owner of item.533 T::CrossAccountId,534 /// New owner of item.535 T::CrossAccountId,536 /// Amount of token pieces transfered. Always 1 for NFT.537 u128,538 ),539540 /// Amount pieces of token owned by `sender` was approved for `spender`.541 Approved(542 /// Id of collection to which item is belong.543 CollectionId,544 /// Id of an item.545 TokenId,546 /// Original owner of item.547 T::CrossAccountId,548 /// Id for which the approval was granted.549 T::CrossAccountId,550 /// Amount of token pieces transfered. Always 1 for NFT.551 u128,552 ),553554 /// A `sender` approves operations on all owned tokens for `spender`.555 ApprovedForAll(556 /// Id of collection to which item is belong.557 CollectionId,558 /// Owner of a wallet.559 T::CrossAccountId,560 /// Id for which operator status was granted or rewoked.561 T::CrossAccountId,562 /// Is operator status granted or revoked?563 bool,564 ),565566 /// The colletion property has been added or edited.567 CollectionPropertySet(568 /// Id of collection to which property has been set.569 CollectionId,570 /// The property that was set.571 PropertyKey,572 ),573574 /// The property has been deleted.575 CollectionPropertyDeleted(576 /// Id of collection to which property has been deleted.577 CollectionId,578 /// The property that was deleted.579 PropertyKey,580 ),581582 /// The token property has been added or edited.583 TokenPropertySet(584 /// Identifier of the collection whose token has the property set.585 CollectionId,586 /// The token for which the property was set.587 TokenId,588 /// The property that was set.589 PropertyKey,590 ),591592 /// The token property has been deleted.593 TokenPropertyDeleted(594 /// Identifier of the collection whose token has the property deleted.595 CollectionId,596 /// The token for which the property was deleted.597 TokenId,598 /// The property that was deleted.599 PropertyKey,600 ),601602 /// The token property permission of a collection has been set.603 PropertyPermissionSet(604 /// ID of collection to which property permission has been set.605 CollectionId,606 /// The property permission that was set.607 PropertyKey,608 ),609610 /// Address was added to the allow list.611 AllowListAddressAdded(612 /// ID of the affected collection.613 CollectionId,614 /// Address of the added account.615 T::CrossAccountId,616 ),617618 /// Address was removed from the allow list.619 AllowListAddressRemoved(620 /// ID of the affected collection.621 CollectionId,622 /// Address of the removed account.623 T::CrossAccountId,624 ),625626 /// Collection admin was added.627 CollectionAdminAdded(628 /// ID of the affected collection.629 CollectionId,630 /// Admin address.631 T::CrossAccountId,632 ),633634 /// Collection admin was removed.635 CollectionAdminRemoved(636 /// ID of the affected collection.637 CollectionId,638 /// Removed admin address.639 T::CrossAccountId,640 ),641642 /// Collection limits were set.643 CollectionLimitSet(644 /// ID of the affected collection.645 CollectionId,646 ),647648 /// Collection owned was changed.649 CollectionOwnerChanged(650 /// ID of the affected collection.651 CollectionId,652 /// New owner address.653 T::AccountId,654 ),655656 /// Collection permissions were set.657 CollectionPermissionSet(658 /// ID of the affected collection.659 CollectionId,660 ),661662 /// Collection sponsor was set.663 CollectionSponsorSet(664 /// ID of the affected collection.665 CollectionId,666 /// New sponsor address.667 T::AccountId,668 ),669670 /// New sponsor was confirm.671 SponsorshipConfirmed(672 /// ID of the affected collection.673 CollectionId,674 /// New sponsor address.675 T::AccountId,676 ),677678 /// Collection sponsor was removed.679 CollectionSponsorRemoved(680 /// ID of the affected collection.681 CollectionId,682 ),683 }684685 #[pallet::error]686 pub enum Error<T> {687 /// This collection does not exist.688 CollectionNotFound,689 /// Sender parameter and item owner must be equal.690 MustBeTokenOwner,691 /// No permission to perform action692 NoPermission,693 /// Destroying only empty collections is allowed694 CantDestroyNotEmptyCollection,695 /// Collection is not in mint mode.696 PublicMintingNotAllowed,697 /// Address is not in allow list.698 AddressNotInAllowlist,699700 /// Collection name can not be longer than 63 char.701 CollectionNameLimitExceeded,702 /// Collection description can not be longer than 255 char.703 CollectionDescriptionLimitExceeded,704 /// Token prefix can not be longer than 15 char.705 CollectionTokenPrefixLimitExceeded,706 /// Total collections bound exceeded.707 TotalCollectionsLimitExceeded,708 /// Exceeded max admin count709 CollectionAdminCountExceeded,710 /// Collection limit bounds per collection exceeded711 CollectionLimitBoundsExceeded,712 /// Tried to enable permissions which are only permitted to be disabled713 OwnerPermissionsCantBeReverted,714 /// Collection settings not allowing items transferring715 TransferNotAllowed,716 /// Account token limit exceeded per collection717 AccountTokenLimitExceeded,718 /// Collection token limit exceeded719 CollectionTokenLimitExceeded,720 /// Metadata flag frozen721 MetadataFlagFrozen,722723 /// Item does not exist724 TokenNotFound,725 /// Item is balance not enough726 TokenValueTooLow,727 /// Requested value is more than the approved728 ApprovedValueTooLow,729 /// Tried to approve more than owned730 CantApproveMoreThanOwned,731 /// Only spending from eth mirror could be approved732 AddressIsNotEthMirror,733734 /// Can't transfer tokens to ethereum zero address735 AddressIsZero,736737 /// The operation is not supported738 UnsupportedOperation,739740 /// Insufficient funds to perform an action741 NotSufficientFounds,742743 /// User does not satisfy the nesting rule744 UserIsNotAllowedToNest,745 /// Only tokens from specific collections may nest tokens under this one746 SourceCollectionIsNotAllowedToNest,747748 /// Tried to store more data than allowed in collection field749 CollectionFieldSizeExceeded,750751 /// Tried to store more property data than allowed752 NoSpaceForProperty,753754 /// Tried to store more property keys than allowed755 PropertyLimitReached,756757 /// Property key is too long758 PropertyKeyIsTooLong,759760 /// Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed761 InvalidCharacterInPropertyKey,762763 /// Empty property keys are forbidden764 EmptyPropertyKey,765766 /// Tried to access an external collection with an internal API767 CollectionIsExternal,768769 /// Tried to access an internal collection with an external API770 CollectionIsInternal,771772 /// This address is not set as sponsor, use setCollectionSponsor first.773 ConfirmSponsorshipFail,774775 /// The user is not an administrator.776 UserIsNotCollectionAdmin,777 }778779 /// Storage of the count of created collections. Essentially contains the last collection ID.780 #[pallet::storage]781 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;782783 /// Storage of the count of deleted collections.784 #[pallet::storage]785 pub type DestroyedCollectionCount<T> =786 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;787788 /// Storage of collection info.789 #[pallet::storage]790 pub type CollectionById<T> = StorageMap<791 Hasher = Blake2_128Concat,792 Key = CollectionId,793 Value = Collection<<T as frame_system::Config>::AccountId>,794 QueryKind = OptionQuery,795 >;796797 /// Storage of collection properties.798 #[pallet::storage]799 #[pallet::getter(fn collection_properties)]800 pub type CollectionProperties<T> = StorageMap<801 Hasher = Blake2_128Concat,802 Key = CollectionId,803 Value = CollectionPropertiesT,804 QueryKind = ValueQuery,805 >;806807 /// Storage of token property permissions of a collection.808 #[pallet::storage]809 #[pallet::getter(fn property_permissions)]810 pub type CollectionPropertyPermissions<T> = StorageMap<811 Hasher = Blake2_128Concat,812 Key = CollectionId,813 Value = PropertiesPermissionMap,814 QueryKind = ValueQuery,815 >;816817 /// Storage of the amount of collection admins.818 #[pallet::storage]819 pub type AdminAmount<T> = StorageMap<820 Hasher = Blake2_128Concat,821 Key = CollectionId,822 Value = u32,823 QueryKind = ValueQuery,824 >;825826 /// List of collection admins.827 #[pallet::storage]828 pub type IsAdmin<T: Config> = StorageNMap<829 Key = (830 Key<Blake2_128Concat, CollectionId>,831 Key<Blake2_128Concat, T::CrossAccountId>,832 ),833 Value = bool,834 QueryKind = ValueQuery,835 >;836837 /// Allowlisted collection users.838 #[pallet::storage]839 pub type Allowlist<T: Config> = StorageNMap<840 Key = (841 Key<Blake2_128Concat, CollectionId>,842 Key<Blake2_128Concat, T::CrossAccountId>,843 ),844 Value = bool,845 QueryKind = ValueQuery,846 >;847848 /// Not used by code, exists only to provide some types to metadata.849 #[pallet::storage]850 pub type DummyStorageValue<T: Config> = StorageValue<851 Value = (852 CollectionStats,853 CollectionId,854 TokenId,855 TokenChild,856 PhantomType<(857 TokenData<T::CrossAccountId>,858 RpcCollection<T::AccountId>,859 // PoV Estimate Info860 PovInfo,861 )>,862 ),863 QueryKind = OptionQuery,864 >;865}866867impl<T: Config> Pallet<T> {868 /// Enshure that receiver address is correct.869 ///870 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens.871 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {872 ensure!(873 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,874 <Error<T>>::AddressIsZero875 );876 Ok(())877 }878879 /// Get a vector of collection admins.880 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {881 <IsAdmin<T>>::iter_prefix((collection,))882 .map(|(a, _)| a)883 .collect()884 }885886 /// Get a vector of users allowed to mint tokens.887 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {888 <Allowlist<T>>::iter_prefix((collection,))889 .map(|(a, _)| a)890 .collect()891 }892893 /// Is `user` allowed to mint token in `collection`.894 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {895 <Allowlist<T>>::get((collection, user))896 }897898 /// Get statistics of collections.899 pub fn collection_stats() -> CollectionStats {900 let created = <CreatedCollectionCount<T>>::get();901 let destroyed = <DestroyedCollectionCount<T>>::get();902 CollectionStats {903 created: created.0,904 destroyed: destroyed.0,905 alive: created.0 - destroyed.0,906 }907 }908909 /// Get the effective limits for the collection.910 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {911 let collection = <CollectionById<T>>::get(collection)?;912 let limits = collection.limits;913 let effective_limits = CollectionLimits {914 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),915 sponsored_data_size: Some(limits.sponsored_data_size()),916 sponsored_data_rate_limit: Some(917 limits918 .sponsored_data_rate_limit919 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),920 ),921 token_limit: Some(limits.token_limit()),922 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(923 match collection.mode {924 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,925 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,926 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,927 },928 )),929 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),930 owner_can_transfer: Some(limits.owner_can_transfer()),931 owner_can_destroy: Some(limits.owner_can_destroy()),932 transfers_enabled: Some(limits.transfers_enabled()),933 };934935 Some(effective_limits)936 }937938 /// Returns information about the `collection` adapted for rpc.939 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {940 let Collection {941 name,942 description,943 owner,944 mode,945 token_prefix,946 sponsorship,947 limits,948 permissions,949 flags,950 } = <CollectionById<T>>::get(collection)?;951952 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)953 .into_iter()954 .map(|(key, permission)| PropertyKeyPermission { key, permission })955 .collect();956957 let properties = <CollectionProperties<T>>::get(collection)958 .into_iter()959 .map(|(key, value)| Property { key, value })960 .collect();961962 let permissions = CollectionPermissions {963 access: Some(permissions.access()),964 mint_mode: Some(permissions.mint_mode()),965 nesting: Some(permissions.nesting().clone()),966 };967968 Some(RpcCollection {969 name: name.into_inner(),970 description: description.into_inner(),971 owner,972 mode,973 token_prefix: token_prefix.into_inner(),974 sponsorship,975 limits,976 permissions,977 token_property_permissions,978 properties,979 read_only: flags.external,980981 flags: RpcCollectionFlags {982 foreign: flags.foreign,983 erc721metadata: flags.erc721metadata,984 },985 })986 }987}988989macro_rules! limit_default {990 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{991 $(992 if let Some($new) = $new.$field {993 let $old = $old.$field($($arg)?);994 let _ = $new;995 let _ = $old;996 $check997 } else {998 $new.$field = $old.$field999 }1000 )*1001 }};1002}1003macro_rules! limit_default_clone {1004 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1005 $(1006 if let Some($new) = $new.$field.clone() {1007 let $old = $old.$field($($arg)?);1008 let _ = $new;1009 let _ = $old;1010 $check1011 } else {1012 $new.$field = $old.$field.clone()1013 }1014 )*1015 }};1016}10171018impl<T: Config> Pallet<T> {1019 /// Create new collection.1020 ///1021 /// * `owner` - The owner of the collection.1022 /// * `data` - Description of the created collection.1023 /// * `flags` - Extra flags to store.1024 pub fn init_collection(1025 owner: T::CrossAccountId,1026 payer: T::CrossAccountId,1027 data: CreateCollectionData<T::AccountId>,1028 flags: CollectionFlags,1029 ) -> Result<CollectionId, DispatchError> {1030 {1031 ensure!(1032 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1033 Error::<T>::CollectionTokenPrefixLimitExceeded1034 );1035 }10361037 let created_count = <CreatedCollectionCount<T>>::get()1038 .01039 .checked_add(1)1040 .ok_or(ArithmeticError::Overflow)?;1041 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1042 let id = CollectionId(created_count);10431044 // bound Total number of collections1045 ensure!(1046 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1047 <Error<T>>::TotalCollectionsLimitExceeded1048 );10491050 // =========10511052 let collection = Collection {1053 owner: owner.as_sub().clone(),1054 name: data.name,1055 mode: data.mode.clone(),1056 description: data.description,1057 token_prefix: data.token_prefix,1058 sponsorship: data1059 .pending_sponsor1060 .map(SponsorshipState::Unconfirmed)1061 .unwrap_or_default(),1062 limits: data1063 .limits1064 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1065 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1066 permissions: data1067 .permissions1068 .map(|permissions| {1069 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1070 })1071 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1072 flags,1073 };10741075 let mut collection_properties = CollectionPropertiesT::new();1076 collection_properties1077 .try_set_from_iter(data.properties.into_iter())1078 .map_err(<Error<T>>::from)?;10791080 CollectionProperties::<T>::insert(id, collection_properties);10811082 let mut token_props_permissions = PropertiesPermissionMap::new();1083 token_props_permissions1084 .try_set_from_iter(data.token_property_permissions.into_iter())1085 .map_err(<Error<T>>::from)?;10861087 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);10881089 // Take a (non-refundable) deposit of collection creation1090 {1091 let mut imbalance =1092 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1093 imbalance.subsume(1094 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1095 &T::TreasuryAccountId::get(),1096 T::CollectionCreationPrice::get(),1097 ),1098 );1099 <T as Config>::Currency::settle(1100 payer.as_sub(),1101 imbalance,1102 WithdrawReasons::TRANSFER,1103 ExistenceRequirement::KeepAlive,1104 )1105 .map_err(|_| Error::<T>::NotSufficientFounds)?;1106 }11071108 <CreatedCollectionCount<T>>::put(created_count);1109 <Pallet<T>>::deposit_event(Event::CollectionCreated(1110 id,1111 data.mode.id(),1112 owner.as_sub().clone(),1113 ));1114 <PalletEvm<T>>::deposit_log(1115 erc::CollectionHelpersEvents::CollectionCreated {1116 owner: *owner.as_eth(),1117 collection_id: eth::collection_id_to_address(id),1118 }1119 .to_log(T::ContractAddress::get()),1120 );1121 <CollectionById<T>>::insert(id, collection);1122 Ok(id)1123 }11241125 /// Destroy collection.1126 ///1127 /// * `collection` - Collection handler.1128 /// * `sender` - The owner or administrator of the collection.1129 pub fn destroy_collection(1130 collection: CollectionHandle<T>,1131 sender: &T::CrossAccountId,1132 ) -> DispatchResult {1133 ensure!(1134 collection.limits.owner_can_destroy(),1135 <Error<T>>::NoPermission,1136 );1137 collection.check_is_owner(sender)?;11381139 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1140 .01141 .checked_add(1)1142 .ok_or(ArithmeticError::Overflow)?;11431144 // =========11451146 <DestroyedCollectionCount<T>>::put(destroyed_collections);1147 <CollectionById<T>>::remove(collection.id);1148 <AdminAmount<T>>::remove(collection.id);1149 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1150 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1151 <CollectionProperties<T>>::remove(collection.id);11521153 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11541155 <PalletEvm<T>>::deposit_log(1156 erc::CollectionHelpersEvents::CollectionDestroyed {1157 collection_id: eth::collection_id_to_address(collection.id),1158 }1159 .to_log(T::ContractAddress::get()),1160 );1161 Ok(())1162 }11631164 /// This function sets or removes a collection properties according to1165 /// `properties_updates` contents:1166 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1167 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1168 ///1169 /// This function fires an event for each property change.1170 /// In case of an error, all the changes (including the events) will be reverted1171 /// since the function is transactional.1172 #[transactional]1173 fn modify_collection_properties(1174 collection: &CollectionHandle<T>,1175 sender: &T::CrossAccountId,1176 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1177 ) -> DispatchResult {1178 collection.check_is_owner_or_admin(sender)?;11791180 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);11811182 for (key, value) in properties_updates {1183 match value {1184 Some(value) => {1185 stored_properties1186 .try_set(key.clone(), value)1187 .map_err(<Error<T>>::from)?;11881189 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1190 <PalletEvm<T>>::deposit_log(1191 erc::CollectionHelpersEvents::CollectionChanged {1192 collection_id: eth::collection_id_to_address(collection.id),1193 }1194 .to_log(T::ContractAddress::get()),1195 );1196 }1197 None => {1198 stored_properties.remove(&key).map_err(<Error<T>>::from)?;11991200 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1201 <PalletEvm<T>>::deposit_log(1202 erc::CollectionHelpersEvents::CollectionChanged {1203 collection_id: eth::collection_id_to_address(collection.id),1204 }1205 .to_log(T::ContractAddress::get()),1206 );1207 }1208 }1209 }12101211 <CollectionProperties<T>>::set(collection.id, stored_properties);12121213 Ok(())1214 }12151216 /// A batch operation to add, edit or remove properties for a token.1217 /// It sets or removes a token's properties according to1218 /// `properties_updates` contents:1219 /// * sets a property under the <key> with the value provided `(<key>, Some(<value>))`1220 /// * removes a property under the <key> if the value is `None` `(<key>, None)`.1221 ///1222 /// - `nesting_budget`: Limit for searching parents in-depth to check ownership.1223 /// - `is_token_create`: Indicates that method is called during token initialization.1224 /// Allows to bypass ownership check.1225 ///1226 /// All affected properties should have `mutable` permission1227 /// to be **deleted** or to be **set more than once**,1228 /// and the sender should have permission to edit those properties.1229 ///1230 /// This function fires an event for each property change.1231 /// In case of an error, all the changes (including the events) will be reverted1232 /// since the function is transactional.1233 pub fn modify_token_properties(1234 collection: &CollectionHandle<T>,1235 sender: &T::CrossAccountId,1236 token_id: TokenId,1237 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1238 is_token_create: bool,1239 mut stored_properties: TokenProperties,1240 is_token_owner: impl Fn() -> Result<bool, DispatchError>,1241 set_token_properties: impl FnOnce(TokenProperties),1242 log: evm_coder::ethereum::Log,1243 ) -> DispatchResult {1244 let is_collection_admin = collection.is_owner_or_admin(sender);1245 let permissions = Self::property_permissions(collection.id);12461247 let mut token_owner_result = None;1248 let mut is_token_owner = || -> Result<bool, DispatchError> {1249 *token_owner_result.get_or_insert_with(&is_token_owner)1250 };12511252 for (key, value) in properties_updates {1253 let permission = permissions1254 .get(&key)1255 .cloned()1256 .unwrap_or_else(PropertyPermission::none);12571258 let is_property_exists = stored_properties.get(&key).is_some();12591260 match permission {1261 PropertyPermission { mutable: false, .. } if is_property_exists => {1262 return Err(<Error<T>>::NoPermission.into());1263 }12641265 PropertyPermission {1266 collection_admin,1267 token_owner,1268 ..1269 } => {1270 //TODO: investigate threats during public minting.1271 let is_token_create =1272 is_token_create && (collection_admin || token_owner) && value.is_some();1273 if !(is_token_create1274 || (collection_admin && is_collection_admin)1275 || (token_owner && is_token_owner()?))1276 {1277 fail!(<Error<T>>::NoPermission);1278 }1279 }1280 }12811282 match value {1283 Some(value) => {1284 stored_properties1285 .try_set(key.clone(), value)1286 .map_err(<Error<T>>::from)?;12871288 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1289 }1290 None => {1291 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12921293 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1294 }1295 }12961297 <PalletEvm<T>>::deposit_log(log.clone());1298 }12991300 set_token_properties(stored_properties);13011302 Ok(())1303 }13041305 /// Sets or unsets the approval of a given operator.1306 ///1307 /// The `operator` is allowed to transfer all token pieces of the `owner` on their behalf.1308 /// - `owner`: Token owner1309 /// - `operator`: Operator1310 /// - `approve`: Should operator status be granted or revoked?1311 pub fn set_allowance_for_all(1312 collection: &CollectionHandle<T>,1313 owner: &T::CrossAccountId,1314 operator: &T::CrossAccountId,1315 approve: bool,1316 set_allowance: impl FnOnce(),1317 log: evm_coder::ethereum::Log,1318 ) -> DispatchResult {1319 if collection.permissions.access() == AccessMode::AllowList {1320 collection.check_allowlist(owner)?;1321 collection.check_allowlist(operator)?;1322 }13231324 Self::ensure_correct_receiver(operator)?;13251326 set_allowance();13271328 <PalletEvm<T>>::deposit_log(log);1329 Self::deposit_event(Event::ApprovedForAll(1330 collection.id,1331 owner.clone(),1332 operator.clone(),1333 approve,1334 ));1335 Ok(())1336 }13371338 /// Set collection property.1339 ///1340 /// * `collection` - Collection handler.1341 /// * `sender` - The owner or administrator of the collection.1342 /// * `property` - The property to set.1343 pub fn set_collection_property(1344 collection: &CollectionHandle<T>,1345 sender: &T::CrossAccountId,1346 property: Property,1347 ) -> DispatchResult {1348 Self::set_collection_properties(collection, sender, [property].into_iter())1349 }13501351 /// Set a scoped collection property, where the scope is a special prefix1352 /// prohibiting a user access to change the property directly.1353 ///1354 /// * `collection_id` - ID of the collection for which the property is being set.1355 /// * `scope` - Property scope.1356 /// * `property` - The property to set.1357 pub fn set_scoped_collection_property(1358 collection_id: CollectionId,1359 scope: PropertyScope,1360 property: Property,1361 ) -> DispatchResult {1362 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1363 properties.try_scoped_set(scope, property.key, property.value)1364 })1365 .map_err(<Error<T>>::from)?;13661367 Ok(())1368 }13691370 /// Set scoped collection properties, where the scope is a special prefix1371 /// prohibiting a user access to change the properties directly.1372 ///1373 /// * `collection_id` - ID of the collection for which the properties is being set.1374 /// * `scope` - Property scope.1375 /// * `properties` - The properties to set.1376 pub fn set_scoped_collection_properties(1377 collection_id: CollectionId,1378 scope: PropertyScope,1379 properties: impl Iterator<Item = Property>,1380 ) -> DispatchResult {1381 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1382 stored_properties.try_scoped_set_from_iter(scope, properties)1383 })1384 .map_err(<Error<T>>::from)?;13851386 Ok(())1387 }13881389 /// Set collection properties.1390 ///1391 /// * `collection` - Collection handler.1392 /// * `sender` - The owner or administrator of the collection.1393 /// * `properties` - The properties to set.1394 pub fn set_collection_properties(1395 collection: &CollectionHandle<T>,1396 sender: &T::CrossAccountId,1397 properties: impl Iterator<Item = Property>,1398 ) -> DispatchResult {1399 Self::modify_collection_properties(1400 collection,1401 sender,1402 properties.map(|property| (property.key, Some(property.value))),1403 )1404 }14051406 /// Delete collection property.1407 ///1408 /// * `collection` - Collection handler.1409 /// * `sender` - The owner or administrator of the collection.1410 /// * `property` - The property to delete.1411 pub fn delete_collection_property(1412 collection: &CollectionHandle<T>,1413 sender: &T::CrossAccountId,1414 property_key: PropertyKey,1415 ) -> DispatchResult {1416 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1417 }14181419 /// Delete collection properties.1420 ///1421 /// * `collection` - Collection handler.1422 /// * `sender` - The owner or administrator of the collection.1423 /// * `properties` - The properties to delete.1424 pub fn delete_collection_properties(1425 collection: &CollectionHandle<T>,1426 sender: &T::CrossAccountId,1427 property_keys: impl Iterator<Item = PropertyKey>,1428 ) -> DispatchResult {1429 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1430 }14311432 /// Set collection propetry permission without any checks.1433 ///1434 /// Used for migrations.1435 ///1436 /// * `collection` - Collection handler.1437 /// * `property_permissions` - Property permissions.1438 pub fn set_property_permission_unchecked(1439 collection: CollectionId,1440 property_permission: PropertyKeyPermission,1441 ) -> DispatchResult {1442 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1443 permissions.try_set(property_permission.key, property_permission.permission)1444 })1445 .map_err(<Error<T>>::from)?;1446 Ok(())1447 }14481449 /// Set collection property permission.1450 ///1451 /// * `collection` - Collection handler.1452 /// * `sender` - The owner or administrator of the collection.1453 /// * `property_permission` - Property permission.1454 pub fn set_property_permission(1455 collection: &CollectionHandle<T>,1456 sender: &T::CrossAccountId,1457 property_permission: PropertyKeyPermission,1458 ) -> DispatchResult {1459 Self::set_scoped_property_permission(1460 collection,1461 sender,1462 PropertyScope::None,1463 property_permission,1464 )1465 }14661467 /// Set collection property permission with scope.1468 ///1469 /// * `collection` - Collection handler.1470 /// * `sender` - The owner or administrator of the collection.1471 /// * `scope` - Property scope.1472 /// * `property_permission` - Property permission.1473 pub fn set_scoped_property_permission(1474 collection: &CollectionHandle<T>,1475 sender: &T::CrossAccountId,1476 scope: PropertyScope,1477 property_permission: PropertyKeyPermission,1478 ) -> DispatchResult {1479 collection.check_is_owner_or_admin(sender)?;14801481 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1482 let current_permission = all_permissions.get(&property_permission.key);1483 if matches![1484 current_permission,1485 Some(PropertyPermission { mutable: false, .. })1486 ] {1487 return Err(<Error<T>>::NoPermission.into());1488 }14891490 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1491 let property_permission = property_permission.clone();1492 permissions.try_scoped_set(1493 scope,1494 property_permission.key,1495 property_permission.permission,1496 )1497 })1498 .map_err(<Error<T>>::from)?;14991500 Self::deposit_event(Event::PropertyPermissionSet(1501 collection.id,1502 property_permission.key,1503 ));1504 <PalletEvm<T>>::deposit_log(1505 erc::CollectionHelpersEvents::CollectionChanged {1506 collection_id: eth::collection_id_to_address(collection.id),1507 }1508 .to_log(T::ContractAddress::get()),1509 );15101511 Ok(())1512 }15131514 /// Set token property permission.1515 ///1516 /// * `collection` - Collection handler.1517 /// * `sender` - The owner or administrator of the collection.1518 /// * `property_permissions` - Property permissions.1519 #[transactional]1520 pub fn set_token_property_permissions(1521 collection: &CollectionHandle<T>,1522 sender: &T::CrossAccountId,1523 property_permissions: Vec<PropertyKeyPermission>,1524 ) -> DispatchResult {1525 Self::set_scoped_token_property_permissions(1526 collection,1527 sender,1528 PropertyScope::None,1529 property_permissions,1530 )1531 }15321533 /// Set token property permission with scope.1534 ///1535 /// * `collection` - Collection handler.1536 /// * `sender` - The owner or administrator of the collection.1537 /// * `scope` - Property scope.1538 /// * `property_permissions` - Property permissions.1539 #[transactional]1540 pub fn set_scoped_token_property_permissions(1541 collection: &CollectionHandle<T>,1542 sender: &T::CrossAccountId,1543 scope: PropertyScope,1544 property_permissions: Vec<PropertyKeyPermission>,1545 ) -> DispatchResult {1546 for prop_pemission in property_permissions {1547 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1548 }15491550 Ok(())1551 }15521553 /// Get collection property.1554 pub fn get_collection_property(1555 collection_id: CollectionId,1556 key: &PropertyKey,1557 ) -> Option<PropertyValue> {1558 Self::collection_properties(collection_id).get(key).cloned()1559 }15601561 /// Convert byte vector to property key vector.1562 pub fn bytes_keys_to_property_keys(1563 keys: Vec<Vec<u8>>,1564 ) -> Result<Vec<PropertyKey>, DispatchError> {1565 keys.into_iter()1566 .map(|key| -> Result<PropertyKey, DispatchError> {1567 key.try_into()1568 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1569 })1570 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1571 }15721573 /// Get properties according to given keys.1574 pub fn filter_collection_properties(1575 collection_id: CollectionId,1576 keys: Option<Vec<PropertyKey>>,1577 ) -> Result<Vec<Property>, DispatchError> {1578 let properties = Self::collection_properties(collection_id);15791580 let properties = keys1581 .map(|keys| {1582 keys.into_iter()1583 .filter_map(|key| {1584 properties.get(&key).map(|value| Property {1585 key,1586 value: value.clone(),1587 })1588 })1589 .collect()1590 })1591 .unwrap_or_else(|| {1592 properties1593 .into_iter()1594 .map(|(key, value)| Property { key, value })1595 .collect()1596 });15971598 Ok(properties)1599 }16001601 /// Get property permissions according to given keys.1602 pub fn filter_property_permissions(1603 collection_id: CollectionId,1604 keys: Option<Vec<PropertyKey>>,1605 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1606 let permissions = Self::property_permissions(collection_id);16071608 let key_permissions = keys1609 .map(|keys| {1610 keys.into_iter()1611 .filter_map(|key| {1612 permissions1613 .get(&key)1614 .map(|permission| PropertyKeyPermission {1615 key,1616 permission: permission.clone(),1617 })1618 })1619 .collect()1620 })1621 .unwrap_or_else(|| {1622 permissions1623 .into_iter()1624 .map(|(key, permission)| PropertyKeyPermission { key, permission })1625 .collect()1626 });16271628 Ok(key_permissions)1629 }16301631 /// Toggle `user` participation in the `collection`'s allow list.1632 /// #### Store read/writes1633 /// 1 writes1634 pub fn toggle_allowlist(1635 collection: &CollectionHandle<T>,1636 sender: &T::CrossAccountId,1637 user: &T::CrossAccountId,1638 allowed: bool,1639 ) -> DispatchResult {1640 collection.check_is_owner_or_admin(sender)?;16411642 // =========16431644 if allowed {1645 <Allowlist<T>>::insert((collection.id, user), true);1646 Self::deposit_event(Event::<T>::AllowListAddressAdded(1647 collection.id,1648 user.clone(),1649 ));1650 } else {1651 <Allowlist<T>>::remove((collection.id, user));1652 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1653 collection.id,1654 user.clone(),1655 ));1656 }16571658 <PalletEvm<T>>::deposit_log(1659 erc::CollectionHelpersEvents::CollectionChanged {1660 collection_id: eth::collection_id_to_address(collection.id),1661 }1662 .to_log(T::ContractAddress::get()),1663 );16641665 Ok(())1666 }16671668 /// Toggle `user` participation in the `collection`'s admin list.1669 /// #### Store read/writes1670 /// 2 reads, 2 writes1671 pub fn toggle_admin(1672 collection: &CollectionHandle<T>,1673 sender: &T::CrossAccountId,1674 user: &T::CrossAccountId,1675 admin: bool,1676 ) -> DispatchResult {1677 collection.check_is_internal()?;1678 collection.check_is_owner(sender)?;16791680 let is_admin = <IsAdmin<T>>::get((collection.id, user));1681 if is_admin == admin {1682 if admin {1683 return Ok(());1684 } else {1685 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1686 }1687 }1688 let amount = <AdminAmount<T>>::get(collection.id);16891690 // =========16911692 if admin {1693 let amount = amount1694 .checked_add(1)1695 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1696 ensure!(1697 amount <= Self::collection_admins_limit(),1698 <Error<T>>::CollectionAdminCountExceeded,1699 );17001701 <AdminAmount<T>>::insert(collection.id, amount);1702 <IsAdmin<T>>::insert((collection.id, user), true);17031704 Self::deposit_event(Event::<T>::CollectionAdminAdded(1705 collection.id,1706 user.clone(),1707 ));1708 } else {1709 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1710 <IsAdmin<T>>::remove((collection.id, user));17111712 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1713 collection.id,1714 user.clone(),1715 ));1716 }17171718 <PalletEvm<T>>::deposit_log(1719 erc::CollectionHelpersEvents::CollectionChanged {1720 collection_id: eth::collection_id_to_address(collection.id),1721 }1722 .to_log(T::ContractAddress::get()),1723 );17241725 Ok(())1726 }17271728 /// Update collection limits.1729 pub fn update_limits(1730 user: &T::CrossAccountId,1731 collection: &mut CollectionHandle<T>,1732 new_limit: CollectionLimits,1733 ) -> DispatchResult {1734 collection.check_is_internal()?;1735 collection.check_is_owner_or_admin(user)?;17361737 collection.limits =1738 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17391740 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1741 <PalletEvm<T>>::deposit_log(1742 erc::CollectionHelpersEvents::CollectionChanged {1743 collection_id: eth::collection_id_to_address(collection.id),1744 }1745 .to_log(T::ContractAddress::get()),1746 );17471748 collection.save()1749 }17501751 /// Merge set fields from `new_limit` to `old_limit`.1752 fn clamp_limits(1753 mode: CollectionMode,1754 old_limit: &CollectionLimits,1755 mut new_limit: CollectionLimits,1756 ) -> Result<CollectionLimits, DispatchError> {1757 let limits = old_limit;1758 limit_default!(old_limit, new_limit,1759 account_token_ownership_limit => ensure!(1760 new_limit <= MAX_TOKEN_OWNERSHIP,1761 <Error<T>>::CollectionLimitBoundsExceeded,1762 ),1763 sponsored_data_size => ensure!(1764 new_limit <= CUSTOM_DATA_LIMIT,1765 <Error<T>>::CollectionLimitBoundsExceeded,1766 ),17671768 sponsored_data_rate_limit => {},1769 token_limit => ensure!(1770 old_limit >= new_limit && new_limit > 0,1771 <Error<T>>::CollectionTokenLimitExceeded1772 ),17731774 sponsor_transfer_timeout(match mode {1775 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1776 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1777 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1778 }) => ensure!(1779 new_limit <= MAX_SPONSOR_TIMEOUT,1780 <Error<T>>::CollectionLimitBoundsExceeded,1781 ),1782 sponsor_approve_timeout => {},1783 owner_can_transfer => ensure!(1784 !limits.owner_can_transfer_instaled() ||1785 old_limit || !new_limit,1786 <Error<T>>::OwnerPermissionsCantBeReverted,1787 ),1788 owner_can_destroy => ensure!(1789 old_limit || !new_limit,1790 <Error<T>>::OwnerPermissionsCantBeReverted,1791 ),1792 transfers_enabled => {},1793 );1794 Ok(new_limit)1795 }17961797 /// Update collection permissions.1798 pub fn update_permissions(1799 user: &T::CrossAccountId,1800 collection: &mut CollectionHandle<T>,1801 new_permission: CollectionPermissions,1802 ) -> DispatchResult {1803 collection.check_is_internal()?;1804 collection.check_is_owner_or_admin(user)?;1805 collection.permissions = Self::clamp_permissions(1806 collection.mode.clone(),1807 &collection.permissions,1808 new_permission,1809 )?;18101811 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1812 <PalletEvm<T>>::deposit_log(1813 erc::CollectionHelpersEvents::CollectionChanged {1814 collection_id: eth::collection_id_to_address(collection.id),1815 }1816 .to_log(T::ContractAddress::get()),1817 );18181819 collection.save()1820 }18211822 /// Merge set fields from `new_permission` to `old_permission`.1823 fn clamp_permissions(1824 _mode: CollectionMode,1825 old_permission: &CollectionPermissions,1826 mut new_permission: CollectionPermissions,1827 ) -> Result<CollectionPermissions, DispatchError> {1828 limit_default_clone!(old_permission, new_permission,1829 access => {},1830 mint_mode => {},1831 nesting => { /* todo check for permissive, if only it gets out of benchmarks */ },1832 );1833 Ok(new_permission)1834 }18351836 /// Repair possibly broken properties of a collection.1837 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1838 CollectionProperties::<T>::mutate(collection_id, |properties| {1839 properties.recompute_consumed_space();1840 });18411842 Ok(())1843 }1844}18451846/// Indicates unsupported methods by returning [Error::UnsupportedOperation].1847#[macro_export]1848macro_rules! unsupported {1849 ($runtime:path) => {1850 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1851 };1852}18531854/// Return weights for various worst-case operations.1855pub trait CommonWeightInfo<CrossAccountId> {1856 /// Weight of item creation.1857 fn create_item(data: &CreateItemData) -> Weight {1858 Self::create_multiple_items(from_ref(data))1859 }18601861 /// Weight of items creation.1862 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18631864 /// Weight of items creation.1865 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18661867 /// The weight of the burning item.1868 fn burn_item() -> Weight;18691870 /// Property setting weight.1871 ///1872 /// * `amount`- The number of properties to set.1873 fn set_collection_properties(amount: u32) -> Weight;18741875 /// Collection property deletion weight.1876 ///1877 /// * `amount`- The number of properties to set.1878 fn delete_collection_properties(amount: u32) -> Weight;18791880 /// Token property setting weight.1881 ///1882 /// * `amount`- The number of properties to set.1883 fn set_token_properties(amount: u32) -> Weight;18841885 /// Token property deletion weight.1886 ///1887 /// * `amount`- The number of properties to delete.1888 fn delete_token_properties(amount: u32) -> Weight;18891890 /// Token property permissions set weight.1891 ///1892 /// * `amount`- The number of property permissions to set.1893 fn set_token_property_permissions(amount: u32) -> Weight;18941895 /// Transfer price of the token or its parts.1896 fn transfer() -> Weight;18971898 /// The price of setting the permission of the operation from another user.1899 fn approve() -> Weight;19001901 /// The price of setting the permission of the operation from another user for eth mirror.1902 fn approve_from() -> Weight;19031904 /// Transfer price from another user.1905 fn transfer_from() -> Weight;19061907 /// The price of burning a token from another user.1908 fn burn_from() -> Weight;19091910 /// Differs from burn_item in case of Fungible and Refungible, as it should burn1911 /// whole users's balance.1912 ///1913 /// This method shouldn't be used directly, as it doesn't count breadth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1914 fn burn_recursively_self_raw() -> Weight;19151916 /// Cost of iterating over `amount` children while burning, without counting child burning itself.1917 ///1918 /// This method shouldn't be used directly, as it doesn't count depth price, use [burn_recursively](CommonWeightInfo::burn_recursively) instead1919 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19201921 /// The price of recursive burning a token.1922 ///1923 /// `max_selfs` - The maximum burning weight of the token itself.1924 /// `max_breadth` - The maximum number of nested tokens to burn.1925 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1926 Self::burn_recursively_self_raw()1927 .saturating_mul(max_selfs.max(1) as u64)1928 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1929 }19301931 /// The price of retrieving token owner1932 fn token_owner() -> Weight;19331934 /// The price of setting approval for all1935 fn set_allowance_for_all() -> Weight;19361937 /// The price of repairing an item.1938 fn force_repair_item() -> Weight;1939}19401941/// Weight info extension trait for refungible pallet.1942pub trait RefungibleExtensionsWeightInfo {1943 /// Weight of token repartition.1944 fn repartition() -> Weight;1945}19461947/// Common collection operations.1948///1949/// It wraps methods in Fungible, Nonfungible and Refungible pallets1950/// and adds weight info.1951pub trait CommonCollectionOperations<T: Config> {1952 /// Create token.1953 ///1954 /// * `sender` - The user who mint the token and pays for the transaction.1955 /// * `to` - The user who will own the token.1956 /// * `data` - Token data.1957 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1958 fn create_item(1959 &self,1960 sender: T::CrossAccountId,1961 to: T::CrossAccountId,1962 data: CreateItemData,1963 nesting_budget: &dyn Budget,1964 ) -> DispatchResultWithPostInfo;19651966 /// Create multiple tokens.1967 ///1968 /// * `sender` - The user who mint the token and pays for the transaction.1969 /// * `to` - The user who will own the token.1970 /// * `data` - Token data.1971 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1972 fn create_multiple_items(1973 &self,1974 sender: T::CrossAccountId,1975 to: T::CrossAccountId,1976 data: Vec<CreateItemData>,1977 nesting_budget: &dyn Budget,1978 ) -> DispatchResultWithPostInfo;19791980 /// Create multiple tokens.1981 ///1982 /// * `sender` - The user who mint the token and pays for the transaction.1983 /// * `to` - The user who will own the token.1984 /// * `data` - Token data.1985 /// * `nesting_budget` - A budget that can be spent on nesting tokens.1986 fn create_multiple_items_ex(1987 &self,1988 sender: T::CrossAccountId,1989 data: CreateItemExData<T::CrossAccountId>,1990 nesting_budget: &dyn Budget,1991 ) -> DispatchResultWithPostInfo;19921993 /// Burn token.1994 ///1995 /// * `sender` - The user who owns the token.1996 /// * `token` - Token id that will burned.1997 /// * `amount` - The number of parts of the token that will be burned.1998 fn burn_item(1999 &self,2000 sender: T::CrossAccountId,2001 token: TokenId,2002 amount: u128,2003 ) -> DispatchResultWithPostInfo;20042005 /// Burn token and all nested tokens recursievly.2006 ///2007 /// * `sender` - The user who owns the token.2008 /// * `token` - Token id that will burned.2009 /// * `self_budget` - The budget that can be spent on burning tokens.2010 /// * `breadth_budget` - The budget that can be spent on burning nested tokens.2011 fn burn_item_recursively(2012 &self,2013 sender: T::CrossAccountId,2014 token: TokenId,2015 self_budget: &dyn Budget,2016 breadth_budget: &dyn Budget,2017 ) -> DispatchResultWithPostInfo;20182019 /// Set collection properties.2020 ///2021 /// * `sender` - Must be either the owner of the collection or its admin.2022 /// * `properties` - Properties to be set.2023 fn set_collection_properties(2024 &self,2025 sender: T::CrossAccountId,2026 properties: Vec<Property>,2027 ) -> DispatchResultWithPostInfo;20282029 /// Delete collection properties.2030 ///2031 /// * `sender` - Must be either the owner of the collection or its admin.2032 /// * `properties` - The properties to be removed.2033 fn delete_collection_properties(2034 &self,2035 sender: &T::CrossAccountId,2036 property_keys: Vec<PropertyKey>,2037 ) -> DispatchResultWithPostInfo;20382039 /// Set token properties.2040 ///2041 /// The appropriate [`PropertyPermission`] for the token property2042 /// must be set with [`Self::set_token_property_permissions`].2043 ///2044 /// * `sender` - Must be either the owner of the token or its admin.2045 /// * `token_id` - The token for which the properties are being set.2046 /// * `properties` - Properties to be set.2047 /// * `budget` - Budget for setting properties.2048 fn set_token_properties(2049 &self,2050 sender: T::CrossAccountId,2051 token_id: TokenId,2052 properties: Vec<Property>,2053 budget: &dyn Budget,2054 ) -> DispatchResultWithPostInfo;20552056 /// Remove token properties.2057 ///2058 /// The appropriate [`PropertyPermission`] for the token property2059 /// must be set with [`Self::set_token_property_permissions`].2060 ///2061 /// * `sender` - Must be either the owner of the token or its admin.2062 /// * `token_id` - The token for which the properties are being remove.2063 /// * `property_keys` - Keys to remove corresponding properties.2064 /// * `budget` - Budget for removing properties.2065 fn delete_token_properties(2066 &self,2067 sender: T::CrossAccountId,2068 token_id: TokenId,2069 property_keys: Vec<PropertyKey>,2070 budget: &dyn Budget,2071 ) -> DispatchResultWithPostInfo;20722073 /// Set token property permissions.2074 ///2075 /// * `sender` - Must be either the owner of the token or its admin.2076 /// * `token_id` - The token for which the properties are being set.2077 /// * `property_permissions` - Property permissions to be set.2078 /// * `budget` - Budget for setting properties.2079 fn set_token_property_permissions(2080 &self,2081 sender: &T::CrossAccountId,2082 property_permissions: Vec<PropertyKeyPermission>,2083 ) -> DispatchResultWithPostInfo;20842085 /// Transfer amount of token pieces.2086 ///2087 /// * `sender` - Donor user.2088 /// * `to` - Recepient user.2089 /// * `token` - The token of which parts are being sent.2090 /// * `amount` - The number of parts of the token that will be transferred.2091 /// * `budget` - The maximum budget that can be spent on the transfer.2092 fn transfer(2093 &self,2094 sender: T::CrossAccountId,2095 to: T::CrossAccountId,2096 token: TokenId,2097 amount: u128,2098 budget: &dyn Budget,2099 ) -> DispatchResultWithPostInfo;21002101 /// Grant access to another account to transfer parts of the token owned by the calling user via [Self::transfer_from].2102 ///2103 /// * `sender` - The user who grants access to the token.2104 /// * `spender` - The user to whom the rights are granted.2105 /// * `token` - The token to which access is granted.2106 /// * `amount` - The amount of pieces that another user can dispose of.2107 fn approve(2108 &self,2109 sender: T::CrossAccountId,2110 spender: T::CrossAccountId,2111 token: TokenId,2112 amount: u128,2113 ) -> DispatchResultWithPostInfo;21142115 /// Grant access to another account to transfer parts of the token owned by the calling user's eth mirror via [Self::transfer_from].2116 ///2117 /// * `sender` - The user who grants access to the token.2118 /// * `from` - Spender's eth mirror.2119 /// * `to` - The user to whom the rights are granted.2120 /// * `token` - The token to which access is granted.2121 /// * `amount` - The amount of pieces that another user can dispose of.2122 fn approve_from(2123 &self,2124 sender: T::CrossAccountId,2125 from: T::CrossAccountId,2126 to: T::CrossAccountId,2127 token: TokenId,2128 amount: u128,2129 ) -> DispatchResultWithPostInfo;21302131 /// Send parts of a token owned by another user.2132 ///2133 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2134 ///2135 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2136 /// * `from` - The user who owns the token.2137 /// * `to` - Recepient user.2138 /// * `token` - The token of which parts are being sent.2139 /// * `amount` - The number of parts of the token that will be transferred.2140 /// * `budget` - The maximum budget that can be spent on the transfer.2141 fn transfer_from(2142 &self,2143 sender: T::CrossAccountId,2144 from: T::CrossAccountId,2145 to: T::CrossAccountId,2146 token: TokenId,2147 amount: u128,2148 budget: &dyn Budget,2149 ) -> DispatchResultWithPostInfo;21502151 /// Burn parts of a token owned by another user.2152 ///2153 /// Before calling this method, you must grant rights to the calling user via [`Self::approve`].2154 ///2155 /// * `sender` - The user who must have access to the token (see [`Self::approve`]).2156 /// * `from` - The user who owns the token.2157 /// * `token` - The token of which parts are being sent.2158 /// * `amount` - The number of parts of the token that will be transferred.2159 /// * `budget` - The maximum budget that can be spent on the burn.2160 fn burn_from(2161 &self,2162 sender: T::CrossAccountId,2163 from: T::CrossAccountId,2164 token: TokenId,2165 amount: u128,2166 budget: &dyn Budget,2167 ) -> DispatchResultWithPostInfo;21682169 /// Check permission to nest token.2170 ///2171 /// * `sender` - The user who initiated the check.2172 /// * `from` - The token that is checked for embedding.2173 /// * `under` - Token under which to check.2174 /// * `budget` - The maximum budget that can be spent on the check.2175 fn check_nesting(2176 &self,2177 sender: T::CrossAccountId,2178 from: (CollectionId, TokenId),2179 under: TokenId,2180 budget: &dyn Budget,2181 ) -> DispatchResult;21822183 /// Nest one token into another.2184 ///2185 /// * `under` - Token holder.2186 /// * `to_nest` - Nested token.2187 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21882189 /// Unnest token.2190 ///2191 /// * `under` - Token holder.2192 /// * `to_nest` - Token to unnest.2193 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21942195 /// Get all user tokens.2196 ///2197 /// * `account` - Account for which you need to get tokens.2198 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;21992200 /// Get all the tokens in the collection.2201 fn collection_tokens(&self) -> Vec<TokenId>;22022203 /// Check if the token exists.2204 ///2205 /// * `token` - Id token to check.2206 fn token_exists(&self, token: TokenId) -> bool;22072208 /// Get the id of the last minted token.2209 fn last_token_id(&self) -> TokenId;22102211 /// Get the owner of the token.2212 ///2213 /// * `token` - The token for which you need to find out the owner.2214 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22152216 /// Returns 10 tokens owners in no particular order.2217 ///2218 /// * `token` - The token for which you need to find out the owners.2219 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22202221 /// Get the value of the token property by key.2222 ///2223 /// * `token` - Token with the property to get.2224 /// * `key` - Property name.2225 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22262227 /// Get a set of token properties by key vector.2228 ///2229 /// * `token` - Token with the property to get.2230 /// * `keys` - Vector of property keys. If this parameter is [None](sp_std::result::Result),2231 /// then all properties are returned.2232 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22332234 /// Amount of unique collection tokens2235 fn total_supply(&self) -> u32;22362237 /// Amount of different tokens account has.2238 ///2239 /// * `account` - The account for which need to get the balance.2240 fn account_balance(&self, account: T::CrossAccountId) -> u32;22412242 /// Amount of specific token account have.2243 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22442245 /// Amount of token pieces2246 fn total_pieces(&self, token: TokenId) -> Option<u128>;22472248 /// Get the number of parts of the token that a trusted user can manage.2249 ///2250 /// * `sender` - Trusted user.2251 /// * `spender` - Owner of the token.2252 /// * `token` - The token for which to get the value.2253 fn allowance(2254 &self,2255 sender: T::CrossAccountId,2256 spender: T::CrossAccountId,2257 token: TokenId,2258 ) -> u128;22592260 /// Get extension for RFT collection.2261 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22622263 /// The `operator` is allowed to transfer all tokens of the `owner` on their behalf.2264 /// * `owner` - Token owner2265 /// * `operator` - Operator2266 /// * `approve` - Should operator status be granted or revoked?2267 fn set_allowance_for_all(2268 &self,2269 owner: T::CrossAccountId,2270 operator: T::CrossAccountId,2271 approve: bool,2272 ) -> DispatchResultWithPostInfo;22732274 /// Tells whether the given `owner` approves the `operator`.2275 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;22762277 /// Repairs a possibly broken item.2278 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2279}22802281/// Extension for RFT collection.2282pub trait RefungibleExtensions<T>2283where2284 T: Config,2285{2286 /// Change the number of parts of the token.2287 ///2288 /// When the value changes down, this function is equivalent to burning parts of the token.2289 ///2290 /// * `sender` - The user calling the repartition operation. Must be the owner of the token.2291 /// * `token` - The token for which you want to change the number of parts.2292 /// * `amount` - The new value of the parts of the token.2293 fn repartition(2294 &self,2295 sender: &T::CrossAccountId,2296 token: TokenId,2297 amount: u128,2298 ) -> DispatchResultWithPostInfo;2299}23002301/// Merge [`DispatchResult`] with [`Weight`] into [`DispatchResultWithPostInfo`].2302///2303/// Used for [`CommonCollectionOperations`] implementations and flexible enough to do so.2304pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2305 let post_info = PostDispatchInfo {2306 actual_weight: Some(weight),2307 pays_fee: Pays::Yes,2308 };2309 match res {2310 Ok(()) => Ok(post_info),2311 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2312 }2313}23142315impl<T: Config> From<PropertiesError> for Error<T> {2316 fn from(error: PropertiesError) -> Self {2317 match error {2318 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2319 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2320 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2321 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2322 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2323 }2324 }2325}23262327/// Consume gas for reading.2328pub fn consume_store_reads<T: Config>(2329 recorder: &SubstrateRecorder<T>,2330 reads: u64,2331) -> pallet_evm_coder_substrate::execution::Result<()> {2332 recorder.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(2333 <T as frame_system::Config>::DbWeight::get()2334 .read2335 .saturating_mul(reads),2336 // TODO: measure proof2337 0,2338 )))2339}23402341/// Consume gas for writing.2342pub fn consume_store_writes<T: Config>(2343 recorder: &SubstrateRecorder<T>,2344 writes: u64,2345) -> pallet_evm_coder_substrate::execution::Result<()> {2346 recorder.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(2347 <T as frame_system::Config>::DbWeight::get()2348 .write2349 .saturating_mul(writes),2350 // TODO: measure proof2351 0,2352 )))2353}23542355/// Consume gas for reading and writing.2356pub fn consume_store_reads_and_writes<T: Config>(2357 recorder: &SubstrateRecorder<T>,2358 reads: u64,2359 writes: u64,2360) -> pallet_evm_coder_substrate::execution::Result<()> {2361 let weight = <T as frame_system::Config>::DbWeight::get();2362 let reads = weight.read.saturating_mul(reads);2363 let writes = weight.read.saturating_mul(writes);2364 recorder.consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(2365 reads.saturating_add(writes),2366 // TODO: measure proof2367 0,2368 )))2369}