1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::{vec::Vec, collections::btree_map::BTreeMap};22use pallet_evm::account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29};30use pallet_evm::GasWeightMapping;31use up_data_structs::{32 COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionId, CreateItemData,33 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId,34 CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,35 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,36 CUSTOM_DATA_LIMIT, CollectionLimits, CustomDataLimit, CreateCollectionData, SponsorshipState,37 CreateItemExData, SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField,38 PhantomType, Property, Properties, PropertiesPermissionMap, PropertyKey, PropertyPermission,39 PropertiesError, PropertyKeyPermission,40};41pub use pallet::*;42use sp_core::H160;43use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};44#[cfg(feature = "runtime-benchmarks")]45pub mod benchmarking;46pub mod dispatch;47pub mod erc;48pub mod eth;4950#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]51pub struct CollectionHandle<T: Config> {52 pub id: CollectionId,53 collection: Collection<T::AccountId>,54 pub recorder: SubstrateRecorder<T>,55}56impl<T: Config> WithRecorder<T> for CollectionHandle<T> {57 fn recorder(&self) -> &SubstrateRecorder<T> {58 &self.recorder59 }60 fn into_recorder(self) -> SubstrateRecorder<T> {61 self.recorder62 }63}64impl<T: Config> CollectionHandle<T> {65 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {66 <CollectionById<T>>::get(id).map(|collection| Self {67 id,68 collection,69 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),70 })71 }72 pub fn new(id: CollectionId) -> Option<Self> {73 Self::new_with_gas_limit(id, u64::MAX)74 }75 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {76 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)77 }78 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {79 self.recorder.log_mirrored(log)80 }81 pub fn log_direct(&self, log: impl evm_coder::ToLog) {82 self.recorder.log_direct(log)83 }84 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {85 self.recorder86 .consume_gas(T::GasWeightMapping::weight_to_gas(87 <T as frame_system::Config>::DbWeight::get()88 .read89 .saturating_mul(reads),90 ))91 }92 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {93 self.recorder94 .consume_gas(T::GasWeightMapping::weight_to_gas(95 <T as frame_system::Config>::DbWeight::get()96 .write97 .saturating_mul(writes),98 ))99 }100 pub fn submit_logs(self) {101 self.recorder.submit_logs()102 }103 pub fn save(self) -> DispatchResult {104 self.recorder.submit_logs();105 <CollectionById<T>>::insert(self.id, self.collection);106 Ok(())107 }108}109impl<T: Config> Deref for CollectionHandle<T> {110 type Target = Collection<T::AccountId>;111112 fn deref(&self) -> &Self::Target {113 &self.collection114 }115}116117impl<T: Config> DerefMut for CollectionHandle<T> {118 fn deref_mut(&mut self) -> &mut Self::Target {119 &mut self.collection120 }121}122123impl<T: Config> CollectionHandle<T> {124 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {125 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);126 Ok(())127 }128 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {129 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))130 }131 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {132 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);133 Ok(())134 }135 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {136 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)137 }138 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {139 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)140 }141 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {142 ensure!(143 <Allowlist<T>>::get((self.id, user)),144 <Error<T>>::AddressNotInAllowlist145 );146 Ok(())147 }148149 pub fn check_can_update_meta(150 &self,151 subject: &T::CrossAccountId,152 item_owner: &T::CrossAccountId,153 ) -> DispatchResult {154 match self.meta_update_permission {155 MetaUpdatePermission::ItemOwner => {156 ensure!(subject == item_owner, <Error<T>>::NoPermission);157 Ok(())158 }159 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),160 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),161 }162 }163}164165#[frame_support::pallet]166pub mod pallet {167 use super::*;168 use pallet_evm::account;169 use dispatch::CollectionDispatch;170 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};171 use frame_system::pallet_prelude::*;172 use frame_support::traits::Currency;173 use up_data_structs::{TokenId, mapping::TokenAddressMapping};174 use scale_info::TypeInfo;175176 #[pallet::config]177 pub trait Config:178 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config179 {180 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;181182 type Currency: Currency<Self::AccountId>;183184 #[pallet::constant]185 type CollectionCreationPrice: Get<186 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,187 >;188 type CollectionDispatch: CollectionDispatch<Self>;189190 type TreasuryAccountId: Get<Self::AccountId>;191192 type EvmTokenAddressMapping: TokenAddressMapping<H160>;193 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;194 }195196 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);197198 #[pallet::pallet]199 #[pallet::storage_version(STORAGE_VERSION)]200 #[pallet::generate_store(pub(super) trait Store)]201 pub struct Pallet<T>(_);202203 #[pallet::extra_constants]204 impl<T: Config> Pallet<T> {205 pub fn collection_admins_limit() -> u32 {206 COLLECTION_ADMINS_LIMIT207 }208 }209210 #[pallet::event]211 #[pallet::generate_deposit(pub fn deposit_event)]212 pub enum Event<T: Config> {213 214 215 216 217 218 219 220 221 222 CollectionCreated(CollectionId, u8, T::AccountId),223224 225 226 227 228 229 CollectionDestroyed(CollectionId),230231 232 233 234 235 236 237 238 239 240 241 242 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),243244 245 246 247 248 249 250 251 252 253 254 255 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),256257 258 259 260 261 262 263 264 265 266 267 268 Transfer(269 CollectionId,270 TokenId,271 T::CrossAccountId,272 T::CrossAccountId,273 u128,274 ),275276 277 278 279 280 281 282 283 284 285 Approved(286 CollectionId,287 TokenId,288 T::CrossAccountId,289 T::CrossAccountId,290 u128,291 ),292293 CollectionPropertySet(CollectionId, Property),294295 CollectionPropertyDeleted(CollectionId, PropertyKey),296297 TokenPropertySet(CollectionId, TokenId, Property),298299 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),300301 PropertyPermissionSet(CollectionId, PropertyKeyPermission),302 }303304 #[pallet::error]305 pub enum Error<T> {306 307 CollectionNotFound,308 309 MustBeTokenOwner,310 311 NoPermission,312 313 PublicMintingNotAllowed,314 315 AddressNotInAllowlist,316317 318 CollectionNameLimitExceeded,319 320 CollectionDescriptionLimitExceeded,321 322 CollectionTokenPrefixLimitExceeded,323 324 TotalCollectionsLimitExceeded,325 326 TokenVariableDataLimitExceeded,327 328 CollectionAdminCountExceeded,329 330 CollectionLimitBoundsExceeded,331 332 OwnerPermissionsCantBeReverted,333 334 TransferNotAllowed,335 336 AccountTokenLimitExceeded,337 338 CollectionTokenLimitExceeded,339 340 MetadataFlagFrozen,341342 343 TokenNotFound,344 345 TokenValueTooLow,346 347 ApprovedValueTooLow,348 349 CantApproveMoreThanOwned,350351 352 AddressIsZero,353 354 UnsupportedOperation,355356 357 NotSufficientFounds,358359 360 NestingIsDisabled,361 362 OnlyOwnerAllowedToNest,363 364 SourceCollectionIsNotAllowedToNest,365366 367 CollectionFieldSizeExceeded,368369 NoSpaceForProperty,370371 PropertyLimitReached,372 }373374 #[pallet::storage]375 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;376 #[pallet::storage]377 pub type DestroyedCollectionCount<T> =378 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;379380 381 #[pallet::storage]382 pub type CollectionById<T> = StorageMap<383 Hasher = Blake2_128Concat,384 Key = CollectionId,385 Value = Collection<<T as frame_system::Config>::AccountId>,386 QueryKind = OptionQuery,387 >;388389 390 #[pallet::storage]391 #[pallet::getter(fn collection_properties)]392 pub type CollectionProperties<T> = StorageMap<393 Hasher = Blake2_128Concat,394 Key = CollectionId,395 Value = Properties,396 QueryKind = ValueQuery,397 OnEmpty = up_data_structs::CollectionProperties,398 >;399400 #[pallet::storage]401 #[pallet::getter(fn property_permissions)]402 pub type CollectionPropertyPermissions<T> = StorageMap<403 Hasher = Blake2_128Concat,404 Key = CollectionId,405 Value = PropertiesPermissionMap,406 QueryKind = ValueQuery,407 >;408409 410 #[pallet::storage]411 pub type CollectionData<T> = StorageNMap<412 Key = (413 Key<Twox64Concat, CollectionId>,414 Key<Twox64Concat, CollectionField>,415 ),416 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,417 QueryKind = ValueQuery,418 >;419420 #[pallet::storage]421 pub type AdminAmount<T> = StorageMap<422 Hasher = Blake2_128Concat,423 Key = CollectionId,424 Value = u32,425 QueryKind = ValueQuery,426 >;427428 429 #[pallet::storage]430 pub type IsAdmin<T: Config> = StorageNMap<431 Key = (432 Key<Blake2_128Concat, CollectionId>,433 Key<Blake2_128Concat, T::CrossAccountId>,434 ),435 Value = bool,436 QueryKind = ValueQuery,437 >;438439 440 #[pallet::storage]441 pub type Allowlist<T: Config> = StorageNMap<442 Key = (443 Key<Blake2_128Concat, CollectionId>,444 Key<Blake2_128Concat, T::CrossAccountId>,445 ),446 Value = bool,447 QueryKind = ValueQuery,448 >;449450 451 #[pallet::storage]452 pub type DummyStorageValue<T: Config> = StorageValue<453 Value = (454 CollectionStats,455 CollectionId,456 TokenId,457 PhantomType<RpcCollection<T::AccountId>>,458 ),459 QueryKind = OptionQuery,460 >;461462 #[pallet::hooks]463 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {464 fn on_runtime_upgrade() -> Weight {465 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {466 use up_data_structs::{CollectionVersion1, CollectionVersion2};467 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {468 Self::set_field_raw(469 id,470 CollectionField::OffchainSchema,471 v.offchain_schema.clone().into_inner(),472 )473 .expect("data has lower bounds than field");474 Self::set_field_raw(475 id,476 CollectionField::VariableOnChainSchema,477 v.variable_on_chain_schema.clone().into_inner(),478 )479 .expect("data has lower bounds than field");480 Self::set_field_raw(481 id,482 CollectionField::ConstOnChainSchema,483 v.const_on_chain_schema.clone().into_inner(),484 )485 .expect("data has lower bounds than field");486487 Some(CollectionVersion2::from(v))488 });489 }490491 0492 }493 }494}495496impl<T: Config> Pallet<T> {497 498 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {499 ensure!(500 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,501 <Error<T>>::AddressIsZero502 );503 Ok(())504 }505 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {506 <IsAdmin<T>>::iter_prefix((collection,))507 .map(|(a, _)| a)508 .collect()509 }510 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {511 <Allowlist<T>>::iter_prefix((collection,))512 .map(|(a, _)| a)513 .collect()514 }515 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {516 <Allowlist<T>>::get((collection, user))517 }518 pub fn collection_stats() -> CollectionStats {519 let created = <CreatedCollectionCount<T>>::get();520 let destroyed = <DestroyedCollectionCount<T>>::get();521 CollectionStats {522 created: created.0,523 destroyed: destroyed.0,524 alive: created.0 - destroyed.0,525 }526 }527528 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {529 let collection = <CollectionById<T>>::get(collection);530 if collection.is_none() {531 return None;532 }533534 let collection = collection.unwrap();535 let limits = collection.limits;536 let effective_limits = CollectionLimits {537 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),538 sponsored_data_size: Some(limits.sponsored_data_size()),539 sponsored_data_rate_limit: Some(540 limits541 .sponsored_data_rate_limit542 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),543 ),544 token_limit: Some(limits.token_limit()),545 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(546 match collection.mode {547 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,548 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,549 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,550 },551 )),552 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),553 owner_can_transfer: Some(limits.owner_can_transfer()),554 owner_can_destroy: Some(limits.owner_can_destroy()),555 transfers_enabled: Some(limits.transfers_enabled()),556 nesting_rule: Some(limits.nesting_rule().clone()),557 };558559 Some(effective_limits)560 }561562 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {563 let Collection {564 name,565 description,566 owner,567 mode,568 access,569 token_prefix,570 mint_mode,571 schema_version,572 sponsorship,573 limits,574 meta_update_permission,575 ..576 } = <CollectionById<T>>::get(collection)?;577 Some(RpcCollection {578 name: name.into_inner(),579 description: description.into_inner(),580 owner,581 mode,582 access,583 token_prefix: token_prefix.into_inner(),584 mint_mode,585 schema_version,586 sponsorship,587 limits,588 meta_update_permission,589 offchain_schema: <CollectionData<T>>::get((590 collection,591 CollectionField::OffchainSchema,592 ))593 .into_inner(),594 const_on_chain_schema: <CollectionData<T>>::get((595 collection,596 CollectionField::ConstOnChainSchema,597 ))598 .into_inner(),599 variable_on_chain_schema: <CollectionData<T>>::get((600 collection,601 CollectionField::VariableOnChainSchema,602 ))603 .into_inner(),604 })605 }606}607608impl<T: Config> Pallet<T> {609 pub fn init_collection(610 owner: T::AccountId,611 data: CreateCollectionData<T::AccountId>,612 ) -> Result<CollectionId, DispatchError> {613 {614 ensure!(615 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,616 Error::<T>::CollectionTokenPrefixLimitExceeded617 );618 }619620 let created_count = <CreatedCollectionCount<T>>::get()621 .0622 .checked_add(1)623 .ok_or(ArithmeticError::Overflow)?;624 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;625 let id = CollectionId(created_count);626627 628 ensure!(629 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,630 <Error<T>>::TotalCollectionsLimitExceeded631 );632633 634635 let collection = Collection {636 owner: owner.clone(),637 name: data.name,638 mode: data.mode.clone(),639 mint_mode: false,640 access: data.access.unwrap_or_default(),641 description: data.description,642 token_prefix: data.token_prefix,643 schema_version: data.schema_version.unwrap_or_default(),644 sponsorship: data645 .pending_sponsor646 .map(SponsorshipState::Unconfirmed)647 .unwrap_or_default(),648 limits: data649 .limits650 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))651 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,652 meta_update_permission: data.meta_update_permission.unwrap_or_default(),653 654 655 };656657 CollectionProperties::<T>::insert(658 id,659 Properties::from_collection_props_vec(data.properties)660 .map_err(|e| -> Error<T> { e.into() })?,661 );662663 let token_props_permissions: PropertiesPermissionMap = data664 .token_property_permissions665 .into_iter()666 .map(|property| (property.key, property.permission))667 .collect::<BTreeMap<_, _>>()668 .try_into()669 .map_err(|_| -> Error<T> { PropertiesError::PropertyLimitReached.into() })?;670671 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);672673 674 {675 let mut imbalance =676 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();677 imbalance.subsume(678 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(679 &T::TreasuryAccountId::get(),680 T::CollectionCreationPrice::get(),681 ),682 );683 <T as Config>::Currency::settle(684 &owner,685 imbalance,686 WithdrawReasons::TRANSFER,687 ExistenceRequirement::KeepAlive,688 )689 .map_err(|_| Error::<T>::NotSufficientFounds)?;690 }691692 <CreatedCollectionCount<T>>::put(created_count);693 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));694 <CollectionById<T>>::insert(id, collection);695 Self::set_field_raw(696 id,697 CollectionField::OffchainSchema,698 data.offchain_schema.into_inner(),699 )700 .expect("data has lower bounds than field");701 Self::set_field_raw(702 id,703 CollectionField::VariableOnChainSchema,704 data.variable_on_chain_schema.into_inner(),705 )706 .expect("data has lower bounds than field");707 Self::set_field_raw(708 id,709 CollectionField::ConstOnChainSchema,710 data.const_on_chain_schema.into_inner(),711 )712 .expect("data has lower bounds than field");713 Ok(id)714 }715716 pub fn destroy_collection(717 collection: CollectionHandle<T>,718 sender: &T::CrossAccountId,719 ) -> DispatchResult {720 ensure!(721 collection.limits.owner_can_destroy(),722 <Error<T>>::NoPermission,723 );724 collection.check_is_owner(sender)?;725726 let destroyed_collections = <DestroyedCollectionCount<T>>::get()727 .0728 .checked_add(1)729 .ok_or(ArithmeticError::Overflow)?;730731 732733 <DestroyedCollectionCount<T>>::put(destroyed_collections);734 <CollectionById<T>>::remove(collection.id);735 <CollectionData<T>>::remove_prefix((collection.id,), None);736 <AdminAmount<T>>::remove(collection.id);737 <IsAdmin<T>>::remove_prefix((collection.id,), None);738 <Allowlist<T>>::remove_prefix((collection.id,), None);739740 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));741 Ok(())742 }743744 pub fn set_collection_property(745 collection: &CollectionHandle<T>,746 sender: &T::CrossAccountId,747 property: Property,748 ) -> DispatchResult {749 collection.check_is_owner_or_admin(sender)?;750751 CollectionProperties::<T>::try_mutate(collection.id, |properties| {752 properties.try_set_property(property.clone())753 })754 .map_err(|e| -> Error<T> { e.into() })?;755756 Self::deposit_event(Event::CollectionPropertySet(collection.id, property));757758 Ok(())759 }760761 pub fn set_collection_properties(762 collection: &CollectionHandle<T>,763 sender: &T::CrossAccountId,764 properties: Vec<Property>,765 ) -> DispatchResult {766 for property in properties {767 Self::set_collection_property(collection, sender, property)?;768 }769770 Ok(())771 }772773 pub fn delete_collection_property(774 collection: &CollectionHandle<T>,775 sender: &T::CrossAccountId,776 property_key: PropertyKey,777 ) -> DispatchResult {778 collection.check_is_owner_or_admin(sender)?;779780 CollectionProperties::<T>::mutate(collection.id, |properties| {781 properties.remove_property(&property_key);782 });783784 Self::deposit_event(Event::CollectionPropertyDeleted(785 collection.id,786 property_key,787 ));788789 Ok(())790 }791792 pub fn delete_collection_properties(793 collection: &CollectionHandle<T>,794 sender: &T::CrossAccountId,795 property_keys: Vec<PropertyKey>,796 ) -> DispatchResult {797 for key in property_keys {798 Self::delete_collection_property(collection, sender, key)?;799 }800801 Ok(())802 }803804 pub fn set_property_permission(805 collection: &CollectionHandle<T>,806 sender: &T::CrossAccountId,807 property_permission: PropertyKeyPermission,808 ) -> DispatchResult {809 collection.check_is_owner_or_admin(sender)?;810811 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);812 let current_permission = all_permissions.get(&property_permission.key);813 if matches![814 current_permission,815 Some(PropertyPermission::AdminConst | PropertyPermission::ItemOwnerConst)816 ] {817 return Err(<Error<T>>::NoPermission.into());818 }819820 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {821 let property_permission = property_permission.clone();822 permissions.try_insert(property_permission.key, property_permission.permission)823 })824 .map_err(|_| -> Error<T> { PropertiesError::PropertyLimitReached.into() })?;825826 Self::deposit_event(Event::PropertyPermissionSet(827 collection.id,828 property_permission,829 ));830831 Ok(())832 }833834 pub fn set_property_permissions(835 collection: &CollectionHandle<T>,836 sender: &T::CrossAccountId,837 property_permissions: Vec<PropertyKeyPermission>,838 ) -> DispatchResult {839 for prop_pemission in property_permissions {840 Self::set_property_permission(collection, sender, prop_pemission)?;841 }842843 Ok(())844 }845846 fn set_field_raw(847 collection_id: CollectionId,848 field: CollectionField,849 value: Vec<u8>,850 ) -> DispatchResult {851 if !value.is_empty() {852 <CollectionData<T>>::insert(853 (collection_id, field),854 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,855 )856 } else {857 <CollectionData<T>>::remove((collection_id, field));858 }859 Ok(())860 }861862 pub fn set_field(863 collection: &CollectionHandle<T>,864 sender: &T::CrossAccountId,865 field: CollectionField,866 value: Vec<u8>,867 ) -> DispatchResult {868 collection.check_is_owner_or_admin(sender)?;869870 871872 Self::set_field_raw(collection.id, field, value)873 }874875 pub fn toggle_allowlist(876 collection: &CollectionHandle<T>,877 sender: &T::CrossAccountId,878 user: &T::CrossAccountId,879 allowed: bool,880 ) -> DispatchResult {881 collection.check_is_owner_or_admin(sender)?;882883 884885 if allowed {886 <Allowlist<T>>::insert((collection.id, user), true);887 } else {888 <Allowlist<T>>::remove((collection.id, user));889 }890891 Ok(())892 }893894 pub fn toggle_admin(895 collection: &CollectionHandle<T>,896 sender: &T::CrossAccountId,897 user: &T::CrossAccountId,898 admin: bool,899 ) -> DispatchResult {900 collection.check_is_owner_or_admin(sender)?;901902 let was_admin = <IsAdmin<T>>::get((collection.id, user));903 if was_admin == admin {904 return Ok(());905 }906 let amount = <AdminAmount<T>>::get(collection.id);907908 if admin {909 let amount = amount910 .checked_add(1)911 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;912 ensure!(913 amount <= Self::collection_admins_limit(),914 <Error<T>>::CollectionAdminCountExceeded,915 );916917 918919 <AdminAmount<T>>::insert(collection.id, amount);920 <IsAdmin<T>>::insert((collection.id, user), true);921 } else {922 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));923 <IsAdmin<T>>::remove((collection.id, user));924 }925926 Ok(())927 }928929 pub fn clamp_limits(930 mode: CollectionMode,931 old_limit: &CollectionLimits,932 mut new_limit: CollectionLimits,933 ) -> Result<CollectionLimits, DispatchError> {934 macro_rules! limit_default {935 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{936 $(937 if let Some($new) = $new.$field {938 let $old = $old.$field($($arg)?);939 let _ = $new;940 let _ = $old;941 $check942 } else {943 $new.$field = $old.$field944 }945 )*946 }};947 }948949 limit_default!(old_limit, new_limit,950 account_token_ownership_limit => ensure!(951 new_limit <= MAX_TOKEN_OWNERSHIP,952 <Error<T>>::CollectionLimitBoundsExceeded,953 ),954 sponsor_transfer_timeout(match mode {955 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,956 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,957 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,958 }) => ensure!(959 new_limit <= MAX_SPONSOR_TIMEOUT,960 <Error<T>>::CollectionLimitBoundsExceeded,961 ),962 sponsored_data_size => ensure!(963 new_limit <= CUSTOM_DATA_LIMIT,964 <Error<T>>::CollectionLimitBoundsExceeded,965 ),966 token_limit => ensure!(967 old_limit >= new_limit && new_limit > 0,968 <Error<T>>::CollectionTokenLimitExceeded969 ),970 owner_can_transfer => ensure!(971 old_limit || !new_limit,972 <Error<T>>::OwnerPermissionsCantBeReverted,973 ),974 owner_can_destroy => ensure!(975 old_limit || !new_limit,976 <Error<T>>::OwnerPermissionsCantBeReverted,977 ),978 sponsored_data_rate_limit => {},979 transfers_enabled => {},980 );981 Ok(new_limit)982 }983}984985#[macro_export]986macro_rules! unsupported {987 () => {988 Err(<Error<T>>::UnsupportedOperation.into())989 };990}991992993pub trait CommonWeightInfo<CrossAccountId> {994 fn create_item() -> Weight;995 fn create_multiple_items(amount: u32) -> Weight;996 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;997 fn burn_item() -> Weight;998 fn set_collection_properties(amount: u32) -> Weight;999 fn delete_collection_properties(amount: u32) -> Weight;1000 fn set_token_properties(amount: u32) -> Weight;1001 fn delete_token_properties(amount: u32) -> Weight;1002 fn set_property_permissions(amount: u32) -> Weight;1003 fn transfer() -> Weight;1004 fn approve() -> Weight;1005 fn transfer_from() -> Weight;1006 fn burn_from() -> Weight;1007 fn set_variable_metadata(bytes: u32) -> Weight;1008}10091010pub trait CommonCollectionOperations<T: Config> {1011 fn create_item(1012 &self,1013 sender: T::CrossAccountId,1014 to: T::CrossAccountId,1015 data: CreateItemData,1016 nesting_budget: &dyn Budget,1017 ) -> DispatchResultWithPostInfo;1018 fn create_multiple_items(1019 &self,1020 sender: T::CrossAccountId,1021 to: T::CrossAccountId,1022 data: Vec<CreateItemData>,1023 nesting_budget: &dyn Budget,1024 ) -> DispatchResultWithPostInfo;1025 fn create_multiple_items_ex(1026 &self,1027 sender: T::CrossAccountId,1028 data: CreateItemExData<T::CrossAccountId>,1029 nesting_budget: &dyn Budget,1030 ) -> DispatchResultWithPostInfo;1031 fn burn_item(1032 &self,1033 sender: T::CrossAccountId,1034 token: TokenId,1035 amount: u128,1036 ) -> DispatchResultWithPostInfo;1037 fn set_collection_properties(1038 &self,1039 sender: T::CrossAccountId,1040 properties: Vec<Property>,1041 ) -> DispatchResultWithPostInfo;1042 fn delete_collection_properties(1043 &self,1044 sender: &T::CrossAccountId,1045 property_keys: Vec<PropertyKey>,1046 ) -> DispatchResultWithPostInfo;1047 fn set_token_properties(1048 &self,1049 sender: T::CrossAccountId,1050 token_id: TokenId,1051 property: Vec<Property>,1052 ) -> DispatchResultWithPostInfo;1053 fn delete_token_properties(1054 &self,1055 sender: T::CrossAccountId,1056 token_id: TokenId,1057 property_keys: Vec<PropertyKey>,1058 ) -> DispatchResultWithPostInfo;1059 fn set_property_permissions(1060 &self,1061 sender: &T::CrossAccountId,1062 property_permissions: Vec<PropertyKeyPermission>,1063 ) -> DispatchResultWithPostInfo;1064 fn transfer(1065 &self,1066 sender: T::CrossAccountId,1067 to: T::CrossAccountId,1068 token: TokenId,1069 amount: u128,1070 nesting_budget: &dyn Budget,1071 ) -> DispatchResultWithPostInfo;1072 fn approve(1073 &self,1074 sender: T::CrossAccountId,1075 spender: T::CrossAccountId,1076 token: TokenId,1077 amount: u128,1078 ) -> DispatchResultWithPostInfo;1079 fn transfer_from(1080 &self,1081 sender: T::CrossAccountId,1082 from: T::CrossAccountId,1083 to: T::CrossAccountId,1084 token: TokenId,1085 amount: u128,1086 nesting_budget: &dyn Budget,1087 ) -> DispatchResultWithPostInfo;1088 fn burn_from(1089 &self,1090 sender: T::CrossAccountId,1091 from: T::CrossAccountId,1092 token: TokenId,1093 amount: u128,1094 nesting_budget: &dyn Budget,1095 ) -> DispatchResultWithPostInfo;10961097 fn set_variable_metadata(1098 &self,1099 sender: T::CrossAccountId,1100 token: TokenId,1101 data: BoundedVec<u8, CustomDataLimit>,1102 ) -> DispatchResultWithPostInfo;11031104 fn check_nesting(1105 &self,1106 sender: T::CrossAccountId,1107 from: (CollectionId, TokenId),1108 under: TokenId,1109 budget: &dyn Budget,1110 ) -> DispatchResult;11111112 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1113 fn collection_tokens(&self) -> Vec<TokenId>;1114 fn token_exists(&self, token: TokenId) -> bool;1115 fn last_token_id(&self) -> TokenId;11161117 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1118 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1119 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;11201121 1122 fn total_supply(&self) -> u32;1123 1124 fn account_balance(&self, account: T::CrossAccountId) -> u32;1125 1126 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1127 fn allowance(1128 &self,1129 sender: T::CrossAccountId,1130 spender: T::CrossAccountId,1131 token: TokenId,1132 ) -> u128;1133}113411351136pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1137 let post_info = PostDispatchInfo {1138 actual_weight: Some(weight),1139 pays_fee: Pays::Yes,1140 };1141 match res {1142 Ok(()) => Ok(post_info),1143 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1144 }1145}11461147impl<T: Config> From<PropertiesError> for Error<T> {1148 fn from(error: PropertiesError) -> Self {1149 match error {1150 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1151 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1152 }1153 }1154}