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 SchemaVersion,116};117118pub use pallet::*;119use sp_core::H160;120use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};121#[cfg(feature = "runtime-benchmarks")]122pub mod benchmarking;123pub mod dispatch;124pub mod erc;125pub mod eth;126pub mod weights;127128129pub type SelfWeightOf<T> = <T as Config>::WeightInfo;130131132133134135136137#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]138pub struct CollectionHandle<T: Config> {139 140 pub id: CollectionId,141 collection: Collection<T::AccountId>,142 143 pub recorder: SubstrateRecorder<T>,144}145146impl<T: Config> WithRecorder<T> for CollectionHandle<T> {147 fn recorder(&self) -> &SubstrateRecorder<T> {148 &self.recorder149 }150 fn into_recorder(self) -> SubstrateRecorder<T> {151 self.recorder152 }153}154155impl<T: Config> CollectionHandle<T> {156 157 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {158 <CollectionById<T>>::get(id).map(|collection| Self {159 id,160 collection,161 recorder: SubstrateRecorder::new(gas_limit),162 })163 }164165 166 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {167 <CollectionById<T>>::get(id).map(|collection| Self {168 id,169 collection,170 recorder,171 })172 }173174 175 176 pub fn new(id: CollectionId) -> Option<Self> {177 Self::new_with_gas_limit(id, u64::MAX)178 }179180 181 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {182 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)183 }184185 186 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {187 self.recorder188 .consume_gas(T::GasWeightMapping::weight_to_gas(189 <T as frame_system::Config>::DbWeight::get()190 .read191 .saturating_mul(reads),192 ))193 }194195 196 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {197 self.recorder198 .consume_gas(T::GasWeightMapping::weight_to_gas(199 <T as frame_system::Config>::DbWeight::get()200 .write201 .saturating_mul(writes),202 ))203 }204205 206 pub fn save(&self) -> DispatchResult {207 <CollectionById<T>>::insert(self.id, &self.collection);208 Ok(())209 }210211 212 213 214 215 216 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {217 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);218 Ok(())219 }220221 222 223 224 225 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {226 if self.collection.sponsorship.pending_sponsor() != Some(sender) {227 return Ok(false);228 }229230 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());231 Ok(true)232 }233234 235 236 pub fn check_is_internal(&self) -> DispatchResult {237 if self.external_collection {238 return Err(<Error<T>>::CollectionIsExternal)?;239 }240241 Ok(())242 }243244 245 246 pub fn check_is_external(&self) -> DispatchResult {247 if !self.external_collection {248 return Err(<Error<T>>::CollectionIsInternal)?;249 }250251 Ok(())252 }253}254255impl<T: Config> Deref for CollectionHandle<T> {256 type Target = Collection<T::AccountId>;257258 fn deref(&self) -> &Self::Target {259 &self.collection260 }261}262263impl<T: Config> DerefMut for CollectionHandle<T> {264 fn deref_mut(&mut self) -> &mut Self::Target {265 &mut self.collection266 }267}268269impl<T: Config> CollectionHandle<T> {270 271 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {272 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);273 Ok(())274 }275276 277 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {278 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))279 }280281 282 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {283 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);284 Ok(())285 }286287 288 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {289 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)290 }291292 293 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {294 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)295 }296297 298 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {299 ensure!(300 <Allowlist<T>>::get((self.id, user)),301 <Error<T>>::AddressNotInAllowlist302 );303 Ok(())304 }305306 307 fn set_owner_internal(308 &mut self,309 caller: T::CrossAccountId,310 new_owner: T::CrossAccountId,311 ) -> DispatchResult {312 self.check_is_owner(&caller)?;313 self.collection.owner = new_owner.as_sub().clone();314 self.save()315 }316}317318#[frame_support::pallet]319pub mod pallet {320 use super::*;321 use pallet_evm::account;322 use dispatch::CollectionDispatch;323 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};324 use frame_system::pallet_prelude::*;325 use frame_support::traits::Currency;326 use up_data_structs::{TokenId, mapping::TokenAddressMapping};327 use scale_info::TypeInfo;328 use weights::WeightInfo;329330 #[pallet::config]331 pub trait Config:332 frame_system::Config333 + pallet_evm_coder_substrate::Config334 + pallet_evm::Config335 + TypeInfo336 + account::Config337 {338 339 type WeightInfo: WeightInfo;340341 342 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;343344 345 type Currency: Currency<Self::AccountId>;346347 348 #[pallet::constant]349 type CollectionCreationPrice: Get<350 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,351 >;352353 354 type CollectionDispatch: CollectionDispatch<Self>;355356 357 type TreasuryAccountId: Get<Self::AccountId>;358359 360 type ContractAddress: Get<H160>;361362 363 type EvmTokenAddressMapping: TokenAddressMapping<H160>;364365 366 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;367 }368369 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);370371 #[pallet::pallet]372 #[pallet::storage_version(STORAGE_VERSION)]373 #[pallet::generate_store(pub(super) trait Store)]374 pub struct Pallet<T>(_);375376 #[pallet::extra_constants]377 impl<T: Config> Pallet<T> {378 379 pub fn collection_admins_limit() -> u32 {380 COLLECTION_ADMINS_LIMIT381 }382 }383384 #[pallet::event]385 #[pallet::generate_deposit(pub fn deposit_event)]386 pub enum Event<T: Config> {387 388 CollectionCreated(389 390 CollectionId,391 392 u8,393 394 T::AccountId,395 ),396397 398 CollectionDestroyed(399 400 CollectionId,401 ),402403 404 ItemCreated(405 406 CollectionId,407 408 TokenId,409 410 T::CrossAccountId,411 412 u128,413 ),414415 416 ItemDestroyed(417 418 CollectionId,419 420 TokenId,421 422 T::CrossAccountId,423 424 u128,425 ),426427 428 Transfer(429 430 CollectionId,431 432 TokenId,433 434 T::CrossAccountId,435 436 T::CrossAccountId,437 438 u128,439 ),440441 442 Approved(443 444 CollectionId,445 446 TokenId,447 448 T::CrossAccountId,449 450 T::CrossAccountId,451 452 u128,453 ),454455 456 CollectionPropertySet(457 458 CollectionId,459 460 PropertyKey,461 ),462463 464 CollectionPropertyDeleted(465 466 CollectionId,467 468 PropertyKey,469 ),470471 472 TokenPropertySet(473 474 CollectionId,475 476 TokenId,477 478 PropertyKey,479 ),480481 482 TokenPropertyDeleted(483 484 CollectionId,485 486 TokenId,487 488 PropertyKey,489 ),490491 492 PropertyPermissionSet(493 494 CollectionId,495 496 PropertyKey,497 ),498 }499500 #[pallet::error]501 pub enum Error<T> {502 503 CollectionNotFound,504 505 MustBeTokenOwner,506 507 NoPermission,508 509 CantDestroyNotEmptyCollection,510 511 PublicMintingNotAllowed,512 513 AddressNotInAllowlist,514515 516 CollectionNameLimitExceeded,517 518 CollectionDescriptionLimitExceeded,519 520 CollectionTokenPrefixLimitExceeded,521 522 TotalCollectionsLimitExceeded,523 524 CollectionAdminCountExceeded,525 526 CollectionLimitBoundsExceeded,527 528 OwnerPermissionsCantBeReverted,529 530 TransferNotAllowed,531 532 AccountTokenLimitExceeded,533 534 CollectionTokenLimitExceeded,535 536 MetadataFlagFrozen,537538 539 TokenNotFound,540 541 TokenValueTooLow,542 543 ApprovedValueTooLow,544 545 CantApproveMoreThanOwned,546547 548 AddressIsZero,549550 551 UnsupportedOperation,552553 554 NotSufficientFounds,555556 557 UserIsNotAllowedToNest,558 559 SourceCollectionIsNotAllowedToNest,560561 562 CollectionFieldSizeExceeded,563564 565 NoSpaceForProperty,566567 568 PropertyLimitReached,569570 571 PropertyKeyIsTooLong,572573 574 InvalidCharacterInPropertyKey,575576 577 EmptyPropertyKey,578579 580 CollectionIsExternal,581582 583 CollectionIsInternal,584 }585586 587 #[pallet::storage]588 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;589590 591 #[pallet::storage]592 pub type DestroyedCollectionCount<T> =593 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;594595 596 #[pallet::storage]597 pub type CollectionById<T> = StorageMap<598 Hasher = Blake2_128Concat,599 Key = CollectionId,600 Value = Collection<<T as frame_system::Config>::AccountId>,601 QueryKind = OptionQuery,602 >;603604 605 #[pallet::storage]606 #[pallet::getter(fn collection_properties)]607 pub type CollectionProperties<T> = StorageMap<608 Hasher = Blake2_128Concat,609 Key = CollectionId,610 Value = Properties,611 QueryKind = ValueQuery,612 OnEmpty = up_data_structs::CollectionProperties,613 >;614615 616 #[pallet::storage]617 #[pallet::getter(fn property_permissions)]618 pub type CollectionPropertyPermissions<T> = StorageMap<619 Hasher = Blake2_128Concat,620 Key = CollectionId,621 Value = PropertiesPermissionMap,622 QueryKind = ValueQuery,623 >;624625 626 #[pallet::storage]627 pub type AdminAmount<T> = StorageMap<628 Hasher = Blake2_128Concat,629 Key = CollectionId,630 Value = u32,631 QueryKind = ValueQuery,632 >;633634 635 #[pallet::storage]636 pub type IsAdmin<T: Config> = StorageNMap<637 Key = (638 Key<Blake2_128Concat, CollectionId>,639 Key<Blake2_128Concat, T::CrossAccountId>,640 ),641 Value = bool,642 QueryKind = ValueQuery,643 >;644645 646 #[pallet::storage]647 pub type Allowlist<T: Config> = StorageNMap<648 Key = (649 Key<Blake2_128Concat, CollectionId>,650 Key<Blake2_128Concat, T::CrossAccountId>,651 ),652 Value = bool,653 QueryKind = ValueQuery,654 >;655656 657 #[pallet::storage]658 pub type DummyStorageValue<T: Config> = StorageValue<659 Value = (660 CollectionStats,661 CollectionId,662 TokenId,663 TokenChild,664 PhantomType<(665 TokenData<T::CrossAccountId>,666 RpcCollection<T::AccountId>,667 668 RmrkCollectionInfo<T::AccountId>,669 RmrkInstanceInfo<T::AccountId>,670 RmrkResourceInfo,671 RmrkPropertyInfo,672 RmrkBaseInfo<T::AccountId>,673 RmrkPartType,674 RmrkBoundedTheme,675 RmrkNftChild,676 )>,677 ),678 QueryKind = OptionQuery,679 >;680681 #[pallet::hooks]682 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {683 fn on_runtime_upgrade() -> Weight {684 StorageVersion::new(1).put::<Pallet<T>>();685686 0687 }688 }689}690691impl<T: Config> Pallet<T> {692 693 694 695 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {696 ensure!(697 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,698 <Error<T>>::AddressIsZero699 );700 Ok(())701 }702703 704 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {705 <IsAdmin<T>>::iter_prefix((collection,))706 .map(|(a, _)| a)707 .collect()708 }709710 711 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {712 <Allowlist<T>>::iter_prefix((collection,))713 .map(|(a, _)| a)714 .collect()715 }716717 718 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {719 <Allowlist<T>>::get((collection, user))720 }721722 723 pub fn collection_stats() -> CollectionStats {724 let created = <CreatedCollectionCount<T>>::get();725 let destroyed = <DestroyedCollectionCount<T>>::get();726 CollectionStats {727 created: created.0,728 destroyed: destroyed.0,729 alive: created.0 - destroyed.0,730 }731 }732733 734 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {735 let collection = <CollectionById<T>>::get(collection);736 if collection.is_none() {737 return None;738 }739740 let collection = collection.unwrap();741 let limits = collection.limits;742 let effective_limits = CollectionLimits {743 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),744 sponsored_data_size: Some(limits.sponsored_data_size()),745 sponsored_data_rate_limit: Some(746 limits747 .sponsored_data_rate_limit748 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),749 ),750 token_limit: Some(limits.token_limit()),751 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(752 match collection.mode {753 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,754 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,755 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,756 },757 )),758 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),759 owner_can_transfer: Some(limits.owner_can_transfer()),760 owner_can_destroy: Some(limits.owner_can_destroy()),761 transfers_enabled: Some(limits.transfers_enabled()),762 };763764 Some(effective_limits)765 }766767 768 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {769 let Collection {770 name,771 description,772 owner,773 mode,774 token_prefix,775 sponsorship,776 limits,777 permissions,778 external_collection,779 } = <CollectionById<T>>::get(collection)?;780781 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)782 .into_iter()783 .map(|(key, permission)| PropertyKeyPermission { key, permission })784 .collect();785786 let properties = <CollectionProperties<T>>::get(collection)787 .into_iter()788 .map(|(key, value)| Property { key, value })789 .collect();790791 let permissions = CollectionPermissions {792 access: Some(permissions.access()),793 mint_mode: Some(permissions.mint_mode()),794 nesting: Some(permissions.nesting().clone()),795 };796797 Some(RpcCollection {798 name: name.into_inner(),799 description: description.into_inner(),800 owner,801 mode,802 token_prefix: token_prefix.into_inner(),803 sponsorship,804 limits,805 permissions,806 token_property_permissions,807 properties,808 read_only: external_collection,809 })810 }811}812813macro_rules! limit_default {814 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{815 $(816 if let Some($new) = $new.$field {817 let $old = $old.$field($($arg)?);818 let _ = $new;819 let _ = $old;820 $check821 } else {822 $new.$field = $old.$field823 }824 )*825 }};826}827macro_rules! limit_default_clone {828 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{829 $(830 if let Some($new) = $new.$field.clone() {831 let $old = $old.$field($($arg)?);832 let _ = $new;833 let _ = $old;834 $check835 } else {836 $new.$field = $old.$field.clone()837 }838 )*839 }};840}841842impl<T: Config> Pallet<T> {843 844 845 846 847 848 pub fn init_collection(849 owner: T::CrossAccountId,850 data: CreateCollectionData<T::AccountId>,851 is_external: bool,852 ) -> Result<CollectionId, DispatchError> {853 {854 ensure!(855 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,856 Error::<T>::CollectionTokenPrefixLimitExceeded857 );858 }859860 let created_count = <CreatedCollectionCount<T>>::get()861 .0862 .checked_add(1)863 .ok_or(ArithmeticError::Overflow)?;864 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;865 let id = CollectionId(created_count);866867 868 ensure!(869 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,870 <Error<T>>::TotalCollectionsLimitExceeded871 );872873 874875 let collection = Collection {876 owner: owner.as_sub().clone(),877 name: data.name,878 mode: data.mode.clone(),879 description: data.description,880 token_prefix: data.token_prefix,881 sponsorship: data882 .pending_sponsor883 .map(SponsorshipState::Unconfirmed)884 .unwrap_or_default(),885 limits: data886 .limits887 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))888 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,889 permissions: data890 .permissions891 .map(|permissions| {892 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)893 })894 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,895 external_collection: is_external,896 };897898 let mut collection_properties = up_data_structs::CollectionProperties::get();899 collection_properties900 .try_set_from_iter(data.properties.into_iter())901 .map_err(<Error<T>>::from)?;902903 CollectionProperties::<T>::insert(id, collection_properties);904905 let mut token_props_permissions = PropertiesPermissionMap::new();906 token_props_permissions907 .try_set_from_iter(data.token_property_permissions.into_iter())908 .map_err(<Error<T>>::from)?;909910 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);911912 913 {914 let mut imbalance =915 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();916 imbalance.subsume(917 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(918 &T::TreasuryAccountId::get(),919 T::CollectionCreationPrice::get(),920 ),921 );922 <T as Config>::Currency::settle(923 owner.as_sub(),924 imbalance,925 WithdrawReasons::TRANSFER,926 ExistenceRequirement::KeepAlive,927 )928 .map_err(|_| Error::<T>::NotSufficientFounds)?;929 }930931 <CreatedCollectionCount<T>>::put(created_count);932 <Pallet<T>>::deposit_event(Event::CollectionCreated(933 id,934 data.mode.id(),935 owner.as_sub().clone(),936 ));937 <PalletEvm<T>>::deposit_log(938 erc::CollectionHelpersEvents::CollectionCreated {939 owner: *owner.as_eth(),940 collection_id: eth::collection_id_to_address(id),941 }942 .to_log(T::ContractAddress::get()),943 );944 <CollectionById<T>>::insert(id, collection);945 Ok(id)946 }947948 949 950 951 952 pub fn destroy_collection(953 collection: CollectionHandle<T>,954 sender: &T::CrossAccountId,955 ) -> DispatchResult {956 ensure!(957 collection.limits.owner_can_destroy(),958 <Error<T>>::NoPermission,959 );960 collection.check_is_owner(sender)?;961962 let destroyed_collections = <DestroyedCollectionCount<T>>::get()963 .0964 .checked_add(1)965 .ok_or(ArithmeticError::Overflow)?;966967 968969 <DestroyedCollectionCount<T>>::put(destroyed_collections);970 <CollectionById<T>>::remove(collection.id);971 <AdminAmount<T>>::remove(collection.id);972 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);973 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);974 <CollectionProperties<T>>::remove(collection.id);975976 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));977 Ok(())978 }979980 981 982 983 984 985 pub fn set_collection_property(986 collection: &CollectionHandle<T>,987 sender: &T::CrossAccountId,988 property: Property,989 ) -> DispatchResult {990 collection.check_is_owner_or_admin(sender)?;991992 CollectionProperties::<T>::try_mutate(collection.id, |properties| {993 let property = property.clone();994 properties.try_set(property.key, property.value)995 })996 .map_err(<Error<T>>::from)?;997998 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));9991000 Ok(())1001 }10021003 1004 1005 1006 1007 1008 pub fn set_scoped_collection_property(1009 collection_id: CollectionId,1010 scope: PropertyScope,1011 property: Property,1012 ) -> DispatchResult {1013 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1014 properties.try_scoped_set(scope, property.key, property.value)1015 })1016 .map_err(<Error<T>>::from)?;10171018 Ok(())1019 }10201021 1022 1023 1024 1025 1026 pub fn set_scoped_collection_properties(1027 collection_id: CollectionId,1028 scope: PropertyScope,1029 properties: impl Iterator<Item = Property>,1030 ) -> DispatchResult {1031 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1032 stored_properties.try_scoped_set_from_iter(scope, properties)1033 })1034 .map_err(<Error<T>>::from)?;10351036 Ok(())1037 }10381039 1040 1041 1042 1043 1044 #[transactional]1045 pub fn set_collection_properties(1046 collection: &CollectionHandle<T>,1047 sender: &T::CrossAccountId,1048 properties: Vec<Property>,1049 ) -> DispatchResult {1050 for property in properties {1051 Self::set_collection_property(collection, sender, property)?;1052 }10531054 Ok(())1055 }10561057 1058 1059 1060 1061 1062 pub fn delete_collection_property(1063 collection: &CollectionHandle<T>,1064 sender: &T::CrossAccountId,1065 property_key: PropertyKey,1066 ) -> DispatchResult {1067 collection.check_is_owner_or_admin(sender)?;10681069 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1070 properties.remove(&property_key)1071 })1072 .map_err(<Error<T>>::from)?;10731074 Self::deposit_event(Event::CollectionPropertyDeleted(1075 collection.id,1076 property_key,1077 ));10781079 Ok(())1080 }10811082 1083 1084 1085 1086 1087 #[transactional]1088 pub fn delete_collection_properties(1089 collection: &CollectionHandle<T>,1090 sender: &T::CrossAccountId,1091 property_keys: Vec<PropertyKey>,1092 ) -> DispatchResult {1093 for key in property_keys {1094 Self::delete_collection_property(collection, sender, key)?;1095 }10961097 Ok(())1098 }10991100 1101 1102 1103 1104 1105 1106 pub fn set_property_permission_unchecked(1107 collection: CollectionId,1108 property_permission: PropertyKeyPermission,1109 ) -> DispatchResult {1110 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1111 permissions.try_set(property_permission.key, property_permission.permission)1112 })1113 .map_err(<Error<T>>::from)?;1114 Ok(())1115 }11161117 1118 1119 1120 1121 1122 pub fn set_property_permission(1123 collection: &CollectionHandle<T>,1124 sender: &T::CrossAccountId,1125 property_permission: PropertyKeyPermission,1126 ) -> DispatchResult {1127 Self::set_scoped_property_permission(1128 collection,1129 sender,1130 PropertyScope::None,1131 property_permission,1132 )1133 }11341135 1136 1137 1138 1139 1140 1141 pub fn set_scoped_property_permission(1142 collection: &CollectionHandle<T>,1143 sender: &T::CrossAccountId,1144 scope: PropertyScope,1145 property_permission: PropertyKeyPermission,1146 ) -> DispatchResult {1147 collection.check_is_owner_or_admin(sender)?;11481149 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1150 let current_permission = all_permissions.get(&property_permission.key);1151 if matches![1152 current_permission,1153 Some(PropertyPermission { mutable: false, .. })1154 ] {1155 return Err(<Error<T>>::NoPermission.into());1156 }11571158 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1159 let property_permission = property_permission.clone();1160 permissions.try_scoped_set(1161 scope,1162 property_permission.key,1163 property_permission.permission,1164 )1165 })1166 .map_err(<Error<T>>::from)?;11671168 Self::deposit_event(Event::PropertyPermissionSet(1169 collection.id,1170 property_permission.key,1171 ));11721173 Ok(())1174 }11751176 1177 1178 1179 1180 1181 #[transactional]1182 pub fn set_token_property_permissions(1183 collection: &CollectionHandle<T>,1184 sender: &T::CrossAccountId,1185 property_permissions: Vec<PropertyKeyPermission>,1186 ) -> DispatchResult {1187 Self::set_scoped_token_property_permissions(1188 collection,1189 sender,1190 PropertyScope::None,1191 property_permissions,1192 )1193 }11941195 1196 1197 1198 1199 1200 1201 #[transactional]1202 pub fn set_scoped_token_property_permissions(1203 collection: &CollectionHandle<T>,1204 sender: &T::CrossAccountId,1205 scope: PropertyScope,1206 property_permissions: Vec<PropertyKeyPermission>,1207 ) -> DispatchResult {1208 for prop_pemission in property_permissions {1209 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1210 }12111212 Ok(())1213 }12141215 1216 pub fn get_collection_property(1217 collection_id: CollectionId,1218 key: &PropertyKey,1219 ) -> Option<PropertyValue> {1220 Self::collection_properties(collection_id).get(key).cloned()1221 }12221223 1224 pub fn bytes_keys_to_property_keys(1225 keys: Vec<Vec<u8>>,1226 ) -> Result<Vec<PropertyKey>, DispatchError> {1227 keys.into_iter()1228 .map(|key| -> Result<PropertyKey, DispatchError> {1229 key.try_into()1230 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1231 })1232 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1233 }12341235 1236 pub fn filter_collection_properties(1237 collection_id: CollectionId,1238 keys: Option<Vec<PropertyKey>>,1239 ) -> Result<Vec<Property>, DispatchError> {1240 let properties = Self::collection_properties(collection_id);12411242 let properties = keys1243 .map(|keys| {1244 keys.into_iter()1245 .filter_map(|key| {1246 properties.get(&key).map(|value| Property {1247 key,1248 value: value.clone(),1249 })1250 })1251 .collect()1252 })1253 .unwrap_or_else(|| {1254 properties1255 .into_iter()1256 .map(|(key, value)| Property { key, value })1257 .collect()1258 });12591260 Ok(properties)1261 }12621263 1264 pub fn filter_property_permissions(1265 collection_id: CollectionId,1266 keys: Option<Vec<PropertyKey>>,1267 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1268 let permissions = Self::property_permissions(collection_id);12691270 let key_permissions = keys1271 .map(|keys| {1272 keys.into_iter()1273 .filter_map(|key| {1274 permissions1275 .get(&key)1276 .map(|permission| PropertyKeyPermission {1277 key,1278 permission: permission.clone(),1279 })1280 })1281 .collect()1282 })1283 .unwrap_or_else(|| {1284 permissions1285 .into_iter()1286 .map(|(key, permission)| PropertyKeyPermission { key, permission })1287 .collect()1288 });12891290 Ok(key_permissions)1291 }12921293 1294 pub fn toggle_allowlist(1295 collection: &CollectionHandle<T>,1296 sender: &T::CrossAccountId,1297 user: &T::CrossAccountId,1298 allowed: bool,1299 ) -> DispatchResult {1300 collection.check_is_owner_or_admin(sender)?;13011302 13031304 if allowed {1305 <Allowlist<T>>::insert((collection.id, user), true);1306 } else {1307 <Allowlist<T>>::remove((collection.id, user));1308 }13091310 Ok(())1311 }13121313 1314 pub fn toggle_admin(1315 collection: &CollectionHandle<T>,1316 sender: &T::CrossAccountId,1317 user: &T::CrossAccountId,1318 admin: bool,1319 ) -> DispatchResult {1320 collection.check_is_owner(sender)?;13211322 let was_admin = <IsAdmin<T>>::get((collection.id, user));1323 if was_admin == admin {1324 return Ok(());1325 }1326 let amount = <AdminAmount<T>>::get(collection.id);13271328 if admin {1329 let amount = amount1330 .checked_add(1)1331 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1332 ensure!(1333 amount <= Self::collection_admins_limit(),1334 <Error<T>>::CollectionAdminCountExceeded,1335 );13361337 13381339 <AdminAmount<T>>::insert(collection.id, amount);1340 <IsAdmin<T>>::insert((collection.id, user), true);1341 } else {1342 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1343 <IsAdmin<T>>::remove((collection.id, user));1344 }13451346 Ok(())1347 }13481349 1350 pub fn clamp_limits(1351 mode: CollectionMode,1352 old_limit: &CollectionLimits,1353 mut new_limit: CollectionLimits,1354 ) -> Result<CollectionLimits, DispatchError> {1355 let limits = old_limit;1356 limit_default!(old_limit, new_limit,1357 account_token_ownership_limit => ensure!(1358 new_limit <= MAX_TOKEN_OWNERSHIP,1359 <Error<T>>::CollectionLimitBoundsExceeded,1360 ),1361 sponsored_data_size => ensure!(1362 new_limit <= CUSTOM_DATA_LIMIT,1363 <Error<T>>::CollectionLimitBoundsExceeded,1364 ),13651366 sponsored_data_rate_limit => {},1367 token_limit => ensure!(1368 old_limit >= new_limit && new_limit > 0,1369 <Error<T>>::CollectionTokenLimitExceeded1370 ),13711372 sponsor_transfer_timeout(match mode {1373 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1374 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1375 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1376 }) => ensure!(1377 new_limit <= MAX_SPONSOR_TIMEOUT,1378 <Error<T>>::CollectionLimitBoundsExceeded,1379 ),1380 sponsor_approve_timeout => {},1381 owner_can_transfer => ensure!(1382 !limits.owner_can_transfer_instaled() ||1383 old_limit || !new_limit,1384 <Error<T>>::OwnerPermissionsCantBeReverted,1385 ),1386 owner_can_destroy => ensure!(1387 old_limit || !new_limit,1388 <Error<T>>::OwnerPermissionsCantBeReverted,1389 ),1390 transfers_enabled => {},1391 );1392 Ok(new_limit)1393 }13941395 1396 pub fn clamp_permissions(1397 _mode: CollectionMode,1398 old_permission: &CollectionPermissions,1399 mut new_permission: CollectionPermissions,1400 ) -> Result<CollectionPermissions, DispatchError> {1401 limit_default_clone!(old_permission, new_permission,1402 access => {},1403 mint_mode => {},1404 nesting => { },1405 );1406 Ok(new_permission)1407 }1408}140914101411#[macro_export]1412macro_rules! unsupported {1413 ($runtime:path) => {1414 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1415 };1416}141714181419pub trait CommonWeightInfo<CrossAccountId> {1420 1421 fn create_item() -> Weight;14221423 1424 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14251426 1427 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14281429 1430 fn burn_item() -> Weight;14311432 1433 1434 1435 fn set_collection_properties(amount: u32) -> Weight;14361437 1438 1439 1440 fn delete_collection_properties(amount: u32) -> Weight;14411442 1443 1444 1445 fn set_token_properties(amount: u32) -> Weight;14461447 1448 1449 1450 fn delete_token_properties(amount: u32) -> Weight;14511452 1453 1454 1455 fn set_token_property_permissions(amount: u32) -> Weight;14561457 1458 fn transfer() -> Weight;14591460 1461 fn approve() -> Weight;14621463 1464 fn transfer_from() -> Weight;14651466 1467 fn burn_from() -> Weight;14681469 1470 1471 1472 1473 fn burn_recursively_self_raw() -> Weight;14741475 1476 1477 1478 fn burn_recursively_breadth_raw(amount: u32) -> Weight;14791480 1481 1482 1483 1484 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1485 Self::burn_recursively_self_raw()1486 .saturating_mul(max_selfs.max(1) as u64)1487 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1488 }14891490 1491 fn token_owner() -> Weight;1492}149314941495pub trait RefungibleExtensionsWeightInfo {1496 1497 fn repartition() -> Weight;1498}149915001501150215031504pub trait CommonCollectionOperations<T: Config> {1505 1506 1507 1508 1509 1510 1511 fn create_item(1512 &self,1513 sender: T::CrossAccountId,1514 to: T::CrossAccountId,1515 data: CreateItemData,1516 nesting_budget: &dyn Budget,1517 ) -> DispatchResultWithPostInfo;15181519 1520 1521 1522 1523 1524 1525 fn create_multiple_items(1526 &self,1527 sender: T::CrossAccountId,1528 to: T::CrossAccountId,1529 data: Vec<CreateItemData>,1530 nesting_budget: &dyn Budget,1531 ) -> DispatchResultWithPostInfo;15321533 1534 1535 1536 1537 1538 1539 fn create_multiple_items_ex(1540 &self,1541 sender: T::CrossAccountId,1542 data: CreateItemExData<T::CrossAccountId>,1543 nesting_budget: &dyn Budget,1544 ) -> DispatchResultWithPostInfo;15451546 1547 1548 1549 1550 1551 fn burn_item(1552 &self,1553 sender: T::CrossAccountId,1554 token: TokenId,1555 amount: u128,1556 ) -> DispatchResultWithPostInfo;15571558 1559 1560 1561 1562 1563 1564 fn burn_item_recursively(1565 &self,1566 sender: T::CrossAccountId,1567 token: TokenId,1568 self_budget: &dyn Budget,1569 breadth_budget: &dyn Budget,1570 ) -> DispatchResultWithPostInfo;15711572 1573 1574 1575 1576 fn set_collection_properties(1577 &self,1578 sender: T::CrossAccountId,1579 properties: Vec<Property>,1580 ) -> DispatchResultWithPostInfo;15811582 1583 1584 1585 1586 fn delete_collection_properties(1587 &self,1588 sender: &T::CrossAccountId,1589 property_keys: Vec<PropertyKey>,1590 ) -> DispatchResultWithPostInfo;15911592 1593 1594 1595 1596 1597 1598 1599 1600 1601 fn set_token_properties(1602 &self,1603 sender: T::CrossAccountId,1604 token_id: TokenId,1605 properties: Vec<Property>,1606 budget: &dyn Budget,1607 ) -> DispatchResultWithPostInfo;16081609 1610 1611 1612 1613 1614 1615 1616 1617 1618 fn delete_token_properties(1619 &self,1620 sender: T::CrossAccountId,1621 token_id: TokenId,1622 property_keys: Vec<PropertyKey>,1623 budget: &dyn Budget,1624 ) -> DispatchResultWithPostInfo;16251626 1627 1628 1629 1630 1631 1632 fn set_token_property_permissions(1633 &self,1634 sender: &T::CrossAccountId,1635 property_permissions: Vec<PropertyKeyPermission>,1636 ) -> DispatchResultWithPostInfo;16371638 1639 1640 1641 1642 1643 1644 1645 fn transfer(1646 &self,1647 sender: T::CrossAccountId,1648 to: T::CrossAccountId,1649 token: TokenId,1650 amount: u128,1651 budget: &dyn Budget,1652 ) -> DispatchResultWithPostInfo;16531654 1655 1656 1657 1658 1659 1660 fn approve(1661 &self,1662 sender: T::CrossAccountId,1663 spender: T::CrossAccountId,1664 token: TokenId,1665 amount: u128,1666 ) -> DispatchResultWithPostInfo;16671668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 fn transfer_from(1679 &self,1680 sender: T::CrossAccountId,1681 from: T::CrossAccountId,1682 to: T::CrossAccountId,1683 token: TokenId,1684 amount: u128,1685 budget: &dyn Budget,1686 ) -> DispatchResultWithPostInfo;16871688 1689 1690 1691 1692 1693 1694 1695 1696 1697 fn burn_from(1698 &self,1699 sender: T::CrossAccountId,1700 from: T::CrossAccountId,1701 token: TokenId,1702 amount: u128,1703 budget: &dyn Budget,1704 ) -> DispatchResultWithPostInfo;17051706 1707 1708 1709 1710 1711 1712 fn check_nesting(1713 &self,1714 sender: T::CrossAccountId,1715 from: (CollectionId, TokenId),1716 under: TokenId,1717 budget: &dyn Budget,1718 ) -> DispatchResult;17191720 1721 1722 1723 1724 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17251726 1727 1728 1729 1730 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17311732 1733 1734 1735 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17361737 1738 fn collection_tokens(&self) -> Vec<TokenId>;17391740 1741 1742 1743 fn token_exists(&self, token: TokenId) -> bool;17441745 1746 fn last_token_id(&self) -> TokenId;17471748 1749 1750 1751 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17521753 1754 1755 1756 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17571758 1759 1760 1761 1762 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17631764 1765 1766 1767 1768 1769 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17701771 1772 fn total_supply(&self) -> u32;17731774 1775 1776 1777 fn account_balance(&self, account: T::CrossAccountId) -> u32;17781779 1780 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;17811782 1783 fn total_pieces(&self, token: TokenId) -> Option<u128>;17841785 1786 1787 1788 1789 1790 fn allowance(1791 &self,1792 sender: T::CrossAccountId,1793 spender: T::CrossAccountId,1794 token: TokenId,1795 ) -> u128;17961797 1798 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1799}180018011802pub trait RefungibleExtensions<T>1803where1804 T: Config,1805{1806 1807 1808 1809 1810 1811 1812 1813 fn repartition(1814 &self,1815 sender: &T::CrossAccountId,1816 token: TokenId,1817 amount: u128,1818 ) -> DispatchResultWithPostInfo;1819}18201821182218231824pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1825 let post_info = PostDispatchInfo {1826 actual_weight: Some(weight),1827 pays_fee: Pays::Yes,1828 };1829 match res {1830 Ok(()) => Ok(post_info),1831 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1832 }1833}18341835impl<T: Config> From<PropertiesError> for Error<T> {1836 fn from(error: PropertiesError) -> Self {1837 match error {1838 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1839 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1840 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1841 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1842 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1843 }1844 }1845}