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 }117118 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {119 <CollectionById<T>>::get(id).map(|collection| Self {120 id,121 collection,122 recorder,123 })124 }125126 pub fn new(id: CollectionId) -> Option<Self> {127 Self::new_with_gas_limit(id, u64::MAX)128 }129 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {130 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)131 }132 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {133 self.recorder134 .consume_gas(T::GasWeightMapping::weight_to_gas(135 <T as frame_system::Config>::DbWeight::get()136 .read137 .saturating_mul(reads),138 ))139 }140 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {141 self.recorder142 .consume_gas(T::GasWeightMapping::weight_to_gas(143 <T as frame_system::Config>::DbWeight::get()144 .write145 .saturating_mul(writes),146 ))147 }148 pub fn save(self) -> DispatchResult {149 <CollectionById<T>>::insert(self.id, self.collection);150 Ok(())151 }152153 pub fn set_sponsor(&mut self, sponsor: T::AccountId) {154 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);155 }156157 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {158 if self.collection.sponsorship.pending_sponsor() != Some(sender) {159 return false;160 };161162 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());163 true164 }165}166impl<T: Config> Deref for CollectionHandle<T> {167 type Target = Collection<T::AccountId>;168169 fn deref(&self) -> &Self::Target {170 &self.collection171 }172}173174impl<T: Config> DerefMut for CollectionHandle<T> {175 fn deref_mut(&mut self) -> &mut Self::Target {176 &mut self.collection177 }178}179180impl<T: Config> CollectionHandle<T> {181 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {182 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);183 Ok(())184 }185 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {186 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))187 }188 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {189 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);190 Ok(())191 }192 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {193 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)194 }195 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {196 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)197 }198 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {199 ensure!(200 <Allowlist<T>>::get((self.id, user)),201 <Error<T>>::AddressNotInAllowlist202 );203 Ok(())204 }205}206207#[frame_support::pallet]208pub mod pallet {209 use super::*;210 use pallet_evm::account;211 use dispatch::CollectionDispatch;212 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};213 use frame_system::pallet_prelude::*;214 use frame_support::traits::Currency;215 use up_data_structs::{TokenId, mapping::TokenAddressMapping};216 use scale_info::TypeInfo;217 use weights::WeightInfo;218219 #[pallet::config]220 pub trait Config:221 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config222 {223 type WeightInfo: WeightInfo;224 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;225226 type Currency: Currency<Self::AccountId>;227228 #[pallet::constant]229 type CollectionCreationPrice: Get<230 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,231 >;232 type CollectionDispatch: CollectionDispatch<Self>;233234 type TreasuryAccountId: Get<Self::AccountId>;235236 type EvmTokenAddressMapping: TokenAddressMapping<H160>;237 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;238 }239240 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);241242 #[pallet::pallet]243 #[pallet::storage_version(STORAGE_VERSION)]244 #[pallet::generate_store(pub(super) trait Store)]245 pub struct Pallet<T>(_);246247 #[pallet::extra_constants]248 impl<T: Config> Pallet<T> {249 pub fn collection_admins_limit() -> u32 {250 COLLECTION_ADMINS_LIMIT251 }252 }253254 #[pallet::event]255 #[pallet::generate_deposit(pub fn deposit_event)]256 pub enum Event<T: Config> {257 258 259 260 261 262 263 264 265 266 CollectionCreated(CollectionId, u8, T::AccountId),267268 269 270 271 272 273 CollectionDestroyed(CollectionId),274275 276 277 278 279 280 281 282 283 284 285 286 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),287288 289 290 291 292 293 294 295 296 297 298 299 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),300301 302 303 304 305 306 307 308 309 310 311 312 Transfer(313 CollectionId,314 TokenId,315 T::CrossAccountId,316 T::CrossAccountId,317 u128,318 ),319320 321 322 323 324 325 326 327 328 329 Approved(330 CollectionId,331 TokenId,332 T::CrossAccountId,333 T::CrossAccountId,334 u128,335 ),336337 CollectionPropertySet(CollectionId, PropertyKey),338339 CollectionPropertyDeleted(CollectionId, PropertyKey),340341 TokenPropertySet(CollectionId, TokenId, PropertyKey),342343 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),344345 PropertyPermissionSet(CollectionId, PropertyKey),346 }347348 #[pallet::error]349 pub enum Error<T> {350 351 CollectionNotFound,352 353 MustBeTokenOwner,354 355 NoPermission,356 357 CantDestroyNotEmptyCollection,358 359 PublicMintingNotAllowed,360 361 AddressNotInAllowlist,362363 364 CollectionNameLimitExceeded,365 366 CollectionDescriptionLimitExceeded,367 368 CollectionTokenPrefixLimitExceeded,369 370 TotalCollectionsLimitExceeded,371 372 CollectionAdminCountExceeded,373 374 CollectionLimitBoundsExceeded,375 376 OwnerPermissionsCantBeReverted,377 378 TransferNotAllowed,379 380 AccountTokenLimitExceeded,381 382 CollectionTokenLimitExceeded,383 384 MetadataFlagFrozen,385386 387 TokenNotFound,388 389 TokenValueTooLow,390 391 ApprovedValueTooLow,392 393 CantApproveMoreThanOwned,394395 396 AddressIsZero,397 398 UnsupportedOperation,399400 401 NotSufficientFounds,402403 404 NestingIsDisabled,405 406 OnlyOwnerAllowedToNest,407 408 SourceCollectionIsNotAllowedToNest,409410 411 CollectionFieldSizeExceeded,412413 414 NoSpaceForProperty,415416 417 PropertyLimitReached,418419 420 PropertyKeyIsTooLong,421422 423 InvalidCharacterInPropertyKey,424425 426 EmptyPropertyKey,427 }428429 #[pallet::storage]430 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;431 #[pallet::storage]432 pub type DestroyedCollectionCount<T> =433 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;434435 436 #[pallet::storage]437 pub type CollectionById<T> = StorageMap<438 Hasher = Blake2_128Concat,439 Key = CollectionId,440 Value = Collection<<T as frame_system::Config>::AccountId>,441 QueryKind = OptionQuery,442 >;443444 445 #[pallet::storage]446 #[pallet::getter(fn collection_properties)]447 pub type CollectionProperties<T> = StorageMap<448 Hasher = Blake2_128Concat,449 Key = CollectionId,450 Value = Properties,451 QueryKind = ValueQuery,452 OnEmpty = up_data_structs::CollectionProperties,453 >;454455 #[pallet::storage]456 #[pallet::getter(fn property_permissions)]457 pub type CollectionPropertyPermissions<T> = StorageMap<458 Hasher = Blake2_128Concat,459 Key = CollectionId,460 Value = PropertiesPermissionMap,461 QueryKind = ValueQuery,462 >;463464 #[pallet::storage]465 pub type AdminAmount<T> = StorageMap<466 Hasher = Blake2_128Concat,467 Key = CollectionId,468 Value = u32,469 QueryKind = ValueQuery,470 >;471472 473 #[pallet::storage]474 pub type IsAdmin<T: Config> = StorageNMap<475 Key = (476 Key<Blake2_128Concat, CollectionId>,477 Key<Blake2_128Concat, T::CrossAccountId>,478 ),479 Value = bool,480 QueryKind = ValueQuery,481 >;482483 484 #[pallet::storage]485 pub type Allowlist<T: Config> = StorageNMap<486 Key = (487 Key<Blake2_128Concat, CollectionId>,488 Key<Blake2_128Concat, T::CrossAccountId>,489 ),490 Value = bool,491 QueryKind = ValueQuery,492 >;493494 495 #[pallet::storage]496 pub type DummyStorageValue<T: Config> = StorageValue<497 Value = (498 CollectionStats,499 CollectionId,500 TokenId,501 PhantomType<(502 TokenData<T::CrossAccountId>,503 RpcCollection<T::AccountId>,504 505 RmrkCollectionInfo<T::AccountId>,506 RmrkInstanceInfo<T::AccountId>,507 RmrkResourceInfo,508 RmrkPropertyInfo,509 RmrkBaseInfo<T::AccountId>,510 RmrkPartType,511 RmrkTheme,512 RmrkNftChild,513 )>,514 ),515 QueryKind = OptionQuery,516 >;517518 #[pallet::hooks]519 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {520 fn on_runtime_upgrade() -> Weight {521 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {522 use up_data_structs::{CollectionVersion1, CollectionVersion2};523 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {524 let mut props = Vec::new();525 if !v.offchain_schema.is_empty() {526 props.push(Property {527 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),528 value: v529 .offchain_schema530 .clone()531 .into_inner()532 .try_into()533 .expect("offchain schema too big"),534 });535 }536 if !v.variable_on_chain_schema.is_empty() {537 props.push(Property {538 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),539 value: v540 .variable_on_chain_schema541 .clone()542 .into_inner()543 .try_into()544 .expect("offchain schema too big"),545 });546 }547 if !v.const_on_chain_schema.is_empty() {548 props.push(Property {549 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),550 value: v551 .const_on_chain_schema552 .clone()553 .into_inner()554 .try_into()555 .expect("offchain schema too big"),556 });557 }558 props.push(Property {559 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),560 value: match v.schema_version {561 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),562 SchemaVersion::Unique => b"Unique".as_slice(),563 }564 .to_vec()565 .try_into()566 .unwrap(),567 });568 Self::set_scoped_collection_properties(569 id,570 PropertyScope::None,571 props.into_iter(),572 )573 .expect("existing data larger than properties");574 let mut new = CollectionVersion2::from(v.clone());575 new.permissions.access = Some(v.access);576 new.permissions.mint_mode = Some(v.mint_mode);577 Some(new)578 });579 }580581 0582 }583 }584}585586impl<T: Config> Pallet<T> {587 588 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {589 ensure!(590 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,591 <Error<T>>::AddressIsZero592 );593 Ok(())594 }595 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {596 <IsAdmin<T>>::iter_prefix((collection,))597 .map(|(a, _)| a)598 .collect()599 }600 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {601 <Allowlist<T>>::iter_prefix((collection,))602 .map(|(a, _)| a)603 .collect()604 }605 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {606 <Allowlist<T>>::get((collection, user))607 }608 pub fn collection_stats() -> CollectionStats {609 let created = <CreatedCollectionCount<T>>::get();610 let destroyed = <DestroyedCollectionCount<T>>::get();611 CollectionStats {612 created: created.0,613 destroyed: destroyed.0,614 alive: created.0 - destroyed.0,615 }616 }617618 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {619 let collection = <CollectionById<T>>::get(collection);620 if collection.is_none() {621 return None;622 }623624 let collection = collection.unwrap();625 let limits = collection.limits;626 let effective_limits = CollectionLimits {627 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),628 sponsored_data_size: Some(limits.sponsored_data_size()),629 sponsored_data_rate_limit: Some(630 limits631 .sponsored_data_rate_limit632 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),633 ),634 token_limit: Some(limits.token_limit()),635 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(636 match collection.mode {637 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,638 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,639 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,640 },641 )),642 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),643 owner_can_transfer: Some(limits.owner_can_transfer()),644 owner_can_destroy: Some(limits.owner_can_destroy()),645 transfers_enabled: Some(limits.transfers_enabled()),646 };647648 Some(effective_limits)649 }650651 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {652 let Collection {653 name,654 description,655 owner,656 mode,657 token_prefix,658 sponsorship,659 limits,660 permissions,661 } = <CollectionById<T>>::get(collection)?;662663 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)664 .into_iter()665 .map(|(key, permission)| PropertyKeyPermission { key, permission })666 .collect();667668 let properties = <CollectionProperties<T>>::get(collection)669 .into_iter()670 .map(|(key, value)| Property { key, value })671 .collect();672673 let permissions = CollectionPermissions {674 access: Some(permissions.access()),675 mint_mode: Some(permissions.mint_mode()),676 nesting: Some(permissions.nesting().clone()),677 };678679 Some(RpcCollection {680 name: name.into_inner(),681 description: description.into_inner(),682 owner,683 mode,684 token_prefix: token_prefix.into_inner(),685 sponsorship,686 limits,687 permissions,688 token_property_permissions,689 properties,690 })691 }692}693694macro_rules! limit_default {695 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{696 $(697 if let Some($new) = $new.$field {698 let $old = $old.$field($($arg)?);699 let _ = $new;700 let _ = $old;701 $check702 } else {703 $new.$field = $old.$field704 }705 )*706 }};707}708macro_rules! limit_default_clone {709 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{710 $(711 if let Some($new) = $new.$field.clone() {712 let $old = $old.$field($($arg)?);713 let _ = $new;714 let _ = $old;715 $check716 } else {717 $new.$field = $old.$field.clone()718 }719 )*720 }};721}722723impl<T: Config> Pallet<T> {724 pub fn init_collection(725 owner: T::AccountId,726 data: CreateCollectionData<T::AccountId>,727 ) -> Result<CollectionId, DispatchError> {728 {729 ensure!(730 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,731 Error::<T>::CollectionTokenPrefixLimitExceeded732 );733 }734735 let created_count = <CreatedCollectionCount<T>>::get()736 .0737 .checked_add(1)738 .ok_or(ArithmeticError::Overflow)?;739 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;740 let id = CollectionId(created_count);741742 743 ensure!(744 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,745 <Error<T>>::TotalCollectionsLimitExceeded746 );747748 749750 let collection = Collection {751 owner: owner.clone(),752 name: data.name,753 mode: data.mode.clone(),754 description: data.description,755 token_prefix: data.token_prefix,756 sponsorship: data757 .pending_sponsor758 .map(SponsorshipState::Unconfirmed)759 .unwrap_or_default(),760 limits: data761 .limits762 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))763 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,764 permissions: data765 .permissions766 .map(|permissions| {767 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)768 })769 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,770 };771772 let mut collection_properties = up_data_structs::CollectionProperties::get();773 collection_properties774 .try_set_from_iter(data.properties.into_iter())775 .map_err(<Error<T>>::from)?;776777 CollectionProperties::<T>::insert(id, collection_properties);778779 let mut token_props_permissions = PropertiesPermissionMap::new();780 token_props_permissions781 .try_set_from_iter(data.token_property_permissions.into_iter())782 .map_err(<Error<T>>::from)?;783784 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);785786 787 {788 let mut imbalance =789 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();790 imbalance.subsume(791 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(792 &T::TreasuryAccountId::get(),793 T::CollectionCreationPrice::get(),794 ),795 );796 <T as Config>::Currency::settle(797 &owner,798 imbalance,799 WithdrawReasons::TRANSFER,800 ExistenceRequirement::KeepAlive,801 )802 .map_err(|_| Error::<T>::NotSufficientFounds)?;803 }804805 <CreatedCollectionCount<T>>::put(created_count);806 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));807 <CollectionById<T>>::insert(id, collection);808 Ok(id)809 }810811 pub fn destroy_collection(812 collection: CollectionHandle<T>,813 sender: &T::CrossAccountId,814 ) -> DispatchResult {815 ensure!(816 collection.limits.owner_can_destroy(),817 <Error<T>>::NoPermission,818 );819 collection.check_is_owner(sender)?;820821 let destroyed_collections = <DestroyedCollectionCount<T>>::get()822 .0823 .checked_add(1)824 .ok_or(ArithmeticError::Overflow)?;825826 827828 <DestroyedCollectionCount<T>>::put(destroyed_collections);829 <CollectionById<T>>::remove(collection.id);830 <AdminAmount<T>>::remove(collection.id);831 <IsAdmin<T>>::remove_prefix((collection.id,), None);832 <Allowlist<T>>::remove_prefix((collection.id,), None);833 <CollectionProperties<T>>::remove(collection.id);834835 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));836 Ok(())837 }838839 pub fn set_collection_property(840 collection: &CollectionHandle<T>,841 sender: &T::CrossAccountId,842 property: Property,843 ) -> DispatchResult {844 collection.check_is_owner_or_admin(sender)?;845846 CollectionProperties::<T>::try_mutate(collection.id, |properties| {847 let property = property.clone();848 properties.try_set(property.key, property.value)849 })850 .map_err(<Error<T>>::from)?;851852 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));853854 Ok(())855 }856857 pub fn set_scoped_collection_property(858 collection_id: CollectionId,859 scope: PropertyScope,860 property: Property,861 ) -> DispatchResult {862 CollectionProperties::<T>::try_mutate(collection_id, |properties| {863 properties.try_scoped_set(scope, property.key, property.value)864 })865 .map_err(<Error<T>>::from)?;866867 Ok(())868 }869870 pub fn set_scoped_collection_properties(871 collection_id: CollectionId,872 scope: PropertyScope,873 properties: impl Iterator<Item = Property>,874 ) -> DispatchResult {875 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {876 stored_properties.try_scoped_set_from_iter(scope, properties)877 })878 .map_err(<Error<T>>::from)?;879880 Ok(())881 }882883 #[transactional]884 pub fn set_collection_properties(885 collection: &CollectionHandle<T>,886 sender: &T::CrossAccountId,887 properties: Vec<Property>,888 ) -> DispatchResult {889 for property in properties {890 Self::set_collection_property(collection, sender, property)?;891 }892893 Ok(())894 }895896 pub fn delete_collection_property(897 collection: &CollectionHandle<T>,898 sender: &T::CrossAccountId,899 property_key: PropertyKey,900 ) -> DispatchResult {901 collection.check_is_owner_or_admin(sender)?;902903 CollectionProperties::<T>::try_mutate(collection.id, |properties| {904 properties.remove(&property_key)905 })906 .map_err(<Error<T>>::from)?;907908 Self::deposit_event(Event::CollectionPropertyDeleted(909 collection.id,910 property_key,911 ));912913 Ok(())914 }915916 #[transactional]917 pub fn delete_collection_properties(918 collection: &CollectionHandle<T>,919 sender: &T::CrossAccountId,920 property_keys: Vec<PropertyKey>,921 ) -> DispatchResult {922 for key in property_keys {923 Self::delete_collection_property(collection, sender, key)?;924 }925926 Ok(())927 }928929 930 pub fn set_property_permission_unchecked(931 collection: CollectionId,932 property_permission: PropertyKeyPermission,933 ) -> DispatchResult {934 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {935 permissions.try_set(property_permission.key, property_permission.permission)936 })937 .map_err(<Error<T>>::from)?;938 Ok(())939 }940941 pub fn set_property_permission(942 collection: &CollectionHandle<T>,943 sender: &T::CrossAccountId,944 property_permission: PropertyKeyPermission,945 ) -> DispatchResult {946 collection.check_is_owner_or_admin(sender)?;947948 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);949 let current_permission = all_permissions.get(&property_permission.key);950 if matches![951 current_permission,952 Some(PropertyPermission { mutable: false, .. })953 ] {954 return Err(<Error<T>>::NoPermission.into());955 }956957 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {958 let property_permission = property_permission.clone();959 permissions.try_set(property_permission.key, property_permission.permission)960 })961 .map_err(<Error<T>>::from)?;962963 Self::deposit_event(Event::PropertyPermissionSet(964 collection.id,965 property_permission.key,966 ));967968 Ok(())969 }970971 #[transactional]972 pub fn set_property_permissions(973 collection: &CollectionHandle<T>,974 sender: &T::CrossAccountId,975 property_permissions: Vec<PropertyKeyPermission>,976 ) -> DispatchResult {977 for prop_pemission in property_permissions {978 Self::set_property_permission(collection, sender, prop_pemission)?;979 }980981 Ok(())982 }983984 pub fn get_collection_property(985 collection_id: CollectionId,986 key: &PropertyKey,987 ) -> Option<PropertyValue> {988 Self::collection_properties(collection_id).get(key).cloned()989 }990991 pub fn bytes_keys_to_property_keys(992 keys: Vec<Vec<u8>>,993 ) -> Result<Vec<PropertyKey>, DispatchError> {994 keys.into_iter()995 .map(|key| -> Result<PropertyKey, DispatchError> {996 key.try_into()997 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())998 })999 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1000 }10011002 pub fn filter_collection_properties(1003 collection_id: CollectionId,1004 keys: Option<Vec<PropertyKey>>,1005 ) -> Result<Vec<Property>, DispatchError> {1006 let properties = Self::collection_properties(collection_id);10071008 let properties = keys1009 .map(|keys| {1010 keys.into_iter()1011 .filter_map(|key| {1012 properties.get(&key).map(|value| Property {1013 key,1014 value: value.clone(),1015 })1016 })1017 .collect()1018 })1019 .unwrap_or_else(|| {1020 properties1021 .into_iter()1022 .map(|(key, value)| Property { key, value })1023 .collect()1024 });10251026 Ok(properties)1027 }10281029 pub fn filter_property_permissions(1030 collection_id: CollectionId,1031 keys: Option<Vec<PropertyKey>>,1032 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1033 let permissions = Self::property_permissions(collection_id);10341035 let key_permissions = keys1036 .map(|keys| {1037 keys.into_iter()1038 .filter_map(|key| {1039 permissions1040 .get(&key)1041 .map(|permission| PropertyKeyPermission {1042 key,1043 permission: permission.clone(),1044 })1045 })1046 .collect()1047 })1048 .unwrap_or_else(|| {1049 permissions1050 .into_iter()1051 .map(|(key, permission)| PropertyKeyPermission { key, permission })1052 .collect()1053 });10541055 Ok(key_permissions)1056 }10571058 pub fn toggle_allowlist(1059 collection: &CollectionHandle<T>,1060 sender: &T::CrossAccountId,1061 user: &T::CrossAccountId,1062 allowed: bool,1063 ) -> DispatchResult {1064 collection.check_is_owner_or_admin(sender)?;10651066 10671068 if allowed {1069 <Allowlist<T>>::insert((collection.id, user), true);1070 } else {1071 <Allowlist<T>>::remove((collection.id, user));1072 }10731074 Ok(())1075 }10761077 pub fn toggle_admin(1078 collection: &CollectionHandle<T>,1079 sender: &T::CrossAccountId,1080 user: &T::CrossAccountId,1081 admin: bool,1082 ) -> DispatchResult {1083 collection.check_is_owner_or_admin(sender)?;10841085 let was_admin = <IsAdmin<T>>::get((collection.id, user));1086 if was_admin == admin {1087 return Ok(());1088 }1089 let amount = <AdminAmount<T>>::get(collection.id);10901091 if admin {1092 let amount = amount1093 .checked_add(1)1094 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1095 ensure!(1096 amount <= Self::collection_admins_limit(),1097 <Error<T>>::CollectionAdminCountExceeded,1098 );10991100 11011102 <AdminAmount<T>>::insert(collection.id, amount);1103 <IsAdmin<T>>::insert((collection.id, user), true);1104 } else {1105 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1106 <IsAdmin<T>>::remove((collection.id, user));1107 }11081109 Ok(())1110 }11111112 pub fn clamp_limits(1113 mode: CollectionMode,1114 old_limit: &CollectionLimits,1115 mut new_limit: CollectionLimits,1116 ) -> Result<CollectionLimits, DispatchError> {1117 limit_default!(old_limit, new_limit,1118 account_token_ownership_limit => ensure!(1119 new_limit <= MAX_TOKEN_OWNERSHIP,1120 <Error<T>>::CollectionLimitBoundsExceeded,1121 ),1122 sponsored_data_size => ensure!(1123 new_limit <= CUSTOM_DATA_LIMIT,1124 <Error<T>>::CollectionLimitBoundsExceeded,1125 ),11261127 sponsored_data_rate_limit => {},1128 token_limit => ensure!(1129 old_limit >= new_limit && new_limit > 0,1130 <Error<T>>::CollectionTokenLimitExceeded1131 ),11321133 sponsor_transfer_timeout(match mode {1134 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1135 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1136 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1137 }) => ensure!(1138 new_limit <= MAX_SPONSOR_TIMEOUT,1139 <Error<T>>::CollectionLimitBoundsExceeded,1140 ),1141 sponsor_approve_timeout => {},1142 owner_can_transfer => ensure!(1143 old_limit || !new_limit,1144 <Error<T>>::OwnerPermissionsCantBeReverted,1145 ),1146 owner_can_destroy => ensure!(1147 old_limit || !new_limit,1148 <Error<T>>::OwnerPermissionsCantBeReverted,1149 ),1150 transfers_enabled => {},1151 );1152 Ok(new_limit)1153 }1154 pub fn clamp_permissions(1155 mode: CollectionMode,1156 old_limit: &CollectionPermissions,1157 mut new_limit: CollectionPermissions,1158 ) -> Result<CollectionPermissions, DispatchError> {1159 limit_default_clone!(old_limit, new_limit,1160 access => {},1161 mint_mode => {},1162 nesting => {},1163 );1164 Ok(new_limit)1165 }1166}11671168#[macro_export]1169macro_rules! unsupported {1170 () => {1171 Err(<Error<T>>::UnsupportedOperation.into())1172 };1173}117411751176pub trait CommonWeightInfo<CrossAccountId> {1177 fn create_item() -> Weight;1178 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1179 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1180 fn burn_item() -> Weight;1181 fn set_collection_properties(amount: u32) -> Weight;1182 fn delete_collection_properties(amount: u32) -> Weight;1183 fn set_token_properties(amount: u32) -> Weight;1184 fn delete_token_properties(amount: u32) -> Weight;1185 fn set_property_permissions(amount: u32) -> Weight;1186 fn transfer() -> Weight;1187 fn approve() -> Weight;1188 fn transfer_from() -> Weight;1189 fn burn_from() -> Weight;1190}11911192pub trait CommonCollectionOperations<T: Config> {1193 fn create_item(1194 &self,1195 sender: T::CrossAccountId,1196 to: T::CrossAccountId,1197 data: CreateItemData,1198 nesting_budget: &dyn Budget,1199 ) -> DispatchResultWithPostInfo;1200 fn create_multiple_items(1201 &self,1202 sender: T::CrossAccountId,1203 to: T::CrossAccountId,1204 data: Vec<CreateItemData>,1205 nesting_budget: &dyn Budget,1206 ) -> DispatchResultWithPostInfo;1207 fn create_multiple_items_ex(1208 &self,1209 sender: T::CrossAccountId,1210 data: CreateItemExData<T::CrossAccountId>,1211 nesting_budget: &dyn Budget,1212 ) -> DispatchResultWithPostInfo;1213 fn burn_item(1214 &self,1215 sender: T::CrossAccountId,1216 token: TokenId,1217 amount: u128,1218 ) -> DispatchResultWithPostInfo;1219 fn set_collection_properties(1220 &self,1221 sender: T::CrossAccountId,1222 properties: Vec<Property>,1223 ) -> DispatchResultWithPostInfo;1224 fn delete_collection_properties(1225 &self,1226 sender: &T::CrossAccountId,1227 property_keys: Vec<PropertyKey>,1228 ) -> DispatchResultWithPostInfo;1229 fn set_token_properties(1230 &self,1231 sender: T::CrossAccountId,1232 token_id: TokenId,1233 property: Vec<Property>,1234 ) -> DispatchResultWithPostInfo;1235 fn delete_token_properties(1236 &self,1237 sender: T::CrossAccountId,1238 token_id: TokenId,1239 property_keys: Vec<PropertyKey>,1240 ) -> DispatchResultWithPostInfo;1241 fn set_property_permissions(1242 &self,1243 sender: &T::CrossAccountId,1244 property_permissions: Vec<PropertyKeyPermission>,1245 ) -> DispatchResultWithPostInfo;1246 fn transfer(1247 &self,1248 sender: T::CrossAccountId,1249 to: T::CrossAccountId,1250 token: TokenId,1251 amount: u128,1252 nesting_budget: &dyn Budget,1253 ) -> DispatchResultWithPostInfo;1254 fn approve(1255 &self,1256 sender: T::CrossAccountId,1257 spender: T::CrossAccountId,1258 token: TokenId,1259 amount: u128,1260 ) -> DispatchResultWithPostInfo;1261 fn transfer_from(1262 &self,1263 sender: T::CrossAccountId,1264 from: T::CrossAccountId,1265 to: T::CrossAccountId,1266 token: TokenId,1267 amount: u128,1268 nesting_budget: &dyn Budget,1269 ) -> DispatchResultWithPostInfo;1270 fn burn_from(1271 &self,1272 sender: T::CrossAccountId,1273 from: T::CrossAccountId,1274 token: TokenId,1275 amount: u128,1276 nesting_budget: &dyn Budget,1277 ) -> DispatchResultWithPostInfo;12781279 fn check_nesting(1280 &self,1281 sender: T::CrossAccountId,1282 from: (CollectionId, TokenId),1283 under: TokenId,1284 budget: &dyn Budget,1285 ) -> DispatchResult;12861287 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));12881289 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));12901291 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1292 fn collection_tokens(&self) -> Vec<TokenId>;1293 fn token_exists(&self, token: TokenId) -> bool;1294 fn last_token_id(&self) -> TokenId;12951296 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1297 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1298 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1299 1300 fn total_supply(&self) -> u32;1301 1302 fn account_balance(&self, account: T::CrossAccountId) -> u32;1303 1304 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1305 fn allowance(1306 &self,1307 sender: T::CrossAccountId,1308 spender: T::CrossAccountId,1309 token: TokenId,1310 ) -> u128;1311}131213131314pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1315 let post_info = PostDispatchInfo {1316 actual_weight: Some(weight),1317 pays_fee: Pays::Yes,1318 };1319 match res {1320 Ok(()) => Ok(post_info),1321 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1322 }1323}13241325impl<T: Config> From<PropertiesError> for Error<T> {1326 fn from(error: PropertiesError) -> Self {1327 match error {1328 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1329 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1330 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1331 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1332 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1333 }1334 }1335}