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;24use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28 ensure,29 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},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 TokenChild,44 CollectionStats,45 MAX_TOKEN_OWNERSHIP,46 CollectionMode,47 NFT_SPONSOR_TRANSFER_TIMEOUT,48 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50 MAX_SPONSOR_TIMEOUT,51 CUSTOM_DATA_LIMIT,52 CollectionLimits,53 CreateCollectionData,54 SponsorshipState,55 CreateItemExData,56 SponsoringRateLimit,57 budget::Budget,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 }129130 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132 }133134 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135 self.recorder136 .consume_gas(T::GasWeightMapping::weight_to_gas(137 <T as frame_system::Config>::DbWeight::get()138 .read139 .saturating_mul(reads),140 ))141 }142143 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144 self.recorder145 .consume_gas(T::GasWeightMapping::weight_to_gas(146 <T as frame_system::Config>::DbWeight::get()147 .write148 .saturating_mul(writes),149 ))150 }151 pub fn save(self) -> Result<(), DispatchError> {152 self.check_is_read_only()?;153 <CollectionById<T>>::insert(self.id, self.collection);154 Ok(())155 }156157 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {158 self.check_is_read_only()?;159 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);160 Ok(())161 }162163 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {164 self.check_is_read_only()?;165166 if self.collection.sponsorship.pending_sponsor() != Some(sender) {167 return Ok(false);168 }169170 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());171 Ok(true)172 }173174 pub fn check_is_read_only(&self) -> DispatchResult {175 if self.read_only {176 return Err(<Error<T>>::CollectionNotFound)?;177 }178179 Ok(())180 }181}182183impl<T: Config> Deref for CollectionHandle<T> {184 type Target = Collection<T::AccountId>;185186 fn deref(&self) -> &Self::Target {187 &self.collection188 }189}190191impl<T: Config> DerefMut for CollectionHandle<T> {192 fn deref_mut(&mut self) -> &mut Self::Target {193 &mut self.collection194 }195}196197impl<T: Config> CollectionHandle<T> {198 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {199 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);200 Ok(())201 }202 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {203 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))204 }205 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {206 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);207 Ok(())208 }209 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {210 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)211 }212 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {213 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)214 }215 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {216 ensure!(217 <Allowlist<T>>::get((self.id, user)),218 <Error<T>>::AddressNotInAllowlist219 );220 Ok(())221 }222}223224#[frame_support::pallet]225pub mod pallet {226 use super::*;227 use pallet_evm::account;228 use dispatch::CollectionDispatch;229 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};230 use frame_system::pallet_prelude::*;231 use frame_support::traits::Currency;232 use up_data_structs::{TokenId, mapping::TokenAddressMapping};233 use scale_info::TypeInfo;234 use weights::WeightInfo;235236 #[pallet::config]237 pub trait Config:238 frame_system::Config239 + pallet_evm_coder_substrate::Config240 + pallet_evm::Config241 + TypeInfo242 + account::Config243 {244 type WeightInfo: WeightInfo;245 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;246247 type Currency: Currency<Self::AccountId>;248249 #[pallet::constant]250 type CollectionCreationPrice: Get<251 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,252 >;253 type CollectionDispatch: CollectionDispatch<Self>;254255 type TreasuryAccountId: Get<Self::AccountId>;256 type ContractAddress: Get<H160>;257258 type EvmTokenAddressMapping: TokenAddressMapping<H160>;259 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;260 }261262 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);263264 #[pallet::pallet]265 #[pallet::storage_version(STORAGE_VERSION)]266 #[pallet::generate_store(pub(super) trait Store)]267 pub struct Pallet<T>(_);268269 #[pallet::extra_constants]270 impl<T: Config> Pallet<T> {271 pub fn collection_admins_limit() -> u32 {272 COLLECTION_ADMINS_LIMIT273 }274 }275276 #[pallet::event]277 #[pallet::generate_deposit(pub fn deposit_event)]278 pub enum Event<T: Config> {279 280 281 282 283 284 285 286 287 288 CollectionCreated(CollectionId, u8, T::AccountId),289290 291 292 293 294 295 CollectionDestroyed(CollectionId),296297 298 299 300 301 302 303 304 305 306 307 308 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),309310 311 312 313 314 315 316 317 318 319 320 321 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),322323 324 325 326 327 328 329 330 331 332 333 334 Transfer(335 CollectionId,336 TokenId,337 T::CrossAccountId,338 T::CrossAccountId,339 u128,340 ),341342 343 344 345 346 347 348 349 350 351 Approved(352 CollectionId,353 TokenId,354 T::CrossAccountId,355 T::CrossAccountId,356 u128,357 ),358359 CollectionPropertySet(CollectionId, PropertyKey),360361 CollectionPropertyDeleted(CollectionId, PropertyKey),362363 TokenPropertySet(CollectionId, TokenId, PropertyKey),364365 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),366367 PropertyPermissionSet(CollectionId, PropertyKey),368 }369370 #[pallet::error]371 pub enum Error<T> {372 373 CollectionNotFound,374 375 MustBeTokenOwner,376 377 NoPermission,378 379 CantDestroyNotEmptyCollection,380 381 PublicMintingNotAllowed,382 383 AddressNotInAllowlist,384385 386 CollectionNameLimitExceeded,387 388 CollectionDescriptionLimitExceeded,389 390 CollectionTokenPrefixLimitExceeded,391 392 TotalCollectionsLimitExceeded,393 394 CollectionAdminCountExceeded,395 396 CollectionLimitBoundsExceeded,397 398 OwnerPermissionsCantBeReverted,399 400 TransferNotAllowed,401 402 AccountTokenLimitExceeded,403 404 CollectionTokenLimitExceeded,405 406 MetadataFlagFrozen,407408 409 TokenNotFound,410 411 TokenValueTooLow,412 413 ApprovedValueTooLow,414 415 CantApproveMoreThanOwned,416417 418 AddressIsZero,419 420 UnsupportedOperation,421422 423 NotSufficientFounds,424425 426 NestingIsDisabled,427 428 OnlyOwnerAllowedToNest,429 430 SourceCollectionIsNotAllowedToNest,431432 433 CollectionFieldSizeExceeded,434435 436 NoSpaceForProperty,437438 439 PropertyLimitReached,440441 442 PropertyKeyIsTooLong,443444 445 InvalidCharacterInPropertyKey,446447 448 EmptyPropertyKey,449450 451 CollectionIsReadOnly,452 }453454 #[pallet::storage]455 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;456 #[pallet::storage]457 pub type DestroyedCollectionCount<T> =458 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;459460 461 #[pallet::storage]462 pub type CollectionById<T> = StorageMap<463 Hasher = Blake2_128Concat,464 Key = CollectionId,465 Value = Collection<<T as frame_system::Config>::AccountId>,466 QueryKind = OptionQuery,467 >;468469 470 #[pallet::storage]471 #[pallet::getter(fn collection_properties)]472 pub type CollectionProperties<T> = StorageMap<473 Hasher = Blake2_128Concat,474 Key = CollectionId,475 Value = Properties,476 QueryKind = ValueQuery,477 OnEmpty = up_data_structs::CollectionProperties,478 >;479480 #[pallet::storage]481 #[pallet::getter(fn property_permissions)]482 pub type CollectionPropertyPermissions<T> = StorageMap<483 Hasher = Blake2_128Concat,484 Key = CollectionId,485 Value = PropertiesPermissionMap,486 QueryKind = ValueQuery,487 >;488489 #[pallet::storage]490 pub type AdminAmount<T> = StorageMap<491 Hasher = Blake2_128Concat,492 Key = CollectionId,493 Value = u32,494 QueryKind = ValueQuery,495 >;496497 498 #[pallet::storage]499 pub type IsAdmin<T: Config> = StorageNMap<500 Key = (501 Key<Blake2_128Concat, CollectionId>,502 Key<Blake2_128Concat, T::CrossAccountId>,503 ),504 Value = bool,505 QueryKind = ValueQuery,506 >;507508 509 #[pallet::storage]510 pub type Allowlist<T: Config> = StorageNMap<511 Key = (512 Key<Blake2_128Concat, CollectionId>,513 Key<Blake2_128Concat, T::CrossAccountId>,514 ),515 Value = bool,516 QueryKind = ValueQuery,517 >;518519 520 #[pallet::storage]521 pub type DummyStorageValue<T: Config> = StorageValue<522 Value = (523 CollectionStats,524 CollectionId,525 TokenId,526 TokenChild,527 PhantomType<(528 TokenData<T::CrossAccountId>,529 RpcCollection<T::AccountId>,530 531 RmrkCollectionInfo<T::AccountId>,532 RmrkInstanceInfo<T::AccountId>,533 RmrkResourceInfo,534 RmrkPropertyInfo,535 RmrkBaseInfo<T::AccountId>,536 RmrkPartType,537 RmrkTheme,538 RmrkNftChild,539 )>,540 ),541 QueryKind = OptionQuery,542 >;543544 #[pallet::hooks]545 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {546 fn on_runtime_upgrade() -> Weight {547 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {548 use up_data_structs::{CollectionVersion1, CollectionVersion2};549 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {550 let mut props = Vec::new();551 if !v.offchain_schema.is_empty() {552 props.push(Property {553 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),554 value: v555 .offchain_schema556 .clone()557 .into_inner()558 .try_into()559 .expect("offchain schema too big"),560 });561 }562 if !v.variable_on_chain_schema.is_empty() {563 props.push(Property {564 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),565 value: v566 .variable_on_chain_schema567 .clone()568 .into_inner()569 .try_into()570 .expect("offchain schema too big"),571 });572 }573 if !v.const_on_chain_schema.is_empty() {574 props.push(Property {575 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),576 value: v577 .const_on_chain_schema578 .clone()579 .into_inner()580 .try_into()581 .expect("offchain schema too big"),582 });583 }584 props.push(Property {585 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),586 value: match v.schema_version {587 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),588 SchemaVersion::Unique => b"Unique".as_slice(),589 }590 .to_vec()591 .try_into()592 .unwrap(),593 });594 Self::set_scoped_collection_properties(595 id,596 PropertyScope::None,597 props.into_iter(),598 )599 .expect("existing data larger than properties");600 let mut new = CollectionVersion2::from(v.clone());601 new.permissions.access = Some(v.access);602 new.permissions.mint_mode = Some(v.mint_mode);603 Some(new)604 });605 }606607 0608 }609 }610}611612impl<T: Config> Pallet<T> {613 614 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {615 ensure!(616 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,617 <Error<T>>::AddressIsZero618 );619 Ok(())620 }621 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {622 <IsAdmin<T>>::iter_prefix((collection,))623 .map(|(a, _)| a)624 .collect()625 }626 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {627 <Allowlist<T>>::iter_prefix((collection,))628 .map(|(a, _)| a)629 .collect()630 }631 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {632 <Allowlist<T>>::get((collection, user))633 }634 pub fn collection_stats() -> CollectionStats {635 let created = <CreatedCollectionCount<T>>::get();636 let destroyed = <DestroyedCollectionCount<T>>::get();637 CollectionStats {638 created: created.0,639 destroyed: destroyed.0,640 alive: created.0 - destroyed.0,641 }642 }643644 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {645 let collection = <CollectionById<T>>::get(collection);646 if collection.is_none() {647 return None;648 }649650 let collection = collection.unwrap();651 let limits = collection.limits;652 let effective_limits = CollectionLimits {653 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),654 sponsored_data_size: Some(limits.sponsored_data_size()),655 sponsored_data_rate_limit: Some(656 limits657 .sponsored_data_rate_limit658 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),659 ),660 token_limit: Some(limits.token_limit()),661 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(662 match collection.mode {663 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,664 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,665 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,666 },667 )),668 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),669 owner_can_transfer: Some(limits.owner_can_transfer()),670 owner_can_destroy: Some(limits.owner_can_destroy()),671 transfers_enabled: Some(limits.transfers_enabled()),672 };673674 Some(effective_limits)675 }676677 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {678 let Collection {679 name,680 description,681 owner,682 mode,683 token_prefix,684 sponsorship,685 limits,686 permissions,687 read_only,688 } = <CollectionById<T>>::get(collection)?;689690 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)691 .into_iter()692 .map(|(key, permission)| PropertyKeyPermission { key, permission })693 .collect();694695 let properties = <CollectionProperties<T>>::get(collection)696 .into_iter()697 .map(|(key, value)| Property { key, value })698 .collect();699700 let permissions = CollectionPermissions {701 access: Some(permissions.access()),702 mint_mode: Some(permissions.mint_mode()),703 nesting: Some(permissions.nesting().clone()),704 };705706 Some(RpcCollection {707 name: name.into_inner(),708 description: description.into_inner(),709 owner,710 mode,711 token_prefix: token_prefix.into_inner(),712 sponsorship,713 limits,714 permissions,715 token_property_permissions,716 properties,717 read_only,718 })719 }720}721722macro_rules! limit_default {723 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{724 $(725 if let Some($new) = $new.$field {726 let $old = $old.$field($($arg)?);727 let _ = $new;728 let _ = $old;729 $check730 } else {731 $new.$field = $old.$field732 }733 )*734 }};735}736macro_rules! limit_default_clone {737 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{738 $(739 if let Some($new) = $new.$field.clone() {740 let $old = $old.$field($($arg)?);741 let _ = $new;742 let _ = $old;743 $check744 } else {745 $new.$field = $old.$field.clone()746 }747 )*748 }};749}750751impl<T: Config> Pallet<T> {752 pub fn init_collection(753 owner: T::CrossAccountId,754 data: CreateCollectionData<T::AccountId>,755 ) -> Result<CollectionId, DispatchError> {756 {757 ensure!(758 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,759 Error::<T>::CollectionTokenPrefixLimitExceeded760 );761 }762763 let created_count = <CreatedCollectionCount<T>>::get()764 .0765 .checked_add(1)766 .ok_or(ArithmeticError::Overflow)?;767 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;768 let id = CollectionId(created_count);769770 771 ensure!(772 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,773 <Error<T>>::TotalCollectionsLimitExceeded774 );775776 777778 let collection = Collection {779 owner: owner.as_sub().clone(),780 name: data.name,781 mode: data.mode.clone(),782 description: data.description,783 token_prefix: data.token_prefix,784 sponsorship: data785 .pending_sponsor786 .map(SponsorshipState::Unconfirmed)787 .unwrap_or_default(),788 limits: data789 .limits790 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))791 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,792 permissions: data793 .permissions794 .map(|permissions| {795 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)796 })797 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,798 read_only: false,799 };800801 let mut collection_properties = up_data_structs::CollectionProperties::get();802 collection_properties803 .try_set_from_iter(data.properties.into_iter())804 .map_err(<Error<T>>::from)?;805806 CollectionProperties::<T>::insert(id, collection_properties);807808 let mut token_props_permissions = PropertiesPermissionMap::new();809 token_props_permissions810 .try_set_from_iter(data.token_property_permissions.into_iter())811 .map_err(<Error<T>>::from)?;812813 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);814815 816 {817 let mut imbalance =818 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();819 imbalance.subsume(820 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(821 &T::TreasuryAccountId::get(),822 T::CollectionCreationPrice::get(),823 ),824 );825 <T as Config>::Currency::settle(826 &owner.as_sub(),827 imbalance,828 WithdrawReasons::TRANSFER,829 ExistenceRequirement::KeepAlive,830 )831 .map_err(|_| Error::<T>::NotSufficientFounds)?;832 }833834 <CreatedCollectionCount<T>>::put(created_count);835 <Pallet<T>>::deposit_event(Event::CollectionCreated(836 id,837 data.mode.id(),838 owner.as_sub().clone(),839 ));840 <PalletEvm<T>>::deposit_log(841 erc::CollectionHelpersEvents::CollectionCreated {842 owner: *owner.as_eth(),843 collection_id: eth::collection_id_to_address(id),844 }845 .to_log(T::ContractAddress::get()),846 );847 <CollectionById<T>>::insert(id, collection);848 Ok(id)849 }850851 pub fn destroy_collection(852 collection: CollectionHandle<T>,853 sender: &T::CrossAccountId,854 ) -> DispatchResult {855 collection.check_is_read_only()?;856 ensure!(857 collection.limits.owner_can_destroy(),858 <Error<T>>::NoPermission,859 );860 collection.check_is_owner(sender)?;861862 let destroyed_collections = <DestroyedCollectionCount<T>>::get()863 .0864 .checked_add(1)865 .ok_or(ArithmeticError::Overflow)?;866867 868869 <DestroyedCollectionCount<T>>::put(destroyed_collections);870 <CollectionById<T>>::remove(collection.id);871 <AdminAmount<T>>::remove(collection.id);872 <IsAdmin<T>>::remove_prefix((collection.id,), None);873 <Allowlist<T>>::remove_prefix((collection.id,), None);874 <CollectionProperties<T>>::remove(collection.id);875876 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));877 Ok(())878 }879880 pub fn set_collection_property(881 collection: &CollectionHandle<T>,882 sender: &T::CrossAccountId,883 property: Property,884 ) -> DispatchResult {885 collection.check_is_read_only()?;886 collection.check_is_owner_or_admin(sender)?;887888 CollectionProperties::<T>::try_mutate(collection.id, |properties| {889 let property = property.clone();890 properties.try_set(property.key, property.value)891 })892 .map_err(<Error<T>>::from)?;893894 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));895896 Ok(())897 }898899 pub fn set_scoped_collection_property(900 collection_id: CollectionId,901 scope: PropertyScope,902 property: Property,903 ) -> DispatchResult {904 CollectionProperties::<T>::try_mutate(collection_id, |properties| {905 properties.try_scoped_set(scope, property.key, property.value)906 })907 .map_err(<Error<T>>::from)?;908909 Ok(())910 }911912 pub fn set_scoped_collection_properties(913 collection_id: CollectionId,914 scope: PropertyScope,915 properties: impl Iterator<Item = Property>,916 ) -> DispatchResult {917 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {918 stored_properties.try_scoped_set_from_iter(scope, properties)919 })920 .map_err(<Error<T>>::from)?;921922 Ok(())923 }924925 #[transactional]926 pub fn set_collection_properties(927 collection: &CollectionHandle<T>,928 sender: &T::CrossAccountId,929 properties: Vec<Property>,930 ) -> DispatchResult {931 collection.check_is_read_only()?;932933 for property in properties {934 Self::set_collection_property(collection, sender, property)?;935 }936937 Ok(())938 }939940 pub fn delete_collection_property(941 collection: &CollectionHandle<T>,942 sender: &T::CrossAccountId,943 property_key: PropertyKey,944 ) -> DispatchResult {945 collection.check_is_read_only()?;946 collection.check_is_owner_or_admin(sender)?;947948 CollectionProperties::<T>::try_mutate(collection.id, |properties| {949 properties.remove(&property_key)950 })951 .map_err(<Error<T>>::from)?;952953 Self::deposit_event(Event::CollectionPropertyDeleted(954 collection.id,955 property_key,956 ));957958 Ok(())959 }960961 #[transactional]962 pub fn delete_collection_properties(963 collection: &CollectionHandle<T>,964 sender: &T::CrossAccountId,965 property_keys: Vec<PropertyKey>,966 ) -> DispatchResult {967 collection.check_is_read_only()?;968969 for key in property_keys {970 Self::delete_collection_property(collection, sender, key)?;971 }972973 Ok(())974 }975976 977 pub fn set_property_permission_unchecked(978 collection: CollectionId,979 property_permission: PropertyKeyPermission,980 ) -> DispatchResult {981 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {982 permissions.try_set(property_permission.key, property_permission.permission)983 })984 .map_err(<Error<T>>::from)?;985 Ok(())986 }987988 pub fn set_property_permission(989 collection: &CollectionHandle<T>,990 sender: &T::CrossAccountId,991 property_permission: PropertyKeyPermission,992 ) -> DispatchResult {993 collection.check_is_read_only()?;994 collection.check_is_owner_or_admin(sender)?;995996 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);997 let current_permission = all_permissions.get(&property_permission.key);998 if matches![999 current_permission,1000 Some(PropertyPermission { mutable: false, .. })1001 ] {1002 return Err(<Error<T>>::NoPermission.into());1003 }10041005 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1006 let property_permission = property_permission.clone();1007 permissions.try_set(property_permission.key, property_permission.permission)1008 })1009 .map_err(<Error<T>>::from)?;10101011 Self::deposit_event(Event::PropertyPermissionSet(1012 collection.id,1013 property_permission.key,1014 ));10151016 Ok(())1017 }10181019 #[transactional]1020 pub fn set_property_permissions(1021 collection: &CollectionHandle<T>,1022 sender: &T::CrossAccountId,1023 property_permissions: Vec<PropertyKeyPermission>,1024 ) -> DispatchResult {1025 collection.check_is_read_only()?;10261027 for prop_pemission in property_permissions {1028 Self::set_property_permission(collection, sender, prop_pemission)?;1029 }10301031 Ok(())1032 }10331034 pub fn get_collection_property(1035 collection_id: CollectionId,1036 key: &PropertyKey,1037 ) -> Option<PropertyValue> {1038 Self::collection_properties(collection_id).get(key).cloned()1039 }10401041 pub fn bytes_keys_to_property_keys(1042 keys: Vec<Vec<u8>>,1043 ) -> Result<Vec<PropertyKey>, DispatchError> {1044 keys.into_iter()1045 .map(|key| -> Result<PropertyKey, DispatchError> {1046 key.try_into()1047 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1048 })1049 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1050 }10511052 pub fn filter_collection_properties(1053 collection_id: CollectionId,1054 keys: Option<Vec<PropertyKey>>,1055 ) -> Result<Vec<Property>, DispatchError> {1056 let properties = Self::collection_properties(collection_id);10571058 let properties = keys1059 .map(|keys| {1060 keys.into_iter()1061 .filter_map(|key| {1062 properties.get(&key).map(|value| Property {1063 key,1064 value: value.clone(),1065 })1066 })1067 .collect()1068 })1069 .unwrap_or_else(|| {1070 properties1071 .into_iter()1072 .map(|(key, value)| Property { key, value })1073 .collect()1074 });10751076 Ok(properties)1077 }10781079 pub fn filter_property_permissions(1080 collection_id: CollectionId,1081 keys: Option<Vec<PropertyKey>>,1082 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1083 let permissions = Self::property_permissions(collection_id);10841085 let key_permissions = keys1086 .map(|keys| {1087 keys.into_iter()1088 .filter_map(|key| {1089 permissions1090 .get(&key)1091 .map(|permission| PropertyKeyPermission {1092 key,1093 permission: permission.clone(),1094 })1095 })1096 .collect()1097 })1098 .unwrap_or_else(|| {1099 permissions1100 .into_iter()1101 .map(|(key, permission)| PropertyKeyPermission { key, permission })1102 .collect()1103 });11041105 Ok(key_permissions)1106 }11071108 pub fn toggle_allowlist(1109 collection: &CollectionHandle<T>,1110 sender: &T::CrossAccountId,1111 user: &T::CrossAccountId,1112 allowed: bool,1113 ) -> DispatchResult {1114 collection.check_is_read_only()?;1115 collection.check_is_owner_or_admin(sender)?;11161117 11181119 if allowed {1120 <Allowlist<T>>::insert((collection.id, user), true);1121 } else {1122 <Allowlist<T>>::remove((collection.id, user));1123 }11241125 Ok(())1126 }11271128 pub fn toggle_admin(1129 collection: &CollectionHandle<T>,1130 sender: &T::CrossAccountId,1131 user: &T::CrossAccountId,1132 admin: bool,1133 ) -> DispatchResult {1134 collection.check_is_read_only()?;1135 collection.check_is_owner_or_admin(sender)?;11361137 let was_admin = <IsAdmin<T>>::get((collection.id, user));1138 if was_admin == admin {1139 return Ok(());1140 }1141 let amount = <AdminAmount<T>>::get(collection.id);11421143 if admin {1144 let amount = amount1145 .checked_add(1)1146 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1147 ensure!(1148 amount <= Self::collection_admins_limit(),1149 <Error<T>>::CollectionAdminCountExceeded,1150 );11511152 11531154 <AdminAmount<T>>::insert(collection.id, amount);1155 <IsAdmin<T>>::insert((collection.id, user), true);1156 } else {1157 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1158 <IsAdmin<T>>::remove((collection.id, user));1159 }11601161 Ok(())1162 }11631164 pub fn clamp_limits(1165 mode: CollectionMode,1166 old_limit: &CollectionLimits,1167 mut new_limit: CollectionLimits,1168 ) -> Result<CollectionLimits, DispatchError> {1169 limit_default!(old_limit, new_limit,1170 account_token_ownership_limit => ensure!(1171 new_limit <= MAX_TOKEN_OWNERSHIP,1172 <Error<T>>::CollectionLimitBoundsExceeded,1173 ),1174 sponsored_data_size => ensure!(1175 new_limit <= CUSTOM_DATA_LIMIT,1176 <Error<T>>::CollectionLimitBoundsExceeded,1177 ),11781179 sponsored_data_rate_limit => {},1180 token_limit => ensure!(1181 old_limit >= new_limit && new_limit > 0,1182 <Error<T>>::CollectionTokenLimitExceeded1183 ),11841185 sponsor_transfer_timeout(match mode {1186 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1187 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1188 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1189 }) => ensure!(1190 new_limit <= MAX_SPONSOR_TIMEOUT,1191 <Error<T>>::CollectionLimitBoundsExceeded,1192 ),1193 sponsor_approve_timeout => {},1194 owner_can_transfer => ensure!(1195 old_limit || !new_limit,1196 <Error<T>>::OwnerPermissionsCantBeReverted,1197 ),1198 owner_can_destroy => ensure!(1199 old_limit || !new_limit,1200 <Error<T>>::OwnerPermissionsCantBeReverted,1201 ),1202 transfers_enabled => {},1203 );1204 Ok(new_limit)1205 }12061207 pub fn clamp_permissions(1208 _mode: CollectionMode,1209 old_limit: &CollectionPermissions,1210 mut new_limit: CollectionPermissions,1211 ) -> Result<CollectionPermissions, DispatchError> {1212 limit_default_clone!(old_limit, new_limit,1213 access => {},1214 mint_mode => {},1215 nesting => {},1216 );1217 Ok(new_limit)1218 }1219}12201221#[macro_export]1222macro_rules! unsupported {1223 () => {1224 Err(<Error<T>>::UnsupportedOperation.into())1225 };1226}122712281229pub trait CommonWeightInfo<CrossAccountId> {1230 fn create_item() -> Weight;1231 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1232 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1233 fn burn_item() -> Weight;1234 fn set_collection_properties(amount: u32) -> Weight;1235 fn delete_collection_properties(amount: u32) -> Weight;1236 fn set_token_properties(amount: u32) -> Weight;1237 fn delete_token_properties(amount: u32) -> Weight;1238 fn set_property_permissions(amount: u32) -> Weight;1239 fn transfer() -> Weight;1240 fn approve() -> Weight;1241 fn transfer_from() -> Weight;1242 fn burn_from() -> Weight;12431244 1245 1246 1247 1248 fn burn_recursively_self_raw() -> Weight;1249 1250 1251 1252 fn burn_recursively_breadth_raw(amount: u32) -> Weight;12531254 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1255 Self::burn_recursively_self_raw()1256 .saturating_mul(max_selfs.max(1) as u64)1257 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1258 }1259}12601261pub trait CommonCollectionOperations<T: Config> {1262 fn create_item(1263 &self,1264 sender: T::CrossAccountId,1265 to: T::CrossAccountId,1266 data: CreateItemData,1267 nesting_budget: &dyn Budget,1268 ) -> DispatchResultWithPostInfo;1269 fn create_multiple_items(1270 &self,1271 sender: T::CrossAccountId,1272 to: T::CrossAccountId,1273 data: Vec<CreateItemData>,1274 nesting_budget: &dyn Budget,1275 ) -> DispatchResultWithPostInfo;1276 fn create_multiple_items_ex(1277 &self,1278 sender: T::CrossAccountId,1279 data: CreateItemExData<T::CrossAccountId>,1280 nesting_budget: &dyn Budget,1281 ) -> DispatchResultWithPostInfo;1282 fn burn_item(1283 &self,1284 sender: T::CrossAccountId,1285 token: TokenId,1286 amount: u128,1287 ) -> DispatchResultWithPostInfo;1288 fn burn_item_recursively(1289 &self,1290 sender: T::CrossAccountId,1291 token: TokenId,1292 self_budget: &dyn Budget,1293 breadth_budget: &dyn Budget,1294 ) -> DispatchResultWithPostInfo;1295 fn set_collection_properties(1296 &self,1297 sender: T::CrossAccountId,1298 properties: Vec<Property>,1299 ) -> DispatchResultWithPostInfo;1300 fn delete_collection_properties(1301 &self,1302 sender: &T::CrossAccountId,1303 property_keys: Vec<PropertyKey>,1304 ) -> DispatchResultWithPostInfo;1305 fn set_token_properties(1306 &self,1307 sender: T::CrossAccountId,1308 token_id: TokenId,1309 property: Vec<Property>,1310 ) -> DispatchResultWithPostInfo;1311 fn delete_token_properties(1312 &self,1313 sender: T::CrossAccountId,1314 token_id: TokenId,1315 property_keys: Vec<PropertyKey>,1316 ) -> DispatchResultWithPostInfo;1317 fn set_property_permissions(1318 &self,1319 sender: &T::CrossAccountId,1320 property_permissions: Vec<PropertyKeyPermission>,1321 ) -> DispatchResultWithPostInfo;1322 fn transfer(1323 &self,1324 sender: T::CrossAccountId,1325 to: T::CrossAccountId,1326 token: TokenId,1327 amount: u128,1328 nesting_budget: &dyn Budget,1329 ) -> DispatchResultWithPostInfo;1330 fn approve(1331 &self,1332 sender: T::CrossAccountId,1333 spender: T::CrossAccountId,1334 token: TokenId,1335 amount: u128,1336 ) -> DispatchResultWithPostInfo;1337 fn transfer_from(1338 &self,1339 sender: T::CrossAccountId,1340 from: T::CrossAccountId,1341 to: T::CrossAccountId,1342 token: TokenId,1343 amount: u128,1344 nesting_budget: &dyn Budget,1345 ) -> DispatchResultWithPostInfo;1346 fn burn_from(1347 &self,1348 sender: T::CrossAccountId,1349 from: T::CrossAccountId,1350 token: TokenId,1351 amount: u128,1352 nesting_budget: &dyn Budget,1353 ) -> DispatchResultWithPostInfo;13541355 fn check_nesting(1356 &self,1357 sender: T::CrossAccountId,1358 from: (CollectionId, TokenId),1359 under: TokenId,1360 budget: &dyn Budget,1361 ) -> DispatchResult;13621363 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13641365 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13661367 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1368 fn collection_tokens(&self) -> Vec<TokenId>;1369 fn token_exists(&self, token: TokenId) -> bool;1370 fn last_token_id(&self) -> TokenId;13711372 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1373 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1374 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1375 1376 fn total_supply(&self) -> u32;1377 1378 fn account_balance(&self, account: T::CrossAccountId) -> u32;1379 1380 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1381 fn allowance(1382 &self,1383 sender: T::CrossAccountId,1384 spender: T::CrossAccountId,1385 token: TokenId,1386 ) -> u128;1387}138813891390pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1391 let post_info = PostDispatchInfo {1392 actual_weight: Some(weight),1393 pays_fee: Pays::Yes,1394 };1395 match res {1396 Ok(()) => Ok(post_info),1397 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1398 }1399}14001401impl<T: Config> From<PropertiesError> for Error<T> {1402 fn from(error: PropertiesError) -> Self {1403 match error {1404 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1405 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1406 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1407 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1408 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1409 }1410 }1411}