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>,504505 506 RmrkCollectionInfo<T::AccountId>,507 RmrkInstanceInfo<T::AccountId>,508 RmrkResourceInfo,509 RmrkPropertyInfo,510 RmrkBaseInfo<T::AccountId>,511 RmrkPartType,512 RmrkTheme,513 RmrkNftChild,514 )>,515 ),516 QueryKind = OptionQuery,517 >;518519 #[pallet::hooks]520 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {521 fn on_runtime_upgrade() -> Weight {522 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {523 use up_data_structs::{CollectionVersion1, CollectionVersion2};524 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {525 let mut props = Vec::new();526 if !v.offchain_schema.is_empty() {527 props.push(Property {528 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),529 value: v.offchain_schema.clone().into_inner().try_into().expect("offchain schema too big"),530 });531 }532 if !v.variable_on_chain_schema.is_empty() {533 props.push(Property {534 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),535 value: v.variable_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),536 });537 }538 if !v.const_on_chain_schema.is_empty() {539 props.push(Property {540 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),541 value: v.const_on_chain_schema.clone().into_inner().try_into().expect("offchain schema too big"),542 });543 }544 props.push(Property {545 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),546 value: match v.schema_version {547 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),548 SchemaVersion::Unique => b"Unique".as_slice(),549 }.to_vec().try_into().unwrap(),550 });551 Self::set_scoped_collection_properties(552 id,553 PropertyScope::None,554 props.into_iter(),555 ).expect("existing data larger than properties");556 let mut new = CollectionVersion2::from(v.clone());557 new.permissions.access = Some(v.access);558 new.permissions.mint_mode = Some(v.mint_mode);559 Some(new)560 });561 }562563 0564 }565 }566}567568impl<T: Config> Pallet<T> {569 570 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {571 ensure!(572 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,573 <Error<T>>::AddressIsZero574 );575 Ok(())576 }577 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {578 <IsAdmin<T>>::iter_prefix((collection,))579 .map(|(a, _)| a)580 .collect()581 }582 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {583 <Allowlist<T>>::iter_prefix((collection,))584 .map(|(a, _)| a)585 .collect()586 }587 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {588 <Allowlist<T>>::get((collection, user))589 }590 pub fn collection_stats() -> CollectionStats {591 let created = <CreatedCollectionCount<T>>::get();592 let destroyed = <DestroyedCollectionCount<T>>::get();593 CollectionStats {594 created: created.0,595 destroyed: destroyed.0,596 alive: created.0 - destroyed.0,597 }598 }599600 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {601 let collection = <CollectionById<T>>::get(collection);602 if collection.is_none() {603 return None;604 }605606 let collection = collection.unwrap();607 let limits = collection.limits;608 let effective_limits = CollectionLimits {609 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),610 sponsored_data_size: Some(limits.sponsored_data_size()),611 sponsored_data_rate_limit: Some(612 limits613 .sponsored_data_rate_limit614 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),615 ),616 token_limit: Some(limits.token_limit()),617 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(618 match collection.mode {619 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,620 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,621 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,622 },623 )),624 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),625 owner_can_transfer: Some(limits.owner_can_transfer()),626 owner_can_destroy: Some(limits.owner_can_destroy()),627 transfers_enabled: Some(limits.transfers_enabled()),628 };629630 Some(effective_limits)631 }632633 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {634 let Collection {635 name,636 description,637 owner,638 mode,639 token_prefix,640 sponsorship,641 limits,642 permissions,643 } = <CollectionById<T>>::get(collection)?;644645 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)646 .into_iter()647 .map(|(key, permission)| PropertyKeyPermission {648 key,649 permission,650 })651 .collect();652653 let properties = <CollectionProperties<T>>::get(collection)654 .into_iter()655 .map(|(key, value)| Property {656 key,657 value,658 })659 .collect();660661 let permissions = CollectionPermissions {662 access: Some(permissions.access()),663 mint_mode: Some(permissions.mint_mode()),664 nesting: Some(permissions.nesting().clone()),665 };666667 Some(RpcCollection {668 name: name.into_inner(),669 description: description.into_inner(),670 owner,671 mode,672 token_prefix: token_prefix.into_inner(),673 sponsorship,674 limits,675 permissions,676 token_property_permissions,677 properties,678 })679 }680}681682macro_rules! limit_default {683 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{684 $(685 if let Some($new) = $new.$field {686 let $old = $old.$field($($arg)?);687 let _ = $new;688 let _ = $old;689 $check690 } else {691 $new.$field = $old.$field692 }693 )*694 }};695}696macro_rules! limit_default_clone {697 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{698 $(699 if let Some($new) = $new.$field.clone() {700 let $old = $old.$field($($arg)?);701 let _ = $new;702 let _ = $old;703 $check704 } else {705 $new.$field = $old.$field.clone()706 }707 )*708 }};709}710711impl<T: Config> Pallet<T> {712 pub fn init_collection(713 owner: T::AccountId,714 data: CreateCollectionData<T::AccountId>,715 ) -> Result<CollectionId, DispatchError> {716 {717 ensure!(718 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,719 Error::<T>::CollectionTokenPrefixLimitExceeded720 );721 }722723 let created_count = <CreatedCollectionCount<T>>::get()724 .0725 .checked_add(1)726 .ok_or(ArithmeticError::Overflow)?;727 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;728 let id = CollectionId(created_count);729730 731 ensure!(732 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,733 <Error<T>>::TotalCollectionsLimitExceeded734 );735736 737738 let collection = Collection {739 owner: owner.clone(),740 name: data.name,741 mode: data.mode.clone(),742 description: data.description,743 token_prefix: data.token_prefix,744 sponsorship: data745 .pending_sponsor746 .map(SponsorshipState::Unconfirmed)747 .unwrap_or_default(),748 limits: data749 .limits750 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))751 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,752 permissions: data753 .permissions754 .map(|permissions| Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions))755 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,756 };757758 let mut collection_properties = up_data_structs::CollectionProperties::get();759 collection_properties760 .try_set_from_iter(data.properties.into_iter())761 .map_err(<Error<T>>::from)?;762763 CollectionProperties::<T>::insert(id, collection_properties);764765 let mut token_props_permissions = PropertiesPermissionMap::new();766 token_props_permissions767 .try_set_from_iter(data.token_property_permissions.into_iter())768 .map_err(<Error<T>>::from)?;769770 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);771772 773 {774 let mut imbalance =775 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();776 imbalance.subsume(777 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(778 &T::TreasuryAccountId::get(),779 T::CollectionCreationPrice::get(),780 ),781 );782 <T as Config>::Currency::settle(783 &owner,784 imbalance,785 WithdrawReasons::TRANSFER,786 ExistenceRequirement::KeepAlive,787 )788 .map_err(|_| Error::<T>::NotSufficientFounds)?;789 }790791 <CreatedCollectionCount<T>>::put(created_count);792 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));793 <CollectionById<T>>::insert(id, collection);794 Ok(id)795 }796797 pub fn destroy_collection(798 collection: CollectionHandle<T>,799 sender: &T::CrossAccountId,800 ) -> DispatchResult {801 ensure!(802 collection.limits.owner_can_destroy(),803 <Error<T>>::NoPermission,804 );805 collection.check_is_owner(sender)?;806807 let destroyed_collections = <DestroyedCollectionCount<T>>::get()808 .0809 .checked_add(1)810 .ok_or(ArithmeticError::Overflow)?;811812 813814 <DestroyedCollectionCount<T>>::put(destroyed_collections);815 <CollectionById<T>>::remove(collection.id);816 <AdminAmount<T>>::remove(collection.id);817 <IsAdmin<T>>::remove_prefix((collection.id,), None);818 <Allowlist<T>>::remove_prefix((collection.id,), None);819 <CollectionProperties<T>>::remove(collection.id);820821 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));822 Ok(())823 }824825 pub fn set_collection_property(826 collection: &CollectionHandle<T>,827 sender: &T::CrossAccountId,828 property: Property,829 ) -> DispatchResult {830 collection.check_is_owner_or_admin(sender)?;831832 CollectionProperties::<T>::try_mutate(collection.id, |properties| {833 let property = property.clone();834 properties.try_set(property.key, property.value)835 })836 .map_err(<Error<T>>::from)?;837838 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));839840 Ok(())841 }842843 pub fn set_scoped_collection_property(844 collection_id: CollectionId,845 scope: PropertyScope,846 property: Property,847 ) -> DispatchResult {848 CollectionProperties::<T>::try_mutate(collection_id, |properties| {849 properties.try_scoped_set(scope, property.key, property.value)850 })851 .map_err(<Error<T>>::from)?;852853 Ok(())854 }855856 pub fn set_scoped_collection_properties(857 collection_id: CollectionId,858 scope: PropertyScope,859 properties: impl Iterator<Item = Property>,860 ) -> DispatchResult {861 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {862 stored_properties.try_scoped_set_from_iter(scope, properties)863 })864 .map_err(<Error<T>>::from)?;865866 Ok(())867 }868869 #[transactional]870 pub fn set_collection_properties(871 collection: &CollectionHandle<T>,872 sender: &T::CrossAccountId,873 properties: Vec<Property>,874 ) -> DispatchResult {875 for property in properties {876 Self::set_collection_property(collection, sender, property)?;877 }878879 Ok(())880 }881882 pub fn delete_collection_property(883 collection: &CollectionHandle<T>,884 sender: &T::CrossAccountId,885 property_key: PropertyKey,886 ) -> DispatchResult {887 collection.check_is_owner_or_admin(sender)?;888889 CollectionProperties::<T>::try_mutate(collection.id, |properties| {890 properties.remove(&property_key)891 })892 .map_err(<Error<T>>::from)?;893894 Self::deposit_event(Event::CollectionPropertyDeleted(895 collection.id,896 property_key,897 ));898899 Ok(())900 }901902 #[transactional]903 pub fn delete_collection_properties(904 collection: &CollectionHandle<T>,905 sender: &T::CrossAccountId,906 property_keys: Vec<PropertyKey>,907 ) -> DispatchResult {908 for key in property_keys {909 Self::delete_collection_property(collection, sender, key)?;910 }911912 Ok(())913 }914915 916 pub fn set_property_permission_unchecked(917 collection: CollectionId,918 property_permission: PropertyKeyPermission,919 ) -> DispatchResult {920 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {921 permissions.try_set(property_permission.key, property_permission.permission)922 })923 .map_err(<Error<T>>::from)?;924 Ok(())925 }926927 pub fn set_property_permission(928 collection: &CollectionHandle<T>,929 sender: &T::CrossAccountId,930 property_permission: PropertyKeyPermission,931 ) -> DispatchResult {932 collection.check_is_owner_or_admin(sender)?;933934 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);935 let current_permission = all_permissions.get(&property_permission.key);936 if matches![937 current_permission,938 Some(PropertyPermission { mutable: false, .. })939 ] {940 return Err(<Error<T>>::NoPermission.into());941 }942943 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {944 let property_permission = property_permission.clone();945 permissions.try_set(property_permission.key, property_permission.permission)946 })947 .map_err(<Error<T>>::from)?;948949 Self::deposit_event(Event::PropertyPermissionSet(950 collection.id,951 property_permission.key,952 ));953954 Ok(())955 }956957 #[transactional]958 pub fn set_property_permissions(959 collection: &CollectionHandle<T>,960 sender: &T::CrossAccountId,961 property_permissions: Vec<PropertyKeyPermission>,962 ) -> DispatchResult {963 for prop_pemission in property_permissions {964 Self::set_property_permission(collection, sender, prop_pemission)?;965 }966967 Ok(())968 }969970 pub fn get_collection_property(971 collection_id: CollectionId,972 key: &PropertyKey,973 ) -> Option<PropertyValue> {974 Self::collection_properties(collection_id).get(key).cloned()975 }976977 pub fn bytes_keys_to_property_keys(978 keys: Vec<Vec<u8>>,979 ) -> Result<Vec<PropertyKey>, DispatchError> {980 keys.into_iter()981 .map(|key| -> Result<PropertyKey, DispatchError> {982 key.try_into()983 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())984 })985 .collect::<Result<Vec<PropertyKey>, DispatchError>>()986 }987988 pub fn filter_collection_properties(989 collection_id: CollectionId,990 keys: Option<Vec<PropertyKey>>,991 ) -> Result<Vec<Property>, DispatchError> {992 let properties = Self::collection_properties(collection_id);993994 let properties = keys995 .map(|keys| {996 keys.into_iter()997 .filter_map(|key| {998 properties.get(&key).map(|value| Property {999 key,1000 value: value.clone(),1001 })1002 })1003 .collect()1004 })1005 .unwrap_or_else(|| {1006 properties1007 .into_iter()1008 .map(|(key, value)| Property {1009 key,1010 value,1011 })1012 .collect()1013 });10141015 Ok(properties)1016 }10171018 pub fn filter_property_permissions(1019 collection_id: CollectionId,1020 keys: Option<Vec<PropertyKey>>,1021 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1022 let permissions = Self::property_permissions(collection_id);10231024 let key_permissions = keys1025 .map(|keys| {1026 keys.into_iter()1027 .filter_map(|key| {1028 permissions1029 .get(&key)1030 .map(|permission| PropertyKeyPermission {1031 key,1032 permission: permission.clone(),1033 })1034 })1035 .collect()1036 })1037 .unwrap_or_else(|| {1038 permissions1039 .into_iter()1040 .map(|(key, permission)| PropertyKeyPermission {1041 key,1042 permission,1043 })1044 .collect()1045 });10461047 Ok(key_permissions)1048 }10491050 pub fn toggle_allowlist(1051 collection: &CollectionHandle<T>,1052 sender: &T::CrossAccountId,1053 user: &T::CrossAccountId,1054 allowed: bool,1055 ) -> DispatchResult {1056 collection.check_is_owner_or_admin(sender)?;10571058 10591060 if allowed {1061 <Allowlist<T>>::insert((collection.id, user), true);1062 } else {1063 <Allowlist<T>>::remove((collection.id, user));1064 }10651066 Ok(())1067 }10681069 pub fn toggle_admin(1070 collection: &CollectionHandle<T>,1071 sender: &T::CrossAccountId,1072 user: &T::CrossAccountId,1073 admin: bool,1074 ) -> DispatchResult {1075 collection.check_is_owner_or_admin(sender)?;10761077 let was_admin = <IsAdmin<T>>::get((collection.id, user));1078 if was_admin == admin {1079 return Ok(());1080 }1081 let amount = <AdminAmount<T>>::get(collection.id);10821083 if admin {1084 let amount = amount1085 .checked_add(1)1086 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1087 ensure!(1088 amount <= Self::collection_admins_limit(),1089 <Error<T>>::CollectionAdminCountExceeded,1090 );10911092 10931094 <AdminAmount<T>>::insert(collection.id, amount);1095 <IsAdmin<T>>::insert((collection.id, user), true);1096 } else {1097 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1098 <IsAdmin<T>>::remove((collection.id, user));1099 }11001101 Ok(())1102 }11031104 pub fn clamp_limits(1105 mode: CollectionMode,1106 old_limit: &CollectionLimits,1107 mut new_limit: CollectionLimits,1108 ) -> Result<CollectionLimits, DispatchError> {1109 limit_default!(old_limit, new_limit,1110 account_token_ownership_limit => ensure!(1111 new_limit <= MAX_TOKEN_OWNERSHIP,1112 <Error<T>>::CollectionLimitBoundsExceeded,1113 ),1114 sponsor_transfer_timeout(match mode {1115 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1116 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1117 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1118 }) => ensure!(1119 new_limit <= MAX_SPONSOR_TIMEOUT,1120 <Error<T>>::CollectionLimitBoundsExceeded,1121 ),1122 sponsored_data_size => ensure!(1123 new_limit <= CUSTOM_DATA_LIMIT,1124 <Error<T>>::CollectionLimitBoundsExceeded,1125 ),1126 token_limit => ensure!(1127 old_limit >= new_limit && new_limit > 0,1128 <Error<T>>::CollectionTokenLimitExceeded1129 ),1130 owner_can_transfer => ensure!(1131 old_limit || !new_limit,1132 <Error<T>>::OwnerPermissionsCantBeReverted,1133 ),1134 owner_can_destroy => ensure!(1135 old_limit || !new_limit,1136 <Error<T>>::OwnerPermissionsCantBeReverted,1137 ),1138 sponsored_data_rate_limit => {},1139 transfers_enabled => {},1140 );1141 Ok(new_limit)1142 }1143 pub fn clamp_permissions(1144 mode: CollectionMode,1145 old_limit: &CollectionPermissions,1146 mut new_limit: CollectionPermissions,1147 ) -> Result<CollectionPermissions, DispatchError> {1148 limit_default_clone!(old_limit, new_limit,1149 );1150 Ok(new_limit)1151 }1152}11531154#[macro_export]1155macro_rules! unsupported {1156 () => {1157 Err(<Error<T>>::UnsupportedOperation.into())1158 };1159}116011611162pub trait CommonWeightInfo<CrossAccountId> {1163 fn create_item() -> Weight;1164 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1165 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1166 fn burn_item() -> Weight;1167 fn set_collection_properties(amount: u32) -> Weight;1168 fn delete_collection_properties(amount: u32) -> Weight;1169 fn set_token_properties(amount: u32) -> Weight;1170 fn delete_token_properties(amount: u32) -> Weight;1171 fn set_property_permissions(amount: u32) -> Weight;1172 fn transfer() -> Weight;1173 fn approve() -> Weight;1174 fn transfer_from() -> Weight;1175 fn burn_from() -> Weight;1176}11771178pub trait CommonCollectionOperations<T: Config> {1179 fn create_item(1180 &self,1181 sender: T::CrossAccountId,1182 to: T::CrossAccountId,1183 data: CreateItemData,1184 nesting_budget: &dyn Budget,1185 ) -> DispatchResultWithPostInfo;1186 fn create_multiple_items(1187 &self,1188 sender: T::CrossAccountId,1189 to: T::CrossAccountId,1190 data: Vec<CreateItemData>,1191 nesting_budget: &dyn Budget,1192 ) -> DispatchResultWithPostInfo;1193 fn create_multiple_items_ex(1194 &self,1195 sender: T::CrossAccountId,1196 data: CreateItemExData<T::CrossAccountId>,1197 nesting_budget: &dyn Budget,1198 ) -> DispatchResultWithPostInfo;1199 fn burn_item(1200 &self,1201 sender: T::CrossAccountId,1202 token: TokenId,1203 amount: u128,1204 ) -> DispatchResultWithPostInfo;1205 fn set_collection_properties(1206 &self,1207 sender: T::CrossAccountId,1208 properties: Vec<Property>,1209 ) -> DispatchResultWithPostInfo;1210 fn delete_collection_properties(1211 &self,1212 sender: &T::CrossAccountId,1213 property_keys: Vec<PropertyKey>,1214 ) -> DispatchResultWithPostInfo;1215 fn set_token_properties(1216 &self,1217 sender: T::CrossAccountId,1218 token_id: TokenId,1219 property: Vec<Property>,1220 ) -> DispatchResultWithPostInfo;1221 fn delete_token_properties(1222 &self,1223 sender: T::CrossAccountId,1224 token_id: TokenId,1225 property_keys: Vec<PropertyKey>,1226 ) -> DispatchResultWithPostInfo;1227 fn set_property_permissions(1228 &self,1229 sender: &T::CrossAccountId,1230 property_permissions: Vec<PropertyKeyPermission>,1231 ) -> DispatchResultWithPostInfo;1232 fn transfer(1233 &self,1234 sender: T::CrossAccountId,1235 to: T::CrossAccountId,1236 token: TokenId,1237 amount: u128,1238 nesting_budget: &dyn Budget,1239 ) -> DispatchResultWithPostInfo;1240 fn approve(1241 &self,1242 sender: T::CrossAccountId,1243 spender: T::CrossAccountId,1244 token: TokenId,1245 amount: u128,1246 ) -> DispatchResultWithPostInfo;1247 fn transfer_from(1248 &self,1249 sender: T::CrossAccountId,1250 from: T::CrossAccountId,1251 to: T::CrossAccountId,1252 token: TokenId,1253 amount: u128,1254 nesting_budget: &dyn Budget,1255 ) -> DispatchResultWithPostInfo;1256 fn burn_from(1257 &self,1258 sender: T::CrossAccountId,1259 from: T::CrossAccountId,1260 token: TokenId,1261 amount: u128,1262 nesting_budget: &dyn Budget,1263 ) -> DispatchResultWithPostInfo;12641265 fn check_nesting(1266 &self,1267 sender: T::CrossAccountId,1268 from: (CollectionId, TokenId),1269 under: TokenId,1270 budget: &dyn Budget,1271 ) -> DispatchResult;12721273 fn nest(1274 &self,1275 under: TokenId,1276 to_nest: (CollectionId, TokenId)1277 );12781279 fn unnest(1280 &self,1281 under: TokenId,1282 to_nest: (CollectionId, TokenId)1283 );12841285 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1286 fn collection_tokens(&self) -> Vec<TokenId>;1287 fn token_exists(&self, token: TokenId) -> bool;1288 fn last_token_id(&self) -> TokenId;12891290 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1291 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1292 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1293 1294 fn total_supply(&self) -> u32;1295 1296 fn account_balance(&self, account: T::CrossAccountId) -> u32;1297 1298 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1299 fn allowance(1300 &self,1301 sender: T::CrossAccountId,1302 spender: T::CrossAccountId,1303 token: TokenId,1304 ) -> u128;1305}130613071308pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1309 let post_info = PostDispatchInfo {1310 actual_weight: Some(weight),1311 pays_fee: Pays::Yes,1312 };1313 match res {1314 Ok(()) => Ok(post_info),1315 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1316 }1317}13181319impl<T: Config> From<PropertiesError> for Error<T> {1320 fn from(error: PropertiesError) -> Self {1321 match error {1322 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1323 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1324 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1325 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1326 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1327 }1328 }1329}