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 CollectionId,74 CreateItemData,75 MAX_TOKEN_PREFIX_LENGTH,76 COLLECTION_ADMINS_LIMIT,77 TokenId,78 TokenChild,79 CollectionStats,80 MAX_TOKEN_OWNERSHIP,81 CollectionMode,82 NFT_SPONSOR_TRANSFER_TIMEOUT,83 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,84 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,85 MAX_SPONSOR_TIMEOUT,86 CUSTOM_DATA_LIMIT,87 CollectionLimits,88 CreateCollectionData,89 SponsorshipState,90 CreateItemExData,91 SponsoringRateLimit,92 budget::Budget,93 PhantomType,94 Property,95 Properties,96 PropertiesPermissionMap,97 PropertyKey,98 PropertyValue,99 PropertyPermission,100 PropertiesError,101 PropertyKeyPermission,102 TokenData,103 TrySetProperty,104 PropertyScope,105 106 RmrkCollectionInfo,107 RmrkInstanceInfo,108 RmrkResourceInfo,109 RmrkPropertyInfo,110 RmrkBaseInfo,111 RmrkPartType,112 RmrkBoundedTheme,113 RmrkNftChild,114 CollectionPermissions,115};116117pub use pallet::*;118use sp_core::H160;119use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};120#[cfg(feature = "runtime-benchmarks")]121pub mod benchmarking;122pub mod dispatch;123pub mod erc;124pub mod eth;125pub mod weights;126127128pub type SelfWeightOf<T> = <T as Config>::WeightInfo;129130131132133134135136#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]137pub struct CollectionHandle<T: Config> {138 139 pub id: CollectionId,140 collection: Collection<T::AccountId>,141 142 pub recorder: SubstrateRecorder<T>,143}144145impl<T: Config> WithRecorder<T> for CollectionHandle<T> {146 fn recorder(&self) -> &SubstrateRecorder<T> {147 &self.recorder148 }149 fn into_recorder(self) -> SubstrateRecorder<T> {150 self.recorder151 }152}153154impl<T: Config> CollectionHandle<T> {155 156 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {157 <CollectionById<T>>::get(id).map(|collection| Self {158 id,159 collection,160 recorder: SubstrateRecorder::new(gas_limit),161 })162 }163164 165 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {166 <CollectionById<T>>::get(id).map(|collection| Self {167 id,168 collection,169 recorder,170 })171 }172173 174 175 pub fn new(id: CollectionId) -> Option<Self> {176 Self::new_with_gas_limit(id, u64::MAX)177 }178179 180 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {181 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)182 }183184 185 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {186 self.recorder187 .consume_gas(T::GasWeightMapping::weight_to_gas(188 <T as frame_system::Config>::DbWeight::get()189 .read190 .saturating_mul(reads),191 ))192 }193194 195 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {196 self.recorder197 .consume_gas(T::GasWeightMapping::weight_to_gas(198 <T as frame_system::Config>::DbWeight::get()199 .write200 .saturating_mul(writes),201 ))202 }203204 205 pub fn consume_store_reads_and_writes(206 &self,207 reads: u64,208 writes: u64,209 ) -> evm_coder::execution::Result<()> {210 let weight = <T as frame_system::Config>::DbWeight::get();211 let reads = weight.read.saturating_mul(reads);212 let writes = weight.read.saturating_mul(writes);213 self.recorder214 .consume_gas(T::GasWeightMapping::weight_to_gas(215 reads.saturating_add(writes),216 ))217 }218219 220 pub fn save(&self) -> DispatchResult {221 <CollectionById<T>>::insert(self.id, &self.collection);222 Ok(())223 }224225 226 227 228 229 230 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {231 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);232 Ok(())233 }234235 236 237 238 239 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {240 if self.collection.sponsorship.pending_sponsor() != Some(sender) {241 return Ok(false);242 }243244 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());245 Ok(true)246 }247248 249 pub fn remove_sponsor(&mut self) -> DispatchResult {250 self.collection.sponsorship = SponsorshipState::Disabled;251 Ok(())252 }253254 255 256 pub fn check_is_internal(&self) -> DispatchResult {257 if self.external_collection {258 return Err(<Error<T>>::CollectionIsExternal)?;259 }260261 Ok(())262 }263264 265 266 pub fn check_is_external(&self) -> DispatchResult {267 if !self.external_collection {268 return Err(<Error<T>>::CollectionIsInternal)?;269 }270271 Ok(())272 }273}274275impl<T: Config> Deref for CollectionHandle<T> {276 type Target = Collection<T::AccountId>;277278 fn deref(&self) -> &Self::Target {279 &self.collection280 }281}282283impl<T: Config> DerefMut for CollectionHandle<T> {284 fn deref_mut(&mut self) -> &mut Self::Target {285 &mut self.collection286 }287}288289impl<T: Config> CollectionHandle<T> {290 291 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {292 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);293 Ok(())294 }295296 297 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {298 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))299 }300301 302 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {303 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);304 Ok(())305 }306307 308 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {309 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)310 }311312 313 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {314 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)315 }316317 318 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {319 ensure!(320 <Allowlist<T>>::get((self.id, user)),321 <Error<T>>::AddressNotInAllowlist322 );323 Ok(())324 }325326 327 328 329 fn set_owner_internal(330 &mut self,331 caller: T::CrossAccountId,332 new_owner: T::CrossAccountId,333 ) -> DispatchResult {334 self.check_is_owner(&caller)?;335 self.collection.owner = new_owner.as_sub().clone();336 self.save()337 }338}339340#[frame_support::pallet]341pub mod pallet {342 use super::*;343 use pallet_evm::account;344 use dispatch::CollectionDispatch;345 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};346 use frame_system::pallet_prelude::*;347 use frame_support::traits::Currency;348 use up_data_structs::{TokenId, mapping::TokenAddressMapping};349 use scale_info::TypeInfo;350 use weights::WeightInfo;351352 #[pallet::config]353 pub trait Config:354 frame_system::Config355 + pallet_evm_coder_substrate::Config356 + pallet_evm::Config357 + TypeInfo358 + account::Config359 {360 361 type WeightInfo: WeightInfo;362363 364 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;365366 367 type Currency: Currency<Self::AccountId>;368369 370 #[pallet::constant]371 type CollectionCreationPrice: Get<372 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,373 >;374375 376 type CollectionDispatch: CollectionDispatch<Self>;377378 379 type TreasuryAccountId: Get<Self::AccountId>;380381 382 type ContractAddress: Get<H160>;383384 385 type EvmTokenAddressMapping: TokenAddressMapping<H160>;386387 388 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;389 }390391 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);392393 #[pallet::pallet]394 #[pallet::storage_version(STORAGE_VERSION)]395 #[pallet::generate_store(pub(super) trait Store)]396 pub struct Pallet<T>(_);397398 #[pallet::extra_constants]399 impl<T: Config> Pallet<T> {400 401 pub fn collection_admins_limit() -> u32 {402 COLLECTION_ADMINS_LIMIT403 }404 }405406 #[pallet::event]407 #[pallet::generate_deposit(pub fn deposit_event)]408 pub enum Event<T: Config> {409 410 CollectionCreated(411 412 CollectionId,413 414 u8,415 416 T::AccountId,417 ),418419 420 CollectionDestroyed(421 422 CollectionId,423 ),424425 426 ItemCreated(427 428 CollectionId,429 430 TokenId,431 432 T::CrossAccountId,433 434 u128,435 ),436437 438 ItemDestroyed(439 440 CollectionId,441 442 TokenId,443 444 T::CrossAccountId,445 446 u128,447 ),448449 450 Transfer(451 452 CollectionId,453 454 TokenId,455 456 T::CrossAccountId,457 458 T::CrossAccountId,459 460 u128,461 ),462463 464 Approved(465 466 CollectionId,467 468 TokenId,469 470 T::CrossAccountId,471 472 T::CrossAccountId,473 474 u128,475 ),476477 478 CollectionPropertySet(479 480 CollectionId,481 482 PropertyKey,483 ),484485 486 CollectionPropertyDeleted(487 488 CollectionId,489 490 PropertyKey,491 ),492493 494 TokenPropertySet(495 496 CollectionId,497 498 TokenId,499 500 PropertyKey,501 ),502503 504 TokenPropertyDeleted(505 506 CollectionId,507 508 TokenId,509 510 PropertyKey,511 ),512513 514 PropertyPermissionSet(515 516 CollectionId,517 518 PropertyKey,519 ),520 }521522 #[pallet::error]523 pub enum Error<T> {524 525 CollectionNotFound,526 527 MustBeTokenOwner,528 529 NoPermission,530 531 CantDestroyNotEmptyCollection,532 533 PublicMintingNotAllowed,534 535 AddressNotInAllowlist,536537 538 CollectionNameLimitExceeded,539 540 CollectionDescriptionLimitExceeded,541 542 CollectionTokenPrefixLimitExceeded,543 544 TotalCollectionsLimitExceeded,545 546 CollectionAdminCountExceeded,547 548 CollectionLimitBoundsExceeded,549 550 OwnerPermissionsCantBeReverted,551 552 TransferNotAllowed,553 554 AccountTokenLimitExceeded,555 556 CollectionTokenLimitExceeded,557 558 MetadataFlagFrozen,559560 561 TokenNotFound,562 563 TokenValueTooLow,564 565 ApprovedValueTooLow,566 567 CantApproveMoreThanOwned,568569 570 AddressIsZero,571572 573 UnsupportedOperation,574575 576 NotSufficientFounds,577578 579 UserIsNotAllowedToNest,580 581 SourceCollectionIsNotAllowedToNest,582583 584 CollectionFieldSizeExceeded,585586 587 NoSpaceForProperty,588589 590 PropertyLimitReached,591592 593 PropertyKeyIsTooLong,594595 596 InvalidCharacterInPropertyKey,597598 599 EmptyPropertyKey,600601 602 CollectionIsExternal,603604 605 CollectionIsInternal,606 }607608 609 #[pallet::storage]610 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;611612 613 #[pallet::storage]614 pub type DestroyedCollectionCount<T> =615 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;616617 618 #[pallet::storage]619 pub type CollectionById<T> = StorageMap<620 Hasher = Blake2_128Concat,621 Key = CollectionId,622 Value = Collection<<T as frame_system::Config>::AccountId>,623 QueryKind = OptionQuery,624 >;625626 627 #[pallet::storage]628 #[pallet::getter(fn collection_properties)]629 pub type CollectionProperties<T> = StorageMap<630 Hasher = Blake2_128Concat,631 Key = CollectionId,632 Value = Properties,633 QueryKind = ValueQuery,634 OnEmpty = up_data_structs::CollectionProperties,635 >;636637 638 #[pallet::storage]639 #[pallet::getter(fn property_permissions)]640 pub type CollectionPropertyPermissions<T> = StorageMap<641 Hasher = Blake2_128Concat,642 Key = CollectionId,643 Value = PropertiesPermissionMap,644 QueryKind = ValueQuery,645 >;646647 648 #[pallet::storage]649 pub type AdminAmount<T> = StorageMap<650 Hasher = Blake2_128Concat,651 Key = CollectionId,652 Value = u32,653 QueryKind = ValueQuery,654 >;655656 657 #[pallet::storage]658 pub type IsAdmin<T: Config> = StorageNMap<659 Key = (660 Key<Blake2_128Concat, CollectionId>,661 Key<Blake2_128Concat, T::CrossAccountId>,662 ),663 Value = bool,664 QueryKind = ValueQuery,665 >;666667 668 #[pallet::storage]669 pub type Allowlist<T: Config> = StorageNMap<670 Key = (671 Key<Blake2_128Concat, CollectionId>,672 Key<Blake2_128Concat, T::CrossAccountId>,673 ),674 Value = bool,675 QueryKind = ValueQuery,676 >;677678 679 #[pallet::storage]680 pub type DummyStorageValue<T: Config> = StorageValue<681 Value = (682 CollectionStats,683 CollectionId,684 TokenId,685 TokenChild,686 PhantomType<(687 TokenData<T::CrossAccountId>,688 RpcCollection<T::AccountId>,689 690 RmrkCollectionInfo<T::AccountId>,691 RmrkInstanceInfo<T::AccountId>,692 RmrkResourceInfo,693 RmrkPropertyInfo,694 RmrkBaseInfo<T::AccountId>,695 RmrkPartType,696 RmrkBoundedTheme,697 RmrkNftChild,698 )>,699 ),700 QueryKind = OptionQuery,701 >;702703 #[pallet::hooks]704 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {705 fn on_runtime_upgrade() -> Weight {706 StorageVersion::new(1).put::<Pallet<T>>();707708 0709 }710 }711}712713impl<T: Config> Pallet<T> {714 715 716 717 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {718 ensure!(719 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,720 <Error<T>>::AddressIsZero721 );722 Ok(())723 }724725 726 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {727 <IsAdmin<T>>::iter_prefix((collection,))728 .map(|(a, _)| a)729 .collect()730 }731732 733 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {734 <Allowlist<T>>::iter_prefix((collection,))735 .map(|(a, _)| a)736 .collect()737 }738739 740 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {741 <Allowlist<T>>::get((collection, user))742 }743744 745 pub fn collection_stats() -> CollectionStats {746 let created = <CreatedCollectionCount<T>>::get();747 let destroyed = <DestroyedCollectionCount<T>>::get();748 CollectionStats {749 created: created.0,750 destroyed: destroyed.0,751 alive: created.0 - destroyed.0,752 }753 }754755 756 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {757 let collection = <CollectionById<T>>::get(collection)?;758 let limits = collection.limits;759 let effective_limits = CollectionLimits {760 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),761 sponsored_data_size: Some(limits.sponsored_data_size()),762 sponsored_data_rate_limit: Some(763 limits764 .sponsored_data_rate_limit765 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),766 ),767 token_limit: Some(limits.token_limit()),768 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(769 match collection.mode {770 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,771 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,772 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,773 },774 )),775 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),776 owner_can_transfer: Some(limits.owner_can_transfer()),777 owner_can_destroy: Some(limits.owner_can_destroy()),778 transfers_enabled: Some(limits.transfers_enabled()),779 };780781 Some(effective_limits)782 }783784 785 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {786 let Collection {787 name,788 description,789 owner,790 mode,791 token_prefix,792 sponsorship,793 limits,794 permissions,795 external_collection,796 } = <CollectionById<T>>::get(collection)?;797798 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)799 .into_iter()800 .map(|(key, permission)| PropertyKeyPermission { key, permission })801 .collect();802803 let properties = <CollectionProperties<T>>::get(collection)804 .into_iter()805 .map(|(key, value)| Property { key, value })806 .collect();807808 let permissions = CollectionPermissions {809 access: Some(permissions.access()),810 mint_mode: Some(permissions.mint_mode()),811 nesting: Some(permissions.nesting().clone()),812 };813814 Some(RpcCollection {815 name: name.into_inner(),816 description: description.into_inner(),817 owner,818 mode,819 token_prefix: token_prefix.into_inner(),820 sponsorship,821 limits,822 permissions,823 token_property_permissions,824 properties,825 read_only: external_collection,826 })827 }828}829830macro_rules! limit_default {831 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{832 $(833 if let Some($new) = $new.$field {834 let $old = $old.$field($($arg)?);835 let _ = $new;836 let _ = $old;837 $check838 } else {839 $new.$field = $old.$field840 }841 )*842 }};843}844macro_rules! limit_default_clone {845 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{846 $(847 if let Some($new) = $new.$field.clone() {848 let $old = $old.$field($($arg)?);849 let _ = $new;850 let _ = $old;851 $check852 } else {853 $new.$field = $old.$field.clone()854 }855 )*856 }};857}858859impl<T: Config> Pallet<T> {860 861 862 863 864 865 pub fn init_collection(866 owner: T::CrossAccountId,867 data: CreateCollectionData<T::AccountId>,868 is_external: bool,869 ) -> Result<CollectionId, DispatchError> {870 {871 ensure!(872 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,873 Error::<T>::CollectionTokenPrefixLimitExceeded874 );875 }876877 let created_count = <CreatedCollectionCount<T>>::get()878 .0879 .checked_add(1)880 .ok_or(ArithmeticError::Overflow)?;881 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;882 let id = CollectionId(created_count);883884 885 ensure!(886 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,887 <Error<T>>::TotalCollectionsLimitExceeded888 );889890 891892 let collection = Collection {893 owner: owner.as_sub().clone(),894 name: data.name,895 mode: data.mode.clone(),896 description: data.description,897 token_prefix: data.token_prefix,898 sponsorship: data899 .pending_sponsor900 .map(SponsorshipState::Unconfirmed)901 .unwrap_or_default(),902 limits: data903 .limits904 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))905 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,906 permissions: data907 .permissions908 .map(|permissions| {909 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)910 })911 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,912 external_collection: is_external,913 };914915 let mut collection_properties = up_data_structs::CollectionProperties::get();916 collection_properties917 .try_set_from_iter(data.properties.into_iter())918 .map_err(<Error<T>>::from)?;919920 CollectionProperties::<T>::insert(id, collection_properties);921922 let mut token_props_permissions = PropertiesPermissionMap::new();923 token_props_permissions924 .try_set_from_iter(data.token_property_permissions.into_iter())925 .map_err(<Error<T>>::from)?;926927 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);928929 930 {931 let mut imbalance =932 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();933 imbalance.subsume(934 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(935 &T::TreasuryAccountId::get(),936 T::CollectionCreationPrice::get(),937 ),938 );939 <T as Config>::Currency::settle(940 owner.as_sub(),941 imbalance,942 WithdrawReasons::TRANSFER,943 ExistenceRequirement::KeepAlive,944 )945 .map_err(|_| Error::<T>::NotSufficientFounds)?;946 }947948 <CreatedCollectionCount<T>>::put(created_count);949 <Pallet<T>>::deposit_event(Event::CollectionCreated(950 id,951 data.mode.id(),952 owner.as_sub().clone(),953 ));954 <PalletEvm<T>>::deposit_log(955 erc::CollectionHelpersEvents::CollectionCreated {956 owner: *owner.as_eth(),957 collection_id: eth::collection_id_to_address(id),958 }959 .to_log(T::ContractAddress::get()),960 );961 <CollectionById<T>>::insert(id, collection);962 Ok(id)963 }964965 966 967 968 969 pub fn destroy_collection(970 collection: CollectionHandle<T>,971 sender: &T::CrossAccountId,972 ) -> DispatchResult {973 ensure!(974 collection.limits.owner_can_destroy(),975 <Error<T>>::NoPermission,976 );977 collection.check_is_owner(sender)?;978979 let destroyed_collections = <DestroyedCollectionCount<T>>::get()980 .0981 .checked_add(1)982 .ok_or(ArithmeticError::Overflow)?;983984 985986 <DestroyedCollectionCount<T>>::put(destroyed_collections);987 <CollectionById<T>>::remove(collection.id);988 <AdminAmount<T>>::remove(collection.id);989 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);990 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);991 <CollectionProperties<T>>::remove(collection.id);992993 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));994 Ok(())995 }996997 998 999 1000 1001 1002 pub fn set_collection_property(1003 collection: &CollectionHandle<T>,1004 sender: &T::CrossAccountId,1005 property: Property,1006 ) -> DispatchResult {1007 collection.check_is_owner_or_admin(sender)?;10081009 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1010 let property = property.clone();1011 properties.try_set(property.key, property.value)1012 })1013 .map_err(<Error<T>>::from)?;10141015 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10161017 Ok(())1018 }10191020 1021 1022 1023 1024 1025 pub fn set_scoped_collection_property(1026 collection_id: CollectionId,1027 scope: PropertyScope,1028 property: Property,1029 ) -> DispatchResult {1030 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1031 properties.try_scoped_set(scope, property.key, property.value)1032 })1033 .map_err(<Error<T>>::from)?;10341035 Ok(())1036 }10371038 1039 1040 1041 1042 1043 pub fn set_scoped_collection_properties(1044 collection_id: CollectionId,1045 scope: PropertyScope,1046 properties: impl Iterator<Item = Property>,1047 ) -> DispatchResult {1048 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1049 stored_properties.try_scoped_set_from_iter(scope, properties)1050 })1051 .map_err(<Error<T>>::from)?;10521053 Ok(())1054 }10551056 1057 1058 1059 1060 1061 #[transactional]1062 pub fn set_collection_properties(1063 collection: &CollectionHandle<T>,1064 sender: &T::CrossAccountId,1065 properties: Vec<Property>,1066 ) -> DispatchResult {1067 for property in properties {1068 Self::set_collection_property(collection, sender, property)?;1069 }10701071 Ok(())1072 }10731074 1075 1076 1077 1078 1079 pub fn delete_collection_property(1080 collection: &CollectionHandle<T>,1081 sender: &T::CrossAccountId,1082 property_key: PropertyKey,1083 ) -> DispatchResult {1084 collection.check_is_owner_or_admin(sender)?;10851086 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1087 properties.remove(&property_key)1088 })1089 .map_err(<Error<T>>::from)?;10901091 Self::deposit_event(Event::CollectionPropertyDeleted(1092 collection.id,1093 property_key,1094 ));10951096 Ok(())1097 }10981099 1100 1101 1102 1103 1104 #[transactional]1105 pub fn delete_collection_properties(1106 collection: &CollectionHandle<T>,1107 sender: &T::CrossAccountId,1108 property_keys: Vec<PropertyKey>,1109 ) -> DispatchResult {1110 for key in property_keys {1111 Self::delete_collection_property(collection, sender, key)?;1112 }11131114 Ok(())1115 }11161117 1118 1119 1120 1121 1122 1123 pub fn set_property_permission_unchecked(1124 collection: CollectionId,1125 property_permission: PropertyKeyPermission,1126 ) -> DispatchResult {1127 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1128 permissions.try_set(property_permission.key, property_permission.permission)1129 })1130 .map_err(<Error<T>>::from)?;1131 Ok(())1132 }11331134 1135 1136 1137 1138 1139 pub fn set_property_permission(1140 collection: &CollectionHandle<T>,1141 sender: &T::CrossAccountId,1142 property_permission: PropertyKeyPermission,1143 ) -> DispatchResult {1144 Self::set_scoped_property_permission(1145 collection,1146 sender,1147 PropertyScope::None,1148 property_permission,1149 )1150 }11511152 1153 1154 1155 1156 1157 1158 pub fn set_scoped_property_permission(1159 collection: &CollectionHandle<T>,1160 sender: &T::CrossAccountId,1161 scope: PropertyScope,1162 property_permission: PropertyKeyPermission,1163 ) -> DispatchResult {1164 collection.check_is_owner_or_admin(sender)?;11651166 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1167 let current_permission = all_permissions.get(&property_permission.key);1168 if matches![1169 current_permission,1170 Some(PropertyPermission { mutable: false, .. })1171 ] {1172 return Err(<Error<T>>::NoPermission.into());1173 }11741175 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1176 let property_permission = property_permission.clone();1177 permissions.try_scoped_set(1178 scope,1179 property_permission.key,1180 property_permission.permission,1181 )1182 })1183 .map_err(<Error<T>>::from)?;11841185 Self::deposit_event(Event::PropertyPermissionSet(1186 collection.id,1187 property_permission.key,1188 ));11891190 Ok(())1191 }11921193 1194 1195 1196 1197 1198 #[transactional]1199 pub fn set_token_property_permissions(1200 collection: &CollectionHandle<T>,1201 sender: &T::CrossAccountId,1202 property_permissions: Vec<PropertyKeyPermission>,1203 ) -> DispatchResult {1204 Self::set_scoped_token_property_permissions(1205 collection,1206 sender,1207 PropertyScope::None,1208 property_permissions,1209 )1210 }12111212 1213 1214 1215 1216 1217 1218 #[transactional]1219 pub fn set_scoped_token_property_permissions(1220 collection: &CollectionHandle<T>,1221 sender: &T::CrossAccountId,1222 scope: PropertyScope,1223 property_permissions: Vec<PropertyKeyPermission>,1224 ) -> DispatchResult {1225 for prop_pemission in property_permissions {1226 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1227 }12281229 Ok(())1230 }12311232 1233 pub fn get_collection_property(1234 collection_id: CollectionId,1235 key: &PropertyKey,1236 ) -> Option<PropertyValue> {1237 Self::collection_properties(collection_id).get(key).cloned()1238 }12391240 1241 pub fn bytes_keys_to_property_keys(1242 keys: Vec<Vec<u8>>,1243 ) -> Result<Vec<PropertyKey>, DispatchError> {1244 keys.into_iter()1245 .map(|key| -> Result<PropertyKey, DispatchError> {1246 key.try_into()1247 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1248 })1249 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1250 }12511252 1253 pub fn filter_collection_properties(1254 collection_id: CollectionId,1255 keys: Option<Vec<PropertyKey>>,1256 ) -> Result<Vec<Property>, DispatchError> {1257 let properties = Self::collection_properties(collection_id);12581259 let properties = keys1260 .map(|keys| {1261 keys.into_iter()1262 .filter_map(|key| {1263 properties.get(&key).map(|value| Property {1264 key,1265 value: value.clone(),1266 })1267 })1268 .collect()1269 })1270 .unwrap_or_else(|| {1271 properties1272 .into_iter()1273 .map(|(key, value)| Property { key, value })1274 .collect()1275 });12761277 Ok(properties)1278 }12791280 1281 pub fn filter_property_permissions(1282 collection_id: CollectionId,1283 keys: Option<Vec<PropertyKey>>,1284 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1285 let permissions = Self::property_permissions(collection_id);12861287 let key_permissions = keys1288 .map(|keys| {1289 keys.into_iter()1290 .filter_map(|key| {1291 permissions1292 .get(&key)1293 .map(|permission| PropertyKeyPermission {1294 key,1295 permission: permission.clone(),1296 })1297 })1298 .collect()1299 })1300 .unwrap_or_else(|| {1301 permissions1302 .into_iter()1303 .map(|(key, permission)| PropertyKeyPermission { key, permission })1304 .collect()1305 });13061307 Ok(key_permissions)1308 }13091310 1311 1312 1313 pub fn toggle_allowlist(1314 collection: &CollectionHandle<T>,1315 sender: &T::CrossAccountId,1316 user: &T::CrossAccountId,1317 allowed: bool,1318 ) -> DispatchResult {1319 collection.check_is_owner_or_admin(sender)?;13201321 13221323 if allowed {1324 <Allowlist<T>>::insert((collection.id, user), true);1325 } else {1326 <Allowlist<T>>::remove((collection.id, user));1327 }13281329 Ok(())1330 }13311332 1333 1334 1335 pub fn toggle_admin(1336 collection: &CollectionHandle<T>,1337 sender: &T::CrossAccountId,1338 user: &T::CrossAccountId,1339 admin: bool,1340 ) -> DispatchResult {1341 collection.check_is_owner(sender)?;13421343 let was_admin = <IsAdmin<T>>::get((collection.id, user));1344 if was_admin == admin {1345 return Ok(());1346 }1347 let amount = <AdminAmount<T>>::get(collection.id);13481349 if admin {1350 let amount = amount1351 .checked_add(1)1352 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1353 ensure!(1354 amount <= Self::collection_admins_limit(),1355 <Error<T>>::CollectionAdminCountExceeded,1356 );13571358 13591360 <AdminAmount<T>>::insert(collection.id, amount);1361 <IsAdmin<T>>::insert((collection.id, user), true);1362 } else {1363 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1364 <IsAdmin<T>>::remove((collection.id, user));1365 }13661367 Ok(())1368 }13691370 1371 pub fn clamp_limits(1372 mode: CollectionMode,1373 old_limit: &CollectionLimits,1374 mut new_limit: CollectionLimits,1375 ) -> Result<CollectionLimits, DispatchError> {1376 let limits = old_limit;1377 limit_default!(old_limit, new_limit,1378 account_token_ownership_limit => ensure!(1379 new_limit <= MAX_TOKEN_OWNERSHIP,1380 <Error<T>>::CollectionLimitBoundsExceeded,1381 ),1382 sponsored_data_size => ensure!(1383 new_limit <= CUSTOM_DATA_LIMIT,1384 <Error<T>>::CollectionLimitBoundsExceeded,1385 ),13861387 sponsored_data_rate_limit => {},1388 token_limit => ensure!(1389 old_limit >= new_limit && new_limit > 0,1390 <Error<T>>::CollectionTokenLimitExceeded1391 ),13921393 sponsor_transfer_timeout(match mode {1394 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1395 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1396 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1397 }) => ensure!(1398 new_limit <= MAX_SPONSOR_TIMEOUT,1399 <Error<T>>::CollectionLimitBoundsExceeded,1400 ),1401 sponsor_approve_timeout => {},1402 owner_can_transfer => ensure!(1403 !limits.owner_can_transfer_instaled() ||1404 old_limit || !new_limit,1405 <Error<T>>::OwnerPermissionsCantBeReverted,1406 ),1407 owner_can_destroy => ensure!(1408 old_limit || !new_limit,1409 <Error<T>>::OwnerPermissionsCantBeReverted,1410 ),1411 transfers_enabled => {},1412 );1413 Ok(new_limit)1414 }14151416 1417 pub fn clamp_permissions(1418 _mode: CollectionMode,1419 old_permission: &CollectionPermissions,1420 mut new_permission: CollectionPermissions,1421 ) -> Result<CollectionPermissions, DispatchError> {1422 limit_default_clone!(old_permission, new_permission,1423 access => {},1424 mint_mode => {},1425 nesting => { },1426 );1427 Ok(new_permission)1428 }1429}143014311432#[macro_export]1433macro_rules! unsupported {1434 ($runtime:path) => {1435 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1436 };1437}143814391440pub trait CommonWeightInfo<CrossAccountId> {1441 1442 fn create_item() -> Weight;14431444 1445 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14461447 1448 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14491450 1451 fn burn_item() -> Weight;14521453 1454 1455 1456 fn set_collection_properties(amount: u32) -> Weight;14571458 1459 1460 1461 fn delete_collection_properties(amount: u32) -> Weight;14621463 1464 1465 1466 fn set_token_properties(amount: u32) -> Weight;14671468 1469 1470 1471 fn delete_token_properties(amount: u32) -> Weight;14721473 1474 1475 1476 fn set_token_property_permissions(amount: u32) -> Weight;14771478 1479 fn transfer() -> Weight;14801481 1482 fn approve() -> Weight;14831484 1485 fn transfer_from() -> Weight;14861487 1488 fn burn_from() -> Weight;14891490 1491 1492 1493 1494 fn burn_recursively_self_raw() -> Weight;14951496 1497 1498 1499 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15001501 1502 1503 1504 1505 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1506 Self::burn_recursively_self_raw()1507 .saturating_mul(max_selfs.max(1) as u64)1508 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1509 }15101511 1512 fn token_owner() -> Weight;1513}151415151516pub trait RefungibleExtensionsWeightInfo {1517 1518 fn repartition() -> Weight;1519}152015211522152315241525pub trait CommonCollectionOperations<T: Config> {1526 1527 1528 1529 1530 1531 1532 fn create_item(1533 &self,1534 sender: T::CrossAccountId,1535 to: T::CrossAccountId,1536 data: CreateItemData,1537 nesting_budget: &dyn Budget,1538 ) -> DispatchResultWithPostInfo;15391540 1541 1542 1543 1544 1545 1546 fn create_multiple_items(1547 &self,1548 sender: T::CrossAccountId,1549 to: T::CrossAccountId,1550 data: Vec<CreateItemData>,1551 nesting_budget: &dyn Budget,1552 ) -> DispatchResultWithPostInfo;15531554 1555 1556 1557 1558 1559 1560 fn create_multiple_items_ex(1561 &self,1562 sender: T::CrossAccountId,1563 data: CreateItemExData<T::CrossAccountId>,1564 nesting_budget: &dyn Budget,1565 ) -> DispatchResultWithPostInfo;15661567 1568 1569 1570 1571 1572 fn burn_item(1573 &self,1574 sender: T::CrossAccountId,1575 token: TokenId,1576 amount: u128,1577 ) -> DispatchResultWithPostInfo;15781579 1580 1581 1582 1583 1584 1585 fn burn_item_recursively(1586 &self,1587 sender: T::CrossAccountId,1588 token: TokenId,1589 self_budget: &dyn Budget,1590 breadth_budget: &dyn Budget,1591 ) -> DispatchResultWithPostInfo;15921593 1594 1595 1596 1597 fn set_collection_properties(1598 &self,1599 sender: T::CrossAccountId,1600 properties: Vec<Property>,1601 ) -> DispatchResultWithPostInfo;16021603 1604 1605 1606 1607 fn delete_collection_properties(1608 &self,1609 sender: &T::CrossAccountId,1610 property_keys: Vec<PropertyKey>,1611 ) -> DispatchResultWithPostInfo;16121613 1614 1615 1616 1617 1618 1619 1620 1621 1622 fn set_token_properties(1623 &self,1624 sender: T::CrossAccountId,1625 token_id: TokenId,1626 properties: Vec<Property>,1627 budget: &dyn Budget,1628 ) -> DispatchResultWithPostInfo;16291630 1631 1632 1633 1634 1635 1636 1637 1638 1639 fn delete_token_properties(1640 &self,1641 sender: T::CrossAccountId,1642 token_id: TokenId,1643 property_keys: Vec<PropertyKey>,1644 budget: &dyn Budget,1645 ) -> DispatchResultWithPostInfo;16461647 1648 1649 1650 1651 1652 1653 fn set_token_property_permissions(1654 &self,1655 sender: &T::CrossAccountId,1656 property_permissions: Vec<PropertyKeyPermission>,1657 ) -> DispatchResultWithPostInfo;16581659 1660 1661 1662 1663 1664 1665 1666 fn transfer(1667 &self,1668 sender: T::CrossAccountId,1669 to: T::CrossAccountId,1670 token: TokenId,1671 amount: u128,1672 budget: &dyn Budget,1673 ) -> DispatchResultWithPostInfo;16741675 1676 1677 1678 1679 1680 1681 fn approve(1682 &self,1683 sender: T::CrossAccountId,1684 spender: T::CrossAccountId,1685 token: TokenId,1686 amount: u128,1687 ) -> DispatchResultWithPostInfo;16881689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 fn transfer_from(1700 &self,1701 sender: T::CrossAccountId,1702 from: T::CrossAccountId,1703 to: T::CrossAccountId,1704 token: TokenId,1705 amount: u128,1706 budget: &dyn Budget,1707 ) -> DispatchResultWithPostInfo;17081709 1710 1711 1712 1713 1714 1715 1716 1717 1718 fn burn_from(1719 &self,1720 sender: T::CrossAccountId,1721 from: T::CrossAccountId,1722 token: TokenId,1723 amount: u128,1724 budget: &dyn Budget,1725 ) -> DispatchResultWithPostInfo;17261727 1728 1729 1730 1731 1732 1733 fn check_nesting(1734 &self,1735 sender: T::CrossAccountId,1736 from: (CollectionId, TokenId),1737 under: TokenId,1738 budget: &dyn Budget,1739 ) -> DispatchResult;17401741 1742 1743 1744 1745 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17461747 1748 1749 1750 1751 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17521753 1754 1755 1756 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17571758 1759 fn collection_tokens(&self) -> Vec<TokenId>;17601761 1762 1763 1764 fn token_exists(&self, token: TokenId) -> bool;17651766 1767 fn last_token_id(&self) -> TokenId;17681769 1770 1771 1772 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17731774 1775 1776 1777 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17781779 1780 1781 1782 1783 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17841785 1786 1787 1788 1789 1790 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17911792 1793 fn total_supply(&self) -> u32;17941795 1796 1797 1798 fn account_balance(&self, account: T::CrossAccountId) -> u32;17991800 1801 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18021803 1804 fn total_pieces(&self, token: TokenId) -> Option<u128>;18051806 1807 1808 1809 1810 1811 fn allowance(1812 &self,1813 sender: T::CrossAccountId,1814 spender: T::CrossAccountId,1815 token: TokenId,1816 ) -> u128;18171818 1819 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1820}182118221823pub trait RefungibleExtensions<T>1824where1825 T: Config,1826{1827 1828 1829 1830 1831 1832 1833 1834 fn repartition(1835 &self,1836 sender: &T::CrossAccountId,1837 token: TokenId,1838 amount: u128,1839 ) -> DispatchResultWithPostInfo;1840}18411842184318441845pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1846 let post_info = PostDispatchInfo {1847 actual_weight: Some(weight),1848 pays_fee: Pays::Yes,1849 };1850 match res {1851 Ok(()) => Ok(post_info),1852 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1853 }1854}18551856impl<T: Config> From<PropertiesError> for Error<T> {1857 fn from(error: PropertiesError) -> Self {1858 match error {1859 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1860 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1861 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1862 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1863 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1864 }1865 }1866}