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 pub fn remove_sponsor(&mut self) -> DispatchResult {236 self.collection.sponsorship = SponsorshipState::Disabled;237 Ok(())238 }239240 241 242 pub fn check_is_internal(&self) -> DispatchResult {243 if self.external_collection {244 return Err(<Error<T>>::CollectionIsExternal)?;245 }246247 Ok(())248 }249250 251 252 pub fn check_is_external(&self) -> DispatchResult {253 if !self.external_collection {254 return Err(<Error<T>>::CollectionIsInternal)?;255 }256257 Ok(())258 }259}260261impl<T: Config> Deref for CollectionHandle<T> {262 type Target = Collection<T::AccountId>;263264 fn deref(&self) -> &Self::Target {265 &self.collection266 }267}268269impl<T: Config> DerefMut for CollectionHandle<T> {270 fn deref_mut(&mut self) -> &mut Self::Target {271 &mut self.collection272 }273}274275impl<T: Config> CollectionHandle<T> {276 277 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {278 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);279 Ok(())280 }281282 283 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {284 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))285 }286287 288 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {289 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);290 Ok(())291 }292293 294 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {295 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)296 }297298 299 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {300 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)301 }302303 304 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {305 ensure!(306 <Allowlist<T>>::get((self.id, user)),307 <Error<T>>::AddressNotInAllowlist308 );309 Ok(())310 }311312 313 fn set_owner_internal(314 &mut self,315 caller: T::CrossAccountId,316 new_owner: T::CrossAccountId,317 ) -> DispatchResult {318 self.check_is_owner(&caller)?;319 self.collection.owner = new_owner.as_sub().clone();320 self.save()321 }322}323324#[frame_support::pallet]325pub mod pallet {326 use super::*;327 use pallet_evm::account;328 use dispatch::CollectionDispatch;329 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};330 use frame_system::pallet_prelude::*;331 use frame_support::traits::Currency;332 use up_data_structs::{TokenId, mapping::TokenAddressMapping};333 use scale_info::TypeInfo;334 use weights::WeightInfo;335336 #[pallet::config]337 pub trait Config:338 frame_system::Config339 + pallet_evm_coder_substrate::Config340 + pallet_evm::Config341 + TypeInfo342 + account::Config343 {344 345 type WeightInfo: WeightInfo;346347 348 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;349350 351 type Currency: Currency<Self::AccountId>;352353 354 #[pallet::constant]355 type CollectionCreationPrice: Get<356 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,357 >;358359 360 type CollectionDispatch: CollectionDispatch<Self>;361362 363 type TreasuryAccountId: Get<Self::AccountId>;364365 366 type ContractAddress: Get<H160>;367368 369 type EvmTokenAddressMapping: TokenAddressMapping<H160>;370371 372 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;373 }374375 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);376377 #[pallet::pallet]378 #[pallet::storage_version(STORAGE_VERSION)]379 #[pallet::generate_store(pub(super) trait Store)]380 pub struct Pallet<T>(_);381382 #[pallet::extra_constants]383 impl<T: Config> Pallet<T> {384 385 pub fn collection_admins_limit() -> u32 {386 COLLECTION_ADMINS_LIMIT387 }388 }389390 #[pallet::event]391 #[pallet::generate_deposit(pub fn deposit_event)]392 pub enum Event<T: Config> {393 394 CollectionCreated(395 396 CollectionId,397 398 u8,399 400 T::AccountId,401 ),402403 404 CollectionDestroyed(405 406 CollectionId,407 ),408409 410 ItemCreated(411 412 CollectionId,413 414 TokenId,415 416 T::CrossAccountId,417 418 u128,419 ),420421 422 ItemDestroyed(423 424 CollectionId,425 426 TokenId,427 428 T::CrossAccountId,429 430 u128,431 ),432433 434 Transfer(435 436 CollectionId,437 438 TokenId,439 440 T::CrossAccountId,441 442 T::CrossAccountId,443 444 u128,445 ),446447 448 Approved(449 450 CollectionId,451 452 TokenId,453 454 T::CrossAccountId,455 456 T::CrossAccountId,457 458 u128,459 ),460461 462 CollectionPropertySet(463 464 CollectionId,465 466 PropertyKey,467 ),468469 470 CollectionPropertyDeleted(471 472 CollectionId,473 474 PropertyKey,475 ),476477 478 TokenPropertySet(479 480 CollectionId,481 482 TokenId,483 484 PropertyKey,485 ),486487 488 TokenPropertyDeleted(489 490 CollectionId,491 492 TokenId,493 494 PropertyKey,495 ),496497 498 PropertyPermissionSet(499 500 CollectionId,501 502 PropertyKey,503 ),504 }505506 #[pallet::error]507 pub enum Error<T> {508 509 CollectionNotFound,510 511 MustBeTokenOwner,512 513 NoPermission,514 515 CantDestroyNotEmptyCollection,516 517 PublicMintingNotAllowed,518 519 AddressNotInAllowlist,520521 522 CollectionNameLimitExceeded,523 524 CollectionDescriptionLimitExceeded,525 526 CollectionTokenPrefixLimitExceeded,527 528 TotalCollectionsLimitExceeded,529 530 CollectionAdminCountExceeded,531 532 CollectionLimitBoundsExceeded,533 534 OwnerPermissionsCantBeReverted,535 536 TransferNotAllowed,537 538 AccountTokenLimitExceeded,539 540 CollectionTokenLimitExceeded,541 542 MetadataFlagFrozen,543544 545 TokenNotFound,546 547 TokenValueTooLow,548 549 ApprovedValueTooLow,550 551 CantApproveMoreThanOwned,552553 554 AddressIsZero,555556 557 UnsupportedOperation,558559 560 NotSufficientFounds,561562 563 UserIsNotAllowedToNest,564 565 SourceCollectionIsNotAllowedToNest,566567 568 CollectionFieldSizeExceeded,569570 571 NoSpaceForProperty,572573 574 PropertyLimitReached,575576 577 PropertyKeyIsTooLong,578579 580 InvalidCharacterInPropertyKey,581582 583 EmptyPropertyKey,584585 586 CollectionIsExternal,587588 589 CollectionIsInternal,590 }591592 593 #[pallet::storage]594 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;595596 597 #[pallet::storage]598 pub type DestroyedCollectionCount<T> =599 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;600601 602 #[pallet::storage]603 pub type CollectionById<T> = StorageMap<604 Hasher = Blake2_128Concat,605 Key = CollectionId,606 Value = Collection<<T as frame_system::Config>::AccountId>,607 QueryKind = OptionQuery,608 >;609610 611 #[pallet::storage]612 #[pallet::getter(fn collection_properties)]613 pub type CollectionProperties<T> = StorageMap<614 Hasher = Blake2_128Concat,615 Key = CollectionId,616 Value = Properties,617 QueryKind = ValueQuery,618 OnEmpty = up_data_structs::CollectionProperties,619 >;620621 622 #[pallet::storage]623 #[pallet::getter(fn property_permissions)]624 pub type CollectionPropertyPermissions<T> = StorageMap<625 Hasher = Blake2_128Concat,626 Key = CollectionId,627 Value = PropertiesPermissionMap,628 QueryKind = ValueQuery,629 >;630631 632 #[pallet::storage]633 pub type AdminAmount<T> = StorageMap<634 Hasher = Blake2_128Concat,635 Key = CollectionId,636 Value = u32,637 QueryKind = ValueQuery,638 >;639640 641 #[pallet::storage]642 pub type IsAdmin<T: Config> = StorageNMap<643 Key = (644 Key<Blake2_128Concat, CollectionId>,645 Key<Blake2_128Concat, T::CrossAccountId>,646 ),647 Value = bool,648 QueryKind = ValueQuery,649 >;650651 652 #[pallet::storage]653 pub type Allowlist<T: Config> = StorageNMap<654 Key = (655 Key<Blake2_128Concat, CollectionId>,656 Key<Blake2_128Concat, T::CrossAccountId>,657 ),658 Value = bool,659 QueryKind = ValueQuery,660 >;661662 663 #[pallet::storage]664 pub type DummyStorageValue<T: Config> = StorageValue<665 Value = (666 CollectionStats,667 CollectionId,668 TokenId,669 TokenChild,670 PhantomType<(671 TokenData<T::CrossAccountId>,672 RpcCollection<T::AccountId>,673 674 RmrkCollectionInfo<T::AccountId>,675 RmrkInstanceInfo<T::AccountId>,676 RmrkResourceInfo,677 RmrkPropertyInfo,678 RmrkBaseInfo<T::AccountId>,679 RmrkPartType,680 RmrkBoundedTheme,681 RmrkNftChild,682 )>,683 ),684 QueryKind = OptionQuery,685 >;686687 #[pallet::hooks]688 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {689 fn on_runtime_upgrade() -> Weight {690 StorageVersion::new(1).put::<Pallet<T>>();691692 0693 }694 }695}696697impl<T: Config> Pallet<T> {698 699 700 701 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {702 ensure!(703 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,704 <Error<T>>::AddressIsZero705 );706 Ok(())707 }708709 710 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {711 <IsAdmin<T>>::iter_prefix((collection,))712 .map(|(a, _)| a)713 .collect()714 }715716 717 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {718 <Allowlist<T>>::iter_prefix((collection,))719 .map(|(a, _)| a)720 .collect()721 }722723 724 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {725 <Allowlist<T>>::get((collection, user))726 }727728 729 pub fn collection_stats() -> CollectionStats {730 let created = <CreatedCollectionCount<T>>::get();731 let destroyed = <DestroyedCollectionCount<T>>::get();732 CollectionStats {733 created: created.0,734 destroyed: destroyed.0,735 alive: created.0 - destroyed.0,736 }737 }738739 740 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {741 let collection = <CollectionById<T>>::get(collection)?;742 let limits = collection.limits;743 let effective_limits = CollectionLimits {744 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),745 sponsored_data_size: Some(limits.sponsored_data_size()),746 sponsored_data_rate_limit: Some(747 limits748 .sponsored_data_rate_limit749 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),750 ),751 token_limit: Some(limits.token_limit()),752 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(753 match collection.mode {754 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,755 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,756 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,757 },758 )),759 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),760 owner_can_transfer: Some(limits.owner_can_transfer()),761 owner_can_destroy: Some(limits.owner_can_destroy()),762 transfers_enabled: Some(limits.transfers_enabled()),763 };764765 Some(effective_limits)766 }767768 769 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {770 let Collection {771 name,772 description,773 owner,774 mode,775 token_prefix,776 sponsorship,777 limits,778 permissions,779 external_collection,780 } = <CollectionById<T>>::get(collection)?;781782 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)783 .into_iter()784 .map(|(key, permission)| PropertyKeyPermission { key, permission })785 .collect();786787 let properties = <CollectionProperties<T>>::get(collection)788 .into_iter()789 .map(|(key, value)| Property { key, value })790 .collect();791792 let permissions = CollectionPermissions {793 access: Some(permissions.access()),794 mint_mode: Some(permissions.mint_mode()),795 nesting: Some(permissions.nesting().clone()),796 };797798 Some(RpcCollection {799 name: name.into_inner(),800 description: description.into_inner(),801 owner,802 mode,803 token_prefix: token_prefix.into_inner(),804 sponsorship,805 limits,806 permissions,807 token_property_permissions,808 properties,809 read_only: external_collection,810 })811 }812}813814macro_rules! limit_default {815 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{816 $(817 if let Some($new) = $new.$field {818 let $old = $old.$field($($arg)?);819 let _ = $new;820 let _ = $old;821 $check822 } else {823 $new.$field = $old.$field824 }825 )*826 }};827}828macro_rules! limit_default_clone {829 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{830 $(831 if let Some($new) = $new.$field.clone() {832 let $old = $old.$field($($arg)?);833 let _ = $new;834 let _ = $old;835 $check836 } else {837 $new.$field = $old.$field.clone()838 }839 )*840 }};841}842843impl<T: Config> Pallet<T> {844 845 846 847 848 849 pub fn init_collection(850 owner: T::CrossAccountId,851 data: CreateCollectionData<T::AccountId>,852 is_external: bool,853 ) -> Result<CollectionId, DispatchError> {854 {855 ensure!(856 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,857 Error::<T>::CollectionTokenPrefixLimitExceeded858 );859 }860861 let created_count = <CreatedCollectionCount<T>>::get()862 .0863 .checked_add(1)864 .ok_or(ArithmeticError::Overflow)?;865 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;866 let id = CollectionId(created_count);867868 869 ensure!(870 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,871 <Error<T>>::TotalCollectionsLimitExceeded872 );873874 875876 let collection = Collection {877 owner: owner.as_sub().clone(),878 name: data.name,879 mode: data.mode.clone(),880 description: data.description,881 token_prefix: data.token_prefix,882 sponsorship: data883 .pending_sponsor884 .map(SponsorshipState::Unconfirmed)885 .unwrap_or_default(),886 limits: data887 .limits888 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))889 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,890 permissions: data891 .permissions892 .map(|permissions| {893 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)894 })895 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,896 external_collection: is_external,897 };898899 let mut collection_properties = up_data_structs::CollectionProperties::get();900 collection_properties901 .try_set_from_iter(data.properties.into_iter())902 .map_err(<Error<T>>::from)?;903904 CollectionProperties::<T>::insert(id, collection_properties);905906 let mut token_props_permissions = PropertiesPermissionMap::new();907 token_props_permissions908 .try_set_from_iter(data.token_property_permissions.into_iter())909 .map_err(<Error<T>>::from)?;910911 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);912913 914 {915 let mut imbalance =916 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();917 imbalance.subsume(918 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(919 &T::TreasuryAccountId::get(),920 T::CollectionCreationPrice::get(),921 ),922 );923 <T as Config>::Currency::settle(924 owner.as_sub(),925 imbalance,926 WithdrawReasons::TRANSFER,927 ExistenceRequirement::KeepAlive,928 )929 .map_err(|_| Error::<T>::NotSufficientFounds)?;930 }931932 <CreatedCollectionCount<T>>::put(created_count);933 <Pallet<T>>::deposit_event(Event::CollectionCreated(934 id,935 data.mode.id(),936 owner.as_sub().clone(),937 ));938 <PalletEvm<T>>::deposit_log(939 erc::CollectionHelpersEvents::CollectionCreated {940 owner: *owner.as_eth(),941 collection_id: eth::collection_id_to_address(id),942 }943 .to_log(T::ContractAddress::get()),944 );945 <CollectionById<T>>::insert(id, collection);946 Ok(id)947 }948949 950 951 952 953 pub fn destroy_collection(954 collection: CollectionHandle<T>,955 sender: &T::CrossAccountId,956 ) -> DispatchResult {957 ensure!(958 collection.limits.owner_can_destroy(),959 <Error<T>>::NoPermission,960 );961 collection.check_is_owner(sender)?;962963 let destroyed_collections = <DestroyedCollectionCount<T>>::get()964 .0965 .checked_add(1)966 .ok_or(ArithmeticError::Overflow)?;967968 969970 <DestroyedCollectionCount<T>>::put(destroyed_collections);971 <CollectionById<T>>::remove(collection.id);972 <AdminAmount<T>>::remove(collection.id);973 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);974 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);975 <CollectionProperties<T>>::remove(collection.id);976977 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));978 Ok(())979 }980981 982 983 984 985 986 pub fn set_collection_property(987 collection: &CollectionHandle<T>,988 sender: &T::CrossAccountId,989 property: Property,990 ) -> DispatchResult {991 collection.check_is_owner_or_admin(sender)?;992993 CollectionProperties::<T>::try_mutate(collection.id, |properties| {994 let property = property.clone();995 properties.try_set(property.key, property.value)996 })997 .map_err(<Error<T>>::from)?;998999 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10001001 Ok(())1002 }10031004 1005 1006 1007 1008 1009 pub fn set_scoped_collection_property(1010 collection_id: CollectionId,1011 scope: PropertyScope,1012 property: Property,1013 ) -> DispatchResult {1014 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1015 properties.try_scoped_set(scope, property.key, property.value)1016 })1017 .map_err(<Error<T>>::from)?;10181019 Ok(())1020 }10211022 1023 1024 1025 1026 1027 pub fn set_scoped_collection_properties(1028 collection_id: CollectionId,1029 scope: PropertyScope,1030 properties: impl Iterator<Item = Property>,1031 ) -> DispatchResult {1032 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1033 stored_properties.try_scoped_set_from_iter(scope, properties)1034 })1035 .map_err(<Error<T>>::from)?;10361037 Ok(())1038 }10391040 1041 1042 1043 1044 1045 #[transactional]1046 pub fn set_collection_properties(1047 collection: &CollectionHandle<T>,1048 sender: &T::CrossAccountId,1049 properties: Vec<Property>,1050 ) -> DispatchResult {1051 for property in properties {1052 Self::set_collection_property(collection, sender, property)?;1053 }10541055 Ok(())1056 }10571058 1059 1060 1061 1062 1063 pub fn delete_collection_property(1064 collection: &CollectionHandle<T>,1065 sender: &T::CrossAccountId,1066 property_key: PropertyKey,1067 ) -> DispatchResult {1068 collection.check_is_owner_or_admin(sender)?;10691070 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1071 properties.remove(&property_key)1072 })1073 .map_err(<Error<T>>::from)?;10741075 Self::deposit_event(Event::CollectionPropertyDeleted(1076 collection.id,1077 property_key,1078 ));10791080 Ok(())1081 }10821083 1084 1085 1086 1087 1088 #[transactional]1089 pub fn delete_collection_properties(1090 collection: &CollectionHandle<T>,1091 sender: &T::CrossAccountId,1092 property_keys: Vec<PropertyKey>,1093 ) -> DispatchResult {1094 for key in property_keys {1095 Self::delete_collection_property(collection, sender, key)?;1096 }10971098 Ok(())1099 }11001101 1102 1103 1104 1105 1106 1107 pub fn set_property_permission_unchecked(1108 collection: CollectionId,1109 property_permission: PropertyKeyPermission,1110 ) -> DispatchResult {1111 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1112 permissions.try_set(property_permission.key, property_permission.permission)1113 })1114 .map_err(<Error<T>>::from)?;1115 Ok(())1116 }11171118 1119 1120 1121 1122 1123 pub fn set_property_permission(1124 collection: &CollectionHandle<T>,1125 sender: &T::CrossAccountId,1126 property_permission: PropertyKeyPermission,1127 ) -> DispatchResult {1128 Self::set_scoped_property_permission(1129 collection,1130 sender,1131 PropertyScope::None,1132 property_permission,1133 )1134 }11351136 1137 1138 1139 1140 1141 1142 pub fn set_scoped_property_permission(1143 collection: &CollectionHandle<T>,1144 sender: &T::CrossAccountId,1145 scope: PropertyScope,1146 property_permission: PropertyKeyPermission,1147 ) -> DispatchResult {1148 collection.check_is_owner_or_admin(sender)?;11491150 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1151 let current_permission = all_permissions.get(&property_permission.key);1152 if matches![1153 current_permission,1154 Some(PropertyPermission { mutable: false, .. })1155 ] {1156 return Err(<Error<T>>::NoPermission.into());1157 }11581159 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1160 let property_permission = property_permission.clone();1161 permissions.try_scoped_set(1162 scope,1163 property_permission.key,1164 property_permission.permission,1165 )1166 })1167 .map_err(<Error<T>>::from)?;11681169 Self::deposit_event(Event::PropertyPermissionSet(1170 collection.id,1171 property_permission.key,1172 ));11731174 Ok(())1175 }11761177 1178 1179 1180 1181 1182 #[transactional]1183 pub fn set_token_property_permissions(1184 collection: &CollectionHandle<T>,1185 sender: &T::CrossAccountId,1186 property_permissions: Vec<PropertyKeyPermission>,1187 ) -> DispatchResult {1188 Self::set_scoped_token_property_permissions(1189 collection,1190 sender,1191 PropertyScope::None,1192 property_permissions,1193 )1194 }11951196 1197 1198 1199 1200 1201 1202 #[transactional]1203 pub fn set_scoped_token_property_permissions(1204 collection: &CollectionHandle<T>,1205 sender: &T::CrossAccountId,1206 scope: PropertyScope,1207 property_permissions: Vec<PropertyKeyPermission>,1208 ) -> DispatchResult {1209 for prop_pemission in property_permissions {1210 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1211 }12121213 Ok(())1214 }12151216 1217 pub fn get_collection_property(1218 collection_id: CollectionId,1219 key: &PropertyKey,1220 ) -> Option<PropertyValue> {1221 Self::collection_properties(collection_id).get(key).cloned()1222 }12231224 1225 pub fn bytes_keys_to_property_keys(1226 keys: Vec<Vec<u8>>,1227 ) -> Result<Vec<PropertyKey>, DispatchError> {1228 keys.into_iter()1229 .map(|key| -> Result<PropertyKey, DispatchError> {1230 key.try_into()1231 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1232 })1233 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1234 }12351236 1237 pub fn filter_collection_properties(1238 collection_id: CollectionId,1239 keys: Option<Vec<PropertyKey>>,1240 ) -> Result<Vec<Property>, DispatchError> {1241 let properties = Self::collection_properties(collection_id);12421243 let properties = keys1244 .map(|keys| {1245 keys.into_iter()1246 .filter_map(|key| {1247 properties.get(&key).map(|value| Property {1248 key,1249 value: value.clone(),1250 })1251 })1252 .collect()1253 })1254 .unwrap_or_else(|| {1255 properties1256 .into_iter()1257 .map(|(key, value)| Property { key, value })1258 .collect()1259 });12601261 Ok(properties)1262 }12631264 1265 pub fn filter_property_permissions(1266 collection_id: CollectionId,1267 keys: Option<Vec<PropertyKey>>,1268 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1269 let permissions = Self::property_permissions(collection_id);12701271 let key_permissions = keys1272 .map(|keys| {1273 keys.into_iter()1274 .filter_map(|key| {1275 permissions1276 .get(&key)1277 .map(|permission| PropertyKeyPermission {1278 key,1279 permission: permission.clone(),1280 })1281 })1282 .collect()1283 })1284 .unwrap_or_else(|| {1285 permissions1286 .into_iter()1287 .map(|(key, permission)| PropertyKeyPermission { key, permission })1288 .collect()1289 });12901291 Ok(key_permissions)1292 }12931294 1295 pub fn toggle_allowlist(1296 collection: &CollectionHandle<T>,1297 sender: &T::CrossAccountId,1298 user: &T::CrossAccountId,1299 allowed: bool,1300 ) -> DispatchResult {1301 collection.check_is_owner_or_admin(sender)?;13021303 13041305 if allowed {1306 <Allowlist<T>>::insert((collection.id, user), true);1307 } else {1308 <Allowlist<T>>::remove((collection.id, user));1309 }13101311 Ok(())1312 }13131314 1315 pub fn toggle_admin(1316 collection: &CollectionHandle<T>,1317 sender: &T::CrossAccountId,1318 user: &T::CrossAccountId,1319 admin: bool,1320 ) -> DispatchResult {1321 collection.check_is_owner(sender)?;13221323 let was_admin = <IsAdmin<T>>::get((collection.id, user));1324 if was_admin == admin {1325 return Ok(());1326 }1327 let amount = <AdminAmount<T>>::get(collection.id);13281329 if admin {1330 let amount = amount1331 .checked_add(1)1332 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1333 ensure!(1334 amount <= Self::collection_admins_limit(),1335 <Error<T>>::CollectionAdminCountExceeded,1336 );13371338 13391340 <AdminAmount<T>>::insert(collection.id, amount);1341 <IsAdmin<T>>::insert((collection.id, user), true);1342 } else {1343 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1344 <IsAdmin<T>>::remove((collection.id, user));1345 }13461347 Ok(())1348 }13491350 1351 pub fn clamp_limits(1352 mode: CollectionMode,1353 old_limit: &CollectionLimits,1354 mut new_limit: CollectionLimits,1355 ) -> Result<CollectionLimits, DispatchError> {1356 let limits = old_limit;1357 limit_default!(old_limit, new_limit,1358 account_token_ownership_limit => ensure!(1359 new_limit <= MAX_TOKEN_OWNERSHIP,1360 <Error<T>>::CollectionLimitBoundsExceeded,1361 ),1362 sponsored_data_size => ensure!(1363 new_limit <= CUSTOM_DATA_LIMIT,1364 <Error<T>>::CollectionLimitBoundsExceeded,1365 ),13661367 sponsored_data_rate_limit => {},1368 token_limit => ensure!(1369 old_limit >= new_limit && new_limit > 0,1370 <Error<T>>::CollectionTokenLimitExceeded1371 ),13721373 sponsor_transfer_timeout(match mode {1374 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1375 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1376 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1377 }) => ensure!(1378 new_limit <= MAX_SPONSOR_TIMEOUT,1379 <Error<T>>::CollectionLimitBoundsExceeded,1380 ),1381 sponsor_approve_timeout => {},1382 owner_can_transfer => ensure!(1383 !limits.owner_can_transfer_instaled() ||1384 old_limit || !new_limit,1385 <Error<T>>::OwnerPermissionsCantBeReverted,1386 ),1387 owner_can_destroy => ensure!(1388 old_limit || !new_limit,1389 <Error<T>>::OwnerPermissionsCantBeReverted,1390 ),1391 transfers_enabled => {},1392 );1393 Ok(new_limit)1394 }13951396 1397 pub fn clamp_permissions(1398 _mode: CollectionMode,1399 old_permission: &CollectionPermissions,1400 mut new_permission: CollectionPermissions,1401 ) -> Result<CollectionPermissions, DispatchError> {1402 limit_default_clone!(old_permission, new_permission,1403 access => {},1404 mint_mode => {},1405 nesting => { },1406 );1407 Ok(new_permission)1408 }1409}141014111412#[macro_export]1413macro_rules! unsupported {1414 ($runtime:path) => {1415 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1416 };1417}141814191420pub trait CommonWeightInfo<CrossAccountId> {1421 1422 fn create_item() -> Weight;14231424 1425 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14261427 1428 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14291430 1431 fn burn_item() -> Weight;14321433 1434 1435 1436 fn set_collection_properties(amount: u32) -> Weight;14371438 1439 1440 1441 fn delete_collection_properties(amount: u32) -> Weight;14421443 1444 1445 1446 fn set_token_properties(amount: u32) -> Weight;14471448 1449 1450 1451 fn delete_token_properties(amount: u32) -> Weight;14521453 1454 1455 1456 fn set_token_property_permissions(amount: u32) -> Weight;14571458 1459 fn transfer() -> Weight;14601461 1462 fn approve() -> Weight;14631464 1465 fn transfer_from() -> Weight;14661467 1468 fn burn_from() -> Weight;14691470 1471 1472 1473 1474 fn burn_recursively_self_raw() -> Weight;14751476 1477 1478 1479 fn burn_recursively_breadth_raw(amount: u32) -> Weight;14801481 1482 1483 1484 1485 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1486 Self::burn_recursively_self_raw()1487 .saturating_mul(max_selfs.max(1) as u64)1488 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1489 }14901491 1492 fn token_owner() -> Weight;1493}149414951496pub trait RefungibleExtensionsWeightInfo {1497 1498 fn repartition() -> Weight;1499}150015011502150315041505pub trait CommonCollectionOperations<T: Config> {1506 1507 1508 1509 1510 1511 1512 fn create_item(1513 &self,1514 sender: T::CrossAccountId,1515 to: T::CrossAccountId,1516 data: CreateItemData,1517 nesting_budget: &dyn Budget,1518 ) -> DispatchResultWithPostInfo;15191520 1521 1522 1523 1524 1525 1526 fn create_multiple_items(1527 &self,1528 sender: T::CrossAccountId,1529 to: T::CrossAccountId,1530 data: Vec<CreateItemData>,1531 nesting_budget: &dyn Budget,1532 ) -> DispatchResultWithPostInfo;15331534 1535 1536 1537 1538 1539 1540 fn create_multiple_items_ex(1541 &self,1542 sender: T::CrossAccountId,1543 data: CreateItemExData<T::CrossAccountId>,1544 nesting_budget: &dyn Budget,1545 ) -> DispatchResultWithPostInfo;15461547 1548 1549 1550 1551 1552 fn burn_item(1553 &self,1554 sender: T::CrossAccountId,1555 token: TokenId,1556 amount: u128,1557 ) -> DispatchResultWithPostInfo;15581559 1560 1561 1562 1563 1564 1565 fn burn_item_recursively(1566 &self,1567 sender: T::CrossAccountId,1568 token: TokenId,1569 self_budget: &dyn Budget,1570 breadth_budget: &dyn Budget,1571 ) -> DispatchResultWithPostInfo;15721573 1574 1575 1576 1577 fn set_collection_properties(1578 &self,1579 sender: T::CrossAccountId,1580 properties: Vec<Property>,1581 ) -> DispatchResultWithPostInfo;15821583 1584 1585 1586 1587 fn delete_collection_properties(1588 &self,1589 sender: &T::CrossAccountId,1590 property_keys: Vec<PropertyKey>,1591 ) -> DispatchResultWithPostInfo;15921593 1594 1595 1596 1597 1598 1599 1600 1601 1602 fn set_token_properties(1603 &self,1604 sender: T::CrossAccountId,1605 token_id: TokenId,1606 properties: Vec<Property>,1607 budget: &dyn Budget,1608 ) -> DispatchResultWithPostInfo;16091610 1611 1612 1613 1614 1615 1616 1617 1618 1619 fn delete_token_properties(1620 &self,1621 sender: T::CrossAccountId,1622 token_id: TokenId,1623 property_keys: Vec<PropertyKey>,1624 budget: &dyn Budget,1625 ) -> DispatchResultWithPostInfo;16261627 1628 1629 1630 1631 1632 1633 fn set_token_property_permissions(1634 &self,1635 sender: &T::CrossAccountId,1636 property_permissions: Vec<PropertyKeyPermission>,1637 ) -> DispatchResultWithPostInfo;16381639 1640 1641 1642 1643 1644 1645 1646 fn transfer(1647 &self,1648 sender: T::CrossAccountId,1649 to: T::CrossAccountId,1650 token: TokenId,1651 amount: u128,1652 budget: &dyn Budget,1653 ) -> DispatchResultWithPostInfo;16541655 1656 1657 1658 1659 1660 1661 fn approve(1662 &self,1663 sender: T::CrossAccountId,1664 spender: T::CrossAccountId,1665 token: TokenId,1666 amount: u128,1667 ) -> DispatchResultWithPostInfo;16681669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 fn transfer_from(1680 &self,1681 sender: T::CrossAccountId,1682 from: T::CrossAccountId,1683 to: T::CrossAccountId,1684 token: TokenId,1685 amount: u128,1686 budget: &dyn Budget,1687 ) -> DispatchResultWithPostInfo;16881689 1690 1691 1692 1693 1694 1695 1696 1697 1698 fn burn_from(1699 &self,1700 sender: T::CrossAccountId,1701 from: T::CrossAccountId,1702 token: TokenId,1703 amount: u128,1704 budget: &dyn Budget,1705 ) -> DispatchResultWithPostInfo;17061707 1708 1709 1710 1711 1712 1713 fn check_nesting(1714 &self,1715 sender: T::CrossAccountId,1716 from: (CollectionId, TokenId),1717 under: TokenId,1718 budget: &dyn Budget,1719 ) -> DispatchResult;17201721 1722 1723 1724 1725 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17261727 1728 1729 1730 1731 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17321733 1734 1735 1736 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17371738 1739 fn collection_tokens(&self) -> Vec<TokenId>;17401741 1742 1743 1744 fn token_exists(&self, token: TokenId) -> bool;17451746 1747 fn last_token_id(&self) -> TokenId;17481749 1750 1751 1752 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17531754 1755 1756 1757 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17581759 1760 1761 1762 1763 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17641765 1766 1767 1768 1769 1770 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17711772 1773 fn total_supply(&self) -> u32;17741775 1776 1777 1778 fn account_balance(&self, account: T::CrossAccountId) -> u32;17791780 1781 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;17821783 1784 fn total_pieces(&self, token: TokenId) -> Option<u128>;17851786 1787 1788 1789 1790 1791 fn allowance(1792 &self,1793 sender: T::CrossAccountId,1794 spender: T::CrossAccountId,1795 token: TokenId,1796 ) -> u128;17971798 1799 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1800}180118021803pub trait RefungibleExtensions<T>1804where1805 T: Config,1806{1807 1808 1809 1810 1811 1812 1813 1814 fn repartition(1815 &self,1816 sender: &T::CrossAccountId,1817 token: TokenId,1818 amount: u128,1819 ) -> DispatchResultWithPostInfo;1820}18211822182318241825pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1826 let post_info = PostDispatchInfo {1827 actual_weight: Some(weight),1828 pays_fee: Pays::Yes,1829 };1830 match res {1831 Ok(()) => Ok(post_info),1832 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1833 }1834}18351836impl<T: Config> From<PropertiesError> for Error<T> {1837 fn from(error: PropertiesError) -> Self {1838 match error {1839 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1840 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1841 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1842 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1843 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1844 }1845 }1846}