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 pub type CollectionProperties<T> = StorageMap<392 Hasher = Blake2_128Concat,393 Key = CollectionId,394 Value = Properties,395 QueryKind = ValueQuery,396 OnEmpty = up_data_structs::CollectionProperties,397 >;398399 #[pallet::storage]400 #[pallet::getter(fn property_permission)]401 pub type CollectionPropertyPermissions<T> = StorageMap<402 Hasher = Blake2_128Concat,403 Key = CollectionId,404 Value = PropertiesPermissionMap,405 QueryKind = ValueQuery,406 >;407408 409 #[pallet::storage]410 pub type CollectionData<T> = StorageNMap<411 Key = (412 Key<Twox64Concat, CollectionId>,413 Key<Twox64Concat, CollectionField>,414 ),415 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,416 QueryKind = ValueQuery,417 >;418419 #[pallet::storage]420 pub type AdminAmount<T> = StorageMap<421 Hasher = Blake2_128Concat,422 Key = CollectionId,423 Value = u32,424 QueryKind = ValueQuery,425 >;426427 428 #[pallet::storage]429 pub type IsAdmin<T: Config> = StorageNMap<430 Key = (431 Key<Blake2_128Concat, CollectionId>,432 Key<Blake2_128Concat, T::CrossAccountId>,433 ),434 Value = bool,435 QueryKind = ValueQuery,436 >;437438 439 #[pallet::storage]440 pub type Allowlist<T: Config> = StorageNMap<441 Key = (442 Key<Blake2_128Concat, CollectionId>,443 Key<Blake2_128Concat, T::CrossAccountId>,444 ),445 Value = bool,446 QueryKind = ValueQuery,447 >;448449 450 #[pallet::storage]451 pub type DummyStorageValue<T: Config> = StorageValue<452 Value = (453 CollectionStats,454 CollectionId,455 TokenId,456 PhantomType<RpcCollection<T::AccountId>>,457 ),458 QueryKind = OptionQuery,459 >;460461 #[pallet::hooks]462 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {463 fn on_runtime_upgrade() -> Weight {464 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {465 use up_data_structs::{CollectionVersion1, CollectionVersion2};466 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {467 Self::set_field_raw(468 id,469 CollectionField::OffchainSchema,470 v.offchain_schema.clone().into_inner(),471 )472 .expect("data has lower bounds than field");473 Self::set_field_raw(474 id,475 CollectionField::VariableOnChainSchema,476 v.variable_on_chain_schema.clone().into_inner(),477 )478 .expect("data has lower bounds than field");479 Self::set_field_raw(480 id,481 CollectionField::ConstOnChainSchema,482 v.const_on_chain_schema.clone().into_inner(),483 )484 .expect("data has lower bounds than field");485486 Some(CollectionVersion2::from(v))487 });488 }489490 0491 }492 }493}494495impl<T: Config> Pallet<T> {496 497 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {498 ensure!(499 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,500 <Error<T>>::AddressIsZero501 );502 Ok(())503 }504 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {505 <IsAdmin<T>>::iter_prefix((collection,))506 .map(|(a, _)| a)507 .collect()508 }509 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {510 <Allowlist<T>>::iter_prefix((collection,))511 .map(|(a, _)| a)512 .collect()513 }514 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {515 <Allowlist<T>>::get((collection, user))516 }517 pub fn collection_stats() -> CollectionStats {518 let created = <CreatedCollectionCount<T>>::get();519 let destroyed = <DestroyedCollectionCount<T>>::get();520 CollectionStats {521 created: created.0,522 destroyed: destroyed.0,523 alive: created.0 - destroyed.0,524 }525 }526527 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {528 let collection = <CollectionById<T>>::get(collection);529 if collection.is_none() {530 return None;531 }532533 let collection = collection.unwrap();534 let limits = collection.limits;535 let effective_limits = CollectionLimits {536 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),537 sponsored_data_size: Some(limits.sponsored_data_size()),538 sponsored_data_rate_limit: Some(539 limits540 .sponsored_data_rate_limit541 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),542 ),543 token_limit: Some(limits.token_limit()),544 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(545 match collection.mode {546 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,547 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,548 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,549 },550 )),551 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),552 owner_can_transfer: Some(limits.owner_can_transfer()),553 owner_can_destroy: Some(limits.owner_can_destroy()),554 transfers_enabled: Some(limits.transfers_enabled()),555 nesting_rule: Some(limits.nesting_rule().clone()),556 };557558 Some(effective_limits)559 }560561 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {562 let Collection {563 name,564 description,565 owner,566 mode,567 access,568 token_prefix,569 mint_mode,570 schema_version,571 sponsorship,572 limits,573 meta_update_permission,574 ..575 } = <CollectionById<T>>::get(collection)?;576 Some(RpcCollection {577 name: name.into_inner(),578 description: description.into_inner(),579 owner,580 mode,581 access,582 token_prefix: token_prefix.into_inner(),583 mint_mode,584 schema_version,585 sponsorship,586 limits,587 meta_update_permission,588 offchain_schema: <CollectionData<T>>::get((589 collection,590 CollectionField::OffchainSchema,591 ))592 .into_inner(),593 const_on_chain_schema: <CollectionData<T>>::get((594 collection,595 CollectionField::ConstOnChainSchema,596 ))597 .into_inner(),598 variable_on_chain_schema: <CollectionData<T>>::get((599 collection,600 CollectionField::VariableOnChainSchema,601 ))602 .into_inner(),603 })604 }605}606607impl<T: Config> Pallet<T> {608 pub fn init_collection(609 owner: T::AccountId,610 data: CreateCollectionData<T::AccountId>,611 ) -> Result<CollectionId, DispatchError> {612 {613 ensure!(614 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,615 Error::<T>::CollectionTokenPrefixLimitExceeded616 );617 }618619 let created_count = <CreatedCollectionCount<T>>::get()620 .0621 .checked_add(1)622 .ok_or(ArithmeticError::Overflow)?;623 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;624 let id = CollectionId(created_count);625626 627 ensure!(628 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,629 <Error<T>>::TotalCollectionsLimitExceeded630 );631632 633634 let collection = Collection {635 owner: owner.clone(),636 name: data.name,637 mode: data.mode.clone(),638 mint_mode: false,639 access: data.access.unwrap_or_default(),640 description: data.description,641 token_prefix: data.token_prefix,642 schema_version: data.schema_version.unwrap_or_default(),643 sponsorship: data644 .pending_sponsor645 .map(SponsorshipState::Unconfirmed)646 .unwrap_or_default(),647 limits: data648 .limits649 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))650 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,651 meta_update_permission: data.meta_update_permission.unwrap_or_default(),652 653 654 };655656 CollectionProperties::<T>::insert(657 id,658 Properties::from_collection_props_vec(data.properties)659 .map_err(|e| -> Error::<T> {660 e.into()661 })?,662 );663664 let token_props_permissions: PropertiesPermissionMap = data665 .token_property_permissions666 .into_iter()667 .map(|property| (property.key, property.permission))668 .collect::<BTreeMap<_, _>>()669 .try_into()670 .map_err(|_| -> Error::<T> {671 PropertiesError::PropertyLimitReached.into()672 })?;673674 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);675676 677 {678 let mut imbalance =679 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();680 imbalance.subsume(681 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(682 &T::TreasuryAccountId::get(),683 T::CollectionCreationPrice::get(),684 ),685 );686 <T as Config>::Currency::settle(687 &owner,688 imbalance,689 WithdrawReasons::TRANSFER,690 ExistenceRequirement::KeepAlive,691 )692 .map_err(|_| Error::<T>::NotSufficientFounds)?;693 }694695 <CreatedCollectionCount<T>>::put(created_count);696 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));697 <CollectionById<T>>::insert(id, collection);698 Self::set_field_raw(699 id,700 CollectionField::OffchainSchema,701 data.offchain_schema.into_inner(),702 )703 .expect("data has lower bounds than field");704 Self::set_field_raw(705 id,706 CollectionField::VariableOnChainSchema,707 data.variable_on_chain_schema.into_inner(),708 )709 .expect("data has lower bounds than field");710 Self::set_field_raw(711 id,712 CollectionField::ConstOnChainSchema,713 data.const_on_chain_schema.into_inner(),714 )715 .expect("data has lower bounds than field");716 Ok(id)717 }718719 pub fn destroy_collection(720 collection: CollectionHandle<T>,721 sender: &T::CrossAccountId,722 ) -> DispatchResult {723 ensure!(724 collection.limits.owner_can_destroy(),725 <Error<T>>::NoPermission,726 );727 collection.check_is_owner(sender)?;728729 let destroyed_collections = <DestroyedCollectionCount<T>>::get()730 .0731 .checked_add(1)732 .ok_or(ArithmeticError::Overflow)?;733734 735736 <DestroyedCollectionCount<T>>::put(destroyed_collections);737 <CollectionById<T>>::remove(collection.id);738 <CollectionData<T>>::remove_prefix((collection.id,), None);739 <AdminAmount<T>>::remove(collection.id);740 <IsAdmin<T>>::remove_prefix((collection.id,), None);741 <Allowlist<T>>::remove_prefix((collection.id,), None);742743 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));744 Ok(())745 }746747 pub fn set_collection_property(748 collection: &CollectionHandle<T>,749 sender: &T::CrossAccountId,750 property: Property,751 ) -> DispatchResult {752 collection.check_is_owner_or_admin(sender)?;753754 CollectionProperties::<T>::try_mutate(collection.id, |properties| {755 properties.try_set_property(property.clone())756 })757 .map_err(|e| -> Error::<T> {758 e.into()759 })?;760761 Self::deposit_event(Event::CollectionPropertySet(collection.id, property));762763 Ok(())764 }765766 pub fn set_collection_properties(767 collection: &CollectionHandle<T>,768 sender: &T::CrossAccountId,769 properties: Vec<Property>,770 ) -> DispatchResult {771 for property in properties {772 Self::set_collection_property(collection, sender, property)?;773 }774775 Ok(())776 }777778 pub fn delete_collection_property(779 collection: &CollectionHandle<T>,780 sender: &T::CrossAccountId,781 property_key: PropertyKey,782 ) -> DispatchResult {783 collection.check_is_owner_or_admin(sender)?;784785 CollectionProperties::<T>::mutate(collection.id, |properties| {786 properties.remove_property(&property_key);787 });788789 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, property_key));790791 Ok(())792 }793794 pub fn delete_collection_properties(795 collection: &CollectionHandle<T>,796 sender: &T::CrossAccountId,797 property_keys: Vec<PropertyKey>,798 ) -> DispatchResult {799 for key in property_keys {800 Self::delete_collection_property(collection, sender, key)?;801 }802803 Ok(())804 }805806 pub fn set_property_permission(807 collection: &CollectionHandle<T>,808 sender: &T::CrossAccountId,809 property_permission: PropertyKeyPermission,810 ) -> DispatchResult {811 collection.check_is_owner_or_admin(sender)?;812813 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);814 let current_permission = all_permissions.get(&property_permission.key);815 if matches![816 current_permission,817 Some(PropertyPermission::AdminConst | PropertyPermission::ItemOwnerConst)818 ] {819 return Err(<Error<T>>::NoPermission.into());820 }821822 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {823 let property_permission = property_permission.clone();824 permissions.try_insert(property_permission.key, property_permission.permission)825 })826 .map_err(|_| -> Error::<T> {827 PropertiesError::PropertyLimitReached.into()828 })?;829830 Self::deposit_event(Event::PropertyPermissionSet(831 collection.id,832 property_permission,833 ));834835 Ok(())836 }837838 pub fn set_property_permissions(839 collection: &CollectionHandle<T>,840 sender: &T::CrossAccountId,841 property_permissions: Vec<PropertyKeyPermission>,842 ) -> DispatchResult {843 for prop_pemission in property_permissions {844 Self::set_property_permission(collection, sender, prop_pemission)?;845 }846847 Ok(())848 }849850 fn set_field_raw(851 collection_id: CollectionId,852 field: CollectionField,853 value: Vec<u8>,854 ) -> DispatchResult {855 if !value.is_empty() {856 <CollectionData<T>>::insert(857 (collection_id, field),858 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,859 )860 } else {861 <CollectionData<T>>::remove((collection_id, field));862 }863 Ok(())864 }865866 pub fn set_field(867 collection: &CollectionHandle<T>,868 sender: &T::CrossAccountId,869 field: CollectionField,870 value: Vec<u8>,871 ) -> DispatchResult {872 collection.check_is_owner_or_admin(sender)?;873874 875876 Self::set_field_raw(collection.id, field, value)877 }878879 pub fn toggle_allowlist(880 collection: &CollectionHandle<T>,881 sender: &T::CrossAccountId,882 user: &T::CrossAccountId,883 allowed: bool,884 ) -> DispatchResult {885 collection.check_is_owner_or_admin(sender)?;886887 888889 if allowed {890 <Allowlist<T>>::insert((collection.id, user), true);891 } else {892 <Allowlist<T>>::remove((collection.id, user));893 }894895 Ok(())896 }897898 pub fn toggle_admin(899 collection: &CollectionHandle<T>,900 sender: &T::CrossAccountId,901 user: &T::CrossAccountId,902 admin: bool,903 ) -> DispatchResult {904 collection.check_is_owner_or_admin(sender)?;905906 let was_admin = <IsAdmin<T>>::get((collection.id, user));907 if was_admin == admin {908 return Ok(());909 }910 let amount = <AdminAmount<T>>::get(collection.id);911912 if admin {913 let amount = amount914 .checked_add(1)915 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;916 ensure!(917 amount <= Self::collection_admins_limit(),918 <Error<T>>::CollectionAdminCountExceeded,919 );920921 922923 <AdminAmount<T>>::insert(collection.id, amount);924 <IsAdmin<T>>::insert((collection.id, user), true);925 } else {926 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));927 <IsAdmin<T>>::remove((collection.id, user));928 }929930 Ok(())931 }932933 pub fn clamp_limits(934 mode: CollectionMode,935 old_limit: &CollectionLimits,936 mut new_limit: CollectionLimits,937 ) -> Result<CollectionLimits, DispatchError> {938 macro_rules! limit_default {939 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{940 $(941 if let Some($new) = $new.$field {942 let $old = $old.$field($($arg)?);943 let _ = $new;944 let _ = $old;945 $check946 } else {947 $new.$field = $old.$field948 }949 )*950 }};951 }952953 limit_default!(old_limit, new_limit,954 account_token_ownership_limit => ensure!(955 new_limit <= MAX_TOKEN_OWNERSHIP,956 <Error<T>>::CollectionLimitBoundsExceeded,957 ),958 sponsor_transfer_timeout(match mode {959 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,960 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,961 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,962 }) => ensure!(963 new_limit <= MAX_SPONSOR_TIMEOUT,964 <Error<T>>::CollectionLimitBoundsExceeded,965 ),966 sponsored_data_size => ensure!(967 new_limit <= CUSTOM_DATA_LIMIT,968 <Error<T>>::CollectionLimitBoundsExceeded,969 ),970 token_limit => ensure!(971 old_limit >= new_limit && new_limit > 0,972 <Error<T>>::CollectionTokenLimitExceeded973 ),974 owner_can_transfer => ensure!(975 old_limit || !new_limit,976 <Error<T>>::OwnerPermissionsCantBeReverted,977 ),978 owner_can_destroy => ensure!(979 old_limit || !new_limit,980 <Error<T>>::OwnerPermissionsCantBeReverted,981 ),982 sponsored_data_rate_limit => {},983 transfers_enabled => {},984 );985 Ok(new_limit)986 }987}988989#[macro_export]990macro_rules! unsupported {991 () => {992 Err(<Error<T>>::UnsupportedOperation.into())993 };994}995996997pub trait CommonWeightInfo<CrossAccountId> {998 fn create_item() -> Weight;999 fn create_multiple_items(amount: u32) -> Weight;1000 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1001 fn burn_item() -> Weight;1002 fn set_collection_properties(amount: u32) -> Weight;1003 fn delete_collection_properties(amount: u32) -> Weight;1004 fn set_token_properties(amount: u32) -> Weight;1005 fn delete_token_properties(amount: u32) -> Weight;1006 fn set_property_permissions(amount: u32) -> Weight;1007 fn transfer() -> Weight;1008 fn approve() -> Weight;1009 fn transfer_from() -> Weight;1010 fn burn_from() -> Weight;1011 fn set_variable_metadata(bytes: u32) -> Weight;1012}10131014pub trait CommonCollectionOperations<T: Config> {1015 fn create_item(1016 &self,1017 sender: T::CrossAccountId,1018 to: T::CrossAccountId,1019 data: CreateItemData,1020 nesting_budget: &dyn Budget,1021 ) -> DispatchResultWithPostInfo;1022 fn create_multiple_items(1023 &self,1024 sender: T::CrossAccountId,1025 to: T::CrossAccountId,1026 data: Vec<CreateItemData>,1027 nesting_budget: &dyn Budget,1028 ) -> DispatchResultWithPostInfo;1029 fn create_multiple_items_ex(1030 &self,1031 sender: T::CrossAccountId,1032 data: CreateItemExData<T::CrossAccountId>,1033 nesting_budget: &dyn Budget,1034 ) -> DispatchResultWithPostInfo;1035 fn burn_item(1036 &self,1037 sender: T::CrossAccountId,1038 token: TokenId,1039 amount: u128,1040 ) -> DispatchResultWithPostInfo;1041 fn set_collection_properties(1042 &self,1043 sender: T::CrossAccountId,1044 properties: Vec<Property>,1045 ) -> DispatchResultWithPostInfo;1046 fn delete_collection_properties(1047 &self,1048 sender: &T::CrossAccountId,1049 property_keys: Vec<PropertyKey>,1050 ) -> DispatchResultWithPostInfo;1051 fn set_token_properties(1052 &self,1053 sender: T::CrossAccountId,1054 token_id: TokenId,1055 property: Vec<Property>,1056 ) -> DispatchResultWithPostInfo;1057 fn delete_token_properties(1058 &self,1059 sender: T::CrossAccountId,1060 token_id: TokenId,1061 property_keys: Vec<PropertyKey>,1062 ) -> DispatchResultWithPostInfo;1063 fn set_property_permissions(1064 &self,1065 sender: &T::CrossAccountId,1066 property_permissions: Vec<PropertyKeyPermission>,1067 ) -> DispatchResultWithPostInfo;1068 fn transfer(1069 &self,1070 sender: T::CrossAccountId,1071 to: T::CrossAccountId,1072 token: TokenId,1073 amount: u128,1074 nesting_budget: &dyn Budget,1075 ) -> DispatchResultWithPostInfo;1076 fn approve(1077 &self,1078 sender: T::CrossAccountId,1079 spender: T::CrossAccountId,1080 token: TokenId,1081 amount: u128,1082 ) -> DispatchResultWithPostInfo;1083 fn transfer_from(1084 &self,1085 sender: T::CrossAccountId,1086 from: T::CrossAccountId,1087 to: T::CrossAccountId,1088 token: TokenId,1089 amount: u128,1090 nesting_budget: &dyn Budget,1091 ) -> DispatchResultWithPostInfo;1092 fn burn_from(1093 &self,1094 sender: T::CrossAccountId,1095 from: T::CrossAccountId,1096 token: TokenId,1097 amount: u128,1098 nesting_budget: &dyn Budget,1099 ) -> DispatchResultWithPostInfo;11001101 fn set_variable_metadata(1102 &self,1103 sender: T::CrossAccountId,1104 token: TokenId,1105 data: BoundedVec<u8, CustomDataLimit>,1106 ) -> DispatchResultWithPostInfo;11071108 fn check_nesting(1109 &self,1110 sender: T::CrossAccountId,1111 from: (CollectionId, TokenId),1112 under: TokenId,1113 budget: &dyn Budget,1114 ) -> DispatchResult;11151116 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1117 fn collection_tokens(&self) -> Vec<TokenId>;1118 fn token_exists(&self, token: TokenId) -> bool;1119 fn last_token_id(&self) -> TokenId;11201121 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1122 fn const_metadata(&self, token: TokenId) -> Vec<u8>;1123 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;11241125 1126 fn total_supply(&self) -> u32;1127 1128 fn account_balance(&self, account: T::CrossAccountId) -> u32;1129 1130 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1131 fn allowance(1132 &self,1133 sender: T::CrossAccountId,1134 spender: T::CrossAccountId,1135 token: TokenId,1136 ) -> u128;1137}113811391140pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1141 let post_info = PostDispatchInfo {1142 actual_weight: Some(weight),1143 pays_fee: Pays::Yes,1144 };1145 match res {1146 Ok(()) => Ok(post_info),1147 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1148 }1149}11501151impl<T: Config> From<PropertiesError> for Error<T> {1152 fn from(error: PropertiesError) -> Self {1153 match error {1154 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1155 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1156 }1157 }1158}