difftreelog
refactor optimize properties iteration
in: master
4 files changed
pallets/common/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![cfg_attr(not(feature = "std"), no_std)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::account::CrossAccountId;25use frame_support::{26 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},27 ensure,28 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},29 BoundedVec,30 weights::Pays,31 transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35 COLLECTION_NUMBER_LIMIT,36 Collection,37 RpcCollection,38 CollectionId,39 CreateItemData,40 MAX_TOKEN_PREFIX_LENGTH,41 COLLECTION_ADMINS_LIMIT,42 TokenId,43 CollectionStats,44 MAX_TOKEN_OWNERSHIP,45 CollectionMode,46 NFT_SPONSOR_TRANSFER_TIMEOUT,47 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,48 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 MAX_SPONSOR_TIMEOUT,50 CUSTOM_DATA_LIMIT,51 CollectionLimits,52 CreateCollectionData,53 SponsorshipState,54 CreateItemExData,55 SponsoringRateLimit,56 budget::Budget,57 COLLECTION_FIELD_LIMIT,58 CollectionField,59 PhantomType,60 Property,61 Properties,62 PropertiesPermissionMap,63 PropertyKey,64 PropertyValue,65 PropertyPermission,66 PropertiesError,67 PropertyKeyPermission,68 TokenData,69 TrySetProperty,70 PropertyScope,71 // RMRK72 RmrkCollectionInfo,73 RmrkInstanceInfo,74 RmrkResourceInfo,75 RmrkPropertyInfo,76 RmrkBaseInfo,77 RmrkPartType,78 RmrkTheme,79 RmrkNftChild,80};8182pub use pallet::*;83use sp_core::H160;84use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};85#[cfg(feature = "runtime-benchmarks")]86pub mod benchmarking;87pub mod dispatch;88pub mod erc;89pub mod eth;90pub mod weights;9192pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9394#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]95pub struct CollectionHandle<T: Config> {96 pub id: CollectionId,97 collection: Collection<T::AccountId>,98 pub recorder: SubstrateRecorder<T>,99}100impl<T: Config> WithRecorder<T> for CollectionHandle<T> {101 fn recorder(&self) -> &SubstrateRecorder<T> {102 &self.recorder103 }104 fn into_recorder(self) -> SubstrateRecorder<T> {105 self.recorder106 }107}108impl<T: Config> CollectionHandle<T> {109 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {110 <CollectionById<T>>::get(id).map(|collection| Self {111 id,112 collection,113 recorder: SubstrateRecorder::new(gas_limit),114 })115 }116 pub fn new(id: CollectionId) -> Option<Self> {117 Self::new_with_gas_limit(id, u64::MAX)118 }119 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {120 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)121 }122 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {123 self.recorder124 .consume_gas(T::GasWeightMapping::weight_to_gas(125 <T as frame_system::Config>::DbWeight::get()126 .read127 .saturating_mul(reads),128 ))129 }130 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {131 self.recorder132 .consume_gas(T::GasWeightMapping::weight_to_gas(133 <T as frame_system::Config>::DbWeight::get()134 .write135 .saturating_mul(writes),136 ))137 }138 pub fn save(self) -> DispatchResult {139 <CollectionById<T>>::insert(self.id, self.collection);140 Ok(())141 }142}143impl<T: Config> Deref for CollectionHandle<T> {144 type Target = Collection<T::AccountId>;145146 fn deref(&self) -> &Self::Target {147 &self.collection148 }149}150151impl<T: Config> DerefMut for CollectionHandle<T> {152 fn deref_mut(&mut self) -> &mut Self::Target {153 &mut self.collection154 }155}156157impl<T: Config> CollectionHandle<T> {158 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {159 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);160 Ok(())161 }162 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {163 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))164 }165 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {166 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);167 Ok(())168 }169 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {170 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)171 }172 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {173 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)174 }175 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {176 ensure!(177 <Allowlist<T>>::get((self.id, user)),178 <Error<T>>::AddressNotInAllowlist179 );180 Ok(())181 }182}183184#[frame_support::pallet]185pub mod pallet {186 use super::*;187 use pallet_evm::account;188 use dispatch::CollectionDispatch;189 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};190 use frame_system::pallet_prelude::*;191 use frame_support::traits::Currency;192 use up_data_structs::{TokenId, mapping::TokenAddressMapping};193 use scale_info::TypeInfo;194 use weights::WeightInfo;195196 #[pallet::config]197 pub trait Config:198 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config199 {200 type WeightInfo: WeightInfo;201 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;202203 type Currency: Currency<Self::AccountId>;204205 #[pallet::constant]206 type CollectionCreationPrice: Get<207 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,208 >;209 type CollectionDispatch: CollectionDispatch<Self>;210211 type TreasuryAccountId: Get<Self::AccountId>;212213 type EvmTokenAddressMapping: TokenAddressMapping<H160>;214 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;215 }216217 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);218219 #[pallet::pallet]220 #[pallet::storage_version(STORAGE_VERSION)]221 #[pallet::generate_store(pub(super) trait Store)]222 pub struct Pallet<T>(_);223224 #[pallet::extra_constants]225 impl<T: Config> Pallet<T> {226 pub fn collection_admins_limit() -> u32 {227 COLLECTION_ADMINS_LIMIT228 }229 }230231 #[pallet::event]232 #[pallet::generate_deposit(pub fn deposit_event)]233 pub enum Event<T: Config> {234 /// New collection was created235 ///236 /// # Arguments237 ///238 /// * collection_id: Globally unique identifier of newly created collection.239 ///240 /// * mode: [CollectionMode] converted into u8.241 ///242 /// * account_id: Collection owner.243 CollectionCreated(CollectionId, u8, T::AccountId),244245 /// New collection was destroyed246 ///247 /// # Arguments248 ///249 /// * collection_id: Globally unique identifier of collection.250 CollectionDestroyed(CollectionId),251252 /// New item was created.253 ///254 /// # Arguments255 ///256 /// * collection_id: Id of the collection where item was created.257 ///258 /// * item_id: Id of an item. Unique within the collection.259 ///260 /// * recipient: Owner of newly created item261 ///262 /// * amount: Always 1 for NFT263 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),264265 /// Collection item was burned.266 ///267 /// # Arguments268 ///269 /// * collection_id.270 ///271 /// * item_id: Identifier of burned NFT.272 ///273 /// * owner: which user has destroyed its tokens274 ///275 /// * amount: Always 1 for NFT276 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),277278 /// Item was transferred279 ///280 /// * collection_id: Id of collection to which item is belong281 ///282 /// * item_id: Id of an item283 ///284 /// * sender: Original owner of item285 ///286 /// * recipient: New owner of item287 ///288 /// * amount: Always 1 for NFT289 Transfer(290 CollectionId,291 TokenId,292 T::CrossAccountId,293 T::CrossAccountId,294 u128,295 ),296297 /// * collection_id298 ///299 /// * item_id300 ///301 /// * sender302 ///303 /// * spender304 ///305 /// * amount306 Approved(307 CollectionId,308 TokenId,309 T::CrossAccountId,310 T::CrossAccountId,311 u128,312 ),313314 CollectionPropertySet(CollectionId, PropertyKey),315316 CollectionPropertyDeleted(CollectionId, PropertyKey),317318 TokenPropertySet(CollectionId, TokenId, PropertyKey),319320 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),321322 PropertyPermissionSet(CollectionId, PropertyKey),323 }324325 #[pallet::error]326 pub enum Error<T> {327 /// This collection does not exist.328 CollectionNotFound,329 /// Sender parameter and item owner must be equal.330 MustBeTokenOwner,331 /// No permission to perform action332 NoPermission,333 /// Collection is not in mint mode.334 PublicMintingNotAllowed,335 /// Address is not in allow list.336 AddressNotInAllowlist,337338 /// Collection name can not be longer than 63 char.339 CollectionNameLimitExceeded,340 /// Collection description can not be longer than 255 char.341 CollectionDescriptionLimitExceeded,342 /// Token prefix can not be longer than 15 char.343 CollectionTokenPrefixLimitExceeded,344 /// Total collections bound exceeded.345 TotalCollectionsLimitExceeded,346 /// Exceeded max admin count347 CollectionAdminCountExceeded,348 /// Collection limit bounds per collection exceeded349 CollectionLimitBoundsExceeded,350 /// Tried to enable permissions which are only permitted to be disabled351 OwnerPermissionsCantBeReverted,352 /// Collection settings not allowing items transferring353 TransferNotAllowed,354 /// Account token limit exceeded per collection355 AccountTokenLimitExceeded,356 /// Collection token limit exceeded357 CollectionTokenLimitExceeded,358 /// Metadata flag frozen359 MetadataFlagFrozen,360361 /// Item not exists.362 TokenNotFound,363 /// Item balance not enough.364 TokenValueTooLow,365 /// Requested value more than approved.366 ApprovedValueTooLow,367 /// Tried to approve more than owned368 CantApproveMoreThanOwned,369370 /// Can't transfer tokens to ethereum zero address371 AddressIsZero,372 /// Target collection doesn't supports this operation373 UnsupportedOperation,374375 /// Not sufficient founds to perform action376 NotSufficientFounds,377378 /// Collection has nesting disabled379 NestingIsDisabled,380 /// Only owner may nest tokens under this collection381 OnlyOwnerAllowedToNest,382 /// Only tokens from specific collections may nest tokens under this383 SourceCollectionIsNotAllowedToNest,384385 /// Tried to store more data than allowed in collection field386 CollectionFieldSizeExceeded,387388 /// Tried to store more property data than allowed389 NoSpaceForProperty,390391 /// Tried to store more property keys than allowed392 PropertyLimitReached,393394 /// Property key is too long395 PropertyKeyIsTooLong,396397 /// Only ASCII letters, digits, and '_', '-' are allowed398 InvalidCharacterInPropertyKey,399400 /// Empty property keys are forbidden401 EmptyPropertyKey,402 }403404 #[pallet::storage]405 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;406 #[pallet::storage]407 pub type DestroyedCollectionCount<T> =408 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;409410 /// Collection info411 #[pallet::storage]412 pub type CollectionById<T> = StorageMap<413 Hasher = Blake2_128Concat,414 Key = CollectionId,415 Value = Collection<<T as frame_system::Config>::AccountId>,416 QueryKind = OptionQuery,417 >;418419 /// Collection properties420 #[pallet::storage]421 #[pallet::getter(fn collection_properties)]422 pub type CollectionProperties<T> = StorageMap<423 Hasher = Blake2_128Concat,424 Key = CollectionId,425 Value = Properties,426 QueryKind = ValueQuery,427 OnEmpty = up_data_structs::CollectionProperties,428 >;429430 #[pallet::storage]431 #[pallet::getter(fn property_permissions)]432 pub type CollectionPropertyPermissions<T> = StorageMap<433 Hasher = Blake2_128Concat,434 Key = CollectionId,435 Value = PropertiesPermissionMap,436 QueryKind = ValueQuery,437 >;438439 /// Large variable-size collection fields are extracted here440 #[pallet::storage]441 pub type CollectionData<T> = StorageNMap<442 Key = (443 Key<Twox64Concat, CollectionId>,444 Key<Twox64Concat, CollectionField>,445 ),446 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,447 QueryKind = ValueQuery,448 >;449450 #[pallet::storage]451 pub type AdminAmount<T> = StorageMap<452 Hasher = Blake2_128Concat,453 Key = CollectionId,454 Value = u32,455 QueryKind = ValueQuery,456 >;457458 /// List of collection admins459 #[pallet::storage]460 pub type IsAdmin<T: Config> = StorageNMap<461 Key = (462 Key<Blake2_128Concat, CollectionId>,463 Key<Blake2_128Concat, T::CrossAccountId>,464 ),465 Value = bool,466 QueryKind = ValueQuery,467 >;468469 /// Allowlisted collection users470 #[pallet::storage]471 pub type Allowlist<T: Config> = StorageNMap<472 Key = (473 Key<Blake2_128Concat, CollectionId>,474 Key<Blake2_128Concat, T::CrossAccountId>,475 ),476 Value = bool,477 QueryKind = ValueQuery,478 >;479480 /// Not used by code, exists only to provide some types to metadata481 #[pallet::storage]482 pub type DummyStorageValue<T: Config> = StorageValue<483 Value = (484 CollectionStats,485 CollectionId,486 TokenId,487 PhantomType<TokenData<T::CrossAccountId>>,488 PhantomType<RpcCollection<T::AccountId>>,489 // RMRK490 PhantomType<RmrkCollectionInfo<T::AccountId>>,491 PhantomType<RmrkInstanceInfo<T::AccountId>>,492 PhantomType<RmrkResourceInfo>,493 PhantomType<RmrkPropertyInfo>,494 PhantomType<RmrkBaseInfo<T::AccountId>>,495 PhantomType<RmrkPartType>,496 PhantomType<RmrkTheme>,497 PhantomType<RmrkNftChild>,498 ),499 QueryKind = OptionQuery,500 >;501502 #[pallet::hooks]503 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {504 fn on_runtime_upgrade() -> Weight {505 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {506 use up_data_structs::{CollectionVersion1, CollectionVersion2};507 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {508 Self::set_field_raw(509 id,510 CollectionField::OffchainSchema,511 v.offchain_schema.clone().into_inner(),512 )513 .expect("data has lower bounds than field");514 Self::set_field_raw(515 id,516 CollectionField::ConstOnChainSchema,517 v.const_on_chain_schema.clone().into_inner(),518 )519 .expect("data has lower bounds than field");520521 Some(CollectionVersion2::from(v))522 });523 }524525 0526 }527 }528}529530impl<T: Config> Pallet<T> {531 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens532 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {533 ensure!(534 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,535 <Error<T>>::AddressIsZero536 );537 Ok(())538 }539 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {540 <IsAdmin<T>>::iter_prefix((collection,))541 .map(|(a, _)| a)542 .collect()543 }544 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {545 <Allowlist<T>>::iter_prefix((collection,))546 .map(|(a, _)| a)547 .collect()548 }549 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {550 <Allowlist<T>>::get((collection, user))551 }552 pub fn collection_stats() -> CollectionStats {553 let created = <CreatedCollectionCount<T>>::get();554 let destroyed = <DestroyedCollectionCount<T>>::get();555 CollectionStats {556 created: created.0,557 destroyed: destroyed.0,558 alive: created.0 - destroyed.0,559 }560 }561562 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {563 let collection = <CollectionById<T>>::get(collection);564 if collection.is_none() {565 return None;566 }567568 let collection = collection.unwrap();569 let limits = collection.limits;570 let effective_limits = CollectionLimits {571 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),572 sponsored_data_size: Some(limits.sponsored_data_size()),573 sponsored_data_rate_limit: Some(574 limits575 .sponsored_data_rate_limit576 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),577 ),578 token_limit: Some(limits.token_limit()),579 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(580 match collection.mode {581 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,582 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,583 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,584 },585 )),586 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),587 owner_can_transfer: Some(limits.owner_can_transfer()),588 owner_can_destroy: Some(limits.owner_can_destroy()),589 transfers_enabled: Some(limits.transfers_enabled()),590 nesting_rule: Some(limits.nesting_rule().clone()),591 };592593 Some(effective_limits)594 }595596 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {597 let Collection {598 name,599 description,600 owner,601 mode,602 access,603 token_prefix,604 mint_mode,605 schema_version,606 sponsorship,607 limits,608 } = <CollectionById<T>>::get(collection)?;609610 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)611 .iter()612 .map(|(key, permission)| PropertyKeyPermission {613 key: key.clone(),614 permission: permission.clone(),615 })616 .collect();617618 let properties = <CollectionProperties<T>>::get(collection)619 .iter()620 .map(|(key, value)| Property {621 key: key.clone(),622 value: value.clone(),623 })624 .collect();625626 Some(RpcCollection {627 name: name.into_inner(),628 description: description.into_inner(),629 owner,630 mode,631 access,632 token_prefix: token_prefix.into_inner(),633 mint_mode,634 schema_version,635 sponsorship,636 limits,637 offchain_schema: <CollectionData<T>>::get((638 collection,639 CollectionField::OffchainSchema,640 ))641 .into_inner(),642 const_on_chain_schema: <CollectionData<T>>::get((643 collection,644 CollectionField::ConstOnChainSchema,645 ))646 .into_inner(),647 token_property_permissions,648 properties,649 })650 }651}652653impl<T: Config> Pallet<T> {654 pub fn init_collection(655 owner: T::AccountId,656 data: CreateCollectionData<T::AccountId>,657 ) -> Result<CollectionId, DispatchError> {658 {659 ensure!(660 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,661 Error::<T>::CollectionTokenPrefixLimitExceeded662 );663 }664665 let created_count = <CreatedCollectionCount<T>>::get()666 .0667 .checked_add(1)668 .ok_or(ArithmeticError::Overflow)?;669 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;670 let id = CollectionId(created_count);671672 // bound Total number of collections673 ensure!(674 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,675 <Error<T>>::TotalCollectionsLimitExceeded676 );677678 // =========679680 let collection = Collection {681 owner: owner.clone(),682 name: data.name,683 mode: data.mode.clone(),684 mint_mode: false,685 access: data.access.unwrap_or_default(),686 description: data.description,687 token_prefix: data.token_prefix,688 schema_version: data.schema_version.unwrap_or_default(),689 sponsorship: data690 .pending_sponsor691 .map(SponsorshipState::Unconfirmed)692 .unwrap_or_default(),693 limits: data694 .limits695 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))696 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,697 };698699 let mut collection_properties = up_data_structs::CollectionProperties::get();700 collection_properties701 .try_set_from_iter(data.properties.into_iter())702 .map_err(<Error<T>>::from)?;703704 CollectionProperties::<T>::insert(id, collection_properties);705706 let mut token_props_permissions = PropertiesPermissionMap::new();707 token_props_permissions708 .try_set_from_iter(data.token_property_permissions.into_iter())709 .map_err(<Error<T>>::from)?;710711 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);712713 // Take a (non-refundable) deposit of collection creation714 {715 let mut imbalance =716 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();717 imbalance.subsume(718 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(719 &T::TreasuryAccountId::get(),720 T::CollectionCreationPrice::get(),721 ),722 );723 <T as Config>::Currency::settle(724 &owner,725 imbalance,726 WithdrawReasons::TRANSFER,727 ExistenceRequirement::KeepAlive,728 )729 .map_err(|_| Error::<T>::NotSufficientFounds)?;730 }731732 <CreatedCollectionCount<T>>::put(created_count);733 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));734 <CollectionById<T>>::insert(id, collection);735 Self::set_field_raw(736 id,737 CollectionField::OffchainSchema,738 data.offchain_schema.into_inner(),739 )740 .expect("data has lower bounds than field");741 Self::set_field_raw(742 id,743 CollectionField::ConstOnChainSchema,744 data.const_on_chain_schema.into_inner(),745 )746 .expect("data has lower bounds than field");747 Ok(id)748 }749750 pub fn destroy_collection(751 collection: CollectionHandle<T>,752 sender: &T::CrossAccountId,753 ) -> DispatchResult {754 ensure!(755 collection.limits.owner_can_destroy(),756 <Error<T>>::NoPermission,757 );758 collection.check_is_owner(sender)?;759760 let destroyed_collections = <DestroyedCollectionCount<T>>::get()761 .0762 .checked_add(1)763 .ok_or(ArithmeticError::Overflow)?;764765 // =========766767 <DestroyedCollectionCount<T>>::put(destroyed_collections);768 <CollectionById<T>>::remove(collection.id);769 <CollectionData<T>>::remove_prefix((collection.id,), None);770 <AdminAmount<T>>::remove(collection.id);771 <IsAdmin<T>>::remove_prefix((collection.id,), None);772 <Allowlist<T>>::remove_prefix((collection.id,), None);773 <CollectionProperties<T>>::remove(collection.id);774775 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));776 Ok(())777 }778779 pub fn set_collection_property(780 collection: &CollectionHandle<T>,781 sender: &T::CrossAccountId,782 property: Property,783 ) -> DispatchResult {784 collection.check_is_owner_or_admin(sender)?;785786 CollectionProperties::<T>::try_mutate(collection.id, |properties| {787 let property = property.clone();788 properties.try_set(property.key, property.value)789 })790 .map_err(<Error<T>>::from)?;791792 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));793794 Ok(())795 }796797 pub fn set_scoped_collection_property(798 collection: &CollectionHandle<T>,799 scope: PropertyScope,800 property: Property,801 ) -> DispatchResult {802 CollectionProperties::<T>::try_mutate(collection.id, |properties| {803 properties.try_scoped_set(scope, property.key, property.value)804 })805 .map_err(<Error<T>>::from)?;806807 Ok(())808 }809810 #[transactional]811 pub fn set_scoped_collection_properties(812 collection: &CollectionHandle<T>,813 scope: PropertyScope,814 properties: impl Iterator<Item = Property>,815 ) -> DispatchResult {816 CollectionProperties::<T>::try_mutate(collection.id, |stored_properties| {817 stored_properties.try_scoped_set_from_iter(scope, properties)818 })819 .map_err(<Error<T>>::from)?;820821 Ok(())822 }823824 #[transactional]825 pub fn set_collection_properties(826 collection: &CollectionHandle<T>,827 sender: &T::CrossAccountId,828 properties: Vec<Property>,829 ) -> DispatchResult {830 for property in properties {831 Self::set_collection_property(collection, sender, property)?;832 }833834 Ok(())835 }836837 pub fn delete_collection_property(838 collection: &CollectionHandle<T>,839 sender: &T::CrossAccountId,840 property_key: PropertyKey,841 ) -> DispatchResult {842 collection.check_is_owner_or_admin(sender)?;843844 CollectionProperties::<T>::try_mutate(collection.id, |properties| {845 properties.remove(&property_key)846 })847 .map_err(<Error<T>>::from)?;848849 Self::deposit_event(Event::CollectionPropertyDeleted(850 collection.id,851 property_key,852 ));853854 Ok(())855 }856857 #[transactional]858 pub fn delete_collection_properties(859 collection: &CollectionHandle<T>,860 sender: &T::CrossAccountId,861 property_keys: Vec<PropertyKey>,862 ) -> DispatchResult {863 for key in property_keys {864 Self::delete_collection_property(collection, sender, key)?;865 }866867 Ok(())868 }869870 pub fn set_property_permission(871 collection: &CollectionHandle<T>,872 sender: &T::CrossAccountId,873 property_permission: PropertyKeyPermission,874 ) -> DispatchResult {875 collection.check_is_owner_or_admin(sender)?;876877 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);878 let current_permission = all_permissions.get(&property_permission.key);879 if matches![880 current_permission,881 Some(PropertyPermission { mutable: false, .. })882 ] {883 return Err(<Error<T>>::NoPermission.into());884 }885886 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {887 let property_permission = property_permission.clone();888 permissions.try_set(property_permission.key, property_permission.permission)889 })890 .map_err(<Error<T>>::from)?;891892 Self::deposit_event(Event::PropertyPermissionSet(893 collection.id,894 property_permission.key,895 ));896897 Ok(())898 }899900 #[transactional]901 pub fn set_property_permissions(902 collection: &CollectionHandle<T>,903 sender: &T::CrossAccountId,904 property_permissions: Vec<PropertyKeyPermission>,905 ) -> DispatchResult {906 for prop_pemission in property_permissions {907 Self::set_property_permission(collection, sender, prop_pemission)?;908 }909910 Ok(())911 }912913 pub fn get_collection_property(914 collection_id: CollectionId,915 key: &PropertyKey,916 ) -> Option<PropertyValue> {917 Self::collection_properties(collection_id).get(key).cloned()918 }919920 pub fn bytes_keys_to_property_keys(921 keys: Vec<Vec<u8>>,922 ) -> Result<Vec<PropertyKey>, DispatchError> {923 keys.into_iter()924 .map(|key| -> Result<PropertyKey, DispatchError> {925 key.try_into()926 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())927 })928 .collect::<Result<Vec<PropertyKey>, DispatchError>>()929 }930931 pub fn filter_collection_properties(932 collection_id: CollectionId,933 keys: Option<Vec<PropertyKey>>,934 ) -> Result<Vec<Property>, DispatchError> {935 let properties = Self::collection_properties(collection_id);936937 let properties = keys938 .map(|keys| {939 keys.into_iter()940 .filter_map(|key| {941 properties.get(&key).map(|value| Property {942 key,943 value: value.clone(),944 })945 })946 .collect()947 })948 .unwrap_or_else(|| {949 properties950 .iter()951 .map(|(key, value)| Property {952 key: key.clone(),953 value: value.clone(),954 })955 .collect()956 });957958 Ok(properties)959 }960961 pub fn filter_property_permissions(962 collection_id: CollectionId,963 keys: Option<Vec<PropertyKey>>,964 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {965 let permissions = Self::property_permissions(collection_id);966967 let key_permissions = keys968 .map(|keys| {969 keys.into_iter()970 .filter_map(|key| {971 permissions972 .get(&key)973 .map(|permission| PropertyKeyPermission {974 key,975 permission: permission.clone(),976 })977 })978 .collect()979 })980 .unwrap_or_else(|| {981 permissions982 .iter()983 .map(|(key, permission)| PropertyKeyPermission {984 key: key.clone(),985 permission: permission.clone(),986 })987 .collect()988 });989990 Ok(key_permissions)991 }992993 fn set_field_raw(994 collection_id: CollectionId,995 field: CollectionField,996 value: Vec<u8>,997 ) -> DispatchResult {998 if !value.is_empty() {999 <CollectionData<T>>::insert(1000 (collection_id, field),1001 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,1002 )1003 } else {1004 <CollectionData<T>>::remove((collection_id, field));1005 }1006 Ok(())1007 }10081009 pub fn set_field(1010 collection: &CollectionHandle<T>,1011 sender: &T::CrossAccountId,1012 field: CollectionField,1013 value: Vec<u8>,1014 ) -> DispatchResult {1015 collection.check_is_owner_or_admin(sender)?;10161017 // =========10181019 Self::set_field_raw(collection.id, field, value)1020 }10211022 pub fn toggle_allowlist(1023 collection: &CollectionHandle<T>,1024 sender: &T::CrossAccountId,1025 user: &T::CrossAccountId,1026 allowed: bool,1027 ) -> DispatchResult {1028 collection.check_is_owner_or_admin(sender)?;10291030 // =========10311032 if allowed {1033 <Allowlist<T>>::insert((collection.id, user), true);1034 } else {1035 <Allowlist<T>>::remove((collection.id, user));1036 }10371038 Ok(())1039 }10401041 pub fn toggle_admin(1042 collection: &CollectionHandle<T>,1043 sender: &T::CrossAccountId,1044 user: &T::CrossAccountId,1045 admin: bool,1046 ) -> DispatchResult {1047 collection.check_is_owner_or_admin(sender)?;10481049 let was_admin = <IsAdmin<T>>::get((collection.id, user));1050 if was_admin == admin {1051 return Ok(());1052 }1053 let amount = <AdminAmount<T>>::get(collection.id);10541055 if admin {1056 let amount = amount1057 .checked_add(1)1058 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1059 ensure!(1060 amount <= Self::collection_admins_limit(),1061 <Error<T>>::CollectionAdminCountExceeded,1062 );10631064 // =========10651066 <AdminAmount<T>>::insert(collection.id, amount);1067 <IsAdmin<T>>::insert((collection.id, user), true);1068 } else {1069 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1070 <IsAdmin<T>>::remove((collection.id, user));1071 }10721073 Ok(())1074 }10751076 pub fn clamp_limits(1077 mode: CollectionMode,1078 old_limit: &CollectionLimits,1079 mut new_limit: CollectionLimits,1080 ) -> Result<CollectionLimits, DispatchError> {1081 macro_rules! limit_default {1082 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1083 $(1084 if let Some($new) = $new.$field {1085 let $old = $old.$field($($arg)?);1086 let _ = $new;1087 let _ = $old;1088 $check1089 } else {1090 $new.$field = $old.$field1091 }1092 )*1093 }};1094 }10951096 limit_default!(old_limit, new_limit,1097 account_token_ownership_limit => ensure!(1098 new_limit <= MAX_TOKEN_OWNERSHIP,1099 <Error<T>>::CollectionLimitBoundsExceeded,1100 ),1101 sponsor_transfer_timeout(match mode {1102 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1103 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1104 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1105 }) => ensure!(1106 new_limit <= MAX_SPONSOR_TIMEOUT,1107 <Error<T>>::CollectionLimitBoundsExceeded,1108 ),1109 sponsored_data_size => ensure!(1110 new_limit <= CUSTOM_DATA_LIMIT,1111 <Error<T>>::CollectionLimitBoundsExceeded,1112 ),1113 token_limit => ensure!(1114 old_limit >= new_limit && new_limit > 0,1115 <Error<T>>::CollectionTokenLimitExceeded1116 ),1117 owner_can_transfer => ensure!(1118 old_limit || !new_limit,1119 <Error<T>>::OwnerPermissionsCantBeReverted,1120 ),1121 owner_can_destroy => ensure!(1122 old_limit || !new_limit,1123 <Error<T>>::OwnerPermissionsCantBeReverted,1124 ),1125 sponsored_data_rate_limit => {},1126 transfers_enabled => {},1127 );1128 Ok(new_limit)1129 }1130}11311132#[macro_export]1133macro_rules! unsupported {1134 () => {1135 Err(<Error<T>>::UnsupportedOperation.into())1136 };1137}11381139/// Worst cases1140pub trait CommonWeightInfo<CrossAccountId> {1141 fn create_item() -> Weight;1142 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1143 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1144 fn burn_item() -> Weight;1145 fn set_collection_properties(amount: u32) -> Weight;1146 fn delete_collection_properties(amount: u32) -> Weight;1147 fn set_token_properties(amount: u32) -> Weight;1148 fn delete_token_properties(amount: u32) -> Weight;1149 fn set_property_permissions(amount: u32) -> Weight;1150 fn transfer() -> Weight;1151 fn approve() -> Weight;1152 fn transfer_from() -> Weight;1153 fn burn_from() -> Weight;1154}11551156pub trait CommonCollectionOperations<T: Config> {1157 fn create_item(1158 &self,1159 sender: T::CrossAccountId,1160 to: T::CrossAccountId,1161 data: CreateItemData,1162 nesting_budget: &dyn Budget,1163 ) -> DispatchResultWithPostInfo;1164 fn create_multiple_items(1165 &self,1166 sender: T::CrossAccountId,1167 to: T::CrossAccountId,1168 data: Vec<CreateItemData>,1169 nesting_budget: &dyn Budget,1170 ) -> DispatchResultWithPostInfo;1171 fn create_multiple_items_ex(1172 &self,1173 sender: T::CrossAccountId,1174 data: CreateItemExData<T::CrossAccountId>,1175 nesting_budget: &dyn Budget,1176 ) -> DispatchResultWithPostInfo;1177 fn burn_item(1178 &self,1179 sender: T::CrossAccountId,1180 token: TokenId,1181 amount: u128,1182 ) -> DispatchResultWithPostInfo;1183 fn set_collection_properties(1184 &self,1185 sender: T::CrossAccountId,1186 properties: Vec<Property>,1187 ) -> DispatchResultWithPostInfo;1188 fn delete_collection_properties(1189 &self,1190 sender: &T::CrossAccountId,1191 property_keys: Vec<PropertyKey>,1192 ) -> DispatchResultWithPostInfo;1193 fn set_token_properties(1194 &self,1195 sender: T::CrossAccountId,1196 token_id: TokenId,1197 property: Vec<Property>,1198 ) -> DispatchResultWithPostInfo;1199 fn delete_token_properties(1200 &self,1201 sender: T::CrossAccountId,1202 token_id: TokenId,1203 property_keys: Vec<PropertyKey>,1204 ) -> DispatchResultWithPostInfo;1205 fn set_property_permissions(1206 &self,1207 sender: &T::CrossAccountId,1208 property_permissions: Vec<PropertyKeyPermission>,1209 ) -> DispatchResultWithPostInfo;1210 fn transfer(1211 &self,1212 sender: T::CrossAccountId,1213 to: T::CrossAccountId,1214 token: TokenId,1215 amount: u128,1216 nesting_budget: &dyn Budget,1217 ) -> DispatchResultWithPostInfo;1218 fn approve(1219 &self,1220 sender: T::CrossAccountId,1221 spender: T::CrossAccountId,1222 token: TokenId,1223 amount: u128,1224 ) -> DispatchResultWithPostInfo;1225 fn transfer_from(1226 &self,1227 sender: T::CrossAccountId,1228 from: T::CrossAccountId,1229 to: T::CrossAccountId,1230 token: TokenId,1231 amount: u128,1232 nesting_budget: &dyn Budget,1233 ) -> DispatchResultWithPostInfo;1234 fn burn_from(1235 &self,1236 sender: T::CrossAccountId,1237 from: T::CrossAccountId,1238 token: TokenId,1239 amount: u128,1240 nesting_budget: &dyn Budget,1241 ) -> DispatchResultWithPostInfo;12421243 fn check_nesting(1244 &self,1245 sender: T::CrossAccountId,1246 from: (CollectionId, TokenId),1247 under: TokenId,1248 budget: &dyn Budget,1249 ) -> DispatchResult;12501251 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1252 fn collection_tokens(&self) -> Vec<TokenId>;1253 fn token_exists(&self, token: TokenId) -> bool;1254 fn last_token_id(&self) -> TokenId;12551256 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1257 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1258 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1259 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1260 /// Amount of unique collection tokens1261 fn total_supply(&self) -> u32;1262 /// Amount of different tokens account has (Applicable to nonfungible/refungible)1263 fn account_balance(&self, account: T::CrossAccountId) -> u32;1264 /// Amount of specific token account have (Applicable to fungible/refungible)1265 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1266 fn allowance(1267 &self,1268 sender: T::CrossAccountId,1269 spender: T::CrossAccountId,1270 token: TokenId,1271 ) -> u128;1272}12731274// Flexible enough for implementing CommonCollectionOperations1275pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1276 let post_info = PostDispatchInfo {1277 actual_weight: Some(weight),1278 pays_fee: Pays::Yes,1279 };1280 match res {1281 Ok(()) => Ok(post_info),1282 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1283 }1284}12851286impl<T: Config> From<PropertiesError> for Error<T> {1287 fn from(error: PropertiesError) -> Self {1288 match error {1289 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1290 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1291 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1292 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1293 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1294 }1295 }1296}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#![cfg_attr(not(feature = "std"), no_std)]1819extern crate alloc;2021use core::ops::{Deref, DerefMut};22use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};23use sp_std::vec::Vec;24use pallet_evm::account::CrossAccountId;25use frame_support::{26 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},27 ensure,28 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},29 BoundedVec,30 weights::Pays,31 transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35 COLLECTION_NUMBER_LIMIT,36 Collection,37 RpcCollection,38 CollectionId,39 CreateItemData,40 MAX_TOKEN_PREFIX_LENGTH,41 COLLECTION_ADMINS_LIMIT,42 TokenId,43 CollectionStats,44 MAX_TOKEN_OWNERSHIP,45 CollectionMode,46 NFT_SPONSOR_TRANSFER_TIMEOUT,47 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,48 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 MAX_SPONSOR_TIMEOUT,50 CUSTOM_DATA_LIMIT,51 CollectionLimits,52 CreateCollectionData,53 SponsorshipState,54 CreateItemExData,55 SponsoringRateLimit,56 budget::Budget,57 COLLECTION_FIELD_LIMIT,58 CollectionField,59 PhantomType,60 Property,61 Properties,62 PropertiesPermissionMap,63 PropertyKey,64 PropertyValue,65 PropertyPermission,66 PropertiesError,67 PropertyKeyPermission,68 TokenData,69 TrySetProperty,70 PropertyScope,71 // RMRK72 RmrkCollectionInfo,73 RmrkInstanceInfo,74 RmrkResourceInfo,75 RmrkPropertyInfo,76 RmrkBaseInfo,77 RmrkPartType,78 RmrkTheme,79 RmrkNftChild,80};8182pub use pallet::*;83use sp_core::H160;84use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};85#[cfg(feature = "runtime-benchmarks")]86pub mod benchmarking;87pub mod dispatch;88pub mod erc;89pub mod eth;90pub mod weights;9192pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9394#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]95pub struct CollectionHandle<T: Config> {96 pub id: CollectionId,97 collection: Collection<T::AccountId>,98 pub recorder: SubstrateRecorder<T>,99}100impl<T: Config> WithRecorder<T> for CollectionHandle<T> {101 fn recorder(&self) -> &SubstrateRecorder<T> {102 &self.recorder103 }104 fn into_recorder(self) -> SubstrateRecorder<T> {105 self.recorder106 }107}108impl<T: Config> CollectionHandle<T> {109 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {110 <CollectionById<T>>::get(id).map(|collection| Self {111 id,112 collection,113 recorder: SubstrateRecorder::new(gas_limit),114 })115 }116 pub fn new(id: CollectionId) -> Option<Self> {117 Self::new_with_gas_limit(id, u64::MAX)118 }119 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {120 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)121 }122 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {123 self.recorder124 .consume_gas(T::GasWeightMapping::weight_to_gas(125 <T as frame_system::Config>::DbWeight::get()126 .read127 .saturating_mul(reads),128 ))129 }130 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {131 self.recorder132 .consume_gas(T::GasWeightMapping::weight_to_gas(133 <T as frame_system::Config>::DbWeight::get()134 .write135 .saturating_mul(writes),136 ))137 }138 pub fn save(self) -> DispatchResult {139 <CollectionById<T>>::insert(self.id, self.collection);140 Ok(())141 }142}143impl<T: Config> Deref for CollectionHandle<T> {144 type Target = Collection<T::AccountId>;145146 fn deref(&self) -> &Self::Target {147 &self.collection148 }149}150151impl<T: Config> DerefMut for CollectionHandle<T> {152 fn deref_mut(&mut self) -> &mut Self::Target {153 &mut self.collection154 }155}156157impl<T: Config> CollectionHandle<T> {158 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {159 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);160 Ok(())161 }162 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {163 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))164 }165 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {166 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);167 Ok(())168 }169 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {170 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)171 }172 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {173 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)174 }175 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {176 ensure!(177 <Allowlist<T>>::get((self.id, user)),178 <Error<T>>::AddressNotInAllowlist179 );180 Ok(())181 }182}183184#[frame_support::pallet]185pub mod pallet {186 use super::*;187 use pallet_evm::account;188 use dispatch::CollectionDispatch;189 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};190 use frame_system::pallet_prelude::*;191 use frame_support::traits::Currency;192 use up_data_structs::{TokenId, mapping::TokenAddressMapping};193 use scale_info::TypeInfo;194 use weights::WeightInfo;195196 #[pallet::config]197 pub trait Config:198 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config199 {200 type WeightInfo: WeightInfo;201 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;202203 type Currency: Currency<Self::AccountId>;204205 #[pallet::constant]206 type CollectionCreationPrice: Get<207 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,208 >;209 type CollectionDispatch: CollectionDispatch<Self>;210211 type TreasuryAccountId: Get<Self::AccountId>;212213 type EvmTokenAddressMapping: TokenAddressMapping<H160>;214 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;215 }216217 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);218219 #[pallet::pallet]220 #[pallet::storage_version(STORAGE_VERSION)]221 #[pallet::generate_store(pub(super) trait Store)]222 pub struct Pallet<T>(_);223224 #[pallet::extra_constants]225 impl<T: Config> Pallet<T> {226 pub fn collection_admins_limit() -> u32 {227 COLLECTION_ADMINS_LIMIT228 }229 }230231 #[pallet::event]232 #[pallet::generate_deposit(pub fn deposit_event)]233 pub enum Event<T: Config> {234 /// New collection was created235 ///236 /// # Arguments237 ///238 /// * collection_id: Globally unique identifier of newly created collection.239 ///240 /// * mode: [CollectionMode] converted into u8.241 ///242 /// * account_id: Collection owner.243 CollectionCreated(CollectionId, u8, T::AccountId),244245 /// New collection was destroyed246 ///247 /// # Arguments248 ///249 /// * collection_id: Globally unique identifier of collection.250 CollectionDestroyed(CollectionId),251252 /// New item was created.253 ///254 /// # Arguments255 ///256 /// * collection_id: Id of the collection where item was created.257 ///258 /// * item_id: Id of an item. Unique within the collection.259 ///260 /// * recipient: Owner of newly created item261 ///262 /// * amount: Always 1 for NFT263 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),264265 /// Collection item was burned.266 ///267 /// # Arguments268 ///269 /// * collection_id.270 ///271 /// * item_id: Identifier of burned NFT.272 ///273 /// * owner: which user has destroyed its tokens274 ///275 /// * amount: Always 1 for NFT276 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),277278 /// Item was transferred279 ///280 /// * collection_id: Id of collection to which item is belong281 ///282 /// * item_id: Id of an item283 ///284 /// * sender: Original owner of item285 ///286 /// * recipient: New owner of item287 ///288 /// * amount: Always 1 for NFT289 Transfer(290 CollectionId,291 TokenId,292 T::CrossAccountId,293 T::CrossAccountId,294 u128,295 ),296297 /// * collection_id298 ///299 /// * item_id300 ///301 /// * sender302 ///303 /// * spender304 ///305 /// * amount306 Approved(307 CollectionId,308 TokenId,309 T::CrossAccountId,310 T::CrossAccountId,311 u128,312 ),313314 CollectionPropertySet(CollectionId, PropertyKey),315316 CollectionPropertyDeleted(CollectionId, PropertyKey),317318 TokenPropertySet(CollectionId, TokenId, PropertyKey),319320 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),321322 PropertyPermissionSet(CollectionId, PropertyKey),323 }324325 #[pallet::error]326 pub enum Error<T> {327 /// This collection does not exist.328 CollectionNotFound,329 /// Sender parameter and item owner must be equal.330 MustBeTokenOwner,331 /// No permission to perform action332 NoPermission,333 /// Collection is not in mint mode.334 PublicMintingNotAllowed,335 /// Address is not in allow list.336 AddressNotInAllowlist,337338 /// Collection name can not be longer than 63 char.339 CollectionNameLimitExceeded,340 /// Collection description can not be longer than 255 char.341 CollectionDescriptionLimitExceeded,342 /// Token prefix can not be longer than 15 char.343 CollectionTokenPrefixLimitExceeded,344 /// Total collections bound exceeded.345 TotalCollectionsLimitExceeded,346 /// Exceeded max admin count347 CollectionAdminCountExceeded,348 /// Collection limit bounds per collection exceeded349 CollectionLimitBoundsExceeded,350 /// Tried to enable permissions which are only permitted to be disabled351 OwnerPermissionsCantBeReverted,352 /// Collection settings not allowing items transferring353 TransferNotAllowed,354 /// Account token limit exceeded per collection355 AccountTokenLimitExceeded,356 /// Collection token limit exceeded357 CollectionTokenLimitExceeded,358 /// Metadata flag frozen359 MetadataFlagFrozen,360361 /// Item not exists.362 TokenNotFound,363 /// Item balance not enough.364 TokenValueTooLow,365 /// Requested value more than approved.366 ApprovedValueTooLow,367 /// Tried to approve more than owned368 CantApproveMoreThanOwned,369370 /// Can't transfer tokens to ethereum zero address371 AddressIsZero,372 /// Target collection doesn't supports this operation373 UnsupportedOperation,374375 /// Not sufficient founds to perform action376 NotSufficientFounds,377378 /// Collection has nesting disabled379 NestingIsDisabled,380 /// Only owner may nest tokens under this collection381 OnlyOwnerAllowedToNest,382 /// Only tokens from specific collections may nest tokens under this383 SourceCollectionIsNotAllowedToNest,384385 /// Tried to store more data than allowed in collection field386 CollectionFieldSizeExceeded,387388 /// Tried to store more property data than allowed389 NoSpaceForProperty,390391 /// Tried to store more property keys than allowed392 PropertyLimitReached,393394 /// Property key is too long395 PropertyKeyIsTooLong,396397 /// Only ASCII letters, digits, and '_', '-' are allowed398 InvalidCharacterInPropertyKey,399400 /// Empty property keys are forbidden401 EmptyPropertyKey,402 }403404 #[pallet::storage]405 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;406 #[pallet::storage]407 pub type DestroyedCollectionCount<T> =408 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;409410 /// Collection info411 #[pallet::storage]412 pub type CollectionById<T> = StorageMap<413 Hasher = Blake2_128Concat,414 Key = CollectionId,415 Value = Collection<<T as frame_system::Config>::AccountId>,416 QueryKind = OptionQuery,417 >;418419 /// Collection properties420 #[pallet::storage]421 #[pallet::getter(fn collection_properties)]422 pub type CollectionProperties<T> = StorageMap<423 Hasher = Blake2_128Concat,424 Key = CollectionId,425 Value = Properties,426 QueryKind = ValueQuery,427 OnEmpty = up_data_structs::CollectionProperties,428 >;429430 #[pallet::storage]431 #[pallet::getter(fn property_permissions)]432 pub type CollectionPropertyPermissions<T> = StorageMap<433 Hasher = Blake2_128Concat,434 Key = CollectionId,435 Value = PropertiesPermissionMap,436 QueryKind = ValueQuery,437 >;438439 /// Large variable-size collection fields are extracted here440 #[pallet::storage]441 pub type CollectionData<T> = StorageNMap<442 Key = (443 Key<Twox64Concat, CollectionId>,444 Key<Twox64Concat, CollectionField>,445 ),446 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,447 QueryKind = ValueQuery,448 >;449450 #[pallet::storage]451 pub type AdminAmount<T> = StorageMap<452 Hasher = Blake2_128Concat,453 Key = CollectionId,454 Value = u32,455 QueryKind = ValueQuery,456 >;457458 /// List of collection admins459 #[pallet::storage]460 pub type IsAdmin<T: Config> = StorageNMap<461 Key = (462 Key<Blake2_128Concat, CollectionId>,463 Key<Blake2_128Concat, T::CrossAccountId>,464 ),465 Value = bool,466 QueryKind = ValueQuery,467 >;468469 /// Allowlisted collection users470 #[pallet::storage]471 pub type Allowlist<T: Config> = StorageNMap<472 Key = (473 Key<Blake2_128Concat, CollectionId>,474 Key<Blake2_128Concat, T::CrossAccountId>,475 ),476 Value = bool,477 QueryKind = ValueQuery,478 >;479480 /// Not used by code, exists only to provide some types to metadata481 #[pallet::storage]482 pub type DummyStorageValue<T: Config> = StorageValue<483 Value = (484 CollectionStats,485 CollectionId,486 TokenId,487 PhantomType<TokenData<T::CrossAccountId>>,488 PhantomType<RpcCollection<T::AccountId>>,489 // RMRK490 PhantomType<RmrkCollectionInfo<T::AccountId>>,491 PhantomType<RmrkInstanceInfo<T::AccountId>>,492 PhantomType<RmrkResourceInfo>,493 PhantomType<RmrkPropertyInfo>,494 PhantomType<RmrkBaseInfo<T::AccountId>>,495 PhantomType<RmrkPartType>,496 PhantomType<RmrkTheme>,497 PhantomType<RmrkNftChild>,498 ),499 QueryKind = OptionQuery,500 >;501502 #[pallet::hooks]503 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {504 fn on_runtime_upgrade() -> Weight {505 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {506 use up_data_structs::{CollectionVersion1, CollectionVersion2};507 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {508 Self::set_field_raw(509 id,510 CollectionField::OffchainSchema,511 v.offchain_schema.clone().into_inner(),512 )513 .expect("data has lower bounds than field");514 Self::set_field_raw(515 id,516 CollectionField::ConstOnChainSchema,517 v.const_on_chain_schema.clone().into_inner(),518 )519 .expect("data has lower bounds than field");520521 Some(CollectionVersion2::from(v))522 });523 }524525 0526 }527 }528}529530impl<T: Config> Pallet<T> {531 /// Ethereum receiver 0x0000000000000000000000000000000000000000 is reserved, and shouldn't own tokens532 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {533 ensure!(534 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,535 <Error<T>>::AddressIsZero536 );537 Ok(())538 }539 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {540 <IsAdmin<T>>::iter_prefix((collection,))541 .map(|(a, _)| a)542 .collect()543 }544 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {545 <Allowlist<T>>::iter_prefix((collection,))546 .map(|(a, _)| a)547 .collect()548 }549 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {550 <Allowlist<T>>::get((collection, user))551 }552 pub fn collection_stats() -> CollectionStats {553 let created = <CreatedCollectionCount<T>>::get();554 let destroyed = <DestroyedCollectionCount<T>>::get();555 CollectionStats {556 created: created.0,557 destroyed: destroyed.0,558 alive: created.0 - destroyed.0,559 }560 }561562 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {563 let collection = <CollectionById<T>>::get(collection);564 if collection.is_none() {565 return None;566 }567568 let collection = collection.unwrap();569 let limits = collection.limits;570 let effective_limits = CollectionLimits {571 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),572 sponsored_data_size: Some(limits.sponsored_data_size()),573 sponsored_data_rate_limit: Some(574 limits575 .sponsored_data_rate_limit576 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),577 ),578 token_limit: Some(limits.token_limit()),579 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(580 match collection.mode {581 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,582 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,583 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,584 },585 )),586 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),587 owner_can_transfer: Some(limits.owner_can_transfer()),588 owner_can_destroy: Some(limits.owner_can_destroy()),589 transfers_enabled: Some(limits.transfers_enabled()),590 nesting_rule: Some(limits.nesting_rule().clone()),591 };592593 Some(effective_limits)594 }595596 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {597 let Collection {598 name,599 description,600 owner,601 mode,602 access,603 token_prefix,604 mint_mode,605 schema_version,606 sponsorship,607 limits,608 } = <CollectionById<T>>::get(collection)?;609610 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)611 .into_iter()612 .map(|(key, permission)| PropertyKeyPermission {613 key,614 permission,615 })616 .collect();617618 let properties = <CollectionProperties<T>>::get(collection)619 .into_iter()620 .map(|(key, value)| Property {621 key,622 value,623 })624 .collect();625626 Some(RpcCollection {627 name: name.into_inner(),628 description: description.into_inner(),629 owner,630 mode,631 access,632 token_prefix: token_prefix.into_inner(),633 mint_mode,634 schema_version,635 sponsorship,636 limits,637 offchain_schema: <CollectionData<T>>::get((638 collection,639 CollectionField::OffchainSchema,640 ))641 .into_inner(),642 const_on_chain_schema: <CollectionData<T>>::get((643 collection,644 CollectionField::ConstOnChainSchema,645 ))646 .into_inner(),647 token_property_permissions,648 properties,649 })650 }651}652653impl<T: Config> Pallet<T> {654 pub fn init_collection(655 owner: T::AccountId,656 data: CreateCollectionData<T::AccountId>,657 ) -> Result<CollectionId, DispatchError> {658 {659 ensure!(660 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,661 Error::<T>::CollectionTokenPrefixLimitExceeded662 );663 }664665 let created_count = <CreatedCollectionCount<T>>::get()666 .0667 .checked_add(1)668 .ok_or(ArithmeticError::Overflow)?;669 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;670 let id = CollectionId(created_count);671672 // bound Total number of collections673 ensure!(674 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,675 <Error<T>>::TotalCollectionsLimitExceeded676 );677678 // =========679680 let collection = Collection {681 owner: owner.clone(),682 name: data.name,683 mode: data.mode.clone(),684 mint_mode: false,685 access: data.access.unwrap_or_default(),686 description: data.description,687 token_prefix: data.token_prefix,688 schema_version: data.schema_version.unwrap_or_default(),689 sponsorship: data690 .pending_sponsor691 .map(SponsorshipState::Unconfirmed)692 .unwrap_or_default(),693 limits: data694 .limits695 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))696 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,697 };698699 let mut collection_properties = up_data_structs::CollectionProperties::get();700 collection_properties701 .try_set_from_iter(data.properties.into_iter())702 .map_err(<Error<T>>::from)?;703704 CollectionProperties::<T>::insert(id, collection_properties);705706 let mut token_props_permissions = PropertiesPermissionMap::new();707 token_props_permissions708 .try_set_from_iter(data.token_property_permissions.into_iter())709 .map_err(<Error<T>>::from)?;710711 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);712713 // Take a (non-refundable) deposit of collection creation714 {715 let mut imbalance =716 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();717 imbalance.subsume(718 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(719 &T::TreasuryAccountId::get(),720 T::CollectionCreationPrice::get(),721 ),722 );723 <T as Config>::Currency::settle(724 &owner,725 imbalance,726 WithdrawReasons::TRANSFER,727 ExistenceRequirement::KeepAlive,728 )729 .map_err(|_| Error::<T>::NotSufficientFounds)?;730 }731732 <CreatedCollectionCount<T>>::put(created_count);733 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));734 <CollectionById<T>>::insert(id, collection);735 Self::set_field_raw(736 id,737 CollectionField::OffchainSchema,738 data.offchain_schema.into_inner(),739 )740 .expect("data has lower bounds than field");741 Self::set_field_raw(742 id,743 CollectionField::ConstOnChainSchema,744 data.const_on_chain_schema.into_inner(),745 )746 .expect("data has lower bounds than field");747 Ok(id)748 }749750 pub fn destroy_collection(751 collection: CollectionHandle<T>,752 sender: &T::CrossAccountId,753 ) -> DispatchResult {754 ensure!(755 collection.limits.owner_can_destroy(),756 <Error<T>>::NoPermission,757 );758 collection.check_is_owner(sender)?;759760 let destroyed_collections = <DestroyedCollectionCount<T>>::get()761 .0762 .checked_add(1)763 .ok_or(ArithmeticError::Overflow)?;764765 // =========766767 <DestroyedCollectionCount<T>>::put(destroyed_collections);768 <CollectionById<T>>::remove(collection.id);769 <CollectionData<T>>::remove_prefix((collection.id,), None);770 <AdminAmount<T>>::remove(collection.id);771 <IsAdmin<T>>::remove_prefix((collection.id,), None);772 <Allowlist<T>>::remove_prefix((collection.id,), None);773 <CollectionProperties<T>>::remove(collection.id);774775 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));776 Ok(())777 }778779 pub fn set_collection_property(780 collection: &CollectionHandle<T>,781 sender: &T::CrossAccountId,782 property: Property,783 ) -> DispatchResult {784 collection.check_is_owner_or_admin(sender)?;785786 CollectionProperties::<T>::try_mutate(collection.id, |properties| {787 let property = property.clone();788 properties.try_set(property.key, property.value)789 })790 .map_err(<Error<T>>::from)?;791792 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));793794 Ok(())795 }796797 pub fn set_scoped_collection_property(798 collection: &CollectionHandle<T>,799 scope: PropertyScope,800 property: Property,801 ) -> DispatchResult {802 CollectionProperties::<T>::try_mutate(collection.id, |properties| {803 properties.try_scoped_set(scope, property.key, property.value)804 })805 .map_err(<Error<T>>::from)?;806807 Ok(())808 }809810 #[transactional]811 pub fn set_scoped_collection_properties(812 collection: &CollectionHandle<T>,813 scope: PropertyScope,814 properties: impl Iterator<Item = Property>,815 ) -> DispatchResult {816 CollectionProperties::<T>::try_mutate(collection.id, |stored_properties| {817 stored_properties.try_scoped_set_from_iter(scope, properties)818 })819 .map_err(<Error<T>>::from)?;820821 Ok(())822 }823824 #[transactional]825 pub fn set_collection_properties(826 collection: &CollectionHandle<T>,827 sender: &T::CrossAccountId,828 properties: Vec<Property>,829 ) -> DispatchResult {830 for property in properties {831 Self::set_collection_property(collection, sender, property)?;832 }833834 Ok(())835 }836837 pub fn delete_collection_property(838 collection: &CollectionHandle<T>,839 sender: &T::CrossAccountId,840 property_key: PropertyKey,841 ) -> DispatchResult {842 collection.check_is_owner_or_admin(sender)?;843844 CollectionProperties::<T>::try_mutate(collection.id, |properties| {845 properties.remove(&property_key)846 })847 .map_err(<Error<T>>::from)?;848849 Self::deposit_event(Event::CollectionPropertyDeleted(850 collection.id,851 property_key,852 ));853854 Ok(())855 }856857 #[transactional]858 pub fn delete_collection_properties(859 collection: &CollectionHandle<T>,860 sender: &T::CrossAccountId,861 property_keys: Vec<PropertyKey>,862 ) -> DispatchResult {863 for key in property_keys {864 Self::delete_collection_property(collection, sender, key)?;865 }866867 Ok(())868 }869870 pub fn set_property_permission(871 collection: &CollectionHandle<T>,872 sender: &T::CrossAccountId,873 property_permission: PropertyKeyPermission,874 ) -> DispatchResult {875 collection.check_is_owner_or_admin(sender)?;876877 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);878 let current_permission = all_permissions.get(&property_permission.key);879 if matches![880 current_permission,881 Some(PropertyPermission { mutable: false, .. })882 ] {883 return Err(<Error<T>>::NoPermission.into());884 }885886 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {887 let property_permission = property_permission.clone();888 permissions.try_set(property_permission.key, property_permission.permission)889 })890 .map_err(<Error<T>>::from)?;891892 Self::deposit_event(Event::PropertyPermissionSet(893 collection.id,894 property_permission.key,895 ));896897 Ok(())898 }899900 #[transactional]901 pub fn set_property_permissions(902 collection: &CollectionHandle<T>,903 sender: &T::CrossAccountId,904 property_permissions: Vec<PropertyKeyPermission>,905 ) -> DispatchResult {906 for prop_pemission in property_permissions {907 Self::set_property_permission(collection, sender, prop_pemission)?;908 }909910 Ok(())911 }912913 pub fn get_collection_property(914 collection_id: CollectionId,915 key: &PropertyKey,916 ) -> Option<PropertyValue> {917 Self::collection_properties(collection_id).get(key).cloned()918 }919920 pub fn bytes_keys_to_property_keys(921 keys: Vec<Vec<u8>>,922 ) -> Result<Vec<PropertyKey>, DispatchError> {923 keys.into_iter()924 .map(|key| -> Result<PropertyKey, DispatchError> {925 key.try_into()926 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())927 })928 .collect::<Result<Vec<PropertyKey>, DispatchError>>()929 }930931 pub fn filter_collection_properties(932 collection_id: CollectionId,933 keys: Option<Vec<PropertyKey>>,934 ) -> Result<Vec<Property>, DispatchError> {935 let properties = Self::collection_properties(collection_id);936937 let properties = keys938 .map(|keys| {939 keys.into_iter()940 .filter_map(|key| {941 properties.get(&key).map(|value| Property {942 key,943 value: value.clone(),944 })945 })946 .collect()947 })948 .unwrap_or_else(|| {949 properties950 .into_iter()951 .map(|(key, value)| Property {952 key,953 value,954 })955 .collect()956 });957958 Ok(properties)959 }960961 pub fn filter_property_permissions(962 collection_id: CollectionId,963 keys: Option<Vec<PropertyKey>>,964 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {965 let permissions = Self::property_permissions(collection_id);966967 let key_permissions = keys968 .map(|keys| {969 keys.into_iter()970 .filter_map(|key| {971 permissions972 .get(&key)973 .map(|permission| PropertyKeyPermission {974 key,975 permission: permission.clone(),976 })977 })978 .collect()979 })980 .unwrap_or_else(|| {981 permissions982 .into_iter()983 .map(|(key, permission)| PropertyKeyPermission {984 key,985 permission,986 })987 .collect()988 });989990 Ok(key_permissions)991 }992993 fn set_field_raw(994 collection_id: CollectionId,995 field: CollectionField,996 value: Vec<u8>,997 ) -> DispatchResult {998 if !value.is_empty() {999 <CollectionData<T>>::insert(1000 (collection_id, field),1001 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,1002 )1003 } else {1004 <CollectionData<T>>::remove((collection_id, field));1005 }1006 Ok(())1007 }10081009 pub fn set_field(1010 collection: &CollectionHandle<T>,1011 sender: &T::CrossAccountId,1012 field: CollectionField,1013 value: Vec<u8>,1014 ) -> DispatchResult {1015 collection.check_is_owner_or_admin(sender)?;10161017 // =========10181019 Self::set_field_raw(collection.id, field, value)1020 }10211022 pub fn toggle_allowlist(1023 collection: &CollectionHandle<T>,1024 sender: &T::CrossAccountId,1025 user: &T::CrossAccountId,1026 allowed: bool,1027 ) -> DispatchResult {1028 collection.check_is_owner_or_admin(sender)?;10291030 // =========10311032 if allowed {1033 <Allowlist<T>>::insert((collection.id, user), true);1034 } else {1035 <Allowlist<T>>::remove((collection.id, user));1036 }10371038 Ok(())1039 }10401041 pub fn toggle_admin(1042 collection: &CollectionHandle<T>,1043 sender: &T::CrossAccountId,1044 user: &T::CrossAccountId,1045 admin: bool,1046 ) -> DispatchResult {1047 collection.check_is_owner_or_admin(sender)?;10481049 let was_admin = <IsAdmin<T>>::get((collection.id, user));1050 if was_admin == admin {1051 return Ok(());1052 }1053 let amount = <AdminAmount<T>>::get(collection.id);10541055 if admin {1056 let amount = amount1057 .checked_add(1)1058 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1059 ensure!(1060 amount <= Self::collection_admins_limit(),1061 <Error<T>>::CollectionAdminCountExceeded,1062 );10631064 // =========10651066 <AdminAmount<T>>::insert(collection.id, amount);1067 <IsAdmin<T>>::insert((collection.id, user), true);1068 } else {1069 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1070 <IsAdmin<T>>::remove((collection.id, user));1071 }10721073 Ok(())1074 }10751076 pub fn clamp_limits(1077 mode: CollectionMode,1078 old_limit: &CollectionLimits,1079 mut new_limit: CollectionLimits,1080 ) -> Result<CollectionLimits, DispatchError> {1081 macro_rules! limit_default {1082 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1083 $(1084 if let Some($new) = $new.$field {1085 let $old = $old.$field($($arg)?);1086 let _ = $new;1087 let _ = $old;1088 $check1089 } else {1090 $new.$field = $old.$field1091 }1092 )*1093 }};1094 }10951096 limit_default!(old_limit, new_limit,1097 account_token_ownership_limit => ensure!(1098 new_limit <= MAX_TOKEN_OWNERSHIP,1099 <Error<T>>::CollectionLimitBoundsExceeded,1100 ),1101 sponsor_transfer_timeout(match mode {1102 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1103 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1104 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1105 }) => ensure!(1106 new_limit <= MAX_SPONSOR_TIMEOUT,1107 <Error<T>>::CollectionLimitBoundsExceeded,1108 ),1109 sponsored_data_size => ensure!(1110 new_limit <= CUSTOM_DATA_LIMIT,1111 <Error<T>>::CollectionLimitBoundsExceeded,1112 ),1113 token_limit => ensure!(1114 old_limit >= new_limit && new_limit > 0,1115 <Error<T>>::CollectionTokenLimitExceeded1116 ),1117 owner_can_transfer => ensure!(1118 old_limit || !new_limit,1119 <Error<T>>::OwnerPermissionsCantBeReverted,1120 ),1121 owner_can_destroy => ensure!(1122 old_limit || !new_limit,1123 <Error<T>>::OwnerPermissionsCantBeReverted,1124 ),1125 sponsored_data_rate_limit => {},1126 transfers_enabled => {},1127 );1128 Ok(new_limit)1129 }1130}11311132#[macro_export]1133macro_rules! unsupported {1134 () => {1135 Err(<Error<T>>::UnsupportedOperation.into())1136 };1137}11381139/// Worst cases1140pub trait CommonWeightInfo<CrossAccountId> {1141 fn create_item() -> Weight;1142 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1143 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1144 fn burn_item() -> Weight;1145 fn set_collection_properties(amount: u32) -> Weight;1146 fn delete_collection_properties(amount: u32) -> Weight;1147 fn set_token_properties(amount: u32) -> Weight;1148 fn delete_token_properties(amount: u32) -> Weight;1149 fn set_property_permissions(amount: u32) -> Weight;1150 fn transfer() -> Weight;1151 fn approve() -> Weight;1152 fn transfer_from() -> Weight;1153 fn burn_from() -> Weight;1154}11551156pub trait CommonCollectionOperations<T: Config> {1157 fn create_item(1158 &self,1159 sender: T::CrossAccountId,1160 to: T::CrossAccountId,1161 data: CreateItemData,1162 nesting_budget: &dyn Budget,1163 ) -> DispatchResultWithPostInfo;1164 fn create_multiple_items(1165 &self,1166 sender: T::CrossAccountId,1167 to: T::CrossAccountId,1168 data: Vec<CreateItemData>,1169 nesting_budget: &dyn Budget,1170 ) -> DispatchResultWithPostInfo;1171 fn create_multiple_items_ex(1172 &self,1173 sender: T::CrossAccountId,1174 data: CreateItemExData<T::CrossAccountId>,1175 nesting_budget: &dyn Budget,1176 ) -> DispatchResultWithPostInfo;1177 fn burn_item(1178 &self,1179 sender: T::CrossAccountId,1180 token: TokenId,1181 amount: u128,1182 ) -> DispatchResultWithPostInfo;1183 fn set_collection_properties(1184 &self,1185 sender: T::CrossAccountId,1186 properties: Vec<Property>,1187 ) -> DispatchResultWithPostInfo;1188 fn delete_collection_properties(1189 &self,1190 sender: &T::CrossAccountId,1191 property_keys: Vec<PropertyKey>,1192 ) -> DispatchResultWithPostInfo;1193 fn set_token_properties(1194 &self,1195 sender: T::CrossAccountId,1196 token_id: TokenId,1197 property: Vec<Property>,1198 ) -> DispatchResultWithPostInfo;1199 fn delete_token_properties(1200 &self,1201 sender: T::CrossAccountId,1202 token_id: TokenId,1203 property_keys: Vec<PropertyKey>,1204 ) -> DispatchResultWithPostInfo;1205 fn set_property_permissions(1206 &self,1207 sender: &T::CrossAccountId,1208 property_permissions: Vec<PropertyKeyPermission>,1209 ) -> DispatchResultWithPostInfo;1210 fn transfer(1211 &self,1212 sender: T::CrossAccountId,1213 to: T::CrossAccountId,1214 token: TokenId,1215 amount: u128,1216 nesting_budget: &dyn Budget,1217 ) -> DispatchResultWithPostInfo;1218 fn approve(1219 &self,1220 sender: T::CrossAccountId,1221 spender: T::CrossAccountId,1222 token: TokenId,1223 amount: u128,1224 ) -> DispatchResultWithPostInfo;1225 fn transfer_from(1226 &self,1227 sender: T::CrossAccountId,1228 from: T::CrossAccountId,1229 to: T::CrossAccountId,1230 token: TokenId,1231 amount: u128,1232 nesting_budget: &dyn Budget,1233 ) -> DispatchResultWithPostInfo;1234 fn burn_from(1235 &self,1236 sender: T::CrossAccountId,1237 from: T::CrossAccountId,1238 token: TokenId,1239 amount: u128,1240 nesting_budget: &dyn Budget,1241 ) -> DispatchResultWithPostInfo;12421243 fn check_nesting(1244 &self,1245 sender: T::CrossAccountId,1246 from: (CollectionId, TokenId),1247 under: TokenId,1248 budget: &dyn Budget,1249 ) -> DispatchResult;12501251 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1252 fn collection_tokens(&self) -> Vec<TokenId>;1253 fn token_exists(&self, token: TokenId) -> bool;1254 fn last_token_id(&self) -> TokenId;12551256 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1257 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1258 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1259 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1260 /// Amount of unique collection tokens1261 fn total_supply(&self) -> u32;1262 /// Amount of different tokens account has (Applicable to nonfungible/refungible)1263 fn account_balance(&self, account: T::CrossAccountId) -> u32;1264 /// Amount of specific token account have (Applicable to fungible/refungible)1265 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1266 fn allowance(1267 &self,1268 sender: T::CrossAccountId,1269 spender: T::CrossAccountId,1270 token: TokenId,1271 ) -> u128;1272}12731274// Flexible enough for implementing CommonCollectionOperations1275pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1276 let post_info = PostDispatchInfo {1277 actual_weight: Some(weight),1278 pays_fee: Pays::Yes,1279 };1280 match res {1281 Ok(()) => Ok(post_info),1282 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1283 }1284}12851286impl<T: Config> From<PropertiesError> for Error<T> {1287 fn from(error: PropertiesError) -> Self {1288 match error {1289 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1290 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1291 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1292 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1293 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1294 }1295 }1296}pallets/nonfungible/src/common.rsdiffbeforeafterboth--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -405,7 +405,7 @@
})
.unwrap_or_else(|| {
properties
- .iter()
+ .into_iter()
.map(|(key, value)| Property {
key: key.clone(),
value: value.clone(),
primitives/data-structs/src/lib.rsdiffbeforeafterboth--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -771,14 +771,6 @@
self.0.get(key)
}
- pub fn iter(&self) -> impl Iterator<Item = (&PropertyKey, &Value)> {
- self.0.iter()
- }
-
- pub fn into_iter(self) -> impl Iterator<Item = (PropertyKey, Value)> {
- self.0.into_iter()
- }
-
fn check_property_key(key: &PropertyKey) -> Result<(), PropertiesError> {
if key.is_empty() {
return Err(PropertiesError::EmptyPropertyKey);
@@ -796,6 +788,21 @@
}
}
+impl<Value> IntoIterator for PropertiesMap<Value> {
+ type Item = (PropertyKey, Value);
+ type IntoIter = <
+ BoundedBTreeMap<
+ PropertyKey,
+ Value,
+ ConstU32<MAX_PROPERTIES_PER_ITEM>
+ > as IntoIterator
+ >::IntoIter;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.0.into_iter()
+ }
+}
+
impl<Value> TrySetProperty for PropertiesMap<Value> {
type Value = Value;
@@ -848,14 +855,15 @@
pub fn get(&self, key: &PropertyKey) -> Option<&PropertyValue> {
self.map.get(key)
}
+}
- pub fn iter(&self) -> impl Iterator<Item = (&PropertyKey, &PropertyValue)> {
- self.map.iter()
- }
+impl IntoIterator for Properties {
+ type Item = (PropertyKey, PropertyValue);
+ type IntoIter = <PropertiesMap<PropertyValue> as IntoIterator>::IntoIter;
- pub fn into_iter(self) -> impl Iterator<Item = (PropertyKey, PropertyValue)> {
- self.map.into_iter()
- }
+ fn into_iter(self) -> Self::IntoIter {
+ self.map.into_iter()
+ }
}
impl TrySetProperty for Properties {
runtime/common/src/runtime_apis.rsdiffbeforeafterboth--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -257,7 +257,7 @@
}
None => {
properties
- .iter()
+ .into_iter()
.filter_map(|(key, value)| Some(RmrkPropertyInfo {
key: key.decode_or_default(),
value: value.decode_or_default(),
@@ -296,7 +296,7 @@
}
None => {
properties
- .iter()
+ .into_iter()
.filter_map(|(key, value)| Some(RmrkPropertyInfo {
key: key.decode_or_default(),
value: value.decode_or_default(),