1234567891011121314151617#![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, rc::Rc};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 PhantomType,59 Property,60 Properties,61 PropertiesPermissionMap,62 PropertyKey,63 PropertyValue,64 PropertyPermission,65 PropertiesError,66 PropertyKeyPermission,67 TokenData,68 TrySetProperty,69 PropertyScope,70 71 RmrkCollectionInfo,72 RmrkInstanceInfo,73 RmrkResourceInfo,74 RmrkPropertyInfo,75 RmrkBaseInfo,76 RmrkPartType,77 RmrkTheme,78 RmrkNftChild,79 CollectionPermissions,80 SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97 pub id: CollectionId,98 collection: Collection<T::AccountId>,99 pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102 fn recorder(&self) -> &SubstrateRecorder<T> {103 &self.recorder104 }105 fn into_recorder(self) -> SubstrateRecorder<T> {106 self.recorder107 }108}109impl<T: Config> CollectionHandle<T> {110 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111 <CollectionById<T>>::get(id).map(|collection| Self {112 id,113 collection,114 recorder: SubstrateRecorder::new(gas_limit),115 })116 }117118 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119 <CollectionById<T>>::get(id).map(|collection| Self {120 id,121 collection,122 recorder,123 })124 }125126 pub fn new(id: CollectionId) -> Option<Self> {127 Self::new_with_gas_limit(id, u64::MAX)128 }129 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {130 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)131 }132 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {133 self.recorder134 .consume_gas(T::GasWeightMapping::weight_to_gas(135 <T as frame_system::Config>::DbWeight::get()136 .read137 .saturating_mul(reads),138 ))139 }140 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {141 self.recorder142 .consume_gas(T::GasWeightMapping::weight_to_gas(143 <T as frame_system::Config>::DbWeight::get()144 .write145 .saturating_mul(writes),146 ))147 }148 pub fn save(self) -> DispatchResult {149 <CollectionById<T>>::insert(self.id, self.collection);150 Ok(())151 }152153 pub fn set_sponsor(&mut self, sponsor: T::AccountId) {154 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);155 }156157 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {158 if self.collection.sponsorship.pending_sponsor() != Some(sender) {159 return false;160 };161162 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());163 true164 }165}166impl<T: Config> Deref for CollectionHandle<T> {167 type Target = Collection<T::AccountId>;168169 fn deref(&self) -> &Self::Target {170 &self.collection171 }172}173174impl<T: Config> DerefMut for CollectionHandle<T> {175 fn deref_mut(&mut self) -> &mut Self::Target {176 &mut self.collection177 }178}179180impl<T: Config> CollectionHandle<T> {181 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {182 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);183 Ok(())184 }185 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {186 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))187 }188 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {189 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);190 Ok(())191 }192 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {193 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)194 }195 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {196 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)197 }198 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {199 ensure!(200 <Allowlist<T>>::get((self.id, user)),201 <Error<T>>::AddressNotInAllowlist202 );203 Ok(())204 }205}206207#[frame_support::pallet]208pub mod pallet {209 use super::*;210 use pallet_evm::account;211 use dispatch::CollectionDispatch;212 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};213 use frame_system::pallet_prelude::*;214 use frame_support::traits::Currency;215 use up_data_structs::{TokenId, mapping::TokenAddressMapping};216 use scale_info::TypeInfo;217 use weights::WeightInfo;218219 #[pallet::config]220 pub trait Config:221 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config222 {223 type WeightInfo: WeightInfo;224 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;225226 type Currency: Currency<Self::AccountId>;227228 #[pallet::constant]229 type CollectionCreationPrice: Get<230 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,231 >;232 type CollectionDispatch: CollectionDispatch<Self>;233234 type TreasuryAccountId: Get<Self::AccountId>;235236 type EvmTokenAddressMapping: TokenAddressMapping<H160>;237 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;238 }239240 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);241242 #[pallet::pallet]243 #[pallet::storage_version(STORAGE_VERSION)]244 #[pallet::generate_store(pub(super) trait Store)]245 pub struct Pallet<T>(_);246247 #[pallet::extra_constants]248 impl<T: Config> Pallet<T> {249 pub fn collection_admins_limit() -> u32 {250 COLLECTION_ADMINS_LIMIT251 }252 }253254 #[pallet::event]255 #[pallet::generate_deposit(pub fn deposit_event)]256 pub enum Event<T: Config> {257 258 259 260 261 262 263 264 265 266 CollectionCreated(CollectionId, u8, T::AccountId),267268 269 270 271 272 273 CollectionDestroyed(CollectionId),274275 276 277 278 279 280 281 282 283 284 285 286 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),287288 289 290 291 292 293 294 295 296 297 298 299 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),300301 302 303 304 305 306 307 308 309 310 311 312 Transfer(313 CollectionId,314 TokenId,315 T::CrossAccountId,316 T::CrossAccountId,317 u128,318 ),319320 321 322 323 324 325 326 327 328 329 Approved(330 CollectionId,331 TokenId,332 T::CrossAccountId,333 T::CrossAccountId,334 u128,335 ),336337 CollectionPropertySet(CollectionId, PropertyKey),338339 CollectionPropertyDeleted(CollectionId, PropertyKey),340341 TokenPropertySet(CollectionId, TokenId, PropertyKey),342343 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),344345 PropertyPermissionSet(CollectionId, PropertyKey),346 }347348 #[pallet::error]349 pub enum Error<T> {350 351 CollectionNotFound,352 353 MustBeTokenOwner,354 355 NoPermission,356 357 PublicMintingNotAllowed,358 359 AddressNotInAllowlist,360361 362 CollectionNameLimitExceeded,363 364 CollectionDescriptionLimitExceeded,365 366 CollectionTokenPrefixLimitExceeded,367 368 TotalCollectionsLimitExceeded,369 370 CollectionAdminCountExceeded,371 372 CollectionLimitBoundsExceeded,373 374 OwnerPermissionsCantBeReverted,375 376 TransferNotAllowed,377 378 AccountTokenLimitExceeded,379 380 CollectionTokenLimitExceeded,381 382 MetadataFlagFrozen,383384 385 TokenNotFound,386 387 TokenValueTooLow,388 389 ApprovedValueTooLow,390 391 CantApproveMoreThanOwned,392393 394 AddressIsZero,395 396 UnsupportedOperation,397398 399 NotSufficientFounds,400401 402 NestingIsDisabled,403 404 OnlyOwnerAllowedToNest,405 406 SourceCollectionIsNotAllowedToNest,407408 409 CollectionFieldSizeExceeded,410411 412 NoSpaceForProperty,413414 415 PropertyLimitReached,416417 418 PropertyKeyIsTooLong,419420 421 InvalidCharacterInPropertyKey,422423 424 EmptyPropertyKey,425 }426427 #[pallet::storage]428 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;429 #[pallet::storage]430 pub type DestroyedCollectionCount<T> =431 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;432433 434 #[pallet::storage]435 pub type CollectionById<T> = StorageMap<436 Hasher = Blake2_128Concat,437 Key = CollectionId,438 Value = Collection<<T as frame_system::Config>::AccountId>,439 QueryKind = OptionQuery,440 >;441442 443 #[pallet::storage]444 #[pallet::getter(fn collection_properties)]445 pub type CollectionProperties<T> = StorageMap<446 Hasher = Blake2_128Concat,447 Key = CollectionId,448 Value = Properties,449 QueryKind = ValueQuery,450 OnEmpty = up_data_structs::CollectionProperties,451 >;452453 #[pallet::storage]454 #[pallet::getter(fn property_permissions)]455 pub type CollectionPropertyPermissions<T> = StorageMap<456 Hasher = Blake2_128Concat,457 Key = CollectionId,458 Value = PropertiesPermissionMap,459 QueryKind = ValueQuery,460 >;461462 #[pallet::storage]463 pub type AdminAmount<T> = StorageMap<464 Hasher = Blake2_128Concat,465 Key = CollectionId,466 Value = u32,467 QueryKind = ValueQuery,468 >;469470 471 #[pallet::storage]472 pub type IsAdmin<T: Config> = StorageNMap<473 Key = (474 Key<Blake2_128Concat, CollectionId>,475 Key<Blake2_128Concat, T::CrossAccountId>,476 ),477 Value = bool,478 QueryKind = ValueQuery,479 >;480481 482 #[pallet::storage]483 pub type Allowlist<T: Config> = StorageNMap<484 Key = (485 Key<Blake2_128Concat, CollectionId>,486 Key<Blake2_128Concat, T::CrossAccountId>,487 ),488 Value = bool,489 QueryKind = ValueQuery,490 >;491492 493 #[pallet::storage]494 pub type DummyStorageValue<T: Config> = StorageValue<495 Value = (496 CollectionStats,497 CollectionId,498 TokenId,499 PhantomType<TokenData<T::CrossAccountId>>,500 PhantomType<RpcCollection<T::AccountId>>,501 502 PhantomType<RmrkCollectionInfo<T::AccountId>>,503 PhantomType<RmrkInstanceInfo<T::AccountId>>,504 PhantomType<RmrkResourceInfo>,505 PhantomType<RmrkPropertyInfo>,506 PhantomType<RmrkBaseInfo<T::AccountId>>,507 PhantomType<RmrkPartType>,508 PhantomType<RmrkTheme>,509 PhantomType<RmrkNftChild>,510 ),511 QueryKind = OptionQuery,512 >;513514 #[pallet::hooks]515 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {516 fn on_runtime_upgrade() -> Weight {517 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {518 use up_data_structs::{CollectionVersion1, CollectionVersion2};519 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {520 let mut props = Vec::new();521 if !v.offchain_schema.is_empty() {522 props.push(Property {523 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),524 value: v.offchain_schema.clone().into_inner().try_into().expect("offchain schema too big"),525 });526 }527 if !v.variable_on_chain_schema.is_empty() {528 props.push(Property {529 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),530 value: v.variable_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),531 });532 }533 if !v.const_on_chain_schema.is_empty() {534 props.push(Property {535 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),536 value: v.const_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),537 });538 }539 props.push(Property {540 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),541 value: match v.schema_version {542 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),543 SchemaVersion::Unique => b"Unique".as_slice(),544 }.to_vec().try_into().unwrap(),545 });546 Self::set_scoped_collection_properties(547 id,548 PropertyScope::None,549 props.into_iter(),550 ).expect("existing data larger than properties");551 let mut new = CollectionVersion2::from(v.clone());552 new.permissions.access = Some(v.access);553 new.permissions.mint_mode = Some(v.mint_mode);554 Some(new)555 });556 }557558 0559 }560 }561}562563impl<T: Config> Pallet<T> {564 565 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {566 ensure!(567 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,568 <Error<T>>::AddressIsZero569 );570 Ok(())571 }572 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {573 <IsAdmin<T>>::iter_prefix((collection,))574 .map(|(a, _)| a)575 .collect()576 }577 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {578 <Allowlist<T>>::iter_prefix((collection,))579 .map(|(a, _)| a)580 .collect()581 }582 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {583 <Allowlist<T>>::get((collection, user))584 }585 pub fn collection_stats() -> CollectionStats {586 let created = <CreatedCollectionCount<T>>::get();587 let destroyed = <DestroyedCollectionCount<T>>::get();588 CollectionStats {589 created: created.0,590 destroyed: destroyed.0,591 alive: created.0 - destroyed.0,592 }593 }594595 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {596 let collection = <CollectionById<T>>::get(collection);597 if collection.is_none() {598 return None;599 }600601 let collection = collection.unwrap();602 let limits = collection.limits;603 let effective_limits = CollectionLimits {604 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),605 sponsored_data_size: Some(limits.sponsored_data_size()),606 sponsored_data_rate_limit: Some(607 limits608 .sponsored_data_rate_limit609 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),610 ),611 token_limit: Some(limits.token_limit()),612 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(613 match collection.mode {614 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,615 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,616 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,617 },618 )),619 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),620 owner_can_transfer: Some(limits.owner_can_transfer()),621 owner_can_destroy: Some(limits.owner_can_destroy()),622 transfers_enabled: Some(limits.transfers_enabled()),623 };624625 Some(effective_limits)626 }627628 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {629 let Collection {630 name,631 description,632 owner,633 mode,634 token_prefix,635 sponsorship,636 limits,637 permissions,638 } = <CollectionById<T>>::get(collection)?;639640 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)641 .into_iter()642 .map(|(key, permission)| PropertyKeyPermission {643 key,644 permission,645 })646 .collect();647648 let properties = <CollectionProperties<T>>::get(collection)649 .into_iter()650 .map(|(key, value)| Property {651 key,652 value,653 })654 .collect();655656 Some(RpcCollection {657 name: name.into_inner(),658 description: description.into_inner(),659 owner,660 mode,661 token_prefix: token_prefix.into_inner(),662 sponsorship,663 limits,664 permissions,665 token_property_permissions,666 properties,667 })668 }669}670671macro_rules! limit_default {672 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{673 $(674 if let Some($new) = $new.$field {675 let $old = $old.$field($($arg)?);676 let _ = $new;677 let _ = $old;678 $check679 } else {680 $new.$field = $old.$field681 }682 )*683 }};684}685macro_rules! limit_default_clone {686 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{687 $(688 if let Some($new) = $new.$field.clone() {689 let $old = $old.$field($($arg)?);690 let _ = $new;691 let _ = $old;692 $check693 } else {694 $new.$field = $old.$field.clone()695 }696 )*697 }};698}699700impl<T: Config> Pallet<T> {701 pub fn init_collection(702 owner: T::AccountId,703 data: CreateCollectionData<T::AccountId>,704 ) -> Result<CollectionId, DispatchError> {705 {706 ensure!(707 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,708 Error::<T>::CollectionTokenPrefixLimitExceeded709 );710 }711712 let created_count = <CreatedCollectionCount<T>>::get()713 .0714 .checked_add(1)715 .ok_or(ArithmeticError::Overflow)?;716 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;717 let id = CollectionId(created_count);718719 720 ensure!(721 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,722 <Error<T>>::TotalCollectionsLimitExceeded723 );724725 726727 let collection = Collection {728 owner: owner.clone(),729 name: data.name,730 mode: data.mode.clone(),731 description: data.description,732 token_prefix: data.token_prefix,733 sponsorship: data734 .pending_sponsor735 .map(SponsorshipState::Unconfirmed)736 .unwrap_or_default(),737 limits: data738 .limits739 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))740 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,741 permissions: data742 .permissions743 .map(|permissions| Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions))744 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,745 };746747 let mut collection_properties = up_data_structs::CollectionProperties::get();748 collection_properties749 .try_set_from_iter(data.properties.into_iter())750 .map_err(<Error<T>>::from)?;751752 CollectionProperties::<T>::insert(id, collection_properties);753754 let mut token_props_permissions = PropertiesPermissionMap::new();755 token_props_permissions756 .try_set_from_iter(data.token_property_permissions.into_iter())757 .map_err(<Error<T>>::from)?;758759 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);760761 762 {763 let mut imbalance =764 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();765 imbalance.subsume(766 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(767 &T::TreasuryAccountId::get(),768 T::CollectionCreationPrice::get(),769 ),770 );771 <T as Config>::Currency::settle(772 &owner,773 imbalance,774 WithdrawReasons::TRANSFER,775 ExistenceRequirement::KeepAlive,776 )777 .map_err(|_| Error::<T>::NotSufficientFounds)?;778 }779780 <CreatedCollectionCount<T>>::put(created_count);781 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));782 <CollectionById<T>>::insert(id, collection);783 Ok(id)784 }785786 pub fn destroy_collection(787 collection: CollectionHandle<T>,788 sender: &T::CrossAccountId,789 ) -> DispatchResult {790 ensure!(791 collection.limits.owner_can_destroy(),792 <Error<T>>::NoPermission,793 );794 collection.check_is_owner(sender)?;795796 let destroyed_collections = <DestroyedCollectionCount<T>>::get()797 .0798 .checked_add(1)799 .ok_or(ArithmeticError::Overflow)?;800801 802803 <DestroyedCollectionCount<T>>::put(destroyed_collections);804 <CollectionById<T>>::remove(collection.id);805 <AdminAmount<T>>::remove(collection.id);806 <IsAdmin<T>>::remove_prefix((collection.id,), None);807 <Allowlist<T>>::remove_prefix((collection.id,), None);808 <CollectionProperties<T>>::remove(collection.id);809810 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));811 Ok(())812 }813814 pub fn set_collection_property(815 collection: &CollectionHandle<T>,816 sender: &T::CrossAccountId,817 property: Property,818 ) -> DispatchResult {819 collection.check_is_owner_or_admin(sender)?;820821 CollectionProperties::<T>::try_mutate(collection.id, |properties| {822 let property = property.clone();823 properties.try_set(property.key, property.value)824 })825 .map_err(<Error<T>>::from)?;826827 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));828829 Ok(())830 }831832 pub fn set_scoped_collection_property(833 collection_id: CollectionId,834 scope: PropertyScope,835 property: Property,836 ) -> DispatchResult {837 CollectionProperties::<T>::try_mutate(collection_id, |properties| {838 properties.try_scoped_set(scope, property.key, property.value)839 })840 .map_err(<Error<T>>::from)?;841842 Ok(())843 }844845 pub fn set_scoped_collection_properties(846 collection_id: CollectionId,847 scope: PropertyScope,848 properties: impl Iterator<Item = Property>,849 ) -> DispatchResult {850 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {851 stored_properties.try_scoped_set_from_iter(scope, properties)852 })853 .map_err(<Error<T>>::from)?;854855 Ok(())856 }857858 #[transactional]859 pub fn set_collection_properties(860 collection: &CollectionHandle<T>,861 sender: &T::CrossAccountId,862 properties: Vec<Property>,863 ) -> DispatchResult {864 for property in properties {865 Self::set_collection_property(collection, sender, property)?;866 }867868 Ok(())869 }870871 pub fn delete_collection_property(872 collection: &CollectionHandle<T>,873 sender: &T::CrossAccountId,874 property_key: PropertyKey,875 ) -> DispatchResult {876 collection.check_is_owner_or_admin(sender)?;877878 CollectionProperties::<T>::try_mutate(collection.id, |properties| {879 properties.remove(&property_key)880 })881 .map_err(<Error<T>>::from)?;882883 Self::deposit_event(Event::CollectionPropertyDeleted(884 collection.id,885 property_key,886 ));887888 Ok(())889 }890891 #[transactional]892 pub fn delete_collection_properties(893 collection: &CollectionHandle<T>,894 sender: &T::CrossAccountId,895 property_keys: Vec<PropertyKey>,896 ) -> DispatchResult {897 for key in property_keys {898 Self::delete_collection_property(collection, sender, key)?;899 }900901 Ok(())902 }903904 905 pub fn set_property_permission_unchecked(906 collection: CollectionId,907 property_permission: PropertyKeyPermission,908 ) -> DispatchResult {909 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {910 permissions.try_set(property_permission.key, property_permission.permission)911 })912 .map_err(<Error<T>>::from)?;913 Ok(())914 }915916 pub fn set_property_permission(917 collection: &CollectionHandle<T>,918 sender: &T::CrossAccountId,919 property_permission: PropertyKeyPermission,920 ) -> DispatchResult {921 collection.check_is_owner_or_admin(sender)?;922923 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);924 let current_permission = all_permissions.get(&property_permission.key);925 if matches![926 current_permission,927 Some(PropertyPermission { mutable: false, .. })928 ] {929 return Err(<Error<T>>::NoPermission.into());930 }931932 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {933 let property_permission = property_permission.clone();934 permissions.try_set(property_permission.key, property_permission.permission)935 })936 .map_err(<Error<T>>::from)?;937938 Self::deposit_event(Event::PropertyPermissionSet(939 collection.id,940 property_permission.key,941 ));942943 Ok(())944 }945946 #[transactional]947 pub fn set_property_permissions(948 collection: &CollectionHandle<T>,949 sender: &T::CrossAccountId,950 property_permissions: Vec<PropertyKeyPermission>,951 ) -> DispatchResult {952 for prop_pemission in property_permissions {953 Self::set_property_permission(collection, sender, prop_pemission)?;954 }955956 Ok(())957 }958959 pub fn get_collection_property(960 collection_id: CollectionId,961 key: &PropertyKey,962 ) -> Option<PropertyValue> {963 Self::collection_properties(collection_id).get(key).cloned()964 }965966 pub fn bytes_keys_to_property_keys(967 keys: Vec<Vec<u8>>,968 ) -> Result<Vec<PropertyKey>, DispatchError> {969 keys.into_iter()970 .map(|key| -> Result<PropertyKey, DispatchError> {971 key.try_into()972 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())973 })974 .collect::<Result<Vec<PropertyKey>, DispatchError>>()975 }976977 pub fn filter_collection_properties(978 collection_id: CollectionId,979 keys: Option<Vec<PropertyKey>>,980 ) -> Result<Vec<Property>, DispatchError> {981 let properties = Self::collection_properties(collection_id);982983 let properties = keys984 .map(|keys| {985 keys.into_iter()986 .filter_map(|key| {987 properties.get(&key).map(|value| Property {988 key,989 value: value.clone(),990 })991 })992 .collect()993 })994 .unwrap_or_else(|| {995 properties996 .into_iter()997 .map(|(key, value)| Property {998 key,999 value,1000 })1001 .collect()1002 });10031004 Ok(properties)1005 }10061007 pub fn filter_property_permissions(1008 collection_id: CollectionId,1009 keys: Option<Vec<PropertyKey>>,1010 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1011 let permissions = Self::property_permissions(collection_id);10121013 let key_permissions = keys1014 .map(|keys| {1015 keys.into_iter()1016 .filter_map(|key| {1017 permissions1018 .get(&key)1019 .map(|permission| PropertyKeyPermission {1020 key,1021 permission: permission.clone(),1022 })1023 })1024 .collect()1025 })1026 .unwrap_or_else(|| {1027 permissions1028 .into_iter()1029 .map(|(key, permission)| PropertyKeyPermission {1030 key,1031 permission,1032 })1033 .collect()1034 });10351036 Ok(key_permissions)1037 }10381039 pub fn toggle_allowlist(1040 collection: &CollectionHandle<T>,1041 sender: &T::CrossAccountId,1042 user: &T::CrossAccountId,1043 allowed: bool,1044 ) -> DispatchResult {1045 collection.check_is_owner_or_admin(sender)?;10461047 10481049 if allowed {1050 <Allowlist<T>>::insert((collection.id, user), true);1051 } else {1052 <Allowlist<T>>::remove((collection.id, user));1053 }10541055 Ok(())1056 }10571058 pub fn toggle_admin(1059 collection: &CollectionHandle<T>,1060 sender: &T::CrossAccountId,1061 user: &T::CrossAccountId,1062 admin: bool,1063 ) -> DispatchResult {1064 collection.check_is_owner_or_admin(sender)?;10651066 let was_admin = <IsAdmin<T>>::get((collection.id, user));1067 if was_admin == admin {1068 return Ok(());1069 }1070 let amount = <AdminAmount<T>>::get(collection.id);10711072 if admin {1073 let amount = amount1074 .checked_add(1)1075 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1076 ensure!(1077 amount <= Self::collection_admins_limit(),1078 <Error<T>>::CollectionAdminCountExceeded,1079 );10801081 10821083 <AdminAmount<T>>::insert(collection.id, amount);1084 <IsAdmin<T>>::insert((collection.id, user), true);1085 } else {1086 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1087 <IsAdmin<T>>::remove((collection.id, user));1088 }10891090 Ok(())1091 }10921093 pub fn clamp_limits(1094 mode: CollectionMode,1095 old_limit: &CollectionLimits,1096 mut new_limit: CollectionLimits,1097 ) -> Result<CollectionLimits, DispatchError> {1098 limit_default!(old_limit, new_limit,1099 account_token_ownership_limit => ensure!(1100 new_limit <= MAX_TOKEN_OWNERSHIP,1101 <Error<T>>::CollectionLimitBoundsExceeded,1102 ),1103 sponsor_transfer_timeout(match mode {1104 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1105 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1106 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1107 }) => ensure!(1108 new_limit <= MAX_SPONSOR_TIMEOUT,1109 <Error<T>>::CollectionLimitBoundsExceeded,1110 ),1111 sponsored_data_size => ensure!(1112 new_limit <= CUSTOM_DATA_LIMIT,1113 <Error<T>>::CollectionLimitBoundsExceeded,1114 ),1115 token_limit => ensure!(1116 old_limit >= new_limit && new_limit > 0,1117 <Error<T>>::CollectionTokenLimitExceeded1118 ),1119 owner_can_transfer => ensure!(1120 old_limit || !new_limit,1121 <Error<T>>::OwnerPermissionsCantBeReverted,1122 ),1123 owner_can_destroy => ensure!(1124 old_limit || !new_limit,1125 <Error<T>>::OwnerPermissionsCantBeReverted,1126 ),1127 sponsored_data_rate_limit => {},1128 transfers_enabled => {},1129 );1130 Ok(new_limit)1131 }1132 pub fn clamp_permissions(1133 mode: CollectionMode,1134 old_limit: &CollectionPermissions,1135 mut new_limit: CollectionPermissions,1136 ) -> Result<CollectionPermissions, DispatchError> {1137 limit_default_clone!(old_limit, new_limit,1138 );1139 Ok(new_limit)1140 }1141}11421143#[macro_export]1144macro_rules! unsupported {1145 () => {1146 Err(<Error<T>>::UnsupportedOperation.into())1147 };1148}114911501151pub trait CommonWeightInfo<CrossAccountId> {1152 fn create_item() -> Weight;1153 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1154 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1155 fn burn_item() -> Weight;1156 fn set_collection_properties(amount: u32) -> Weight;1157 fn delete_collection_properties(amount: u32) -> Weight;1158 fn set_token_properties(amount: u32) -> Weight;1159 fn delete_token_properties(amount: u32) -> Weight;1160 fn set_property_permissions(amount: u32) -> Weight;1161 fn transfer() -> Weight;1162 fn approve() -> Weight;1163 fn transfer_from() -> Weight;1164 fn burn_from() -> Weight;1165}11661167pub trait CommonCollectionOperations<T: Config> {1168 fn create_item(1169 &self,1170 sender: T::CrossAccountId,1171 to: T::CrossAccountId,1172 data: CreateItemData,1173 nesting_budget: &dyn Budget,1174 ) -> DispatchResultWithPostInfo;1175 fn create_multiple_items(1176 &self,1177 sender: T::CrossAccountId,1178 to: T::CrossAccountId,1179 data: Vec<CreateItemData>,1180 nesting_budget: &dyn Budget,1181 ) -> DispatchResultWithPostInfo;1182 fn create_multiple_items_ex(1183 &self,1184 sender: T::CrossAccountId,1185 data: CreateItemExData<T::CrossAccountId>,1186 nesting_budget: &dyn Budget,1187 ) -> DispatchResultWithPostInfo;1188 fn burn_item(1189 &self,1190 sender: T::CrossAccountId,1191 token: TokenId,1192 amount: u128,1193 ) -> DispatchResultWithPostInfo;1194 fn set_collection_properties(1195 &self,1196 sender: T::CrossAccountId,1197 properties: Vec<Property>,1198 ) -> DispatchResultWithPostInfo;1199 fn delete_collection_properties(1200 &self,1201 sender: &T::CrossAccountId,1202 property_keys: Vec<PropertyKey>,1203 ) -> DispatchResultWithPostInfo;1204 fn set_token_properties(1205 &self,1206 sender: T::CrossAccountId,1207 token_id: TokenId,1208 property: Vec<Property>,1209 ) -> DispatchResultWithPostInfo;1210 fn delete_token_properties(1211 &self,1212 sender: T::CrossAccountId,1213 token_id: TokenId,1214 property_keys: Vec<PropertyKey>,1215 ) -> DispatchResultWithPostInfo;1216 fn set_property_permissions(1217 &self,1218 sender: &T::CrossAccountId,1219 property_permissions: Vec<PropertyKeyPermission>,1220 ) -> DispatchResultWithPostInfo;1221 fn transfer(1222 &self,1223 sender: T::CrossAccountId,1224 to: T::CrossAccountId,1225 token: TokenId,1226 amount: u128,1227 nesting_budget: &dyn Budget,1228 ) -> DispatchResultWithPostInfo;1229 fn approve(1230 &self,1231 sender: T::CrossAccountId,1232 spender: T::CrossAccountId,1233 token: TokenId,1234 amount: u128,1235 ) -> DispatchResultWithPostInfo;1236 fn transfer_from(1237 &self,1238 sender: T::CrossAccountId,1239 from: T::CrossAccountId,1240 to: T::CrossAccountId,1241 token: TokenId,1242 amount: u128,1243 nesting_budget: &dyn Budget,1244 ) -> DispatchResultWithPostInfo;1245 fn burn_from(1246 &self,1247 sender: T::CrossAccountId,1248 from: T::CrossAccountId,1249 token: TokenId,1250 amount: u128,1251 nesting_budget: &dyn Budget,1252 ) -> DispatchResultWithPostInfo;12531254 fn check_nesting(1255 &self,1256 sender: T::CrossAccountId,1257 from: (CollectionId, TokenId),1258 under: TokenId,1259 budget: &dyn Budget,1260 ) -> DispatchResult;12611262 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1263 fn collection_tokens(&self) -> Vec<TokenId>;1264 fn token_exists(&self, token: TokenId) -> bool;1265 fn last_token_id(&self) -> TokenId;12661267 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1268 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1269 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1270 1271 fn total_supply(&self) -> u32;1272 1273 fn account_balance(&self, account: T::CrossAccountId) -> u32;1274 1275 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1276 fn allowance(1277 &self,1278 sender: T::CrossAccountId,1279 spender: T::CrossAccountId,1280 token: TokenId,1281 ) -> u128;1282}128312841285pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1286 let post_info = PostDispatchInfo {1287 actual_weight: Some(weight),1288 pays_fee: Pays::Yes,1289 };1290 match res {1291 Ok(()) => Ok(post_info),1292 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1293 }1294}12951296impl<T: Config> From<PropertiesError> for Error<T> {1297 fn from(error: PropertiesError) -> Self {1298 match error {1299 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1300 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1301 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1302 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1303 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1304 }1305 }1306}