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, Pallet as PalletEvm};25use evm_coder::ToLog;26use frame_support::{27 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},28 ensure,29 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},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 TokenChild,44 CollectionStats,45 MAX_TOKEN_OWNERSHIP,46 CollectionMode,47 NFT_SPONSOR_TRANSFER_TIMEOUT,48 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,49 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,50 MAX_SPONSOR_TIMEOUT,51 CUSTOM_DATA_LIMIT,52 CollectionLimits,53 CreateCollectionData,54 SponsorshipState,55 CreateItemExData,56 SponsoringRateLimit,57 budget::Budget,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 }129130 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {131 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)132 }133134 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {135 self.recorder136 .consume_gas(T::GasWeightMapping::weight_to_gas(137 <T as frame_system::Config>::DbWeight::get()138 .read139 .saturating_mul(reads),140 ))141 }142143 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {144 self.recorder145 .consume_gas(T::GasWeightMapping::weight_to_gas(146 <T as frame_system::Config>::DbWeight::get()147 .write148 .saturating_mul(writes),149 ))150 }151152 pub fn save(self) -> DispatchResult {153 <CollectionById<T>>::insert(self.id, self.collection);154 Ok(())155 }156157 pub fn set_sponsor(&mut self, sponsor: T::AccountId) {158 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);159 }160161 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> bool {162 if self.collection.sponsorship.pending_sponsor() != Some(sender) {163 return false;164 };165166 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());167 true168 }169}170171impl<T: Config> Deref for CollectionHandle<T> {172 type Target = Collection<T::AccountId>;173174 fn deref(&self) -> &Self::Target {175 &self.collection176 }177}178179impl<T: Config> DerefMut for CollectionHandle<T> {180 fn deref_mut(&mut self) -> &mut Self::Target {181 &mut self.collection182 }183}184185impl<T: Config> CollectionHandle<T> {186 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {187 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);188 Ok(())189 }190 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {191 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))192 }193 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {194 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);195 Ok(())196 }197 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {198 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)199 }200 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {201 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)202 }203 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {204 ensure!(205 <Allowlist<T>>::get((self.id, user)),206 <Error<T>>::AddressNotInAllowlist207 );208 Ok(())209 }210}211212#[frame_support::pallet]213pub mod pallet {214 use super::*;215 use pallet_evm::account;216 use dispatch::CollectionDispatch;217 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};218 use frame_system::pallet_prelude::*;219 use frame_support::traits::Currency;220 use up_data_structs::{TokenId, mapping::TokenAddressMapping};221 use scale_info::TypeInfo;222 use weights::WeightInfo;223224 #[pallet::config]225 pub trait Config:226 frame_system::Config227 + pallet_evm_coder_substrate::Config228 + pallet_evm::Config229 + TypeInfo230 + account::Config231 {232 type WeightInfo: WeightInfo;233 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;234235 type Currency: Currency<Self::AccountId>;236237 #[pallet::constant]238 type CollectionCreationPrice: Get<239 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,240 >;241 type CollectionDispatch: CollectionDispatch<Self>;242243 type TreasuryAccountId: Get<Self::AccountId>;244 type ContractAddress: Get<H160>;245246 type EvmTokenAddressMapping: TokenAddressMapping<H160>;247 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;248 }249250 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);251252 #[pallet::pallet]253 #[pallet::storage_version(STORAGE_VERSION)]254 #[pallet::generate_store(pub(super) trait Store)]255 pub struct Pallet<T>(_);256257 #[pallet::extra_constants]258 impl<T: Config> Pallet<T> {259 pub fn collection_admins_limit() -> u32 {260 COLLECTION_ADMINS_LIMIT261 }262 }263264 #[pallet::event]265 #[pallet::generate_deposit(pub fn deposit_event)]266 pub enum Event<T: Config> {267 268 269 270 271 272 273 274 275 276 CollectionCreated(CollectionId, u8, T::AccountId),277278 279 280 281 282 283 CollectionDestroyed(CollectionId),284285 286 287 288 289 290 291 292 293 294 295 296 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),297298 299 300 301 302 303 304 305 306 307 308 309 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),310311 312 313 314 315 316 317 318 319 320 321 322 Transfer(323 CollectionId,324 TokenId,325 T::CrossAccountId,326 T::CrossAccountId,327 u128,328 ),329330 331 332 333 334 335 336 337 338 339 Approved(340 CollectionId,341 TokenId,342 T::CrossAccountId,343 T::CrossAccountId,344 u128,345 ),346347 CollectionPropertySet(CollectionId, PropertyKey),348349 CollectionPropertyDeleted(CollectionId, PropertyKey),350351 TokenPropertySet(CollectionId, TokenId, PropertyKey),352353 TokenPropertyDeleted(CollectionId, TokenId, PropertyKey),354355 PropertyPermissionSet(CollectionId, PropertyKey),356 }357358 #[pallet::error]359 pub enum Error<T> {360 361 CollectionNotFound,362 363 MustBeTokenOwner,364 365 NoPermission,366 367 CantDestroyNotEmptyCollection,368 369 PublicMintingNotAllowed,370 371 AddressNotInAllowlist,372373 374 CollectionNameLimitExceeded,375 376 CollectionDescriptionLimitExceeded,377 378 CollectionTokenPrefixLimitExceeded,379 380 TotalCollectionsLimitExceeded,381 382 CollectionAdminCountExceeded,383 384 CollectionLimitBoundsExceeded,385 386 OwnerPermissionsCantBeReverted,387 388 TransferNotAllowed,389 390 AccountTokenLimitExceeded,391 392 CollectionTokenLimitExceeded,393 394 MetadataFlagFrozen,395396 397 TokenNotFound,398 399 TokenValueTooLow,400 401 ApprovedValueTooLow,402 403 CantApproveMoreThanOwned,404405 406 AddressIsZero,407 408 UnsupportedOperation,409410 411 NotSufficientFounds,412413 414 NestingIsDisabled,415 416 OnlyOwnerAllowedToNest,417 418 SourceCollectionIsNotAllowedToNest,419420 421 CollectionFieldSizeExceeded,422423 424 NoSpaceForProperty,425426 427 PropertyLimitReached,428429 430 PropertyKeyIsTooLong,431432 433 InvalidCharacterInPropertyKey,434435 436 EmptyPropertyKey,437 }438439 #[pallet::storage]440 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;441 #[pallet::storage]442 pub type DestroyedCollectionCount<T> =443 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;444445 446 #[pallet::storage]447 pub type CollectionById<T> = StorageMap<448 Hasher = Blake2_128Concat,449 Key = CollectionId,450 Value = Collection<<T as frame_system::Config>::AccountId>,451 QueryKind = OptionQuery,452 >;453454 455 #[pallet::storage]456 #[pallet::getter(fn collection_properties)]457 pub type CollectionProperties<T> = StorageMap<458 Hasher = Blake2_128Concat,459 Key = CollectionId,460 Value = Properties,461 QueryKind = ValueQuery,462 OnEmpty = up_data_structs::CollectionProperties,463 >;464465 #[pallet::storage]466 #[pallet::getter(fn property_permissions)]467 pub type CollectionPropertyPermissions<T> = StorageMap<468 Hasher = Blake2_128Concat,469 Key = CollectionId,470 Value = PropertiesPermissionMap,471 QueryKind = ValueQuery,472 >;473474 #[pallet::storage]475 pub type AdminAmount<T> = StorageMap<476 Hasher = Blake2_128Concat,477 Key = CollectionId,478 Value = u32,479 QueryKind = ValueQuery,480 >;481482 483 #[pallet::storage]484 pub type IsAdmin<T: Config> = StorageNMap<485 Key = (486 Key<Blake2_128Concat, CollectionId>,487 Key<Blake2_128Concat, T::CrossAccountId>,488 ),489 Value = bool,490 QueryKind = ValueQuery,491 >;492493 494 #[pallet::storage]495 pub type Allowlist<T: Config> = StorageNMap<496 Key = (497 Key<Blake2_128Concat, CollectionId>,498 Key<Blake2_128Concat, T::CrossAccountId>,499 ),500 Value = bool,501 QueryKind = ValueQuery,502 >;503504 505 #[pallet::storage]506 pub type DummyStorageValue<T: Config> = StorageValue<507 Value = (508 CollectionStats,509 CollectionId,510 TokenId,511 TokenChild,512 PhantomType<(513 TokenData<T::CrossAccountId>,514 RpcCollection<T::AccountId>,515 516 RmrkCollectionInfo<T::AccountId>,517 RmrkInstanceInfo<T::AccountId>,518 RmrkResourceInfo,519 RmrkPropertyInfo,520 RmrkBaseInfo<T::AccountId>,521 RmrkPartType,522 RmrkTheme,523 RmrkNftChild,524 )>,525 ),526 QueryKind = OptionQuery,527 >;528529 #[pallet::hooks]530 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {531 fn on_runtime_upgrade() -> Weight {532 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {533 use up_data_structs::{CollectionVersion1, CollectionVersion2};534 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {535 let mut props = Vec::new();536 if !v.offchain_schema.is_empty() {537 props.push(Property {538 key: b"_old_offchainSchema".to_vec().try_into().unwrap(),539 value: v540 .offchain_schema541 .clone()542 .into_inner()543 .try_into()544 .expect("offchain schema too big"),545 });546 }547 if !v.variable_on_chain_schema.is_empty() {548 props.push(Property {549 key: b"_old_variableOnChainSchema".to_vec().try_into().unwrap(),550 value: v551 .variable_on_chain_schema552 .clone()553 .into_inner()554 .try_into()555 .expect("offchain schema too big"),556 });557 }558 if !v.const_on_chain_schema.is_empty() {559 props.push(Property {560 key: b"_old_constOnChainSchema".to_vec().try_into().unwrap(),561 value: v562 .const_on_chain_schema563 .clone()564 .into_inner()565 .try_into()566 .expect("offchain schema too big"),567 });568 }569 props.push(Property {570 key: b"_old_schemaVersion".to_vec().try_into().unwrap(),571 value: match v.schema_version {572 SchemaVersion::ImageURL => b"ImageUrl".as_slice(),573 SchemaVersion::Unique => b"Unique".as_slice(),574 }575 .to_vec()576 .try_into()577 .unwrap(),578 });579 Self::set_scoped_collection_properties(580 id,581 PropertyScope::None,582 props.into_iter(),583 )584 .expect("existing data larger than properties");585 let mut new = CollectionVersion2::from(v.clone());586 new.permissions.access = Some(v.access);587 new.permissions.mint_mode = Some(v.mint_mode);588 Some(new)589 });590 }591592 0593 }594 }595}596597impl<T: Config> Pallet<T> {598 599 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {600 ensure!(601 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,602 <Error<T>>::AddressIsZero603 );604 Ok(())605 }606 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {607 <IsAdmin<T>>::iter_prefix((collection,))608 .map(|(a, _)| a)609 .collect()610 }611 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {612 <Allowlist<T>>::iter_prefix((collection,))613 .map(|(a, _)| a)614 .collect()615 }616 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {617 <Allowlist<T>>::get((collection, user))618 }619 pub fn collection_stats() -> CollectionStats {620 let created = <CreatedCollectionCount<T>>::get();621 let destroyed = <DestroyedCollectionCount<T>>::get();622 CollectionStats {623 created: created.0,624 destroyed: destroyed.0,625 alive: created.0 - destroyed.0,626 }627 }628629 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {630 let collection = <CollectionById<T>>::get(collection);631 if collection.is_none() {632 return None;633 }634635 let collection = collection.unwrap();636 let limits = collection.limits;637 let effective_limits = CollectionLimits {638 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),639 sponsored_data_size: Some(limits.sponsored_data_size()),640 sponsored_data_rate_limit: Some(641 limits642 .sponsored_data_rate_limit643 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),644 ),645 token_limit: Some(limits.token_limit()),646 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(647 match collection.mode {648 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,649 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,650 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,651 },652 )),653 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),654 owner_can_transfer: Some(limits.owner_can_transfer()),655 owner_can_destroy: Some(limits.owner_can_destroy()),656 transfers_enabled: Some(limits.transfers_enabled()),657 };658659 Some(effective_limits)660 }661662 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {663 let Collection {664 name,665 description,666 owner,667 mode,668 token_prefix,669 sponsorship,670 limits,671 permissions,672 } = <CollectionById<T>>::get(collection)?;673674 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)675 .into_iter()676 .map(|(key, permission)| PropertyKeyPermission { key, permission })677 .collect();678679 let properties = <CollectionProperties<T>>::get(collection)680 .into_iter()681 .map(|(key, value)| Property { key, value })682 .collect();683684 let permissions = CollectionPermissions {685 access: Some(permissions.access()),686 mint_mode: Some(permissions.mint_mode()),687 nesting: Some(permissions.nesting().clone()),688 };689690 Some(RpcCollection {691 name: name.into_inner(),692 description: description.into_inner(),693 owner,694 mode,695 token_prefix: token_prefix.into_inner(),696 sponsorship,697 limits,698 permissions,699 token_property_permissions,700 properties,701 })702 }703}704705macro_rules! limit_default {706 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{707 $(708 if let Some($new) = $new.$field {709 let $old = $old.$field($($arg)?);710 let _ = $new;711 let _ = $old;712 $check713 } else {714 $new.$field = $old.$field715 }716 )*717 }};718}719macro_rules! limit_default_clone {720 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{721 $(722 if let Some($new) = $new.$field.clone() {723 let $old = $old.$field($($arg)?);724 let _ = $new;725 let _ = $old;726 $check727 } else {728 $new.$field = $old.$field.clone()729 }730 )*731 }};732}733734impl<T: Config> Pallet<T> {735 pub fn init_collection(736 owner: T::CrossAccountId,737 data: CreateCollectionData<T::AccountId>,738 ) -> Result<CollectionId, DispatchError> {739 {740 ensure!(741 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,742 Error::<T>::CollectionTokenPrefixLimitExceeded743 );744 }745746 let created_count = <CreatedCollectionCount<T>>::get()747 .0748 .checked_add(1)749 .ok_or(ArithmeticError::Overflow)?;750 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;751 let id = CollectionId(created_count);752753 754 ensure!(755 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,756 <Error<T>>::TotalCollectionsLimitExceeded757 );758759 760761 let collection = Collection {762 owner: owner.as_sub().clone(),763 name: data.name,764 mode: data.mode.clone(),765 description: data.description,766 token_prefix: data.token_prefix,767 sponsorship: data768 .pending_sponsor769 .map(SponsorshipState::Unconfirmed)770 .unwrap_or_default(),771 limits: data772 .limits773 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))774 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,775 permissions: data776 .permissions777 .map(|permissions| {778 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)779 })780 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,781 };782783 let mut collection_properties = up_data_structs::CollectionProperties::get();784 collection_properties785 .try_set_from_iter(data.properties.into_iter())786 .map_err(<Error<T>>::from)?;787788 CollectionProperties::<T>::insert(id, collection_properties);789790 let mut token_props_permissions = PropertiesPermissionMap::new();791 token_props_permissions792 .try_set_from_iter(data.token_property_permissions.into_iter())793 .map_err(<Error<T>>::from)?;794795 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);796797 798 {799 let mut imbalance =800 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();801 imbalance.subsume(802 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(803 &T::TreasuryAccountId::get(),804 T::CollectionCreationPrice::get(),805 ),806 );807 <T as Config>::Currency::settle(808 &owner.as_sub(),809 imbalance,810 WithdrawReasons::TRANSFER,811 ExistenceRequirement::KeepAlive,812 )813 .map_err(|_| Error::<T>::NotSufficientFounds)?;814 }815816 <CreatedCollectionCount<T>>::put(created_count);817 <Pallet<T>>::deposit_event(Event::CollectionCreated(818 id,819 data.mode.id(),820 owner.as_sub().clone(),821 ));822 <PalletEvm<T>>::deposit_log(823 erc::CollectionHelpersEvents::CollectionCreated {824 owner: *owner.as_eth(),825 collection_id: eth::collection_id_to_address(id),826 }827 .to_log(T::ContractAddress::get()),828 );829 <CollectionById<T>>::insert(id, collection);830 Ok(id)831 }832833 pub fn destroy_collection(834 collection: CollectionHandle<T>,835 sender: &T::CrossAccountId,836 ) -> DispatchResult {837 ensure!(838 collection.limits.owner_can_destroy(),839 <Error<T>>::NoPermission,840 );841 collection.check_is_owner(sender)?;842843 let destroyed_collections = <DestroyedCollectionCount<T>>::get()844 .0845 .checked_add(1)846 .ok_or(ArithmeticError::Overflow)?;847848 849850 <DestroyedCollectionCount<T>>::put(destroyed_collections);851 <CollectionById<T>>::remove(collection.id);852 <AdminAmount<T>>::remove(collection.id);853 <IsAdmin<T>>::remove_prefix((collection.id,), None);854 <Allowlist<T>>::remove_prefix((collection.id,), None);855 <CollectionProperties<T>>::remove(collection.id);856857 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));858 Ok(())859 }860861 pub fn set_collection_property(862 collection: &CollectionHandle<T>,863 sender: &T::CrossAccountId,864 property: Property,865 ) -> DispatchResult {866 collection.check_is_owner_or_admin(sender)?;867868 CollectionProperties::<T>::try_mutate(collection.id, |properties| {869 let property = property.clone();870 properties.try_set(property.key, property.value)871 })872 .map_err(<Error<T>>::from)?;873874 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));875876 Ok(())877 }878879 pub fn set_scoped_collection_property(880 collection_id: CollectionId,881 scope: PropertyScope,882 property: Property,883 ) -> DispatchResult {884 CollectionProperties::<T>::try_mutate(collection_id, |properties| {885 properties.try_scoped_set(scope, property.key, property.value)886 })887 .map_err(<Error<T>>::from)?;888889 Ok(())890 }891892 pub fn set_scoped_collection_properties(893 collection_id: CollectionId,894 scope: PropertyScope,895 properties: impl Iterator<Item = Property>,896 ) -> DispatchResult {897 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {898 stored_properties.try_scoped_set_from_iter(scope, properties)899 })900 .map_err(<Error<T>>::from)?;901902 Ok(())903 }904905 #[transactional]906 pub fn set_collection_properties(907 collection: &CollectionHandle<T>,908 sender: &T::CrossAccountId,909 properties: Vec<Property>,910 ) -> DispatchResult {911 for property in properties {912 Self::set_collection_property(collection, sender, property)?;913 }914915 Ok(())916 }917918 pub fn delete_collection_property(919 collection: &CollectionHandle<T>,920 sender: &T::CrossAccountId,921 property_key: PropertyKey,922 ) -> DispatchResult {923 collection.check_is_owner_or_admin(sender)?;924925 CollectionProperties::<T>::try_mutate(collection.id, |properties| {926 properties.remove(&property_key)927 })928 .map_err(<Error<T>>::from)?;929930 Self::deposit_event(Event::CollectionPropertyDeleted(931 collection.id,932 property_key,933 ));934935 Ok(())936 }937938 #[transactional]939 pub fn delete_collection_properties(940 collection: &CollectionHandle<T>,941 sender: &T::CrossAccountId,942 property_keys: Vec<PropertyKey>,943 ) -> DispatchResult {944 for key in property_keys {945 Self::delete_collection_property(collection, sender, key)?;946 }947948 Ok(())949 }950951 952 pub fn set_property_permission_unchecked(953 collection: CollectionId,954 property_permission: PropertyKeyPermission,955 ) -> DispatchResult {956 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {957 permissions.try_set(property_permission.key, property_permission.permission)958 })959 .map_err(<Error<T>>::from)?;960 Ok(())961 }962963 pub fn set_property_permission(964 collection: &CollectionHandle<T>,965 sender: &T::CrossAccountId,966 property_permission: PropertyKeyPermission,967 ) -> DispatchResult {968 collection.check_is_owner_or_admin(sender)?;969970 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);971 let current_permission = all_permissions.get(&property_permission.key);972 if matches![973 current_permission,974 Some(PropertyPermission { mutable: false, .. })975 ] {976 return Err(<Error<T>>::NoPermission.into());977 }978979 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {980 let property_permission = property_permission.clone();981 permissions.try_set(property_permission.key, property_permission.permission)982 })983 .map_err(<Error<T>>::from)?;984985 Self::deposit_event(Event::PropertyPermissionSet(986 collection.id,987 property_permission.key,988 ));989990 Ok(())991 }992993 #[transactional]994 pub fn set_property_permissions(995 collection: &CollectionHandle<T>,996 sender: &T::CrossAccountId,997 property_permissions: Vec<PropertyKeyPermission>,998 ) -> DispatchResult {999 for prop_pemission in property_permissions {1000 Self::set_property_permission(collection, sender, prop_pemission)?;1001 }10021003 Ok(())1004 }10051006 pub fn get_collection_property(1007 collection_id: CollectionId,1008 key: &PropertyKey,1009 ) -> Option<PropertyValue> {1010 Self::collection_properties(collection_id).get(key).cloned()1011 }10121013 pub fn bytes_keys_to_property_keys(1014 keys: Vec<Vec<u8>>,1015 ) -> Result<Vec<PropertyKey>, DispatchError> {1016 keys.into_iter()1017 .map(|key| -> Result<PropertyKey, DispatchError> {1018 key.try_into()1019 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1020 })1021 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1022 }10231024 pub fn filter_collection_properties(1025 collection_id: CollectionId,1026 keys: Option<Vec<PropertyKey>>,1027 ) -> Result<Vec<Property>, DispatchError> {1028 let properties = Self::collection_properties(collection_id);10291030 let properties = keys1031 .map(|keys| {1032 keys.into_iter()1033 .filter_map(|key| {1034 properties.get(&key).map(|value| Property {1035 key,1036 value: value.clone(),1037 })1038 })1039 .collect()1040 })1041 .unwrap_or_else(|| {1042 properties1043 .into_iter()1044 .map(|(key, value)| Property { key, value })1045 .collect()1046 });10471048 Ok(properties)1049 }10501051 pub fn filter_property_permissions(1052 collection_id: CollectionId,1053 keys: Option<Vec<PropertyKey>>,1054 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1055 let permissions = Self::property_permissions(collection_id);10561057 let key_permissions = keys1058 .map(|keys| {1059 keys.into_iter()1060 .filter_map(|key| {1061 permissions1062 .get(&key)1063 .map(|permission| PropertyKeyPermission {1064 key,1065 permission: permission.clone(),1066 })1067 })1068 .collect()1069 })1070 .unwrap_or_else(|| {1071 permissions1072 .into_iter()1073 .map(|(key, permission)| PropertyKeyPermission { key, permission })1074 .collect()1075 });10761077 Ok(key_permissions)1078 }10791080 pub fn toggle_allowlist(1081 collection: &CollectionHandle<T>,1082 sender: &T::CrossAccountId,1083 user: &T::CrossAccountId,1084 allowed: bool,1085 ) -> DispatchResult {1086 collection.check_is_owner_or_admin(sender)?;10871088 10891090 if allowed {1091 <Allowlist<T>>::insert((collection.id, user), true);1092 } else {1093 <Allowlist<T>>::remove((collection.id, user));1094 }10951096 Ok(())1097 }10981099 pub fn toggle_admin(1100 collection: &CollectionHandle<T>,1101 sender: &T::CrossAccountId,1102 user: &T::CrossAccountId,1103 admin: bool,1104 ) -> DispatchResult {1105 collection.check_is_owner_or_admin(sender)?;11061107 let was_admin = <IsAdmin<T>>::get((collection.id, user));1108 if was_admin == admin {1109 return Ok(());1110 }1111 let amount = <AdminAmount<T>>::get(collection.id);11121113 if admin {1114 let amount = amount1115 .checked_add(1)1116 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1117 ensure!(1118 amount <= Self::collection_admins_limit(),1119 <Error<T>>::CollectionAdminCountExceeded,1120 );11211122 11231124 <AdminAmount<T>>::insert(collection.id, amount);1125 <IsAdmin<T>>::insert((collection.id, user), true);1126 } else {1127 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1128 <IsAdmin<T>>::remove((collection.id, user));1129 }11301131 Ok(())1132 }11331134 pub fn clamp_limits(1135 mode: CollectionMode,1136 old_limit: &CollectionLimits,1137 mut new_limit: CollectionLimits,1138 ) -> Result<CollectionLimits, DispatchError> {1139 limit_default!(old_limit, new_limit,1140 account_token_ownership_limit => ensure!(1141 new_limit <= MAX_TOKEN_OWNERSHIP,1142 <Error<T>>::CollectionLimitBoundsExceeded,1143 ),1144 sponsored_data_size => ensure!(1145 new_limit <= CUSTOM_DATA_LIMIT,1146 <Error<T>>::CollectionLimitBoundsExceeded,1147 ),11481149 sponsored_data_rate_limit => {},1150 token_limit => ensure!(1151 old_limit >= new_limit && new_limit > 0,1152 <Error<T>>::CollectionTokenLimitExceeded1153 ),11541155 sponsor_transfer_timeout(match mode {1156 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1157 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1158 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1159 }) => ensure!(1160 new_limit <= MAX_SPONSOR_TIMEOUT,1161 <Error<T>>::CollectionLimitBoundsExceeded,1162 ),1163 sponsor_approve_timeout => {},1164 owner_can_transfer => ensure!(1165 old_limit || !new_limit,1166 <Error<T>>::OwnerPermissionsCantBeReverted,1167 ),1168 owner_can_destroy => ensure!(1169 old_limit || !new_limit,1170 <Error<T>>::OwnerPermissionsCantBeReverted,1171 ),1172 transfers_enabled => {},1173 );1174 Ok(new_limit)1175 }11761177 pub fn clamp_permissions(1178 _mode: CollectionMode,1179 old_limit: &CollectionPermissions,1180 mut new_limit: CollectionPermissions,1181 ) -> Result<CollectionPermissions, DispatchError> {1182 limit_default_clone!(old_limit, new_limit,1183 access => {},1184 mint_mode => {},1185 nesting => {},1186 );1187 Ok(new_limit)1188 }1189}11901191#[macro_export]1192macro_rules! unsupported {1193 () => {1194 Err(<Error<T>>::UnsupportedOperation.into())1195 };1196}119711981199pub trait CommonWeightInfo<CrossAccountId> {1200 fn create_item() -> Weight;1201 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;1202 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;1203 fn burn_item() -> Weight;1204 fn set_collection_properties(amount: u32) -> Weight;1205 fn delete_collection_properties(amount: u32) -> Weight;1206 fn set_token_properties(amount: u32) -> Weight;1207 fn delete_token_properties(amount: u32) -> Weight;1208 fn set_property_permissions(amount: u32) -> Weight;1209 fn transfer() -> Weight;1210 fn approve() -> Weight;1211 fn transfer_from() -> Weight;1212 fn burn_from() -> Weight;12131214 1215 1216 1217 1218 fn burn_recursively_self_raw() -> Weight;1219 1220 1221 1222 fn burn_recursively_breadth_raw(amount: u32) -> Weight;12231224 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1225 Self::burn_recursively_self_raw()1226 .saturating_mul(max_selfs.max(1) as u64)1227 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1228 }1229}12301231pub trait CommonCollectionOperations<T: Config> {1232 fn create_item(1233 &self,1234 sender: T::CrossAccountId,1235 to: T::CrossAccountId,1236 data: CreateItemData,1237 nesting_budget: &dyn Budget,1238 ) -> DispatchResultWithPostInfo;1239 fn create_multiple_items(1240 &self,1241 sender: T::CrossAccountId,1242 to: T::CrossAccountId,1243 data: Vec<CreateItemData>,1244 nesting_budget: &dyn Budget,1245 ) -> DispatchResultWithPostInfo;1246 fn create_multiple_items_ex(1247 &self,1248 sender: T::CrossAccountId,1249 data: CreateItemExData<T::CrossAccountId>,1250 nesting_budget: &dyn Budget,1251 ) -> DispatchResultWithPostInfo;1252 fn burn_item(1253 &self,1254 sender: T::CrossAccountId,1255 token: TokenId,1256 amount: u128,1257 ) -> DispatchResultWithPostInfo;1258 fn burn_item_recursively(1259 &self,1260 sender: T::CrossAccountId,1261 token: TokenId,1262 self_budget: &dyn Budget,1263 breadth_budget: &dyn Budget,1264 ) -> DispatchResultWithPostInfo;1265 fn set_collection_properties(1266 &self,1267 sender: T::CrossAccountId,1268 properties: Vec<Property>,1269 ) -> DispatchResultWithPostInfo;1270 fn delete_collection_properties(1271 &self,1272 sender: &T::CrossAccountId,1273 property_keys: Vec<PropertyKey>,1274 ) -> DispatchResultWithPostInfo;1275 fn set_token_properties(1276 &self,1277 sender: T::CrossAccountId,1278 token_id: TokenId,1279 property: Vec<Property>,1280 ) -> DispatchResultWithPostInfo;1281 fn delete_token_properties(1282 &self,1283 sender: T::CrossAccountId,1284 token_id: TokenId,1285 property_keys: Vec<PropertyKey>,1286 ) -> DispatchResultWithPostInfo;1287 fn set_property_permissions(1288 &self,1289 sender: &T::CrossAccountId,1290 property_permissions: Vec<PropertyKeyPermission>,1291 ) -> DispatchResultWithPostInfo;1292 fn transfer(1293 &self,1294 sender: T::CrossAccountId,1295 to: T::CrossAccountId,1296 token: TokenId,1297 amount: u128,1298 nesting_budget: &dyn Budget,1299 ) -> DispatchResultWithPostInfo;1300 fn approve(1301 &self,1302 sender: T::CrossAccountId,1303 spender: T::CrossAccountId,1304 token: TokenId,1305 amount: u128,1306 ) -> DispatchResultWithPostInfo;1307 fn transfer_from(1308 &self,1309 sender: T::CrossAccountId,1310 from: T::CrossAccountId,1311 to: T::CrossAccountId,1312 token: TokenId,1313 amount: u128,1314 nesting_budget: &dyn Budget,1315 ) -> DispatchResultWithPostInfo;1316 fn burn_from(1317 &self,1318 sender: T::CrossAccountId,1319 from: T::CrossAccountId,1320 token: TokenId,1321 amount: u128,1322 nesting_budget: &dyn Budget,1323 ) -> DispatchResultWithPostInfo;13241325 fn check_nesting(1326 &self,1327 sender: T::CrossAccountId,1328 from: (CollectionId, TokenId),1329 under: TokenId,1330 budget: &dyn Budget,1331 ) -> DispatchResult;13321333 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13341335 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));13361337 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;1338 fn collection_tokens(&self) -> Vec<TokenId>;1339 fn token_exists(&self, token: TokenId) -> bool;1340 fn last_token_id(&self) -> TokenId;13411342 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;1343 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;1344 fn token_properties(&self, token_id: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;1345 1346 fn total_supply(&self) -> u32;1347 1348 fn account_balance(&self, account: T::CrossAccountId) -> u32;1349 1350 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;1351 fn allowance(1352 &self,1353 sender: T::CrossAccountId,1354 spender: T::CrossAccountId,1355 token: TokenId,1356 ) -> u128;1357}135813591360pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1361 let post_info = PostDispatchInfo {1362 actual_weight: Some(weight),1363 pays_fee: Pays::Yes,1364 };1365 match res {1366 Ok(()) => Ok(post_info),1367 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1368 }1369}13701371impl<T: Config> From<PropertiesError> for Error<T> {1372 fn from(error: PropertiesError) -> Self {1373 match error {1374 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1375 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1376 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1377 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1378 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1379 }1380 }1381}