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 ApprovedForAll(477 478 CollectionId,479 480 T::CrossAccountId,481 482 T::CrossAccountId,483 484 bool,485 ),486487 488 CollectionPropertySet(489 490 CollectionId,491 492 PropertyKey,493 ),494495 496 CollectionPropertyDeleted(497 498 CollectionId,499 500 PropertyKey,501 ),502503 504 TokenPropertySet(505 506 CollectionId,507 508 TokenId,509 510 PropertyKey,511 ),512513 514 TokenPropertyDeleted(515 516 CollectionId,517 518 TokenId,519 520 PropertyKey,521 ),522523 524 PropertyPermissionSet(525 526 CollectionId,527 528 PropertyKey,529 ),530 }531532 #[pallet::error]533 pub enum Error<T> {534 535 CollectionNotFound,536 537 MustBeTokenOwner,538 539 NoPermission,540 541 CantDestroyNotEmptyCollection,542 543 PublicMintingNotAllowed,544 545 AddressNotInAllowlist,546547 548 CollectionNameLimitExceeded,549 550 CollectionDescriptionLimitExceeded,551 552 CollectionTokenPrefixLimitExceeded,553 554 TotalCollectionsLimitExceeded,555 556 CollectionAdminCountExceeded,557 558 CollectionLimitBoundsExceeded,559 560 OwnerPermissionsCantBeReverted,561 562 TransferNotAllowed,563 564 AccountTokenLimitExceeded,565 566 CollectionTokenLimitExceeded,567 568 MetadataFlagFrozen,569570 571 TokenNotFound,572 573 TokenValueTooLow,574 575 ApprovedValueTooLow,576 577 CantApproveMoreThanOwned,578579 580 AddressIsZero,581582 583 UnsupportedOperation,584585 586 NotSufficientFounds,587588 589 UserIsNotAllowedToNest,590 591 SourceCollectionIsNotAllowedToNest,592593 594 CollectionFieldSizeExceeded,595596 597 NoSpaceForProperty,598599 600 PropertyLimitReached,601602 603 PropertyKeyIsTooLong,604605 606 InvalidCharacterInPropertyKey,607608 609 EmptyPropertyKey,610611 612 CollectionIsExternal,613614 615 CollectionIsInternal,616 }617618 619 #[pallet::storage]620 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;621622 623 #[pallet::storage]624 pub type DestroyedCollectionCount<T> =625 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;626627 628 #[pallet::storage]629 pub type CollectionById<T> = StorageMap<630 Hasher = Blake2_128Concat,631 Key = CollectionId,632 Value = Collection<<T as frame_system::Config>::AccountId>,633 QueryKind = OptionQuery,634 >;635636 637 #[pallet::storage]638 #[pallet::getter(fn collection_properties)]639 pub type CollectionProperties<T> = StorageMap<640 Hasher = Blake2_128Concat,641 Key = CollectionId,642 Value = Properties,643 QueryKind = ValueQuery,644 OnEmpty = up_data_structs::CollectionProperties,645 >;646647 648 #[pallet::storage]649 #[pallet::getter(fn property_permissions)]650 pub type CollectionPropertyPermissions<T> = StorageMap<651 Hasher = Blake2_128Concat,652 Key = CollectionId,653 Value = PropertiesPermissionMap,654 QueryKind = ValueQuery,655 >;656657 658 #[pallet::storage]659 pub type AdminAmount<T> = StorageMap<660 Hasher = Blake2_128Concat,661 Key = CollectionId,662 Value = u32,663 QueryKind = ValueQuery,664 >;665666 667 #[pallet::storage]668 pub type IsAdmin<T: Config> = StorageNMap<669 Key = (670 Key<Blake2_128Concat, CollectionId>,671 Key<Blake2_128Concat, T::CrossAccountId>,672 ),673 Value = bool,674 QueryKind = ValueQuery,675 >;676677 678 #[pallet::storage]679 pub type Allowlist<T: Config> = StorageNMap<680 Key = (681 Key<Blake2_128Concat, CollectionId>,682 Key<Blake2_128Concat, T::CrossAccountId>,683 ),684 Value = bool,685 QueryKind = ValueQuery,686 >;687688 689 #[pallet::storage]690 pub type DummyStorageValue<T: Config> = StorageValue<691 Value = (692 CollectionStats,693 CollectionId,694 TokenId,695 TokenChild,696 PhantomType<(697 TokenData<T::CrossAccountId>,698 RpcCollection<T::AccountId>,699 700 RmrkCollectionInfo<T::AccountId>,701 RmrkInstanceInfo<T::AccountId>,702 RmrkResourceInfo,703 RmrkPropertyInfo,704 RmrkBaseInfo<T::AccountId>,705 RmrkPartType,706 RmrkBoundedTheme,707 RmrkNftChild,708 )>,709 ),710 QueryKind = OptionQuery,711 >;712713 #[pallet::hooks]714 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {715 fn on_runtime_upgrade() -> Weight {716 StorageVersion::new(1).put::<Pallet<T>>();717718 Weight::zero()719 }720 }721}722723impl<T: Config> Pallet<T> {724 725 726 727 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {728 ensure!(729 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,730 <Error<T>>::AddressIsZero731 );732 Ok(())733 }734735 736 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {737 <IsAdmin<T>>::iter_prefix((collection,))738 .map(|(a, _)| a)739 .collect()740 }741742 743 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {744 <Allowlist<T>>::iter_prefix((collection,))745 .map(|(a, _)| a)746 .collect()747 }748749 750 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {751 <Allowlist<T>>::get((collection, user))752 }753754 755 pub fn collection_stats() -> CollectionStats {756 let created = <CreatedCollectionCount<T>>::get();757 let destroyed = <DestroyedCollectionCount<T>>::get();758 CollectionStats {759 created: created.0,760 destroyed: destroyed.0,761 alive: created.0 - destroyed.0,762 }763 }764765 766 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {767 let collection = <CollectionById<T>>::get(collection)?;768 let limits = collection.limits;769 let effective_limits = CollectionLimits {770 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),771 sponsored_data_size: Some(limits.sponsored_data_size()),772 sponsored_data_rate_limit: Some(773 limits774 .sponsored_data_rate_limit775 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),776 ),777 token_limit: Some(limits.token_limit()),778 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(779 match collection.mode {780 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,781 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,782 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,783 },784 )),785 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),786 owner_can_transfer: Some(limits.owner_can_transfer()),787 owner_can_destroy: Some(limits.owner_can_destroy()),788 transfers_enabled: Some(limits.transfers_enabled()),789 };790791 Some(effective_limits)792 }793794 795 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {796 let Collection {797 name,798 description,799 owner,800 mode,801 token_prefix,802 sponsorship,803 limits,804 permissions,805 flags,806 } = <CollectionById<T>>::get(collection)?;807808 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)809 .into_iter()810 .map(|(key, permission)| PropertyKeyPermission { key, permission })811 .collect();812813 let properties = <CollectionProperties<T>>::get(collection)814 .into_iter()815 .map(|(key, value)| Property { key, value })816 .collect();817818 let permissions = CollectionPermissions {819 access: Some(permissions.access()),820 mint_mode: Some(permissions.mint_mode()),821 nesting: Some(permissions.nesting().clone()),822 };823824 Some(RpcCollection {825 name: name.into_inner(),826 description: description.into_inner(),827 owner,828 mode,829 token_prefix: token_prefix.into_inner(),830 sponsorship,831 limits,832 permissions,833 token_property_permissions,834 properties,835 read_only: flags.external,836837 flags: RpcCollectionFlags {838 foreign: flags.foreign,839 erc721metadata: flags.erc721metadata,840 },841 })842 }843}844845macro_rules! limit_default {846 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{847 $(848 if let Some($new) = $new.$field {849 let $old = $old.$field($($arg)?);850 let _ = $new;851 let _ = $old;852 $check853 } else {854 $new.$field = $old.$field855 }856 )*857 }};858}859macro_rules! limit_default_clone {860 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{861 $(862 if let Some($new) = $new.$field.clone() {863 let $old = $old.$field($($arg)?);864 let _ = $new;865 let _ = $old;866 $check867 } else {868 $new.$field = $old.$field.clone()869 }870 )*871 }};872}873874impl<T: Config> Pallet<T> {875 876 877 878 879 880 pub fn init_collection(881 owner: T::CrossAccountId,882 payer: T::CrossAccountId,883 data: CreateCollectionData<T::AccountId>,884 flags: CollectionFlags,885 ) -> Result<CollectionId, DispatchError> {886 {887 ensure!(888 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,889 Error::<T>::CollectionTokenPrefixLimitExceeded890 );891 }892893 let created_count = <CreatedCollectionCount<T>>::get()894 .0895 .checked_add(1)896 .ok_or(ArithmeticError::Overflow)?;897 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;898 let id = CollectionId(created_count);899900 901 ensure!(902 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,903 <Error<T>>::TotalCollectionsLimitExceeded904 );905906 907908 let collection = Collection {909 owner: owner.as_sub().clone(),910 name: data.name,911 mode: data.mode.clone(),912 description: data.description,913 token_prefix: data.token_prefix,914 sponsorship: data915 .pending_sponsor916 .map(SponsorshipState::Unconfirmed)917 .unwrap_or_default(),918 limits: data919 .limits920 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))921 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,922 permissions: data923 .permissions924 .map(|permissions| {925 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)926 })927 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,928 flags,929 };930931 let mut collection_properties = up_data_structs::CollectionProperties::get();932 collection_properties933 .try_set_from_iter(data.properties.into_iter())934 .map_err(<Error<T>>::from)?;935936 CollectionProperties::<T>::insert(id, collection_properties);937938 let mut token_props_permissions = PropertiesPermissionMap::new();939 token_props_permissions940 .try_set_from_iter(data.token_property_permissions.into_iter())941 .map_err(<Error<T>>::from)?;942943 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);944945 946 {947 let mut imbalance =948 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();949 imbalance.subsume(950 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(951 &T::TreasuryAccountId::get(),952 T::CollectionCreationPrice::get(),953 ),954 );955 <T as Config>::Currency::settle(956 payer.as_sub(),957 imbalance,958 WithdrawReasons::TRANSFER,959 ExistenceRequirement::KeepAlive,960 )961 .map_err(|_| Error::<T>::NotSufficientFounds)?;962 }963964 <CreatedCollectionCount<T>>::put(created_count);965 <Pallet<T>>::deposit_event(Event::CollectionCreated(966 id,967 data.mode.id(),968 owner.as_sub().clone(),969 ));970 <PalletEvm<T>>::deposit_log(971 erc::CollectionHelpersEvents::CollectionCreated {972 owner: *owner.as_eth(),973 collection_id: eth::collection_id_to_address(id),974 }975 .to_log(T::ContractAddress::get()),976 );977 <CollectionById<T>>::insert(id, collection);978 Ok(id)979 }980981 982 983 984 985 pub fn destroy_collection(986 collection: CollectionHandle<T>,987 sender: &T::CrossAccountId,988 ) -> DispatchResult {989 ensure!(990 collection.limits.owner_can_destroy(),991 <Error<T>>::NoPermission,992 );993 collection.check_is_owner(sender)?;994995 let destroyed_collections = <DestroyedCollectionCount<T>>::get()996 .0997 .checked_add(1)998 .ok_or(ArithmeticError::Overflow)?;9991000 10011002 <DestroyedCollectionCount<T>>::put(destroyed_collections);1003 <CollectionById<T>>::remove(collection.id);1004 <AdminAmount<T>>::remove(collection.id);1005 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1006 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1007 <CollectionProperties<T>>::remove(collection.id);10081009 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));10101011 <PalletEvm<T>>::deposit_log(1012 erc::CollectionHelpersEvents::CollectionDestroyed {1013 collection_id: eth::collection_id_to_address(collection.id),1014 }1015 .to_log(T::ContractAddress::get()),1016 );1017 Ok(())1018 }10191020 1021 1022 1023 1024 1025 pub fn set_collection_property(1026 collection: &CollectionHandle<T>,1027 sender: &T::CrossAccountId,1028 property: Property,1029 ) -> DispatchResult {1030 collection.check_is_owner_or_admin(sender)?;10311032 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1033 let property = property.clone();1034 properties.try_set(property.key, property.value)1035 })1036 .map_err(<Error<T>>::from)?;10371038 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));1039 <PalletEvm<T>>::deposit_log(1040 erc::CollectionHelpersEvents::CollectionChanged {1041 collection_id: eth::collection_id_to_address(collection.id),1042 }1043 .to_log(T::ContractAddress::get()),1044 );10451046 Ok(())1047 }10481049 1050 1051 1052 1053 1054 pub fn set_scoped_collection_property(1055 collection_id: CollectionId,1056 scope: PropertyScope,1057 property: Property,1058 ) -> DispatchResult {1059 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1060 properties.try_scoped_set(scope, property.key, property.value)1061 })1062 .map_err(<Error<T>>::from)?;10631064 Ok(())1065 }10661067 1068 1069 1070 1071 1072 pub fn set_scoped_collection_properties(1073 collection_id: CollectionId,1074 scope: PropertyScope,1075 properties: impl Iterator<Item = Property>,1076 ) -> DispatchResult {1077 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1078 stored_properties.try_scoped_set_from_iter(scope, properties)1079 })1080 .map_err(<Error<T>>::from)?;10811082 Ok(())1083 }10841085 1086 1087 1088 1089 1090 #[transactional]1091 pub fn set_collection_properties(1092 collection: &CollectionHandle<T>,1093 sender: &T::CrossAccountId,1094 properties: Vec<Property>,1095 ) -> DispatchResult {1096 for property in properties {1097 Self::set_collection_property(collection, sender, property)?;1098 }10991100 Ok(())1101 }11021103 1104 1105 1106 1107 1108 pub fn delete_collection_property(1109 collection: &CollectionHandle<T>,1110 sender: &T::CrossAccountId,1111 property_key: PropertyKey,1112 ) -> DispatchResult {1113 collection.check_is_owner_or_admin(sender)?;11141115 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1116 properties.remove(&property_key)1117 })1118 .map_err(<Error<T>>::from)?;11191120 Self::deposit_event(Event::CollectionPropertyDeleted(1121 collection.id,1122 property_key,1123 ));1124 <PalletEvm<T>>::deposit_log(1125 erc::CollectionHelpersEvents::CollectionChanged {1126 collection_id: eth::collection_id_to_address(collection.id),1127 }1128 .to_log(T::ContractAddress::get()),1129 );11301131 Ok(())1132 }11331134 1135 1136 1137 1138 1139 #[transactional]1140 pub fn delete_collection_properties(1141 collection: &CollectionHandle<T>,1142 sender: &T::CrossAccountId,1143 property_keys: Vec<PropertyKey>,1144 ) -> DispatchResult {1145 for key in property_keys {1146 Self::delete_collection_property(collection, sender, key)?;1147 }11481149 Ok(())1150 }11511152 1153 1154 1155 1156 1157 1158 pub fn set_property_permission_unchecked(1159 collection: CollectionId,1160 property_permission: PropertyKeyPermission,1161 ) -> DispatchResult {1162 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1163 permissions.try_set(property_permission.key, property_permission.permission)1164 })1165 .map_err(<Error<T>>::from)?;1166 Ok(())1167 }11681169 1170 1171 1172 1173 1174 pub fn set_property_permission(1175 collection: &CollectionHandle<T>,1176 sender: &T::CrossAccountId,1177 property_permission: PropertyKeyPermission,1178 ) -> DispatchResult {1179 Self::set_scoped_property_permission(1180 collection,1181 sender,1182 PropertyScope::None,1183 property_permission,1184 )1185 }11861187 1188 1189 1190 1191 1192 1193 pub fn set_scoped_property_permission(1194 collection: &CollectionHandle<T>,1195 sender: &T::CrossAccountId,1196 scope: PropertyScope,1197 property_permission: PropertyKeyPermission,1198 ) -> DispatchResult {1199 collection.check_is_owner_or_admin(sender)?;12001201 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1202 let current_permission = all_permissions.get(&property_permission.key);1203 if matches![1204 current_permission,1205 Some(PropertyPermission { mutable: false, .. })1206 ] {1207 return Err(<Error<T>>::NoPermission.into());1208 }12091210 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1211 let property_permission = property_permission.clone();1212 permissions.try_scoped_set(1213 scope,1214 property_permission.key,1215 property_permission.permission,1216 )1217 })1218 .map_err(<Error<T>>::from)?;12191220 Self::deposit_event(Event::PropertyPermissionSet(1221 collection.id,1222 property_permission.key,1223 ));1224 <PalletEvm<T>>::deposit_log(1225 erc::CollectionHelpersEvents::CollectionChanged {1226 collection_id: eth::collection_id_to_address(collection.id),1227 }1228 .to_log(T::ContractAddress::get()),1229 );12301231 Ok(())1232 }12331234 1235 1236 1237 1238 1239 #[transactional]1240 pub fn set_token_property_permissions(1241 collection: &CollectionHandle<T>,1242 sender: &T::CrossAccountId,1243 property_permissions: Vec<PropertyKeyPermission>,1244 ) -> DispatchResult {1245 Self::set_scoped_token_property_permissions(1246 collection,1247 sender,1248 PropertyScope::None,1249 property_permissions,1250 )1251 }12521253 1254 1255 1256 1257 1258 1259 #[transactional]1260 pub fn set_scoped_token_property_permissions(1261 collection: &CollectionHandle<T>,1262 sender: &T::CrossAccountId,1263 scope: PropertyScope,1264 property_permissions: Vec<PropertyKeyPermission>,1265 ) -> DispatchResult {1266 for prop_pemission in property_permissions {1267 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1268 }12691270 Ok(())1271 }12721273 1274 pub fn get_collection_property(1275 collection_id: CollectionId,1276 key: &PropertyKey,1277 ) -> Option<PropertyValue> {1278 Self::collection_properties(collection_id).get(key).cloned()1279 }12801281 1282 pub fn bytes_keys_to_property_keys(1283 keys: Vec<Vec<u8>>,1284 ) -> Result<Vec<PropertyKey>, DispatchError> {1285 keys.into_iter()1286 .map(|key| -> Result<PropertyKey, DispatchError> {1287 key.try_into()1288 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1289 })1290 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1291 }12921293 1294 pub fn filter_collection_properties(1295 collection_id: CollectionId,1296 keys: Option<Vec<PropertyKey>>,1297 ) -> Result<Vec<Property>, DispatchError> {1298 let properties = Self::collection_properties(collection_id);12991300 let properties = keys1301 .map(|keys| {1302 keys.into_iter()1303 .filter_map(|key| {1304 properties.get(&key).map(|value| Property {1305 key,1306 value: value.clone(),1307 })1308 })1309 .collect()1310 })1311 .unwrap_or_else(|| {1312 properties1313 .into_iter()1314 .map(|(key, value)| Property { key, value })1315 .collect()1316 });13171318 Ok(properties)1319 }13201321 1322 pub fn filter_property_permissions(1323 collection_id: CollectionId,1324 keys: Option<Vec<PropertyKey>>,1325 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1326 let permissions = Self::property_permissions(collection_id);13271328 let key_permissions = keys1329 .map(|keys| {1330 keys.into_iter()1331 .filter_map(|key| {1332 permissions1333 .get(&key)1334 .map(|permission| PropertyKeyPermission {1335 key,1336 permission: permission.clone(),1337 })1338 })1339 .collect()1340 })1341 .unwrap_or_else(|| {1342 permissions1343 .into_iter()1344 .map(|(key, permission)| PropertyKeyPermission { key, permission })1345 .collect()1346 });13471348 Ok(key_permissions)1349 }13501351 1352 1353 1354 pub fn toggle_allowlist(1355 collection: &CollectionHandle<T>,1356 sender: &T::CrossAccountId,1357 user: &T::CrossAccountId,1358 allowed: bool,1359 ) -> DispatchResult {1360 collection.check_is_owner_or_admin(sender)?;13611362 13631364 if allowed {1365 <Allowlist<T>>::insert((collection.id, user), true);1366 } else {1367 <Allowlist<T>>::remove((collection.id, user));1368 }13691370 Ok(())1371 }13721373 1374 1375 1376 pub fn toggle_admin(1377 collection: &CollectionHandle<T>,1378 sender: &T::CrossAccountId,1379 user: &T::CrossAccountId,1380 admin: bool,1381 ) -> DispatchResult {1382 collection.check_is_owner(sender)?;13831384 let was_admin = <IsAdmin<T>>::get((collection.id, user));1385 if was_admin == admin {1386 return Ok(());1387 }1388 let amount = <AdminAmount<T>>::get(collection.id);13891390 if admin {1391 let amount = amount1392 .checked_add(1)1393 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1394 ensure!(1395 amount <= Self::collection_admins_limit(),1396 <Error<T>>::CollectionAdminCountExceeded,1397 );13981399 14001401 <AdminAmount<T>>::insert(collection.id, amount);1402 <IsAdmin<T>>::insert((collection.id, user), true);1403 } else {1404 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1405 <IsAdmin<T>>::remove((collection.id, user));1406 }14071408 Ok(())1409 }14101411 1412 pub fn clamp_limits(1413 mode: CollectionMode,1414 old_limit: &CollectionLimits,1415 mut new_limit: CollectionLimits,1416 ) -> Result<CollectionLimits, DispatchError> {1417 let limits = old_limit;1418 limit_default!(old_limit, new_limit,1419 account_token_ownership_limit => ensure!(1420 new_limit <= MAX_TOKEN_OWNERSHIP,1421 <Error<T>>::CollectionLimitBoundsExceeded,1422 ),1423 sponsored_data_size => ensure!(1424 new_limit <= CUSTOM_DATA_LIMIT,1425 <Error<T>>::CollectionLimitBoundsExceeded,1426 ),14271428 sponsored_data_rate_limit => {},1429 token_limit => ensure!(1430 old_limit >= new_limit && new_limit > 0,1431 <Error<T>>::CollectionTokenLimitExceeded1432 ),14331434 sponsor_transfer_timeout(match mode {1435 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1436 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1437 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1438 }) => ensure!(1439 new_limit <= MAX_SPONSOR_TIMEOUT,1440 <Error<T>>::CollectionLimitBoundsExceeded,1441 ),1442 sponsor_approve_timeout => {},1443 owner_can_transfer => ensure!(1444 !limits.owner_can_transfer_instaled() ||1445 old_limit || !new_limit,1446 <Error<T>>::OwnerPermissionsCantBeReverted,1447 ),1448 owner_can_destroy => ensure!(1449 old_limit || !new_limit,1450 <Error<T>>::OwnerPermissionsCantBeReverted,1451 ),1452 transfers_enabled => {},1453 );1454 Ok(new_limit)1455 }14561457 1458 pub fn clamp_permissions(1459 _mode: CollectionMode,1460 old_permission: &CollectionPermissions,1461 mut new_permission: CollectionPermissions,1462 ) -> Result<CollectionPermissions, DispatchError> {1463 limit_default_clone!(old_permission, new_permission,1464 access => {},1465 mint_mode => {},1466 nesting => { },1467 );1468 Ok(new_permission)1469 }1470}147114721473#[macro_export]1474macro_rules! unsupported {1475 ($runtime:path) => {1476 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1477 };1478}147914801481pub trait CommonWeightInfo<CrossAccountId> {1482 1483 fn create_item() -> Weight;14841485 1486 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14871488 1489 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14901491 1492 fn burn_item() -> Weight;14931494 1495 1496 1497 fn set_collection_properties(amount: u32) -> Weight;14981499 1500 1501 1502 fn delete_collection_properties(amount: u32) -> Weight;15031504 1505 1506 1507 fn set_token_properties(amount: u32) -> Weight;15081509 1510 1511 1512 fn delete_token_properties(amount: u32) -> Weight;15131514 1515 1516 1517 fn set_token_property_permissions(amount: u32) -> Weight;15181519 1520 fn transfer() -> Weight;15211522 1523 fn approve() -> Weight;15241525 1526 fn transfer_from() -> Weight;15271528 1529 fn burn_from() -> Weight;15301531 1532 1533 1534 1535 fn burn_recursively_self_raw() -> Weight;15361537 1538 1539 1540 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15411542 1543 1544 1545 1546 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1547 Self::burn_recursively_self_raw()1548 .saturating_mul(max_selfs.max(1) as u64)1549 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1550 }15511552 1553 fn token_owner() -> Weight;15541555 1556 fn set_allowance_for_all() -> Weight;1557}155815591560pub trait RefungibleExtensionsWeightInfo {1561 1562 fn repartition() -> Weight;1563}156415651566156715681569pub trait CommonCollectionOperations<T: Config> {1570 1571 1572 1573 1574 1575 1576 fn create_item(1577 &self,1578 sender: T::CrossAccountId,1579 to: T::CrossAccountId,1580 data: CreateItemData,1581 nesting_budget: &dyn Budget,1582 ) -> DispatchResultWithPostInfo;15831584 1585 1586 1587 1588 1589 1590 fn create_multiple_items(1591 &self,1592 sender: T::CrossAccountId,1593 to: T::CrossAccountId,1594 data: Vec<CreateItemData>,1595 nesting_budget: &dyn Budget,1596 ) -> DispatchResultWithPostInfo;15971598 1599 1600 1601 1602 1603 1604 fn create_multiple_items_ex(1605 &self,1606 sender: T::CrossAccountId,1607 data: CreateItemExData<T::CrossAccountId>,1608 nesting_budget: &dyn Budget,1609 ) -> DispatchResultWithPostInfo;16101611 1612 1613 1614 1615 1616 fn burn_item(1617 &self,1618 sender: T::CrossAccountId,1619 token: TokenId,1620 amount: u128,1621 ) -> DispatchResultWithPostInfo;16221623 1624 1625 1626 1627 1628 1629 fn burn_item_recursively(1630 &self,1631 sender: T::CrossAccountId,1632 token: TokenId,1633 self_budget: &dyn Budget,1634 breadth_budget: &dyn Budget,1635 ) -> DispatchResultWithPostInfo;16361637 1638 1639 1640 1641 fn set_collection_properties(1642 &self,1643 sender: T::CrossAccountId,1644 properties: Vec<Property>,1645 ) -> DispatchResultWithPostInfo;16461647 1648 1649 1650 1651 fn delete_collection_properties(1652 &self,1653 sender: &T::CrossAccountId,1654 property_keys: Vec<PropertyKey>,1655 ) -> DispatchResultWithPostInfo;16561657 1658 1659 1660 1661 1662 1663 1664 1665 1666 fn set_token_properties(1667 &self,1668 sender: T::CrossAccountId,1669 token_id: TokenId,1670 properties: Vec<Property>,1671 budget: &dyn Budget,1672 ) -> DispatchResultWithPostInfo;16731674 1675 1676 1677 1678 1679 1680 1681 1682 1683 fn delete_token_properties(1684 &self,1685 sender: T::CrossAccountId,1686 token_id: TokenId,1687 property_keys: Vec<PropertyKey>,1688 budget: &dyn Budget,1689 ) -> DispatchResultWithPostInfo;16901691 1692 1693 1694 1695 1696 1697 fn set_token_property_permissions(1698 &self,1699 sender: &T::CrossAccountId,1700 property_permissions: Vec<PropertyKeyPermission>,1701 ) -> DispatchResultWithPostInfo;17021703 1704 1705 1706 1707 1708 1709 1710 fn transfer(1711 &self,1712 sender: T::CrossAccountId,1713 to: T::CrossAccountId,1714 token: TokenId,1715 amount: u128,1716 budget: &dyn Budget,1717 ) -> DispatchResultWithPostInfo;17181719 1720 1721 1722 1723 1724 1725 fn approve(1726 &self,1727 sender: T::CrossAccountId,1728 spender: T::CrossAccountId,1729 token: TokenId,1730 amount: u128,1731 ) -> DispatchResultWithPostInfo;17321733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 fn transfer_from(1744 &self,1745 sender: T::CrossAccountId,1746 from: T::CrossAccountId,1747 to: T::CrossAccountId,1748 token: TokenId,1749 amount: u128,1750 budget: &dyn Budget,1751 ) -> DispatchResultWithPostInfo;17521753 1754 1755 1756 1757 1758 1759 1760 1761 1762 fn burn_from(1763 &self,1764 sender: T::CrossAccountId,1765 from: T::CrossAccountId,1766 token: TokenId,1767 amount: u128,1768 budget: &dyn Budget,1769 ) -> DispatchResultWithPostInfo;17701771 1772 1773 1774 1775 1776 1777 fn check_nesting(1778 &self,1779 sender: T::CrossAccountId,1780 from: (CollectionId, TokenId),1781 under: TokenId,1782 budget: &dyn Budget,1783 ) -> DispatchResult;17841785 1786 1787 1788 1789 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17901791 1792 1793 1794 1795 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17961797 1798 1799 1800 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;18011802 1803 fn collection_tokens(&self) -> Vec<TokenId>;18041805 1806 1807 1808 fn token_exists(&self, token: TokenId) -> bool;18091810 1811 fn last_token_id(&self) -> TokenId;18121813 1814 1815 1816 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;18171818 1819 1820 1821 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;18221823 1824 1825 1826 1827 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;18281829 1830 1831 1832 1833 1834 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;18351836 1837 fn total_supply(&self) -> u32;18381839 1840 1841 1842 fn account_balance(&self, account: T::CrossAccountId) -> u32;18431844 1845 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18461847 1848 fn total_pieces(&self, token: TokenId) -> Option<u128>;18491850 1851 1852 1853 1854 1855 fn allowance(1856 &self,1857 sender: T::CrossAccountId,1858 spender: T::CrossAccountId,1859 token: TokenId,1860 ) -> u128;18611862 1863 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;18641865 1866 1867 1868 1869 fn set_allowance_for_all(1870 &self,1871 owner: T::CrossAccountId,1872 operator: T::CrossAccountId,1873 approve: bool,1874 ) -> DispatchResultWithPostInfo;18751876 1877 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;1878}187918801881pub trait RefungibleExtensions<T>1882where1883 T: Config,1884{1885 1886 1887 1888 1889 1890 1891 1892 fn repartition(1893 &self,1894 sender: &T::CrossAccountId,1895 token: TokenId,1896 amount: u128,1897 ) -> DispatchResultWithPostInfo;1898}18991900190119021903pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1904 let post_info = PostDispatchInfo {1905 actual_weight: Some(weight),1906 pays_fee: Pays::Yes,1907 };1908 match res {1909 Ok(()) => Ok(post_info),1910 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1911 }1912}19131914impl<T: Config> From<PropertiesError> for Error<T> {1915 fn from(error: PropertiesError) -> Self {1916 match error {1917 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1918 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1919 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1920 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1921 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1922 }1923 }1924}