12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::ops::{Deref, DerefMut};57use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};58use sp_std::vec::Vec;59use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};60use evm_coder::ToLog;61use frame_support::{62 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},63 ensure,64 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},65 weights::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 CollectionId,75 CreateItemData,76 MAX_TOKEN_PREFIX_LENGTH,77 COLLECTION_ADMINS_LIMIT,78 TokenId,79 TokenChild,80 CollectionStats,81 MAX_TOKEN_OWNERSHIP,82 CollectionMode,83 NFT_SPONSOR_TRANSFER_TIMEOUT,84 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,85 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 MAX_SPONSOR_TIMEOUT,87 CUSTOM_DATA_LIMIT,88 CollectionLimits,89 CreateCollectionData,90 SponsorshipState,91 CreateItemExData,92 SponsoringRateLimit,93 budget::Budget,94 PhantomType,95 Property,96 Properties,97 PropertiesPermissionMap,98 PropertyKey,99 PropertyValue,100 PropertyPermission,101 PropertiesError,102 PropertyKeyPermission,103 TokenData,104 TrySetProperty,105 PropertyScope,106 107 RmrkCollectionInfo,108 RmrkInstanceInfo,109 RmrkResourceInfo,110 RmrkPropertyInfo,111 RmrkBaseInfo,112 RmrkPartType,113 RmrkBoundedTheme,114 RmrkNftChild,115 CollectionPermissions,116};117118pub use pallet::*;119use sp_core::H160;120use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};121#[cfg(feature = "runtime-benchmarks")]122pub mod benchmarking;123pub mod dispatch;124pub mod erc;125pub mod eth;126pub mod weights;127128129pub type SelfWeightOf<T> = <T as Config>::WeightInfo;130131132133134135136137#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]138pub struct CollectionHandle<T: Config> {139 140 pub id: CollectionId,141 collection: Collection<T::AccountId>,142 143 pub recorder: SubstrateRecorder<T>,144}145146impl<T: Config> WithRecorder<T> for CollectionHandle<T> {147 fn recorder(&self) -> &SubstrateRecorder<T> {148 &self.recorder149 }150 fn into_recorder(self) -> SubstrateRecorder<T> {151 self.recorder152 }153}154155impl<T: Config> CollectionHandle<T> {156 157 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {158 <CollectionById<T>>::get(id).map(|collection| Self {159 id,160 collection,161 recorder: SubstrateRecorder::new(gas_limit),162 })163 }164165 166 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {167 <CollectionById<T>>::get(id).map(|collection| Self {168 id,169 collection,170 recorder,171 })172 }173174 175 176 pub fn new(id: CollectionId) -> Option<Self> {177 Self::new_with_gas_limit(id, u64::MAX)178 }179180 181 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {182 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)183 }184185 186 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {187 self.recorder188 .consume_gas(T::GasWeightMapping::weight_to_gas(189 <T as frame_system::Config>::DbWeight::get()190 .read191 .saturating_mul(reads),192 ))193 }194195 196 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {197 self.recorder198 .consume_gas(T::GasWeightMapping::weight_to_gas(199 <T as frame_system::Config>::DbWeight::get()200 .write201 .saturating_mul(writes),202 ))203 }204205 206 pub fn consume_store_reads_and_writes(207 &self,208 reads: u64,209 writes: u64,210 ) -> evm_coder::execution::Result<()> {211 let weight = <T as frame_system::Config>::DbWeight::get();212 let reads = weight.read.saturating_mul(reads);213 let writes = weight.read.saturating_mul(writes);214 self.recorder215 .consume_gas(T::GasWeightMapping::weight_to_gas(216 reads.saturating_add(writes),217 ))218 }219220 221 pub fn save(&self) -> DispatchResult {222 <CollectionById<T>>::insert(self.id, &self.collection);223 Ok(())224 }225226 227 228 229 230 231 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {232 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);233 Ok(())234 }235236 237 238 239 240 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {241 if self.collection.sponsorship.pending_sponsor() != Some(sender) {242 return Ok(false);243 }244245 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());246 Ok(true)247 }248249 250 pub fn remove_sponsor(&mut self) -> DispatchResult {251 self.collection.sponsorship = SponsorshipState::Disabled;252 Ok(())253 }254255 256 257 pub fn check_is_internal(&self) -> DispatchResult {258 if self.flags.external {259 return Err(<Error<T>>::CollectionIsExternal)?;260 }261262 Ok(())263 }264265 266 267 pub fn check_is_external(&self) -> DispatchResult {268 if !self.flags.external {269 return Err(<Error<T>>::CollectionIsInternal)?;270 }271272 Ok(())273 }274}275276impl<T: Config> Deref for CollectionHandle<T> {277 type Target = Collection<T::AccountId>;278279 fn deref(&self) -> &Self::Target {280 &self.collection281 }282}283284impl<T: Config> DerefMut for CollectionHandle<T> {285 fn deref_mut(&mut self) -> &mut Self::Target {286 &mut self.collection287 }288}289290impl<T: Config> CollectionHandle<T> {291 292 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {293 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);294 Ok(())295 }296297 298 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {299 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))300 }301302 303 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {304 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);305 Ok(())306 }307308 309 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {310 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)311 }312313 314 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {315 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)316 }317318 319 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {320 ensure!(321 <Allowlist<T>>::get((self.id, user)),322 <Error<T>>::AddressNotInAllowlist323 );324 Ok(())325 }326327 328 329 330 fn set_owner_internal(331 &mut self,332 caller: T::CrossAccountId,333 new_owner: T::CrossAccountId,334 ) -> DispatchResult {335 self.check_is_owner(&caller)?;336 self.collection.owner = new_owner.as_sub().clone();337 self.save()338 }339}340341#[frame_support::pallet]342pub mod pallet {343 use super::*;344 use pallet_evm::account;345 use dispatch::CollectionDispatch;346 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};347 use frame_system::pallet_prelude::*;348 use frame_support::traits::Currency;349 use up_data_structs::{TokenId, mapping::TokenAddressMapping};350 use scale_info::TypeInfo;351 use weights::WeightInfo;352353 #[pallet::config]354 pub trait Config:355 frame_system::Config356 + pallet_evm_coder_substrate::Config357 + pallet_evm::Config358 + TypeInfo359 + account::Config360 {361 362 type WeightInfo: WeightInfo;363364 365 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;366367 368 type Currency: Currency<Self::AccountId>;369370 371 #[pallet::constant]372 type CollectionCreationPrice: Get<373 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,374 >;375376 377 type CollectionDispatch: CollectionDispatch<Self>;378379 380 type TreasuryAccountId: Get<Self::AccountId>;381382 383 type ContractAddress: Get<H160>;384385 386 type EvmTokenAddressMapping: TokenAddressMapping<H160>;387388 389 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;390 }391392 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);393394 #[pallet::pallet]395 #[pallet::storage_version(STORAGE_VERSION)]396 #[pallet::generate_store(pub(super) trait Store)]397 pub struct Pallet<T>(_);398399 #[pallet::extra_constants]400 impl<T: Config> Pallet<T> {401 402 pub fn collection_admins_limit() -> u32 {403 COLLECTION_ADMINS_LIMIT404 }405 }406407 #[pallet::event]408 #[pallet::generate_deposit(pub fn deposit_event)]409 pub enum Event<T: Config> {410 411 CollectionCreated(412 413 CollectionId,414 415 u8,416 417 T::AccountId,418 ),419420 421 CollectionDestroyed(422 423 CollectionId,424 ),425426 427 ItemCreated(428 429 CollectionId,430 431 TokenId,432 433 T::CrossAccountId,434 435 u128,436 ),437438 439 ItemDestroyed(440 441 CollectionId,442 443 TokenId,444 445 T::CrossAccountId,446 447 u128,448 ),449450 451 Transfer(452 453 CollectionId,454 455 TokenId,456 457 T::CrossAccountId,458 459 T::CrossAccountId,460 461 u128,462 ),463464 465 Approved(466 467 CollectionId,468 469 TokenId,470 471 T::CrossAccountId,472 473 T::CrossAccountId,474 475 u128,476 ),477478 479 CollectionPropertySet(480 481 CollectionId,482 483 PropertyKey,484 ),485486 487 CollectionPropertyDeleted(488 489 CollectionId,490 491 PropertyKey,492 ),493494 495 TokenPropertySet(496 497 CollectionId,498 499 TokenId,500 501 PropertyKey,502 ),503504 505 TokenPropertyDeleted(506 507 CollectionId,508 509 TokenId,510 511 PropertyKey,512 ),513514 515 PropertyPermissionSet(516 517 CollectionId,518 519 PropertyKey,520 ),521 }522523 #[pallet::error]524 pub enum Error<T> {525 526 CollectionNotFound,527 528 MustBeTokenOwner,529 530 NoPermission,531 532 CantDestroyNotEmptyCollection,533 534 PublicMintingNotAllowed,535 536 AddressNotInAllowlist,537538 539 CollectionNameLimitExceeded,540 541 CollectionDescriptionLimitExceeded,542 543 CollectionTokenPrefixLimitExceeded,544 545 TotalCollectionsLimitExceeded,546 547 CollectionAdminCountExceeded,548 549 CollectionLimitBoundsExceeded,550 551 OwnerPermissionsCantBeReverted,552 553 TransferNotAllowed,554 555 AccountTokenLimitExceeded,556 557 CollectionTokenLimitExceeded,558 559 MetadataFlagFrozen,560561 562 TokenNotFound,563 564 TokenValueTooLow,565 566 ApprovedValueTooLow,567 568 CantApproveMoreThanOwned,569570 571 AddressIsZero,572573 574 UnsupportedOperation,575576 577 NotSufficientFounds,578579 580 UserIsNotAllowedToNest,581 582 SourceCollectionIsNotAllowedToNest,583584 585 CollectionFieldSizeExceeded,586587 588 NoSpaceForProperty,589590 591 PropertyLimitReached,592593 594 PropertyKeyIsTooLong,595596 597 InvalidCharacterInPropertyKey,598599 600 EmptyPropertyKey,601602 603 CollectionIsExternal,604605 606 CollectionIsInternal,607 }608609 610 #[pallet::storage]611 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;612613 614 #[pallet::storage]615 pub type DestroyedCollectionCount<T> =616 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;617618 619 #[pallet::storage]620 pub type CollectionById<T> = StorageMap<621 Hasher = Blake2_128Concat,622 Key = CollectionId,623 Value = Collection<<T as frame_system::Config>::AccountId>,624 QueryKind = OptionQuery,625 >;626627 628 #[pallet::storage]629 #[pallet::getter(fn collection_properties)]630 pub type CollectionProperties<T> = StorageMap<631 Hasher = Blake2_128Concat,632 Key = CollectionId,633 Value = Properties,634 QueryKind = ValueQuery,635 OnEmpty = up_data_structs::CollectionProperties,636 >;637638 639 #[pallet::storage]640 #[pallet::getter(fn property_permissions)]641 pub type CollectionPropertyPermissions<T> = StorageMap<642 Hasher = Blake2_128Concat,643 Key = CollectionId,644 Value = PropertiesPermissionMap,645 QueryKind = ValueQuery,646 >;647648 649 #[pallet::storage]650 pub type AdminAmount<T> = StorageMap<651 Hasher = Blake2_128Concat,652 Key = CollectionId,653 Value = u32,654 QueryKind = ValueQuery,655 >;656657 658 #[pallet::storage]659 pub type IsAdmin<T: Config> = StorageNMap<660 Key = (661 Key<Blake2_128Concat, CollectionId>,662 Key<Blake2_128Concat, T::CrossAccountId>,663 ),664 Value = bool,665 QueryKind = ValueQuery,666 >;667668 669 #[pallet::storage]670 pub type Allowlist<T: Config> = StorageNMap<671 Key = (672 Key<Blake2_128Concat, CollectionId>,673 Key<Blake2_128Concat, T::CrossAccountId>,674 ),675 Value = bool,676 QueryKind = ValueQuery,677 >;678679 680 #[pallet::storage]681 pub type DummyStorageValue<T: Config> = StorageValue<682 Value = (683 CollectionStats,684 CollectionId,685 TokenId,686 TokenChild,687 PhantomType<(688 TokenData<T::CrossAccountId>,689 RpcCollection<T::AccountId>,690 691 RmrkCollectionInfo<T::AccountId>,692 RmrkInstanceInfo<T::AccountId>,693 RmrkResourceInfo,694 RmrkPropertyInfo,695 RmrkBaseInfo<T::AccountId>,696 RmrkPartType,697 RmrkBoundedTheme,698 RmrkNftChild,699 )>,700 ),701 QueryKind = OptionQuery,702 >;703704 #[pallet::hooks]705 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {706 fn on_runtime_upgrade() -> Weight {707 StorageVersion::new(1).put::<Pallet<T>>();708709 0710 }711 }712}713714impl<T: Config> Pallet<T> {715 716 717 718 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {719 ensure!(720 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,721 <Error<T>>::AddressIsZero722 );723 Ok(())724 }725726 727 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {728 <IsAdmin<T>>::iter_prefix((collection,))729 .map(|(a, _)| a)730 .collect()731 }732733 734 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {735 <Allowlist<T>>::iter_prefix((collection,))736 .map(|(a, _)| a)737 .collect()738 }739740 741 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {742 <Allowlist<T>>::get((collection, user))743 }744745 746 pub fn collection_stats() -> CollectionStats {747 let created = <CreatedCollectionCount<T>>::get();748 let destroyed = <DestroyedCollectionCount<T>>::get();749 CollectionStats {750 created: created.0,751 destroyed: destroyed.0,752 alive: created.0 - destroyed.0,753 }754 }755756 757 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {758 let collection = <CollectionById<T>>::get(collection)?;759 let limits = collection.limits;760 let effective_limits = CollectionLimits {761 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),762 sponsored_data_size: Some(limits.sponsored_data_size()),763 sponsored_data_rate_limit: Some(764 limits765 .sponsored_data_rate_limit766 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),767 ),768 token_limit: Some(limits.token_limit()),769 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(770 match collection.mode {771 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,772 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,773 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,774 },775 )),776 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),777 owner_can_transfer: Some(limits.owner_can_transfer()),778 owner_can_destroy: Some(limits.owner_can_destroy()),779 transfers_enabled: Some(limits.transfers_enabled()),780 };781782 Some(effective_limits)783 }784785 786 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {787 let Collection {788 name,789 description,790 owner,791 mode,792 token_prefix,793 sponsorship,794 limits,795 permissions,796 flags,797 } = <CollectionById<T>>::get(collection)?;798799 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)800 .into_iter()801 .map(|(key, permission)| PropertyKeyPermission { key, permission })802 .collect();803804 let properties = <CollectionProperties<T>>::get(collection)805 .into_iter()806 .map(|(key, value)| Property { key, value })807 .collect();808809 let permissions = CollectionPermissions {810 access: Some(permissions.access()),811 mint_mode: Some(permissions.mint_mode()),812 nesting: Some(permissions.nesting().clone()),813 };814815 Some(RpcCollection {816 name: name.into_inner(),817 description: description.into_inner(),818 owner,819 mode,820 token_prefix: token_prefix.into_inner(),821 sponsorship,822 limits,823 permissions,824 token_property_permissions,825 properties,826 read_only: flags.external,827 foreign: flags.foreign,828 })829 }830}831832macro_rules! limit_default {833 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{834 $(835 if let Some($new) = $new.$field {836 let $old = $old.$field($($arg)?);837 let _ = $new;838 let _ = $old;839 $check840 } else {841 $new.$field = $old.$field842 }843 )*844 }};845}846macro_rules! limit_default_clone {847 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{848 $(849 if let Some($new) = $new.$field.clone() {850 let $old = $old.$field($($arg)?);851 let _ = $new;852 let _ = $old;853 $check854 } else {855 $new.$field = $old.$field.clone()856 }857 )*858 }};859}860861impl<T: Config> Pallet<T> {862 863 864 865 866 867 pub fn init_collection(868 owner: T::CrossAccountId,869 payer: T::CrossAccountId,870 data: CreateCollectionData<T::AccountId>,871 flags: CollectionFlags,872 ) -> Result<CollectionId, DispatchError> {873 {874 ensure!(875 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,876 Error::<T>::CollectionTokenPrefixLimitExceeded877 );878 }879880 let created_count = <CreatedCollectionCount<T>>::get()881 .0882 .checked_add(1)883 .ok_or(ArithmeticError::Overflow)?;884 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;885 let id = CollectionId(created_count);886887 888 ensure!(889 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,890 <Error<T>>::TotalCollectionsLimitExceeded891 );892893 894895 let collection = Collection {896 owner: owner.as_sub().clone(),897 name: data.name,898 mode: data.mode.clone(),899 description: data.description,900 token_prefix: data.token_prefix,901 sponsorship: data902 .pending_sponsor903 .map(SponsorshipState::Unconfirmed)904 .unwrap_or_default(),905 limits: data906 .limits907 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))908 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,909 permissions: data910 .permissions911 .map(|permissions| {912 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)913 })914 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,915 flags,916 };917918 let mut collection_properties = up_data_structs::CollectionProperties::get();919 collection_properties920 .try_set_from_iter(data.properties.into_iter())921 .map_err(<Error<T>>::from)?;922923 CollectionProperties::<T>::insert(id, collection_properties);924925 let mut token_props_permissions = PropertiesPermissionMap::new();926 token_props_permissions927 .try_set_from_iter(data.token_property_permissions.into_iter())928 .map_err(<Error<T>>::from)?;929930 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);931932 933 {934 let mut imbalance =935 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();936 imbalance.subsume(937 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(938 &T::TreasuryAccountId::get(),939 T::CollectionCreationPrice::get(),940 ),941 );942 <T as Config>::Currency::settle(943 payer.as_sub(),944 imbalance,945 WithdrawReasons::TRANSFER,946 ExistenceRequirement::KeepAlive,947 )948 .map_err(|_| Error::<T>::NotSufficientFounds)?;949 }950951 <CreatedCollectionCount<T>>::put(created_count);952 <Pallet<T>>::deposit_event(Event::CollectionCreated(953 id,954 data.mode.id(),955 owner.as_sub().clone(),956 ));957 <PalletEvm<T>>::deposit_log(958 erc::CollectionHelpersEvents::CollectionCreated {959 owner: *owner.as_eth(),960 collection_id: eth::collection_id_to_address(id),961 }962 .to_log(T::ContractAddress::get()),963 );964 <CollectionById<T>>::insert(id, collection);965 Ok(id)966 }967968 969 970 971 972 pub fn destroy_collection(973 collection: CollectionHandle<T>,974 sender: &T::CrossAccountId,975 ) -> DispatchResult {976 ensure!(977 collection.limits.owner_can_destroy(),978 <Error<T>>::NoPermission,979 );980 collection.check_is_owner(sender)?;981982 let destroyed_collections = <DestroyedCollectionCount<T>>::get()983 .0984 .checked_add(1)985 .ok_or(ArithmeticError::Overflow)?;986987 988989 <DestroyedCollectionCount<T>>::put(destroyed_collections);990 <CollectionById<T>>::remove(collection.id);991 <AdminAmount<T>>::remove(collection.id);992 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);993 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);994 <CollectionProperties<T>>::remove(collection.id);995996 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));997 Ok(())998 }9991000 1001 1002 1003 1004 1005 pub fn set_collection_property(1006 collection: &CollectionHandle<T>,1007 sender: &T::CrossAccountId,1008 property: Property,1009 ) -> DispatchResult {1010 collection.check_is_owner_or_admin(sender)?;10111012 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1013 let property = property.clone();1014 properties.try_set(property.key, property.value)1015 })1016 .map_err(<Error<T>>::from)?;10171018 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10191020 Ok(())1021 }10221023 1024 1025 1026 1027 1028 pub fn set_scoped_collection_property(1029 collection_id: CollectionId,1030 scope: PropertyScope,1031 property: Property,1032 ) -> DispatchResult {1033 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1034 properties.try_scoped_set(scope, property.key, property.value)1035 })1036 .map_err(<Error<T>>::from)?;10371038 Ok(())1039 }10401041 1042 1043 1044 1045 1046 pub fn set_scoped_collection_properties(1047 collection_id: CollectionId,1048 scope: PropertyScope,1049 properties: impl Iterator<Item = Property>,1050 ) -> DispatchResult {1051 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1052 stored_properties.try_scoped_set_from_iter(scope, properties)1053 })1054 .map_err(<Error<T>>::from)?;10551056 Ok(())1057 }10581059 1060 1061 1062 1063 1064 #[transactional]1065 pub fn set_collection_properties(1066 collection: &CollectionHandle<T>,1067 sender: &T::CrossAccountId,1068 properties: Vec<Property>,1069 ) -> DispatchResult {1070 for property in properties {1071 Self::set_collection_property(collection, sender, property)?;1072 }10731074 Ok(())1075 }10761077 1078 1079 1080 1081 1082 pub fn delete_collection_property(1083 collection: &CollectionHandle<T>,1084 sender: &T::CrossAccountId,1085 property_key: PropertyKey,1086 ) -> DispatchResult {1087 collection.check_is_owner_or_admin(sender)?;10881089 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1090 properties.remove(&property_key)1091 })1092 .map_err(<Error<T>>::from)?;10931094 Self::deposit_event(Event::CollectionPropertyDeleted(1095 collection.id,1096 property_key,1097 ));10981099 Ok(())1100 }11011102 1103 1104 1105 1106 1107 #[transactional]1108 pub fn delete_collection_properties(1109 collection: &CollectionHandle<T>,1110 sender: &T::CrossAccountId,1111 property_keys: Vec<PropertyKey>,1112 ) -> DispatchResult {1113 for key in property_keys {1114 Self::delete_collection_property(collection, sender, key)?;1115 }11161117 Ok(())1118 }11191120 1121 1122 1123 1124 1125 1126 pub fn set_property_permission_unchecked(1127 collection: CollectionId,1128 property_permission: PropertyKeyPermission,1129 ) -> DispatchResult {1130 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1131 permissions.try_set(property_permission.key, property_permission.permission)1132 })1133 .map_err(<Error<T>>::from)?;1134 Ok(())1135 }11361137 1138 1139 1140 1141 1142 pub fn set_property_permission(1143 collection: &CollectionHandle<T>,1144 sender: &T::CrossAccountId,1145 property_permission: PropertyKeyPermission,1146 ) -> DispatchResult {1147 Self::set_scoped_property_permission(1148 collection,1149 sender,1150 PropertyScope::None,1151 property_permission,1152 )1153 }11541155 1156 1157 1158 1159 1160 1161 pub fn set_scoped_property_permission(1162 collection: &CollectionHandle<T>,1163 sender: &T::CrossAccountId,1164 scope: PropertyScope,1165 property_permission: PropertyKeyPermission,1166 ) -> DispatchResult {1167 collection.check_is_owner_or_admin(sender)?;11681169 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1170 let current_permission = all_permissions.get(&property_permission.key);1171 if matches![1172 current_permission,1173 Some(PropertyPermission { mutable: false, .. })1174 ] {1175 return Err(<Error<T>>::NoPermission.into());1176 }11771178 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1179 let property_permission = property_permission.clone();1180 permissions.try_scoped_set(1181 scope,1182 property_permission.key,1183 property_permission.permission,1184 )1185 })1186 .map_err(<Error<T>>::from)?;11871188 Self::deposit_event(Event::PropertyPermissionSet(1189 collection.id,1190 property_permission.key,1191 ));11921193 Ok(())1194 }11951196 1197 1198 1199 1200 1201 #[transactional]1202 pub fn set_token_property_permissions(1203 collection: &CollectionHandle<T>,1204 sender: &T::CrossAccountId,1205 property_permissions: Vec<PropertyKeyPermission>,1206 ) -> DispatchResult {1207 Self::set_scoped_token_property_permissions(1208 collection,1209 sender,1210 PropertyScope::None,1211 property_permissions,1212 )1213 }12141215 1216 1217 1218 1219 1220 1221 #[transactional]1222 pub fn set_scoped_token_property_permissions(1223 collection: &CollectionHandle<T>,1224 sender: &T::CrossAccountId,1225 scope: PropertyScope,1226 property_permissions: Vec<PropertyKeyPermission>,1227 ) -> DispatchResult {1228 for prop_pemission in property_permissions {1229 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1230 }12311232 Ok(())1233 }12341235 1236 pub fn get_collection_property(1237 collection_id: CollectionId,1238 key: &PropertyKey,1239 ) -> Option<PropertyValue> {1240 Self::collection_properties(collection_id).get(key).cloned()1241 }12421243 1244 pub fn bytes_keys_to_property_keys(1245 keys: Vec<Vec<u8>>,1246 ) -> Result<Vec<PropertyKey>, DispatchError> {1247 keys.into_iter()1248 .map(|key| -> Result<PropertyKey, DispatchError> {1249 key.try_into()1250 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1251 })1252 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1253 }12541255 1256 pub fn filter_collection_properties(1257 collection_id: CollectionId,1258 keys: Option<Vec<PropertyKey>>,1259 ) -> Result<Vec<Property>, DispatchError> {1260 let properties = Self::collection_properties(collection_id);12611262 let properties = keys1263 .map(|keys| {1264 keys.into_iter()1265 .filter_map(|key| {1266 properties.get(&key).map(|value| Property {1267 key,1268 value: value.clone(),1269 })1270 })1271 .collect()1272 })1273 .unwrap_or_else(|| {1274 properties1275 .into_iter()1276 .map(|(key, value)| Property { key, value })1277 .collect()1278 });12791280 Ok(properties)1281 }12821283 1284 pub fn filter_property_permissions(1285 collection_id: CollectionId,1286 keys: Option<Vec<PropertyKey>>,1287 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1288 let permissions = Self::property_permissions(collection_id);12891290 let key_permissions = keys1291 .map(|keys| {1292 keys.into_iter()1293 .filter_map(|key| {1294 permissions1295 .get(&key)1296 .map(|permission| PropertyKeyPermission {1297 key,1298 permission: permission.clone(),1299 })1300 })1301 .collect()1302 })1303 .unwrap_or_else(|| {1304 permissions1305 .into_iter()1306 .map(|(key, permission)| PropertyKeyPermission { key, permission })1307 .collect()1308 });13091310 Ok(key_permissions)1311 }13121313 1314 1315 1316 pub fn toggle_allowlist(1317 collection: &CollectionHandle<T>,1318 sender: &T::CrossAccountId,1319 user: &T::CrossAccountId,1320 allowed: bool,1321 ) -> DispatchResult {1322 collection.check_is_owner_or_admin(sender)?;13231324 13251326 if allowed {1327 <Allowlist<T>>::insert((collection.id, user), true);1328 } else {1329 <Allowlist<T>>::remove((collection.id, user));1330 }13311332 Ok(())1333 }13341335 1336 1337 1338 pub fn toggle_admin(1339 collection: &CollectionHandle<T>,1340 sender: &T::CrossAccountId,1341 user: &T::CrossAccountId,1342 admin: bool,1343 ) -> DispatchResult {1344 collection.check_is_owner(sender)?;13451346 let was_admin = <IsAdmin<T>>::get((collection.id, user));1347 if was_admin == admin {1348 return Ok(());1349 }1350 let amount = <AdminAmount<T>>::get(collection.id);13511352 if admin {1353 let amount = amount1354 .checked_add(1)1355 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1356 ensure!(1357 amount <= Self::collection_admins_limit(),1358 <Error<T>>::CollectionAdminCountExceeded,1359 );13601361 13621363 <AdminAmount<T>>::insert(collection.id, amount);1364 <IsAdmin<T>>::insert((collection.id, user), true);1365 } else {1366 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1367 <IsAdmin<T>>::remove((collection.id, user));1368 }13691370 Ok(())1371 }13721373 1374 pub fn clamp_limits(1375 mode: CollectionMode,1376 old_limit: &CollectionLimits,1377 mut new_limit: CollectionLimits,1378 ) -> Result<CollectionLimits, DispatchError> {1379 let limits = old_limit;1380 limit_default!(old_limit, new_limit,1381 account_token_ownership_limit => ensure!(1382 new_limit <= MAX_TOKEN_OWNERSHIP,1383 <Error<T>>::CollectionLimitBoundsExceeded,1384 ),1385 sponsored_data_size => ensure!(1386 new_limit <= CUSTOM_DATA_LIMIT,1387 <Error<T>>::CollectionLimitBoundsExceeded,1388 ),13891390 sponsored_data_rate_limit => {},1391 token_limit => ensure!(1392 old_limit >= new_limit && new_limit > 0,1393 <Error<T>>::CollectionTokenLimitExceeded1394 ),13951396 sponsor_transfer_timeout(match mode {1397 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1398 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1399 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1400 }) => ensure!(1401 new_limit <= MAX_SPONSOR_TIMEOUT,1402 <Error<T>>::CollectionLimitBoundsExceeded,1403 ),1404 sponsor_approve_timeout => {},1405 owner_can_transfer => ensure!(1406 !limits.owner_can_transfer_instaled() ||1407 old_limit || !new_limit,1408 <Error<T>>::OwnerPermissionsCantBeReverted,1409 ),1410 owner_can_destroy => ensure!(1411 old_limit || !new_limit,1412 <Error<T>>::OwnerPermissionsCantBeReverted,1413 ),1414 transfers_enabled => {},1415 );1416 Ok(new_limit)1417 }14181419 1420 pub fn clamp_permissions(1421 _mode: CollectionMode,1422 old_permission: &CollectionPermissions,1423 mut new_permission: CollectionPermissions,1424 ) -> Result<CollectionPermissions, DispatchError> {1425 limit_default_clone!(old_permission, new_permission,1426 access => {},1427 mint_mode => {},1428 nesting => { },1429 );1430 Ok(new_permission)1431 }1432}143314341435#[macro_export]1436macro_rules! unsupported {1437 ($runtime:path) => {1438 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1439 };1440}144114421443pub trait CommonWeightInfo<CrossAccountId> {1444 1445 fn create_item() -> Weight;14461447 1448 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14491450 1451 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14521453 1454 fn burn_item() -> Weight;14551456 1457 1458 1459 fn set_collection_properties(amount: u32) -> Weight;14601461 1462 1463 1464 fn delete_collection_properties(amount: u32) -> Weight;14651466 1467 1468 1469 fn set_token_properties(amount: u32) -> Weight;14701471 1472 1473 1474 fn delete_token_properties(amount: u32) -> Weight;14751476 1477 1478 1479 fn set_token_property_permissions(amount: u32) -> Weight;14801481 1482 fn transfer() -> Weight;14831484 1485 fn approve() -> Weight;14861487 1488 fn transfer_from() -> Weight;14891490 1491 fn burn_from() -> Weight;14921493 1494 1495 1496 1497 fn burn_recursively_self_raw() -> Weight;14981499 1500 1501 1502 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15031504 1505 1506 1507 1508 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1509 Self::burn_recursively_self_raw()1510 .saturating_mul(max_selfs.max(1) as u64)1511 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1512 }15131514 1515 fn token_owner() -> Weight;1516}151715181519pub trait RefungibleExtensionsWeightInfo {1520 1521 fn repartition() -> Weight;1522}152315241525152615271528pub trait CommonCollectionOperations<T: Config> {1529 1530 1531 1532 1533 1534 1535 fn create_item(1536 &self,1537 sender: T::CrossAccountId,1538 to: T::CrossAccountId,1539 data: CreateItemData,1540 nesting_budget: &dyn Budget,1541 ) -> DispatchResultWithPostInfo;15421543 1544 1545 1546 1547 1548 1549 fn create_multiple_items(1550 &self,1551 sender: T::CrossAccountId,1552 to: T::CrossAccountId,1553 data: Vec<CreateItemData>,1554 nesting_budget: &dyn Budget,1555 ) -> DispatchResultWithPostInfo;15561557 1558 1559 1560 1561 1562 1563 fn create_multiple_items_ex(1564 &self,1565 sender: T::CrossAccountId,1566 data: CreateItemExData<T::CrossAccountId>,1567 nesting_budget: &dyn Budget,1568 ) -> DispatchResultWithPostInfo;15691570 1571 1572 1573 1574 1575 fn burn_item(1576 &self,1577 sender: T::CrossAccountId,1578 token: TokenId,1579 amount: u128,1580 ) -> DispatchResultWithPostInfo;15811582 1583 1584 1585 1586 1587 1588 fn burn_item_recursively(1589 &self,1590 sender: T::CrossAccountId,1591 token: TokenId,1592 self_budget: &dyn Budget,1593 breadth_budget: &dyn Budget,1594 ) -> DispatchResultWithPostInfo;15951596 1597 1598 1599 1600 fn set_collection_properties(1601 &self,1602 sender: T::CrossAccountId,1603 properties: Vec<Property>,1604 ) -> DispatchResultWithPostInfo;16051606 1607 1608 1609 1610 fn delete_collection_properties(1611 &self,1612 sender: &T::CrossAccountId,1613 property_keys: Vec<PropertyKey>,1614 ) -> DispatchResultWithPostInfo;16151616 1617 1618 1619 1620 1621 1622 1623 1624 1625 fn set_token_properties(1626 &self,1627 sender: T::CrossAccountId,1628 token_id: TokenId,1629 properties: Vec<Property>,1630 budget: &dyn Budget,1631 ) -> DispatchResultWithPostInfo;16321633 1634 1635 1636 1637 1638 1639 1640 1641 1642 fn delete_token_properties(1643 &self,1644 sender: T::CrossAccountId,1645 token_id: TokenId,1646 property_keys: Vec<PropertyKey>,1647 budget: &dyn Budget,1648 ) -> DispatchResultWithPostInfo;16491650 1651 1652 1653 1654 1655 1656 fn set_token_property_permissions(1657 &self,1658 sender: &T::CrossAccountId,1659 property_permissions: Vec<PropertyKeyPermission>,1660 ) -> DispatchResultWithPostInfo;16611662 1663 1664 1665 1666 1667 1668 1669 fn transfer(1670 &self,1671 sender: T::CrossAccountId,1672 to: T::CrossAccountId,1673 token: TokenId,1674 amount: u128,1675 budget: &dyn Budget,1676 ) -> DispatchResultWithPostInfo;16771678 1679 1680 1681 1682 1683 1684 fn approve(1685 &self,1686 sender: T::CrossAccountId,1687 spender: T::CrossAccountId,1688 token: TokenId,1689 amount: u128,1690 ) -> DispatchResultWithPostInfo;16911692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 fn transfer_from(1703 &self,1704 sender: T::CrossAccountId,1705 from: T::CrossAccountId,1706 to: T::CrossAccountId,1707 token: TokenId,1708 amount: u128,1709 budget: &dyn Budget,1710 ) -> DispatchResultWithPostInfo;17111712 1713 1714 1715 1716 1717 1718 1719 1720 1721 fn burn_from(1722 &self,1723 sender: T::CrossAccountId,1724 from: T::CrossAccountId,1725 token: TokenId,1726 amount: u128,1727 budget: &dyn Budget,1728 ) -> DispatchResultWithPostInfo;17291730 1731 1732 1733 1734 1735 1736 fn check_nesting(1737 &self,1738 sender: T::CrossAccountId,1739 from: (CollectionId, TokenId),1740 under: TokenId,1741 budget: &dyn Budget,1742 ) -> DispatchResult;17431744 1745 1746 1747 1748 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17491750 1751 1752 1753 1754 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17551756 1757 1758 1759 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17601761 1762 fn collection_tokens(&self) -> Vec<TokenId>;17631764 1765 1766 1767 fn token_exists(&self, token: TokenId) -> bool;17681769 1770 fn last_token_id(&self) -> TokenId;17711772 1773 1774 1775 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17761777 1778 1779 1780 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17811782 1783 1784 1785 1786 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17871788 1789 1790 1791 1792 1793 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17941795 1796 fn total_supply(&self) -> u32;17971798 1799 1800 1801 fn account_balance(&self, account: T::CrossAccountId) -> u32;18021803 1804 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18051806 1807 fn total_pieces(&self, token: TokenId) -> Option<u128>;18081809 1810 1811 1812 1813 1814 fn allowance(1815 &self,1816 sender: T::CrossAccountId,1817 spender: T::CrossAccountId,1818 token: TokenId,1819 ) -> u128;18201821 1822 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1823}182418251826pub trait RefungibleExtensions<T>1827where1828 T: Config,1829{1830 1831 1832 1833 1834 1835 1836 1837 fn repartition(1838 &self,1839 sender: &T::CrossAccountId,1840 token: TokenId,1841 amount: u128,1842 ) -> DispatchResultWithPostInfo;1843}18441845184618471848pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1849 let post_info = PostDispatchInfo {1850 actual_weight: Some(weight),1851 pays_fee: Pays::Yes,1852 };1853 match res {1854 Ok(()) => Ok(post_info),1855 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1856 }1857}18581859impl<T: Config> From<PropertiesError> for Error<T> {1860 fn from(error: PropertiesError) -> Self {1861 match error {1862 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1863 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1864 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1865 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1866 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1867 }1868 }1869}