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;25use frame_support::{26 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},27 ensure,28 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},29 BoundedVec,30 weights::Pays,31 transactional,32};33use pallet_evm::GasWeightMapping;34use up_data_structs::{35 COLLECTION_NUMBER_LIMIT,36 Collection,37 RpcCollection,38 CollectionId,39 CreateItemData,40 MAX_TOKEN_PREFIX_LENGTH,41 COLLECTION_ADMINS_LIMIT,42 TokenId,43 CollectionStats,44 MAX_TOKEN_OWNERSHIP,45 CollectionMode,46 NFT_SPONSOR_TRANSFER_TIMEOUT,47 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,48 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 MAX_SPONSOR_TIMEOUT,50 CUSTOM_DATA_LIMIT,51 CollectionLimits,52 CreateCollectionData,53 SponsorshipState,54 CreateItemExData,55 SponsoringRateLimit,56 budget::Budget,57 COLLECTION_FIELD_LIMIT,58 PhantomType,59 Property,60 Properties,61 PropertiesPermissionMap,62 PropertyKey,63 PropertyValue,64 PropertyPermission,65 PropertiesError,66 PropertyKeyPermission,67 TokenData,68 TrySetProperty,69 PropertyScope,70 71 RmrkCollectionInfo,72 RmrkInstanceInfo,73 RmrkResourceInfo,74 RmrkPropertyInfo,75 RmrkBaseInfo,76 RmrkPartType,77 RmrkTheme,78 RmrkNftChild,79 CollectionPermissions,80 SchemaVersion,81};8283pub use pallet::*;84use sp_core::H160;85use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};86#[cfg(feature = "runtime-benchmarks")]87pub mod benchmarking;88pub mod dispatch;89pub mod erc;90pub mod eth;91pub mod weights;9293pub type SelfWeightOf<T> = <T as Config>::WeightInfo;9495#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]96pub struct CollectionHandle<T: Config> {97 pub id: CollectionId,98 collection: Collection<T::AccountId>,99 pub recorder: SubstrateRecorder<T>,100}101impl<T: Config> WithRecorder<T> for CollectionHandle<T> {102 fn recorder(&self) -> &SubstrateRecorder<T> {103 &self.recorder104 }105 fn into_recorder(self) -> SubstrateRecorder<T> {106 self.recorder107 }108}109impl<T: Config> CollectionHandle<T> {110 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {111 <CollectionById<T>>::get(id).map(|collection| Self {112 id,113 collection,114 recorder: SubstrateRecorder::new(gas_limit),115 })116 }117 pub fn new(id: CollectionId) -> Option<Self> {118 Self::new_with_gas_limit(id, u64::MAX)119 }120 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {121 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)122 }123 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {124 self.recorder125 .consume_gas(T::GasWeightMapping::weight_to_gas(126 <T as frame_system::Config>::DbWeight::get()127 .read128 .saturating_mul(reads),129 ))130 }131 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {132 self.recorder133 .consume_gas(T::GasWeightMapping::weight_to_gas(134 <T as frame_system::Config>::DbWeight::get()135 .write136 .saturating_mul(writes),137 ))138 }139 pub fn save(self) -> DispatchResult {140 <CollectionById<T>>::insert(self.id, self.collection);141 Ok(())142 }143}144impl<T: Config> Deref for CollectionHandle<T> {145 type Target = Collection<T::AccountId>;146147 fn deref(&self) -> &Self::Target {148 &self.collection149 }150}151152impl<T: Config> DerefMut for CollectionHandle<T> {153 fn deref_mut(&mut self) -> &mut Self::Target {154 &mut self.collection155 }156}157158impl<T: Config> CollectionHandle<T> {159 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {160 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);161 Ok(())162 }163 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {164 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))165 }166 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {167 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);168 Ok(())169 }170 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {171 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)172 }173 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {174 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)175 }176 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {177 ensure!(178 <Allowlist<T>>::get((self.id, user)),179 <Error<T>>::AddressNotInAllowlist180 );181 Ok(())182 }183}184185#[frame_support::pallet]186pub mod pallet {187 use super::*;188 use pallet_evm::account;189 use dispatch::CollectionDispatch;190 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};191 use frame_system::pallet_prelude::*;192 use frame_support::traits::Currency;193 use up_data_structs::{TokenId, mapping::TokenAddressMapping};194 use scale_info::TypeInfo;195 use weights::WeightInfo;196197 #[pallet::config]198 pub trait Config:199 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config200 {201 type WeightInfo: WeightInfo;202 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;203204 type Currency: Currency<Self::AccountId>;205206 #[pallet::constant]207 type CollectionCreationPrice: Get<208 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,209 >;210 type CollectionDispatch: CollectionDispatch<Self>;211212 type TreasuryAccountId: Get<Self::AccountId>;213214 type EvmTokenAddressMapping: TokenAddressMapping<H160>;215 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;216 }217218 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);219220 #[pallet::pallet]221 #[pallet::storage_version(STORAGE_VERSION)]222 #[pallet::generate_store(pub(super) trait Store)]223 pub struct Pallet<T>(_);224225 #[pallet::extra_constants]226 impl<T: Config> Pallet<T> {227 pub fn collection_admins_limit() -> u32 {228 COLLECTION_ADMINS_LIMIT229 }230 }231232 #[pallet::event]233 #[pallet::generate_deposit(pub fn deposit_event)]234 pub enum Event<T: Config> {235 236 237 238 239 240 241 242 243 244 CollectionCreated(CollectionId, u8, T::AccountId),245246 247 248 249 250 251 CollectionDestroyed(CollectionId),252253 254 255 256 257 258 259 260 261 262 263 264 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),265266 267 268 269 270 271 272 273 274 275 276 277 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),278279 280 281 282 283 284 285 286 287 288 289 290 Transfer(291 CollectionId,292 TokenId,293 T::CrossAccountId,294 T::CrossAccountId,295 u128,296 ),297298 299 300 301 302 303 304 305 306 307 Approved(308 CollectionId,309 TokenId,310 T::CrossAccountId,311 T::CrossAccountId,312 u128,313 ),314315 CollectionPropertySet(CollectionId, PropertyKey),316317 CollectionPropertyDeleted(CollectionId, PropertyKey),318319 TokenPropertySet(CollectionId, TokenId, PropertyKey),320321 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),322323 PropertyPermissionSet(CollectionId, PropertyKey),324 }325326 #[pallet::error]327 pub enum Error<T> {328 329 CollectionNotFound,330 331 MustBeTokenOwner,332 333 NoPermission,334 335 CantDestroyNotEmptyCollection,336 337 PublicMintingNotAllowed,338 339 AddressNotInAllowlist,340341 342 CollectionNameLimitExceeded,343 344 CollectionDescriptionLimitExceeded,345 346 CollectionTokenPrefixLimitExceeded,347 348 TotalCollectionsLimitExceeded,349 350 CollectionAdminCountExceeded,351 352 CollectionLimitBoundsExceeded,353 354 OwnerPermissionsCantBeReverted,355 356 TransferNotAllowed,357 358 AccountTokenLimitExceeded,359 360 CollectionTokenLimitExceeded,361 362 MetadataFlagFrozen,363364 365 TokenNotFound,366 367 TokenValueTooLow,368 369 ApprovedValueTooLow,370 371 CantApproveMoreThanOwned,372373 374 AddressIsZero,375 376 UnsupportedOperation,377378 379 NotSufficientFounds,380381 382 NestingIsDisabled,383 384 OnlyOwnerAllowedToNest,385 386 SourceCollectionIsNotAllowedToNest,387388 389 CollectionFieldSizeExceeded,390391 392 NoSpaceForProperty,393394 395 PropertyLimitReached,396397 398 PropertyKeyIsTooLong,399400 401 InvalidCharacterInPropertyKey,402403 404 EmptyPropertyKey,405 }406407 #[pallet::storage]408 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;409 #[pallet::storage]410 pub type DestroyedCollectionCount<T> =411 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;412413 414 #[pallet::storage]415 pub type CollectionById<T> = StorageMap<416 Hasher = Blake2_128Concat,417 Key = CollectionId,418 Value = Collection<<T as frame_system::Config>::AccountId>,419 QueryKind = OptionQuery,420 >;421422 423 #[pallet::storage]424 #[pallet::getter(fn collection_properties)]425 pub type CollectionProperties<T> = StorageMap<426 Hasher = Blake2_128Concat,427 Key = CollectionId,428 Value = Properties,429 QueryKind = ValueQuery,430 OnEmpty = up_data_structs::CollectionProperties,431 >;432433 #[pallet::storage]434 #[pallet::getter(fn property_permissions)]435 pub type CollectionPropertyPermissions<T> = StorageMap<436 Hasher = Blake2_128Concat,437 Key = CollectionId,438 Value = PropertiesPermissionMap,439 QueryKind = ValueQuery,440 >;441442 #[pallet::storage]443 pub type AdminAmount<T> = StorageMap<444 Hasher = Blake2_128Concat,445 Key = CollectionId,446 Value = u32,447 QueryKind = ValueQuery,448 >;449450 451 #[pallet::storage]452 pub type IsAdmin<T: Config> = StorageNMap<453 Key = (454 Key<Blake2_128Concat, CollectionId>,455 Key<Blake2_128Concat, T::CrossAccountId>,456 ),457 Value = bool,458 QueryKind = ValueQuery,459 >;460461 462 #[pallet::storage]463 pub type Allowlist<T: Config> = StorageNMap<464 Key = (465 Key<Blake2_128Concat, CollectionId>,466 Key<Blake2_128Concat, T::CrossAccountId>,467 ),468 Value = bool,469 QueryKind = ValueQuery,470 >;471472 473 #[pallet::storage]474 pub type DummyStorageValue<T: Config> = StorageValue<475 Value = (476 CollectionStats,477 CollectionId,478 TokenId,479 PhantomType<TokenData<T::CrossAccountId>>,480 PhantomType<RpcCollection<T::AccountId>>,481 482 PhantomType<RmrkCollectionInfo<T::AccountId>>,483 PhantomType<RmrkInstanceInfo<T::AccountId>>,484 PhantomType<RmrkResourceInfo>,485 PhantomType<RmrkPropertyInfo>,486 PhantomType<RmrkBaseInfo<T::AccountId>>,487 PhantomType<RmrkPartType>,488 PhantomType<RmrkTheme>,489 PhantomType<RmrkNftChild>,490 ),491 QueryKind = OptionQuery,492 >;493494 #[pallet::hooks]495 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {496 fn on_runtime_upgrade() -> Weight {497 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {498 use up_data_structs::{CollectionVersion1, CollectionVersion2};499 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {500 let mut props = Vec::new();501 if !v.offchain_schema.is_empty() {502 props.push(Property {503 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),504 value: v.offchain_schema.clone().into_inner().try_into().expect("offchain schema too big"),505 });506 }507 if !v.variable_on_chain_schema.is_empty() {508 props.push(Property {509 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),510 value: v.variable_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),511 });512 }513 if !v.const_on_chain_schema.is_empty() {514 props.push(Property {515 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),516 value: v.const_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),517 });518 }519 props.push(Property {520 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),521 value: match v.schema_version {522 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),523 SchemaVersion::Unique => b"Unique".as_slice(),524 }.to_vec().try_into().unwrap(),525 });526 Self::set_scoped_collection_properties(527 id,528 PropertyScope::None,529 props.into_iter(),530 ).expect("existing data larger than properties");531 let mut new = CollectionVersion2::from(v.clone());532 new.permissions.access = Some(v.access);533 new.permissions.mint_mode = Some(v.mint_mode);534 Some(new)535 });536 }537538 0539 }540 }541}542543impl<T: Config> Pallet<T> {544 545 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {546 ensure!(547 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,548 <Error<T>>::AddressIsZero549 );550 Ok(())551 }552 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {553 <IsAdmin<T>>::iter_prefix((collection,))554 .map(|(a, _)| a)555 .collect()556 }557 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {558 <Allowlist<T>>::iter_prefix((collection,))559 .map(|(a, _)| a)560 .collect()561 }562 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {563 <Allowlist<T>>::get((collection, user))564 }565 pub fn collection_stats() -> CollectionStats {566 let created = <CreatedCollectionCount<T>>::get();567 let destroyed = <DestroyedCollectionCount<T>>::get();568 CollectionStats {569 created: created.0,570 destroyed: destroyed.0,571 alive: created.0 - destroyed.0,572 }573 }574575 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {576 let collection = <CollectionById<T>>::get(collection);577 if collection.is_none() {578 return None;579 }580581 let collection = collection.unwrap();582 let limits = collection.limits;583 let effective_limits = CollectionLimits {584 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),585 sponsored_data_size: Some(limits.sponsored_data_size()),586 sponsored_data_rate_limit: Some(587 limits588 .sponsored_data_rate_limit589 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),590 ),591 token_limit: Some(limits.token_limit()),592 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(593 match collection.mode {594 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,595 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,596 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,597 },598 )),599 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),600 owner_can_transfer: Some(limits.owner_can_transfer()),601 owner_can_destroy: Some(limits.owner_can_destroy()),602 transfers_enabled: Some(limits.transfers_enabled()),603 };604605 Some(effective_limits)606 }607608 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {609 let Collection {610 name,611 description,612 owner,613 mode,614 token_prefix,615 sponsorship,616 limits,617 permissions,618 } = <CollectionById<T>>::get(collection)?;619620 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)621 .into_iter()622 .map(|(key, permission)| PropertyKeyPermission {623 key,624 permission,625 })626 .collect();627628 let properties = <CollectionProperties<T>>::get(collection)629 .into_iter()630 .map(|(key, value)| Property {631 key,632 value,633 })634 .collect();635636 Some(RpcCollection {637 name: name.into_inner(),638 description: description.into_inner(),639 owner,640 mode,641 token_prefix: token_prefix.into_inner(),642 sponsorship,643 limits,644 permissions,645 token_property_permissions,646 properties,647 })648 }649}650651macro_rules! limit_default {652 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{653 $(654 if let Some($new) = $new.$field {655 let $old = $old.$field($($arg)?);656 let _ = $new;657 let _ = $old;658 $check659 } else {660 $new.$field = $old.$field661 }662 )*663 }};664}665macro_rules! limit_default_clone {666 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{667 $(668 if let Some($new) = $new.$field.clone() {669 let $old = $old.$field($($arg)?);670 let _ = $new;671 let _ = $old;672 $check673 } else {674 $new.$field = $old.$field.clone()675 }676 )*677 }};678}679680impl<T: Config> Pallet<T> {681 pub fn init_collection(682 owner: T::AccountId,683 data: CreateCollectionData<T::AccountId>,684 ) -> Result<CollectionId, DispatchError> {685 {686 ensure!(687 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,688 Error::<T>::CollectionTokenPrefixLimitExceeded689 );690 }691692 let created_count = <CreatedCollectionCount<T>>::get()693 .0694 .checked_add(1)695 .ok_or(ArithmeticError::Overflow)?;696 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;697 let id = CollectionId(created_count);698699 700 ensure!(701 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,702 <Error<T>>::TotalCollectionsLimitExceeded703 );704705 706707 let collection = Collection {708 owner: owner.clone(),709 name: data.name,710 mode: data.mode.clone(),711 description: data.description,712 token_prefix: data.token_prefix,713 sponsorship: data714 .pending_sponsor715 .map(SponsorshipState::Unconfirmed)716 .unwrap_or_default(),717 limits: data718 .limits719 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))720 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,721 permissions: data722 .permissions723 .map(|permissions| Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions))724 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,725 };726727 let mut collection_properties = up_data_structs::CollectionProperties::get();728 collection_properties729 .try_set_from_iter(data.properties.into_iter())730 .map_err(<Error<T>>::from)?;731732 CollectionProperties::<T>::insert(id, collection_properties);733734 let mut token_props_permissions = PropertiesPermissionMap::new();735 token_props_permissions736 .try_set_from_iter(data.token_property_permissions.into_iter())737 .map_err(<Error<T>>::from)?;738739 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);740741 742 {743 let mut imbalance =744 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();745 imbalance.subsume(746 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(747 &T::TreasuryAccountId::get(),748 T::CollectionCreationPrice::get(),749 ),750 );751 <T as Config>::Currency::settle(752 &owner,753 imbalance,754 WithdrawReasons::TRANSFER,755 ExistenceRequirement::KeepAlive,756 )757 .map_err(|_| Error::<T>::NotSufficientFounds)?;758 }759760 <CreatedCollectionCount<T>>::put(created_count);761 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));762 <CollectionById<T>>::insert(id, collection);763 Ok(id)764 }765766 pub fn destroy_collection(767 collection: CollectionHandle<T>,768 sender: &T::CrossAccountId,769 ) -> DispatchResult {770 ensure!(771 collection.limits.owner_can_destroy(),772 <Error<T>>::NoPermission,773 );774 collection.check_is_owner(sender)?;775776 let destroyed_collections = <DestroyedCollectionCount<T>>::get()777 .0778 .checked_add(1)779 .ok_or(ArithmeticError::Overflow)?;780781 782783 <DestroyedCollectionCount<T>>::put(destroyed_collections);784 <CollectionById<T>>::remove(collection.id);785 <AdminAmount<T>>::remove(collection.id);786 <IsAdmin<T>>::remove_prefix((collection.id,), None);787 <Allowlist<T>>::remove_prefix((collection.id,), None);788 <CollectionProperties<T>>::remove(collection.id);789790 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));791 Ok(())792 }793794 pub fn set_collection_property(795 collection: &CollectionHandle<T>,796 sender: &T::CrossAccountId,797 property: Property,798 ) -> DispatchResult {799 collection.check_is_owner_or_admin(sender)?;800801 CollectionProperties::<T>::try_mutate(collection.id, |properties| {802 let property = property.clone();803 properties.try_set(property.key, property.value)804 })805 .map_err(<Error<T>>::from)?;806807 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));808809 Ok(())810 }811812 pub fn set_scoped_collection_property(813 collection_id: CollectionId,814 scope: PropertyScope,815 property: Property,816 ) -> DispatchResult {817 CollectionProperties::<T>::try_mutate(collection_id, |properties| {818 properties.try_scoped_set(scope, property.key, property.value)819 })820 .map_err(<Error<T>>::from)?;821822 Ok(())823 }824825 pub fn set_scoped_collection_properties(826 collection_id: CollectionId,827 scope: PropertyScope,828 properties: impl Iterator<Item = Property>,829 ) -> DispatchResult {830 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {831 stored_properties.try_scoped_set_from_iter(scope, properties)832 })833 .map_err(<Error<T>>::from)?;834835 Ok(())836 }837838 #[transactional]839 pub fn set_collection_properties(840 collection: &CollectionHandle<T>,841 sender: &T::CrossAccountId,842 properties: Vec<Property>,843 ) -> DispatchResult {844 for property in properties {845 Self::set_collection_property(collection, sender, property)?;846 }847848 Ok(())849 }850851 pub fn delete_collection_property(852 collection: &CollectionHandle<T>,853 sender: &T::CrossAccountId,854 property_key: PropertyKey,855 ) -> DispatchResult {856 collection.check_is_owner_or_admin(sender)?;857858 CollectionProperties::<T>::try_mutate(collection.id, |properties| {859 properties.remove(&property_key)860 })861 .map_err(<Error<T>>::from)?;862863 Self::deposit_event(Event::CollectionPropertyDeleted(864 collection.id,865 property_key,866 ));867868 Ok(())869 }870871 #[transactional]872 pub fn delete_collection_properties(873 collection: &CollectionHandle<T>,874 sender: &T::CrossAccountId,875 property_keys: Vec<PropertyKey>,876 ) -> DispatchResult {877 for key in property_keys {878 Self::delete_collection_property(collection, sender, key)?;879 }880881 Ok(())882 }883884 885 pub fn set_property_permission_unchecked(886 collection: CollectionId,887 property_permission: PropertyKeyPermission,888 ) -> DispatchResult {889 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {890 permissions.try_set(property_permission.key, property_permission.permission)891 })892 .map_err(<Error<T>>::from)?;893 Ok(())894 }895896 pub fn set_property_permission(897 collection: &CollectionHandle<T>,898 sender: &T::CrossAccountId,899 property_permission: PropertyKeyPermission,900 ) -> DispatchResult {901 collection.check_is_owner_or_admin(sender)?;902903 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);904 let current_permission = all_permissions.get(&property_permission.key);905 if matches![906 current_permission,907 Some(PropertyPermission { mutable: false, .. })908 ] {909 return Err(<Error<T>>::NoPermission.into());910 }911912 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {913 let property_permission = property_permission.clone();914 permissions.try_set(property_permission.key, property_permission.permission)915 })916 .map_err(<Error<T>>::from)?;917918 Self::deposit_event(Event::PropertyPermissionSet(919 collection.id,920 property_permission.key,921 ));922923 Ok(())924 }925926 #[transactional]927 pub fn set_property_permissions(928 collection: &CollectionHandle<T>,929 sender: &T::CrossAccountId,930 property_permissions: Vec<PropertyKeyPermission>,931 ) -> DispatchResult {932 for prop_pemission in property_permissions {933 Self::set_property_permission(collection, sender, prop_pemission)?;934 }935936 Ok(())937 }938939 pub fn get_collection_property(940 collection_id: CollectionId,941 key: &PropertyKey,942 ) -> Option<PropertyValue> {943 Self::collection_properties(collection_id).get(key).cloned()944 }945946 pub fn bytes_keys_to_property_keys(947 keys: Vec<Vec<u8>>,948 ) -> Result<Vec<PropertyKey>, DispatchError> {949 keys.into_iter()950 .map(|key| -> Result<PropertyKey, DispatchError> {951 key.try_into()952 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())953 })954 .collect::<Result<Vec<PropertyKey>, DispatchError>>()955 }956957 pub fn filter_collection_properties(958 collection_id: CollectionId,959 keys: Option<Vec<PropertyKey>>,960 ) -> Result<Vec<Property>, DispatchError> {961 let properties = Self::collection_properties(collection_id);962963 let properties = keys964 .map(|keys| {965 keys.into_iter()966 .filter_map(|key| {967 properties.get(&key).map(|value| Property {968 key,969 value: value.clone(),970 })971 })972 .collect()973 })974 .unwrap_or_else(|| {975 properties976 .into_iter()977 .map(|(key, value)| Property {978 key,979 value,980 })981 .collect()982 });983984 Ok(properties)985 }986987 pub fn filter_property_permissions(988 collection_id: CollectionId,989 keys: Option<Vec<PropertyKey>>,990 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {991 let permissions = Self::property_permissions(collection_id);992993 let key_permissions = keys994 .map(|keys| {995 keys.into_iter()996 .filter_map(|key| {997 permissions998 .get(&key)999 .map(|permission| PropertyKeyPermission {1000 key,1001 permission: permission.clone(),1002 })1003 })1004 .collect()1005 })1006 .unwrap_or_else(|| {1007 permissions1008 .into_iter()1009 .map(|(key, permission)| PropertyKeyPermission {1010 key,1011 permission,1012 })1013 .collect()1014 });10151016 Ok(key_permissions)1017 }10181019 pub fn toggle_allowlist(1020 collection: &CollectionHandle<T>,1021 sender: &T::CrossAccountId,1022 user: &T::CrossAccountId,1023 allowed: bool,1024 ) -> DispatchResult {1025 collection.check_is_owner_or_admin(sender)?;10261027 10281029 if allowed {1030 <Allowlist<T>>::insert((collection.id, user), true);1031 } else {1032 <Allowlist<T>>::remove((collection.id, user));1033 }10341035 Ok(())1036 }10371038 pub fn toggle_admin(1039 collection: &CollectionHandle<T>,1040 sender: &T::CrossAccountId,1041 user: &T::CrossAccountId,1042 admin: bool,1043 ) -> DispatchResult {1044 collection.check_is_owner_or_admin(sender)?;10451046 let was_admin = <IsAdmin<T>>::get((collection.id, user));1047 if was_admin == admin {1048 return Ok(());1049 }1050 let amount = <AdminAmount<T>>::get(collection.id);10511052 if admin {1053 let amount = amount1054 .checked_add(1)1055 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1056 ensure!(1057 amount <= Self::collection_admins_limit(),1058 <Error<T>>::CollectionAdminCountExceeded,1059 );10601061 10621063 <AdminAmount<T>>::insert(collection.id, amount);1064 <IsAdmin<T>>::insert((collection.id, user), true);1065 } else {1066 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1067 <IsAdmin<T>>::remove((collection.id, user));1068 }10691070 Ok(())1071 }10721073 pub fn clamp_limits(1074 mode: CollectionMode,1075 old_limit: &CollectionLimits,1076 mut new_limit: CollectionLimits,1077 ) -> Result<CollectionLimits, DispatchError> {1078 limit_default!(old_limit, new_limit,1079 account_token_ownership_limit => ensure!(1080 new_limit <= MAX_TOKEN_OWNERSHIP,1081 <Error<T>>::CollectionLimitBoundsExceeded,1082 ),1083 sponsor_transfer_timeout(match mode {1084 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1085 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1086 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1087 }) => ensure!(1088 new_limit <= MAX_SPONSOR_TIMEOUT,1089 <Error<T>>::CollectionLimitBoundsExceeded,1090 ),1091 sponsored_data_size => ensure!(1092 new_limit <= CUSTOM_DATA_LIMIT,1093 <Error<T>>::CollectionLimitBoundsExceeded,1094 ),1095 token_limit => ensure!(1096 old_limit >= new_limit && new_limit > 0,1097 <Error<T>>::CollectionTokenLimitExceeded1098 ),1099 owner_can_transfer => ensure!(1100 old_limit || !new_limit,1101 <Error<T>>::OwnerPermissionsCantBeReverted,1102 ),1103 owner_can_destroy => ensure!(1104 old_limit || !new_limit,1105 <Error<T>>::OwnerPermissionsCantBeReverted,1106 ),1107 sponsored_data_rate_limit => {},1108 transfers_enabled => {},1109 );1110 Ok(new_limit)1111 }1112 pub fn clamp_permissions(1113 mode: CollectionMode,1114 old_limit: &CollectionPermissions,1115 mut new_limit: CollectionPermissions,1116 ) -> Result<CollectionPermissions, DispatchError> {1117 limit_default_clone!(old_limit, new_limit,1118 );1119 Ok(new_limit)1120 }1121}11221123#[macro_export]1124macro_rules! unsupported {1125 () => {1126 Err(<Error<T>>::UnsupportedOperation.into())1127 };1128}112911301131pub trait CommonWeightInfo<CrossAccountId> {1132 fn create_item() -> Weight;1133 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1134 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1135 fn burn_item() -> Weight;1136 fn set_collection_properties(amount: u32) -> Weight;1137 fn delete_collection_properties(amount: u32) -> Weight;1138 fn set_token_properties(amount: u32) -> Weight;1139 fn delete_token_properties(amount: u32) -> Weight;1140 fn set_property_permissions(amount: u32) -> Weight;1141 fn transfer() -> Weight;1142 fn approve() -> Weight;1143 fn transfer_from() -> Weight;1144 fn burn_from() -> Weight;1145}11461147pub trait CommonCollectionOperations<T: Config> {1148 fn create_item(1149 &self,1150 sender: T::CrossAccountId,1151 to: T::CrossAccountId,1152 data: CreateItemData,1153 nesting_budget: &dyn Budget,1154 ) -> DispatchResultWithPostInfo;1155 fn create_multiple_items(1156 &self,1157 sender: T::CrossAccountId,1158 to: T::CrossAccountId,1159 data: Vec<CreateItemData>,1160 nesting_budget: &dyn Budget,1161 ) -> DispatchResultWithPostInfo;1162 fn create_multiple_items_ex(1163 &self,1164 sender: T::CrossAccountId,1165 data: CreateItemExData<T::CrossAccountId>,1166 nesting_budget: &dyn Budget,1167 ) -> DispatchResultWithPostInfo;1168 fn burn_item(1169 &self,1170 sender: T::CrossAccountId,1171 token: TokenId,1172 amount: u128,1173 ) -> DispatchResultWithPostInfo;1174 fn set_collection_properties(1175 &self,1176 sender: T::CrossAccountId,1177 properties: Vec<Property>,1178 ) -> DispatchResultWithPostInfo;1179 fn delete_collection_properties(1180 &self,1181 sender: &T::CrossAccountId,1182 property_keys: Vec<PropertyKey>,1183 ) -> DispatchResultWithPostInfo;1184 fn set_token_properties(1185 &self,1186 sender: T::CrossAccountId,1187 token_id: TokenId,1188 property: Vec<Property>,1189 ) -> DispatchResultWithPostInfo;1190 fn delete_token_properties(1191 &self,1192 sender: T::CrossAccountId,1193 token_id: TokenId,1194 property_keys: Vec<PropertyKey>,1195 ) -> DispatchResultWithPostInfo;1196 fn set_property_permissions(1197 &self,1198 sender: &T::CrossAccountId,1199 property_permissions: Vec<PropertyKeyPermission>,1200 ) -> DispatchResultWithPostInfo;1201 fn transfer(1202 &self,1203 sender: T::CrossAccountId,1204 to: T::CrossAccountId,1205 token: TokenId,1206 amount: u128,1207 nesting_budget: &dyn Budget,1208 ) -> DispatchResultWithPostInfo;1209 fn approve(1210 &self,1211 sender: T::CrossAccountId,1212 spender: T::CrossAccountId,1213 token: TokenId,1214 amount: u128,1215 ) -> DispatchResultWithPostInfo;1216 fn transfer_from(1217 &self,1218 sender: T::CrossAccountId,1219 from: T::CrossAccountId,1220 to: T::CrossAccountId,1221 token: TokenId,1222 amount: u128,1223 nesting_budget: &dyn Budget,1224 ) -> DispatchResultWithPostInfo;1225 fn burn_from(1226 &self,1227 sender: T::CrossAccountId,1228 from: T::CrossAccountId,1229 token: TokenId,1230 amount: u128,1231 nesting_budget: &dyn Budget,1232 ) -> DispatchResultWithPostInfo;12331234 fn check_nesting(1235 &self,1236 sender: T::CrossAccountId,1237 from: (CollectionId, TokenId),1238 under: TokenId,1239 budget: &dyn Budget,1240 ) -> DispatchResult;12411242 fn nest(1243 &self,1244 under: TokenId,1245 to_nest: (CollectionId, TokenId)1246 );12471248 fn unnest(1249 &self,1250 under: TokenId,1251 to_nest: (CollectionId, TokenId)1252 );12531254 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1255 fn collection_tokens(&self) -> Vec<TokenId>;1256 fn token_exists(&self, token: TokenId) -> bool;1257 fn last_token_id(&self) -> TokenId;12581259 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1260 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1261 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1262 1263 fn total_supply(&self) -> u32;1264 1265 fn account_balance(&self, account: T::CrossAccountId) -> u32;1266 1267 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1268 fn allowance(1269 &self,1270 sender: T::CrossAccountId,1271 spender: T::CrossAccountId,1272 token: TokenId,1273 ) -> u128;1274}127512761277pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1278 let post_info = PostDispatchInfo {1279 actual_weight: Some(weight),1280 pays_fee: Pays::Yes,1281 };1282 match res {1283 Ok(()) => Ok(post_info),1284 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1285 }1286}12871288impl<T: Config> From<PropertiesError> for Error<T> {1289 fn from(error: PropertiesError) -> Self {1290 match error {1291 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1292 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1293 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1294 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1295 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1296 }1297 }1298}