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 dispatch::Pays,66 transactional,67};68use pallet_evm::GasWeightMapping;69use up_data_structs::{70 COLLECTION_NUMBER_LIMIT,71 Collection,72 RpcCollection,73 CollectionFlags,74 RpcCollectionFlags,75 CollectionId,76 CreateItemData,77 MAX_TOKEN_PREFIX_LENGTH,78 COLLECTION_ADMINS_LIMIT,79 TokenId,80 TokenChild,81 CollectionStats,82 MAX_TOKEN_OWNERSHIP,83 CollectionMode,84 NFT_SPONSOR_TRANSFER_TIMEOUT,85 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,87 MAX_SPONSOR_TIMEOUT,88 CUSTOM_DATA_LIMIT,89 CollectionLimits,90 CreateCollectionData,91 SponsorshipState,92 CreateItemExData,93 SponsoringRateLimit,94 budget::Budget,95 PhantomType,96 Property,97 Properties,98 PropertiesPermissionMap,99 PropertyKey,100 PropertyValue,101 PropertyPermission,102 PropertiesError,103 PropertyKeyPermission,104 TokenData,105 TrySetProperty,106 PropertyScope,107 108 RmrkCollectionInfo,109 RmrkInstanceInfo,110 RmrkResourceInfo,111 RmrkPropertyInfo,112 RmrkBaseInfo,113 RmrkPartType,114 RmrkBoundedTheme,115 RmrkNftChild,116 CollectionPermissions,117};118119pub use pallet::*;120use sp_core::H160;121use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};122#[cfg(feature = "runtime-benchmarks")]123pub mod benchmarking;124pub mod dispatch;125pub mod erc;126pub mod eth;127pub mod weights;128129130pub type SelfWeightOf<T> = <T as Config>::WeightInfo;131132133134135136137138#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]139pub struct CollectionHandle<T: Config> {140 141 pub id: CollectionId,142 collection: Collection<T::AccountId>,143 144 pub recorder: SubstrateRecorder<T>,145}146147impl<T: Config> WithRecorder<T> for CollectionHandle<T> {148 fn recorder(&self) -> &SubstrateRecorder<T> {149 &self.recorder150 }151 fn into_recorder(self) -> SubstrateRecorder<T> {152 self.recorder153 }154}155156impl<T: Config> CollectionHandle<T> {157 158 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {159 <CollectionById<T>>::get(id).map(|collection| Self {160 id,161 collection,162 recorder: SubstrateRecorder::new(gas_limit),163 })164 }165166 167 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {168 <CollectionById<T>>::get(id).map(|collection| Self {169 id,170 collection,171 recorder,172 })173 }174175 176 177 pub fn new(id: CollectionId) -> Option<Self> {178 Self::new_with_gas_limit(id, u64::MAX)179 }180181 182 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {183 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)184 }185186 187 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {188 self.recorder189 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(190 <T as frame_system::Config>::DbWeight::get()191 .read192 .saturating_mul(reads),193 )))194 }195196 197 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {198 self.recorder199 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(200 <T as frame_system::Config>::DbWeight::get()201 .write202 .saturating_mul(writes),203 )))204 }205206 207 pub fn consume_store_reads_and_writes(208 &self,209 reads: u64,210 writes: u64,211 ) -> evm_coder::execution::Result<()> {212 let weight = <T as frame_system::Config>::DbWeight::get();213 let reads = weight.read.saturating_mul(reads);214 let writes = weight.read.saturating_mul(writes);215 self.recorder216 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(217 reads.saturating_add(writes),218 )))219 }220221 222 pub fn save(&self) -> DispatchResult {223 <CollectionById<T>>::insert(self.id, &self.collection);224 Ok(())225 }226227 228 229 230 231 232 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {233 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);234 Ok(())235 }236237 238 239 240 241 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {242 if self.collection.sponsorship.pending_sponsor() != Some(sender) {243 return Ok(false);244 }245246 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());247 Ok(true)248 }249250 251 pub fn remove_sponsor(&mut self) -> DispatchResult {252 self.collection.sponsorship = SponsorshipState::Disabled;253 Ok(())254 }255256 257 258 pub fn check_is_internal(&self) -> DispatchResult {259 if self.flags.external {260 return Err(<Error<T>>::CollectionIsExternal)?;261 }262263 Ok(())264 }265266 267 268 pub fn check_is_external(&self) -> DispatchResult {269 if !self.flags.external {270 return Err(<Error<T>>::CollectionIsInternal)?;271 }272273 Ok(())274 }275}276277impl<T: Config> Deref for CollectionHandle<T> {278 type Target = Collection<T::AccountId>;279280 fn deref(&self) -> &Self::Target {281 &self.collection282 }283}284285impl<T: Config> DerefMut for CollectionHandle<T> {286 fn deref_mut(&mut self) -> &mut Self::Target {287 &mut self.collection288 }289}290291impl<T: Config> CollectionHandle<T> {292 293 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {294 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);295 Ok(())296 }297298 299 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {300 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))301 }302303 304 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {305 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);306 Ok(())307 }308309 310 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {311 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)312 }313314 315 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {316 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)317 }318319 320 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {321 ensure!(322 <Allowlist<T>>::get((self.id, user)),323 <Error<T>>::AddressNotInAllowlist324 );325 Ok(())326 }327328 329 330 331 fn set_owner_internal(332 &mut self,333 caller: T::CrossAccountId,334 new_owner: T::CrossAccountId,335 ) -> DispatchResult {336 self.check_is_owner(&caller)?;337 self.collection.owner = new_owner.as_sub().clone();338 self.save()339 }340}341342#[frame_support::pallet]343pub mod pallet {344 use super::*;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::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo356 {357 358 type WeightInfo: WeightInfo;359360 361 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;362363 364 type Currency: Currency<Self::AccountId>;365366 367 #[pallet::constant]368 type CollectionCreationPrice: Get<369 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,370 >;371372 373 type CollectionDispatch: CollectionDispatch<Self>;374375 376 type TreasuryAccountId: Get<Self::AccountId>;377378 379 #[pallet::constant]380 type ContractAddress: Get<H160>;381382 383 type EvmTokenAddressMapping: TokenAddressMapping<H160>;384385 386 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;387 }388389 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);390391 #[pallet::pallet]392 #[pallet::storage_version(STORAGE_VERSION)]393 #[pallet::generate_store(pub(super) trait Store)]394 pub struct Pallet<T>(_);395396 #[pallet::extra_constants]397 impl<T: Config> Pallet<T> {398 399 pub fn collection_admins_limit() -> u32 {400 COLLECTION_ADMINS_LIMIT401 }402 }403404 #[pallet::event]405 #[pallet::generate_deposit(pub fn deposit_event)]406 pub enum Event<T: Config> {407 408 CollectionCreated(409 410 CollectionId,411 412 u8,413 414 T::AccountId,415 ),416417 418 CollectionDestroyed(419 420 CollectionId,421 ),422423 424 ItemCreated(425 426 CollectionId,427 428 TokenId,429 430 T::CrossAccountId,431 432 u128,433 ),434435 436 ItemDestroyed(437 438 CollectionId,439 440 TokenId,441 442 T::CrossAccountId,443 444 u128,445 ),446447 448 Transfer(449 450 CollectionId,451 452 TokenId,453 454 T::CrossAccountId,455 456 T::CrossAccountId,457 458 u128,459 ),460461 462 Approved(463 464 CollectionId,465 466 TokenId,467 468 T::CrossAccountId,469 470 T::CrossAccountId,471 472 u128,473 ),474475 476 CollectionPropertySet(477 478 CollectionId,479 480 PropertyKey,481 ),482483 484 CollectionPropertyDeleted(485 486 CollectionId,487 488 PropertyKey,489 ),490491 492 TokenPropertySet(493 494 CollectionId,495 496 TokenId,497 498 PropertyKey,499 ),500501 502 TokenPropertyDeleted(503 504 CollectionId,505 506 TokenId,507 508 PropertyKey,509 ),510511 512 PropertyPermissionSet(513 514 CollectionId,515 516 PropertyKey,517 ),518 }519520 #[pallet::error]521 pub enum Error<T> {522 523 CollectionNotFound,524 525 MustBeTokenOwner,526 527 NoPermission,528 529 CantDestroyNotEmptyCollection,530 531 PublicMintingNotAllowed,532 533 AddressNotInAllowlist,534535 536 CollectionNameLimitExceeded,537 538 CollectionDescriptionLimitExceeded,539 540 CollectionTokenPrefixLimitExceeded,541 542 TotalCollectionsLimitExceeded,543 544 CollectionAdminCountExceeded,545 546 CollectionLimitBoundsExceeded,547 548 OwnerPermissionsCantBeReverted,549 550 TransferNotAllowed,551 552 AccountTokenLimitExceeded,553 554 CollectionTokenLimitExceeded,555 556 MetadataFlagFrozen,557558 559 TokenNotFound,560 561 TokenValueTooLow,562 563 ApprovedValueTooLow,564 565 CantApproveMoreThanOwned,566567 568 AddressIsZero,569570 571 UnsupportedOperation,572573 574 NotSufficientFounds,575576 577 UserIsNotAllowedToNest,578 579 SourceCollectionIsNotAllowedToNest,580581 582 CollectionFieldSizeExceeded,583584 585 NoSpaceForProperty,586587 588 PropertyLimitReached,589590 591 PropertyKeyIsTooLong,592593 594 InvalidCharacterInPropertyKey,595596 597 EmptyPropertyKey,598599 600 CollectionIsExternal,601602 603 CollectionIsInternal,604 }605606 607 #[pallet::storage]608 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;609610 611 #[pallet::storage]612 pub type DestroyedCollectionCount<T> =613 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;614615 616 #[pallet::storage]617 pub type CollectionById<T> = StorageMap<618 Hasher = Blake2_128Concat,619 Key = CollectionId,620 Value = Collection<<T as frame_system::Config>::AccountId>,621 QueryKind = OptionQuery,622 >;623624 625 #[pallet::storage]626 #[pallet::getter(fn collection_properties)]627 pub type CollectionProperties<T> = StorageMap<628 Hasher = Blake2_128Concat,629 Key = CollectionId,630 Value = Properties,631 QueryKind = ValueQuery,632 OnEmpty = up_data_structs::CollectionProperties,633 >;634635 636 #[pallet::storage]637 #[pallet::getter(fn property_permissions)]638 pub type CollectionPropertyPermissions<T> = StorageMap<639 Hasher = Blake2_128Concat,640 Key = CollectionId,641 Value = PropertiesPermissionMap,642 QueryKind = ValueQuery,643 >;644645 646 #[pallet::storage]647 pub type AdminAmount<T> = StorageMap<648 Hasher = Blake2_128Concat,649 Key = CollectionId,650 Value = u32,651 QueryKind = ValueQuery,652 >;653654 655 #[pallet::storage]656 pub type IsAdmin<T: Config> = StorageNMap<657 Key = (658 Key<Blake2_128Concat, CollectionId>,659 Key<Blake2_128Concat, T::CrossAccountId>,660 ),661 Value = bool,662 QueryKind = ValueQuery,663 >;664665 666 #[pallet::storage]667 pub type Allowlist<T: Config> = StorageNMap<668 Key = (669 Key<Blake2_128Concat, CollectionId>,670 Key<Blake2_128Concat, T::CrossAccountId>,671 ),672 Value = bool,673 QueryKind = ValueQuery,674 >;675676 677 #[pallet::storage]678 pub type DummyStorageValue<T: Config> = StorageValue<679 Value = (680 CollectionStats,681 CollectionId,682 TokenId,683 TokenChild,684 PhantomType<(685 TokenData<T::CrossAccountId>,686 RpcCollection<T::AccountId>,687 688 RmrkCollectionInfo<T::AccountId>,689 RmrkInstanceInfo<T::AccountId>,690 RmrkResourceInfo,691 RmrkPropertyInfo,692 RmrkBaseInfo<T::AccountId>,693 RmrkPartType,694 RmrkBoundedTheme,695 RmrkNftChild,696 )>,697 ),698 QueryKind = OptionQuery,699 >;700701 #[pallet::hooks]702 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {703 fn on_runtime_upgrade() -> Weight {704 StorageVersion::new(1).put::<Pallet<T>>();705706 Weight::zero()707 }708 }709}710711impl<T: Config> Pallet<T> {712 713 714 715 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {716 ensure!(717 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,718 <Error<T>>::AddressIsZero719 );720 Ok(())721 }722723 724 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {725 <IsAdmin<T>>::iter_prefix((collection,))726 .map(|(a, _)| a)727 .collect()728 }729730 731 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {732 <Allowlist<T>>::iter_prefix((collection,))733 .map(|(a, _)| a)734 .collect()735 }736737 738 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {739 <Allowlist<T>>::get((collection, user))740 }741742 743 pub fn collection_stats() -> CollectionStats {744 let created = <CreatedCollectionCount<T>>::get();745 let destroyed = <DestroyedCollectionCount<T>>::get();746 CollectionStats {747 created: created.0,748 destroyed: destroyed.0,749 alive: created.0 - destroyed.0,750 }751 }752753 754 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {755 let collection = <CollectionById<T>>::get(collection)?;756 let limits = collection.limits;757 let effective_limits = CollectionLimits {758 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),759 sponsored_data_size: Some(limits.sponsored_data_size()),760 sponsored_data_rate_limit: Some(761 limits762 .sponsored_data_rate_limit763 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),764 ),765 token_limit: Some(limits.token_limit()),766 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(767 match collection.mode {768 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,769 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,770 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,771 },772 )),773 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),774 owner_can_transfer: Some(limits.owner_can_transfer()),775 owner_can_destroy: Some(limits.owner_can_destroy()),776 transfers_enabled: Some(limits.transfers_enabled()),777 };778779 Some(effective_limits)780 }781782 783 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {784 let Collection {785 name,786 description,787 owner,788 mode,789 token_prefix,790 sponsorship,791 limits,792 permissions,793 flags,794 } = <CollectionById<T>>::get(collection)?;795796 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)797 .into_iter()798 .map(|(key, permission)| PropertyKeyPermission { key, permission })799 .collect();800801 let properties = <CollectionProperties<T>>::get(collection)802 .into_iter()803 .map(|(key, value)| Property { key, value })804 .collect();805806 let permissions = CollectionPermissions {807 access: Some(permissions.access()),808 mint_mode: Some(permissions.mint_mode()),809 nesting: Some(permissions.nesting().clone()),810 };811812 Some(RpcCollection {813 name: name.into_inner(),814 description: description.into_inner(),815 owner,816 mode,817 token_prefix: token_prefix.into_inner(),818 sponsorship,819 limits,820 permissions,821 token_property_permissions,822 properties,823 read_only: flags.external,824825 flags: RpcCollectionFlags {826 foreign: flags.foreign,827 erc721metadata: flags.erc721metadata,828 },829 })830 }831}832833macro_rules! limit_default {834 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{835 $(836 if let Some($new) = $new.$field {837 let $old = $old.$field($($arg)?);838 let _ = $new;839 let _ = $old;840 $check841 } else {842 $new.$field = $old.$field843 }844 )*845 }};846}847macro_rules! limit_default_clone {848 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{849 $(850 if let Some($new) = $new.$field.clone() {851 let $old = $old.$field($($arg)?);852 let _ = $new;853 let _ = $old;854 $check855 } else {856 $new.$field = $old.$field.clone()857 }858 )*859 }};860}861862impl<T: Config> Pallet<T> {863 864 865 866 867 868 pub fn init_collection(869 owner: T::CrossAccountId,870 payer: T::CrossAccountId,871 data: CreateCollectionData<T::AccountId>,872 flags: CollectionFlags,873 ) -> Result<CollectionId, DispatchError> {874 {875 ensure!(876 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,877 Error::<T>::CollectionTokenPrefixLimitExceeded878 );879 }880881 let created_count = <CreatedCollectionCount<T>>::get()882 .0883 .checked_add(1)884 .ok_or(ArithmeticError::Overflow)?;885 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;886 let id = CollectionId(created_count);887888 889 ensure!(890 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,891 <Error<T>>::TotalCollectionsLimitExceeded892 );893894 895896 let collection = Collection {897 owner: owner.as_sub().clone(),898 name: data.name,899 mode: data.mode.clone(),900 description: data.description,901 token_prefix: data.token_prefix,902 sponsorship: data903 .pending_sponsor904 .map(SponsorshipState::Unconfirmed)905 .unwrap_or_default(),906 limits: data907 .limits908 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))909 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,910 permissions: data911 .permissions912 .map(|permissions| {913 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)914 })915 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,916 flags,917 };918919 let mut collection_properties = up_data_structs::CollectionProperties::get();920 collection_properties921 .try_set_from_iter(data.properties.into_iter())922 .map_err(<Error<T>>::from)?;923924 CollectionProperties::<T>::insert(id, collection_properties);925926 let mut token_props_permissions = PropertiesPermissionMap::new();927 token_props_permissions928 .try_set_from_iter(data.token_property_permissions.into_iter())929 .map_err(<Error<T>>::from)?;930931 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);932933 934 {935 let mut imbalance =936 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();937 imbalance.subsume(938 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(939 &T::TreasuryAccountId::get(),940 T::CollectionCreationPrice::get(),941 ),942 );943 <T as Config>::Currency::settle(944 payer.as_sub(),945 imbalance,946 WithdrawReasons::TRANSFER,947 ExistenceRequirement::KeepAlive,948 )949 .map_err(|_| Error::<T>::NotSufficientFounds)?;950 }951952 <CreatedCollectionCount<T>>::put(created_count);953 <Pallet<T>>::deposit_event(Event::CollectionCreated(954 id,955 data.mode.id(),956 owner.as_sub().clone(),957 ));958 <PalletEvm<T>>::deposit_log(959 erc::CollectionHelpersEvents::CollectionCreated {960 owner: *owner.as_eth(),961 collection_id: eth::collection_id_to_address(id),962 }963 .to_log(T::ContractAddress::get()),964 );965 <CollectionById<T>>::insert(id, collection);966 Ok(id)967 }968969 970 971 972 973 pub fn destroy_collection(974 collection: CollectionHandle<T>,975 sender: &T::CrossAccountId,976 ) -> DispatchResult {977 ensure!(978 collection.limits.owner_can_destroy(),979 <Error<T>>::NoPermission,980 );981 collection.check_is_owner(sender)?;982983 let destroyed_collections = <DestroyedCollectionCount<T>>::get()984 .0985 .checked_add(1)986 .ok_or(ArithmeticError::Overflow)?;987988 989990 <DestroyedCollectionCount<T>>::put(destroyed_collections);991 <CollectionById<T>>::remove(collection.id);992 <AdminAmount<T>>::remove(collection.id);993 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);994 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);995 <CollectionProperties<T>>::remove(collection.id);996997 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));998999 <PalletEvm<T>>::deposit_log(1000 erc::CollectionHelpersEvents::CollectionDestroyed {1001 collection_id: eth::collection_id_to_address(collection.id),1002 }1003 .to_log(T::ContractAddress::get()),1004 );1005 Ok(())1006 }10071008 1009 1010 1011 1012 1013 pub fn set_collection_property(1014 collection: &CollectionHandle<T>,1015 sender: &T::CrossAccountId,1016 property: Property,1017 ) -> DispatchResult {1018 collection.check_is_owner_or_admin(sender)?;10191020 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1021 let property = property.clone();1022 properties.try_set(property.key, property.value)1023 })1024 .map_err(<Error<T>>::from)?;10251026 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10271028 Ok(())1029 }10301031 1032 1033 1034 1035 1036 pub fn set_scoped_collection_property(1037 collection_id: CollectionId,1038 scope: PropertyScope,1039 property: Property,1040 ) -> DispatchResult {1041 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1042 properties.try_scoped_set(scope, property.key, property.value)1043 })1044 .map_err(<Error<T>>::from)?;10451046 Ok(())1047 }10481049 1050 1051 1052 1053 1054 pub fn set_scoped_collection_properties(1055 collection_id: CollectionId,1056 scope: PropertyScope,1057 properties: impl Iterator<Item = Property>,1058 ) -> DispatchResult {1059 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1060 stored_properties.try_scoped_set_from_iter(scope, properties)1061 })1062 .map_err(<Error<T>>::from)?;10631064 Ok(())1065 }10661067 1068 1069 1070 1071 1072 #[transactional]1073 pub fn set_collection_properties(1074 collection: &CollectionHandle<T>,1075 sender: &T::CrossAccountId,1076 properties: Vec<Property>,1077 ) -> DispatchResult {1078 for property in properties {1079 Self::set_collection_property(collection, sender, property)?;1080 }10811082 Ok(())1083 }10841085 1086 1087 1088 1089 1090 pub fn delete_collection_property(1091 collection: &CollectionHandle<T>,1092 sender: &T::CrossAccountId,1093 property_key: PropertyKey,1094 ) -> DispatchResult {1095 collection.check_is_owner_or_admin(sender)?;10961097 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1098 properties.remove(&property_key)1099 })1100 .map_err(<Error<T>>::from)?;11011102 Self::deposit_event(Event::CollectionPropertyDeleted(1103 collection.id,1104 property_key,1105 ));11061107 Ok(())1108 }11091110 1111 1112 1113 1114 1115 #[transactional]1116 pub fn delete_collection_properties(1117 collection: &CollectionHandle<T>,1118 sender: &T::CrossAccountId,1119 property_keys: Vec<PropertyKey>,1120 ) -> DispatchResult {1121 for key in property_keys {1122 Self::delete_collection_property(collection, sender, key)?;1123 }11241125 Ok(())1126 }11271128 1129 1130 1131 1132 1133 1134 pub fn set_property_permission_unchecked(1135 collection: CollectionId,1136 property_permission: PropertyKeyPermission,1137 ) -> DispatchResult {1138 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1139 permissions.try_set(property_permission.key, property_permission.permission)1140 })1141 .map_err(<Error<T>>::from)?;1142 Ok(())1143 }11441145 1146 1147 1148 1149 1150 pub fn set_property_permission(1151 collection: &CollectionHandle<T>,1152 sender: &T::CrossAccountId,1153 property_permission: PropertyKeyPermission,1154 ) -> DispatchResult {1155 Self::set_scoped_property_permission(1156 collection,1157 sender,1158 PropertyScope::None,1159 property_permission,1160 )1161 }11621163 1164 1165 1166 1167 1168 1169 pub fn set_scoped_property_permission(1170 collection: &CollectionHandle<T>,1171 sender: &T::CrossAccountId,1172 scope: PropertyScope,1173 property_permission: PropertyKeyPermission,1174 ) -> DispatchResult {1175 collection.check_is_owner_or_admin(sender)?;11761177 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1178 let current_permission = all_permissions.get(&property_permission.key);1179 if matches![1180 current_permission,1181 Some(PropertyPermission { mutable: false, .. })1182 ] {1183 return Err(<Error<T>>::NoPermission.into());1184 }11851186 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1187 let property_permission = property_permission.clone();1188 permissions.try_scoped_set(1189 scope,1190 property_permission.key,1191 property_permission.permission,1192 )1193 })1194 .map_err(<Error<T>>::from)?;11951196 Self::deposit_event(Event::PropertyPermissionSet(1197 collection.id,1198 property_permission.key,1199 ));12001201 Ok(())1202 }12031204 1205 1206 1207 1208 1209 #[transactional]1210 pub fn set_token_property_permissions(1211 collection: &CollectionHandle<T>,1212 sender: &T::CrossAccountId,1213 property_permissions: Vec<PropertyKeyPermission>,1214 ) -> DispatchResult {1215 Self::set_scoped_token_property_permissions(1216 collection,1217 sender,1218 PropertyScope::None,1219 property_permissions,1220 )1221 }12221223 1224 1225 1226 1227 1228 1229 #[transactional]1230 pub fn set_scoped_token_property_permissions(1231 collection: &CollectionHandle<T>,1232 sender: &T::CrossAccountId,1233 scope: PropertyScope,1234 property_permissions: Vec<PropertyKeyPermission>,1235 ) -> DispatchResult {1236 for prop_pemission in property_permissions {1237 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1238 }12391240 Ok(())1241 }12421243 1244 pub fn get_collection_property(1245 collection_id: CollectionId,1246 key: &PropertyKey,1247 ) -> Option<PropertyValue> {1248 Self::collection_properties(collection_id).get(key).cloned()1249 }12501251 1252 pub fn bytes_keys_to_property_keys(1253 keys: Vec<Vec<u8>>,1254 ) -> Result<Vec<PropertyKey>, DispatchError> {1255 keys.into_iter()1256 .map(|key| -> Result<PropertyKey, DispatchError> {1257 key.try_into()1258 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1259 })1260 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1261 }12621263 1264 pub fn filter_collection_properties(1265 collection_id: CollectionId,1266 keys: Option<Vec<PropertyKey>>,1267 ) -> Result<Vec<Property>, DispatchError> {1268 let properties = Self::collection_properties(collection_id);12691270 let properties = keys1271 .map(|keys| {1272 keys.into_iter()1273 .filter_map(|key| {1274 properties.get(&key).map(|value| Property {1275 key,1276 value: value.clone(),1277 })1278 })1279 .collect()1280 })1281 .unwrap_or_else(|| {1282 properties1283 .into_iter()1284 .map(|(key, value)| Property { key, value })1285 .collect()1286 });12871288 Ok(properties)1289 }12901291 1292 pub fn filter_property_permissions(1293 collection_id: CollectionId,1294 keys: Option<Vec<PropertyKey>>,1295 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1296 let permissions = Self::property_permissions(collection_id);12971298 let key_permissions = keys1299 .map(|keys| {1300 keys.into_iter()1301 .filter_map(|key| {1302 permissions1303 .get(&key)1304 .map(|permission| PropertyKeyPermission {1305 key,1306 permission: permission.clone(),1307 })1308 })1309 .collect()1310 })1311 .unwrap_or_else(|| {1312 permissions1313 .into_iter()1314 .map(|(key, permission)| PropertyKeyPermission { key, permission })1315 .collect()1316 });13171318 Ok(key_permissions)1319 }13201321 1322 1323 1324 pub fn toggle_allowlist(1325 collection: &CollectionHandle<T>,1326 sender: &T::CrossAccountId,1327 user: &T::CrossAccountId,1328 allowed: bool,1329 ) -> DispatchResult {1330 collection.check_is_owner_or_admin(sender)?;13311332 13331334 if allowed {1335 <Allowlist<T>>::insert((collection.id, user), true);1336 } else {1337 <Allowlist<T>>::remove((collection.id, user));1338 }13391340 Ok(())1341 }13421343 1344 1345 1346 pub fn toggle_admin(1347 collection: &CollectionHandle<T>,1348 sender: &T::CrossAccountId,1349 user: &T::CrossAccountId,1350 admin: bool,1351 ) -> DispatchResult {1352 collection.check_is_owner(sender)?;13531354 let was_admin = <IsAdmin<T>>::get((collection.id, user));1355 if was_admin == admin {1356 return Ok(());1357 }1358 let amount = <AdminAmount<T>>::get(collection.id);13591360 if admin {1361 let amount = amount1362 .checked_add(1)1363 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1364 ensure!(1365 amount <= Self::collection_admins_limit(),1366 <Error<T>>::CollectionAdminCountExceeded,1367 );13681369 13701371 <AdminAmount<T>>::insert(collection.id, amount);1372 <IsAdmin<T>>::insert((collection.id, user), true);1373 } else {1374 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1375 <IsAdmin<T>>::remove((collection.id, user));1376 }13771378 Ok(())1379 }13801381 1382 pub fn clamp_limits(1383 mode: CollectionMode,1384 old_limit: &CollectionLimits,1385 mut new_limit: CollectionLimits,1386 ) -> Result<CollectionLimits, DispatchError> {1387 let limits = old_limit;1388 limit_default!(old_limit, new_limit,1389 account_token_ownership_limit => ensure!(1390 new_limit <= MAX_TOKEN_OWNERSHIP,1391 <Error<T>>::CollectionLimitBoundsExceeded,1392 ),1393 sponsored_data_size => ensure!(1394 new_limit <= CUSTOM_DATA_LIMIT,1395 <Error<T>>::CollectionLimitBoundsExceeded,1396 ),13971398 sponsored_data_rate_limit => {},1399 token_limit => ensure!(1400 old_limit >= new_limit && new_limit > 0,1401 <Error<T>>::CollectionTokenLimitExceeded1402 ),14031404 sponsor_transfer_timeout(match mode {1405 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1406 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1407 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1408 }) => ensure!(1409 new_limit <= MAX_SPONSOR_TIMEOUT,1410 <Error<T>>::CollectionLimitBoundsExceeded,1411 ),1412 sponsor_approve_timeout => {},1413 owner_can_transfer => ensure!(1414 !limits.owner_can_transfer_instaled() ||1415 old_limit || !new_limit,1416 <Error<T>>::OwnerPermissionsCantBeReverted,1417 ),1418 owner_can_destroy => ensure!(1419 old_limit || !new_limit,1420 <Error<T>>::OwnerPermissionsCantBeReverted,1421 ),1422 transfers_enabled => {},1423 );1424 Ok(new_limit)1425 }14261427 1428 pub fn clamp_permissions(1429 _mode: CollectionMode,1430 old_permission: &CollectionPermissions,1431 mut new_permission: CollectionPermissions,1432 ) -> Result<CollectionPermissions, DispatchError> {1433 limit_default_clone!(old_permission, new_permission,1434 access => {},1435 mint_mode => {},1436 nesting => { },1437 );1438 Ok(new_permission)1439 }1440}144114421443#[macro_export]1444macro_rules! unsupported {1445 ($runtime:path) => {1446 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1447 };1448}144914501451pub trait CommonWeightInfo<CrossAccountId> {1452 1453 fn create_item() -> Weight;14541455 1456 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14571458 1459 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14601461 1462 fn burn_item() -> Weight;14631464 1465 1466 1467 fn set_collection_properties(amount: u32) -> Weight;14681469 1470 1471 1472 fn delete_collection_properties(amount: u32) -> Weight;14731474 1475 1476 1477 fn set_token_properties(amount: u32) -> Weight;14781479 1480 1481 1482 fn delete_token_properties(amount: u32) -> Weight;14831484 1485 1486 1487 fn set_token_property_permissions(amount: u32) -> Weight;14881489 1490 fn transfer() -> Weight;14911492 1493 fn approve() -> Weight;14941495 1496 fn transfer_from() -> Weight;14971498 1499 fn burn_from() -> Weight;15001501 1502 1503 1504 1505 fn burn_recursively_self_raw() -> Weight;15061507 1508 1509 1510 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15111512 1513 1514 1515 1516 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1517 Self::burn_recursively_self_raw()1518 .saturating_mul(max_selfs.max(1) as u64)1519 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1520 }15211522 1523 fn token_owner() -> Weight;1524}152515261527pub trait RefungibleExtensionsWeightInfo {1528 1529 fn repartition() -> Weight;1530}153115321533153415351536pub trait CommonCollectionOperations<T: Config> {1537 1538 1539 1540 1541 1542 1543 fn create_item(1544 &self,1545 sender: T::CrossAccountId,1546 to: T::CrossAccountId,1547 data: CreateItemData,1548 nesting_budget: &dyn Budget,1549 ) -> DispatchResultWithPostInfo;15501551 1552 1553 1554 1555 1556 1557 fn create_multiple_items(1558 &self,1559 sender: T::CrossAccountId,1560 to: T::CrossAccountId,1561 data: Vec<CreateItemData>,1562 nesting_budget: &dyn Budget,1563 ) -> DispatchResultWithPostInfo;15641565 1566 1567 1568 1569 1570 1571 fn create_multiple_items_ex(1572 &self,1573 sender: T::CrossAccountId,1574 data: CreateItemExData<T::CrossAccountId>,1575 nesting_budget: &dyn Budget,1576 ) -> DispatchResultWithPostInfo;15771578 1579 1580 1581 1582 1583 fn burn_item(1584 &self,1585 sender: T::CrossAccountId,1586 token: TokenId,1587 amount: u128,1588 ) -> DispatchResultWithPostInfo;15891590 1591 1592 1593 1594 1595 1596 fn burn_item_recursively(1597 &self,1598 sender: T::CrossAccountId,1599 token: TokenId,1600 self_budget: &dyn Budget,1601 breadth_budget: &dyn Budget,1602 ) -> DispatchResultWithPostInfo;16031604 1605 1606 1607 1608 fn set_collection_properties(1609 &self,1610 sender: T::CrossAccountId,1611 properties: Vec<Property>,1612 ) -> DispatchResultWithPostInfo;16131614 1615 1616 1617 1618 fn delete_collection_properties(1619 &self,1620 sender: &T::CrossAccountId,1621 property_keys: Vec<PropertyKey>,1622 ) -> DispatchResultWithPostInfo;16231624 1625 1626 1627 1628 1629 1630 1631 1632 1633 fn set_token_properties(1634 &self,1635 sender: T::CrossAccountId,1636 token_id: TokenId,1637 properties: Vec<Property>,1638 budget: &dyn Budget,1639 ) -> DispatchResultWithPostInfo;16401641 1642 1643 1644 1645 1646 1647 1648 1649 1650 fn delete_token_properties(1651 &self,1652 sender: T::CrossAccountId,1653 token_id: TokenId,1654 property_keys: Vec<PropertyKey>,1655 budget: &dyn Budget,1656 ) -> DispatchResultWithPostInfo;16571658 1659 1660 1661 1662 1663 1664 fn set_token_property_permissions(1665 &self,1666 sender: &T::CrossAccountId,1667 property_permissions: Vec<PropertyKeyPermission>,1668 ) -> DispatchResultWithPostInfo;16691670 1671 1672 1673 1674 1675 1676 1677 fn transfer(1678 &self,1679 sender: T::CrossAccountId,1680 to: T::CrossAccountId,1681 token: TokenId,1682 amount: u128,1683 budget: &dyn Budget,1684 ) -> DispatchResultWithPostInfo;16851686 1687 1688 1689 1690 1691 1692 fn approve(1693 &self,1694 sender: T::CrossAccountId,1695 spender: T::CrossAccountId,1696 token: TokenId,1697 amount: u128,1698 ) -> DispatchResultWithPostInfo;16991700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 fn transfer_from(1711 &self,1712 sender: T::CrossAccountId,1713 from: T::CrossAccountId,1714 to: T::CrossAccountId,1715 token: TokenId,1716 amount: u128,1717 budget: &dyn Budget,1718 ) -> DispatchResultWithPostInfo;17191720 1721 1722 1723 1724 1725 1726 1727 1728 1729 fn burn_from(1730 &self,1731 sender: T::CrossAccountId,1732 from: T::CrossAccountId,1733 token: TokenId,1734 amount: u128,1735 budget: &dyn Budget,1736 ) -> DispatchResultWithPostInfo;17371738 1739 1740 1741 1742 1743 1744 fn check_nesting(1745 &self,1746 sender: T::CrossAccountId,1747 from: (CollectionId, TokenId),1748 under: TokenId,1749 budget: &dyn Budget,1750 ) -> DispatchResult;17511752 1753 1754 1755 1756 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17571758 1759 1760 1761 1762 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17631764 1765 1766 1767 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17681769 1770 fn collection_tokens(&self) -> Vec<TokenId>;17711772 1773 1774 1775 fn token_exists(&self, token: TokenId) -> bool;17761777 1778 fn last_token_id(&self) -> TokenId;17791780 1781 1782 1783 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17841785 1786 1787 1788 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17891790 1791 1792 1793 1794 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17951796 1797 1798 1799 1800 1801 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18021803 1804 fn total_supply(&self) -> u32;18051806 1807 1808 1809 fn account_balance(&self, account: T::CrossAccountId) -> u32;18101811 1812 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18131814 1815 fn total_pieces(&self, token: TokenId) -> Option<u128>;18161817 1818 1819 1820 1821 1822 fn allowance(1823 &self,1824 sender: T::CrossAccountId,1825 spender: T::CrossAccountId,1826 token: TokenId,1827 ) -> u128;18281829 1830 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1831}183218331834pub trait RefungibleExtensions<T>1835where1836 T: Config,1837{1838 1839 1840 1841 1842 1843 1844 1845 fn repartition(1846 &self,1847 sender: &T::CrossAccountId,1848 token: TokenId,1849 amount: u128,1850 ) -> DispatchResultWithPostInfo;1851}18521853185418551856pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1857 let post_info = PostDispatchInfo {1858 actual_weight: Some(weight),1859 pays_fee: Pays::Yes,1860 };1861 match res {1862 Ok(()) => Ok(post_info),1863 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1864 }1865}18661867impl<T: Config> From<PropertiesError> for Error<T> {1868 fn from(error: PropertiesError) -> Self {1869 match error {1870 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1871 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1872 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1873 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1874 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1875 }1876 }1877}