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 CollectionFlags,74 CollectionId,75 CreateItemData,76 MAX_TOKEN_PREFIX_LENGTH,77 COLLECTION_ADMINS_LIMIT,78 TokenId,79 TokenChild,80 CollectionStats,81 MAX_TOKEN_OWNERSHIP,82 CollectionMode,83 NFT_SPONSOR_TRANSFER_TIMEOUT,84 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,85 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,86 MAX_SPONSOR_TIMEOUT,87 CUSTOM_DATA_LIMIT,88 CollectionLimits,89 CreateCollectionData,90 SponsorshipState,91 CreateItemExData,92 SponsoringRateLimit,93 budget::Budget,94 PhantomType,95 Property,96 Properties,97 PropertiesPermissionMap,98 PropertyKey,99 PropertyValue,100 PropertyPermission,101 PropertiesError,102 PropertyKeyPermission,103 TokenData,104 TrySetProperty,105 PropertyScope,106 107 RmrkCollectionInfo,108 RmrkInstanceInfo,109 RmrkResourceInfo,110 RmrkPropertyInfo,111 RmrkBaseInfo,112 RmrkPartType,113 RmrkBoundedTheme,114 RmrkNftChild,115 CollectionPermissions,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 consume_store_reads_and_writes(207 &self,208 reads: u64,209 writes: u64,210 ) -> evm_coder::execution::Result<()> {211 let weight = <T as frame_system::Config>::DbWeight::get();212 let reads = weight.read.saturating_mul(reads);213 let writes = weight.read.saturating_mul(writes);214 self.recorder215 .consume_gas(T::GasWeightMapping::weight_to_gas(216 reads.saturating_add(writes),217 ))218 }219220 221 pub fn save(&self) -> DispatchResult {222 <CollectionById<T>>::insert(self.id, &self.collection);223 Ok(())224 }225226 227 228 229 230 231 pub fn set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {232 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor);233 Ok(())234 }235236 237 238 239 240 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> Result<bool, DispatchError> {241 if self.collection.sponsorship.pending_sponsor() != Some(sender) {242 return Ok(false);243 }244245 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());246 Ok(true)247 }248249 250 pub fn remove_sponsor(&mut self) -> DispatchResult {251 self.collection.sponsorship = SponsorshipState::Disabled;252 Ok(())253 }254255 256 257 pub fn check_is_internal(&self) -> DispatchResult {258 if self.flags.external {259 return Err(<Error<T>>::CollectionIsExternal)?;260 }261262 Ok(())263 }264265 266 267 pub fn check_is_external(&self) -> DispatchResult {268 if !self.flags.external {269 return Err(<Error<T>>::CollectionIsInternal)?;270 }271272 Ok(())273 }274}275276impl<T: Config> Deref for CollectionHandle<T> {277 type Target = Collection<T::AccountId>;278279 fn deref(&self) -> &Self::Target {280 &self.collection281 }282}283284impl<T: Config> DerefMut for CollectionHandle<T> {285 fn deref_mut(&mut self) -> &mut Self::Target {286 &mut self.collection287 }288}289290impl<T: Config> CollectionHandle<T> {291 292 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {293 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);294 Ok(())295 }296297 298 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {299 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))300 }301302 303 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {304 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);305 Ok(())306 }307308 309 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {310 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)311 }312313 314 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {315 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)316 }317318 319 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {320 ensure!(321 <Allowlist<T>>::get((self.id, user)),322 <Error<T>>::AddressNotInAllowlist323 );324 Ok(())325 }326327 328 329 330 fn set_owner_internal(331 &mut self,332 caller: T::CrossAccountId,333 new_owner: T::CrossAccountId,334 ) -> DispatchResult {335 self.check_is_owner(&caller)?;336 self.collection.owner = new_owner.as_sub().clone();337 self.save()338 }339}340341#[frame_support::pallet]342pub mod pallet {343 use super::*;344 use pallet_evm::account;345 use dispatch::CollectionDispatch;346 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};347 use frame_system::pallet_prelude::*;348 use frame_support::traits::Currency;349 use up_data_structs::{TokenId, mapping::TokenAddressMapping};350 use scale_info::TypeInfo;351 use weights::WeightInfo;352353 #[pallet::config]354 pub trait Config:355 frame_system::Config356 + pallet_evm_coder_substrate::Config357 + pallet_evm::Config358 + TypeInfo359 + account::Config360 {361 362 type WeightInfo: WeightInfo;363364 365 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;366367 368 type Currency: Currency<Self::AccountId>;369370 371 #[pallet::constant]372 type CollectionCreationPrice: Get<373 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,374 >;375376 377 type CollectionDispatch: CollectionDispatch<Self>;378379 380 type TreasuryAccountId: Get<Self::AccountId>;381382 383 type ContractAddress: Get<H160>;384385 386 type EvmTokenAddressMapping: TokenAddressMapping<H160>;387388 389 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;390 }391392 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);393394 #[pallet::pallet]395 #[pallet::storage_version(STORAGE_VERSION)]396 #[pallet::generate_store(pub(super) trait Store)]397 pub struct Pallet<T>(_);398399 #[pallet::extra_constants]400 impl<T: Config> Pallet<T> {401 402 pub fn collection_admins_limit() -> u32 {403 COLLECTION_ADMINS_LIMIT404 }405 }406407 #[pallet::event]408 #[pallet::generate_deposit(pub fn deposit_event)]409 pub enum Event<T: Config> {410 411 CollectionCreated(412 413 CollectionId,414 415 u8,416 417 T::AccountId,418 ),419420 421 CollectionDestroyed(422 423 CollectionId,424 ),425426 427 ItemCreated(428 429 CollectionId,430 431 TokenId,432 433 T::CrossAccountId,434 435 u128,436 ),437438 439 ItemDestroyed(440 441 CollectionId,442 443 TokenId,444 445 T::CrossAccountId,446 447 u128,448 ),449450 451 Transfer(452 453 CollectionId,454 455 TokenId,456 457 T::CrossAccountId,458 459 T::CrossAccountId,460 461 u128,462 ),463464 465 Approved(466 467 CollectionId,468 469 TokenId,470 471 T::CrossAccountId,472 473 T::CrossAccountId,474 475 u128,476 ),477478 479 CollectionPropertySet(480 481 CollectionId,482 483 PropertyKey,484 ),485486 487 CollectionPropertyDeleted(488 489 CollectionId,490 491 PropertyKey,492 ),493494 495 TokenPropertySet(496 497 CollectionId,498 499 TokenId,500 501 PropertyKey,502 ),503504 505 TokenPropertyDeleted(506 507 CollectionId,508 509 TokenId,510 511 PropertyKey,512 ),513514 515 PropertyPermissionSet(516 517 CollectionId,518 519 PropertyKey,520 ),521 }522523 #[pallet::error]524 pub enum Error<T> {525 526 CollectionNotFound,527 528 MustBeTokenOwner,529 530 NoPermission,531 532 CantDestroyNotEmptyCollection,533 534 PublicMintingNotAllowed,535 536 AddressNotInAllowlist,537538 539 CollectionNameLimitExceeded,540 541 CollectionDescriptionLimitExceeded,542 543 CollectionTokenPrefixLimitExceeded,544 545 TotalCollectionsLimitExceeded,546 547 CollectionAdminCountExceeded,548 549 CollectionLimitBoundsExceeded,550 551 OwnerPermissionsCantBeReverted,552 553 TransferNotAllowed,554 555 AccountTokenLimitExceeded,556 557 CollectionTokenLimitExceeded,558 559 MetadataFlagFrozen,560561 562 TokenNotFound,563 564 TokenValueTooLow,565 566 ApprovedValueTooLow,567 568 CantApproveMoreThanOwned,569570 571 AddressIsZero,572573 574 UnsupportedOperation,575576 577 NotSufficientFounds,578579 580 UserIsNotAllowedToNest,581 582 SourceCollectionIsNotAllowedToNest,583584 585 CollectionFieldSizeExceeded,586587 588 NoSpaceForProperty,589590 591 PropertyLimitReached,592593 594 PropertyKeyIsTooLong,595596 597 InvalidCharacterInPropertyKey,598599 600 EmptyPropertyKey,601602 603 CollectionIsExternal,604605 606 CollectionIsInternal,607 }608609 610 #[pallet::storage]611 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;612613 614 #[pallet::storage]615 pub type DestroyedCollectionCount<T> =616 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;617618 619 #[pallet::storage]620 pub type CollectionById<T> = StorageMap<621 Hasher = Blake2_128Concat,622 Key = CollectionId,623 Value = Collection<<T as frame_system::Config>::AccountId>,624 QueryKind = OptionQuery,625 >;626627 628 #[pallet::storage]629 #[pallet::getter(fn collection_properties)]630 pub type CollectionProperties<T> = StorageMap<631 Hasher = Blake2_128Concat,632 Key = CollectionId,633 Value = Properties,634 QueryKind = ValueQuery,635 OnEmpty = up_data_structs::CollectionProperties,636 >;637638 639 #[pallet::storage]640 #[pallet::getter(fn property_permissions)]641 pub type CollectionPropertyPermissions<T> = StorageMap<642 Hasher = Blake2_128Concat,643 Key = CollectionId,644 Value = PropertiesPermissionMap,645 QueryKind = ValueQuery,646 >;647648 649 #[pallet::storage]650 pub type AdminAmount<T> = StorageMap<651 Hasher = Blake2_128Concat,652 Key = CollectionId,653 Value = u32,654 QueryKind = ValueQuery,655 >;656657 658 #[pallet::storage]659 pub type IsAdmin<T: Config> = StorageNMap<660 Key = (661 Key<Blake2_128Concat, CollectionId>,662 Key<Blake2_128Concat, T::CrossAccountId>,663 ),664 Value = bool,665 QueryKind = ValueQuery,666 >;667668 669 #[pallet::storage]670 pub type Allowlist<T: Config> = StorageNMap<671 Key = (672 Key<Blake2_128Concat, CollectionId>,673 Key<Blake2_128Concat, T::CrossAccountId>,674 ),675 Value = bool,676 QueryKind = ValueQuery,677 >;678679 680 #[pallet::storage]681 pub type DummyStorageValue<T: Config> = StorageValue<682 Value = (683 CollectionStats,684 CollectionId,685 TokenId,686 TokenChild,687 PhantomType<(688 TokenData<T::CrossAccountId>,689 RpcCollection<T::AccountId>,690 691 RmrkCollectionInfo<T::AccountId>,692 RmrkInstanceInfo<T::AccountId>,693 RmrkResourceInfo,694 RmrkPropertyInfo,695 RmrkBaseInfo<T::AccountId>,696 RmrkPartType,697 RmrkBoundedTheme,698 RmrkNftChild,699 )>,700 ),701 QueryKind = OptionQuery,702 >;703704 #[pallet::hooks]705 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {706 fn on_runtime_upgrade() -> Weight {707 StorageVersion::new(1).put::<Pallet<T>>();708709 0710 }711 }712}713714impl<T: Config> Pallet<T> {715 716 717 718 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {719 ensure!(720 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,721 <Error<T>>::AddressIsZero722 );723 Ok(())724 }725726 727 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {728 <IsAdmin<T>>::iter_prefix((collection,))729 .map(|(a, _)| a)730 .collect()731 }732733 734 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {735 <Allowlist<T>>::iter_prefix((collection,))736 .map(|(a, _)| a)737 .collect()738 }739740 741 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {742 <Allowlist<T>>::get((collection, user))743 }744745 746 pub fn collection_stats() -> CollectionStats {747 let created = <CreatedCollectionCount<T>>::get();748 let destroyed = <DestroyedCollectionCount<T>>::get();749 CollectionStats {750 created: created.0,751 destroyed: destroyed.0,752 alive: created.0 - destroyed.0,753 }754 }755756 757 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {758 let collection = <CollectionById<T>>::get(collection)?;759 let limits = collection.limits;760 let effective_limits = CollectionLimits {761 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),762 sponsored_data_size: Some(limits.sponsored_data_size()),763 sponsored_data_rate_limit: Some(764 limits765 .sponsored_data_rate_limit766 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),767 ),768 token_limit: Some(limits.token_limit()),769 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(770 match collection.mode {771 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,772 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,773 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,774 },775 )),776 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),777 owner_can_transfer: Some(limits.owner_can_transfer()),778 owner_can_destroy: Some(limits.owner_can_destroy()),779 transfers_enabled: Some(limits.transfers_enabled()),780 };781782 Some(effective_limits)783 }784785 786 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {787 let Collection {788 name,789 description,790 owner,791 mode,792 token_prefix,793 sponsorship,794 limits,795 permissions,796 flags,797 } = <CollectionById<T>>::get(collection)?;798799 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)800 .into_iter()801 .map(|(key, permission)| PropertyKeyPermission { key, permission })802 .collect();803804 let properties = <CollectionProperties<T>>::get(collection)805 .into_iter()806 .map(|(key, value)| Property { key, value })807 .collect();808809 let permissions = CollectionPermissions {810 access: Some(permissions.access()),811 mint_mode: Some(permissions.mint_mode()),812 nesting: Some(permissions.nesting().clone()),813 };814815 Some(RpcCollection {816 name: name.into_inner(),817 description: description.into_inner(),818 owner,819 mode,820 token_prefix: token_prefix.into_inner(),821 sponsorship,822 limits,823 permissions,824 token_property_permissions,825 properties,826 read_only: flags.external,827 foreign: flags.foreign,828 })829 }830}831832macro_rules! limit_default {833 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{834 $(835 if let Some($new) = $new.$field {836 let $old = $old.$field($($arg)?);837 let _ = $new;838 let _ = $old;839 $check840 } else {841 $new.$field = $old.$field842 }843 )*844 }};845}846macro_rules! limit_default_clone {847 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{848 $(849 if let Some($new) = $new.$field.clone() {850 let $old = $old.$field($($arg)?);851 let _ = $new;852 let _ = $old;853 $check854 } else {855 $new.$field = $old.$field.clone()856 }857 )*858 }};859}860861impl<T: Config> Pallet<T> {862 863 864 865 866 867 pub fn init_collection(868 owner: T::CrossAccountId,869 data: CreateCollectionData<T::AccountId>,870 flags: CollectionFlags,871 ) -> Result<CollectionId, DispatchError> {872 {873 ensure!(874 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,875 Error::<T>::CollectionTokenPrefixLimitExceeded876 );877 }878879 let created_count = <CreatedCollectionCount<T>>::get()880 .0881 .checked_add(1)882 .ok_or(ArithmeticError::Overflow)?;883 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;884 let id = CollectionId(created_count);885886 887 ensure!(888 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,889 <Error<T>>::TotalCollectionsLimitExceeded890 );891892 893894 let collection = Collection {895 owner: owner.as_sub().clone(),896 name: data.name,897 mode: data.mode.clone(),898 description: data.description,899 token_prefix: data.token_prefix,900 sponsorship: data901 .pending_sponsor902 .map(SponsorshipState::Unconfirmed)903 .unwrap_or_default(),904 limits: data905 .limits906 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))907 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,908 permissions: data909 .permissions910 .map(|permissions| {911 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)912 })913 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,914 flags,915 };916917 let mut collection_properties = up_data_structs::CollectionProperties::get();918 collection_properties919 .try_set_from_iter(data.properties.into_iter())920 .map_err(<Error<T>>::from)?;921922 CollectionProperties::<T>::insert(id, collection_properties);923924 let mut token_props_permissions = PropertiesPermissionMap::new();925 token_props_permissions926 .try_set_from_iter(data.token_property_permissions.into_iter())927 .map_err(<Error<T>>::from)?;928929 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);930931 932 {933 let mut imbalance =934 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();935 imbalance.subsume(936 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(937 &T::TreasuryAccountId::get(),938 T::CollectionCreationPrice::get(),939 ),940 );941 <T as Config>::Currency::settle(942 owner.as_sub(),943 imbalance,944 WithdrawReasons::TRANSFER,945 ExistenceRequirement::KeepAlive,946 )947 .map_err(|_| Error::<T>::NotSufficientFounds)?;948 }949950 <CreatedCollectionCount<T>>::put(created_count);951 <Pallet<T>>::deposit_event(Event::CollectionCreated(952 id,953 data.mode.id(),954 owner.as_sub().clone(),955 ));956 <PalletEvm<T>>::deposit_log(957 erc::CollectionHelpersEvents::CollectionCreated {958 owner: *owner.as_eth(),959 collection_id: eth::collection_id_to_address(id),960 }961 .to_log(T::ContractAddress::get()),962 );963 <CollectionById<T>>::insert(id, collection);964 Ok(id)965 }966967 968 969 970 971 pub fn destroy_collection(972 collection: CollectionHandle<T>,973 sender: &T::CrossAccountId,974 ) -> DispatchResult {975 ensure!(976 collection.limits.owner_can_destroy(),977 <Error<T>>::NoPermission,978 );979 collection.check_is_owner(sender)?;980981 let destroyed_collections = <DestroyedCollectionCount<T>>::get()982 .0983 .checked_add(1)984 .ok_or(ArithmeticError::Overflow)?;985986 987988 <DestroyedCollectionCount<T>>::put(destroyed_collections);989 <CollectionById<T>>::remove(collection.id);990 <AdminAmount<T>>::remove(collection.id);991 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);992 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);993 <CollectionProperties<T>>::remove(collection.id);994995 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));996 Ok(())997 }998999 1000 1001 1002 1003 1004 pub fn set_collection_property(1005 collection: &CollectionHandle<T>,1006 sender: &T::CrossAccountId,1007 property: Property,1008 ) -> DispatchResult {1009 collection.check_is_owner_or_admin(sender)?;10101011 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1012 let property = property.clone();1013 properties.try_set(property.key, property.value)1014 })1015 .map_err(<Error<T>>::from)?;10161017 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));10181019 Ok(())1020 }10211022 1023 1024 1025 1026 1027 pub fn set_scoped_collection_property(1028 collection_id: CollectionId,1029 scope: PropertyScope,1030 property: Property,1031 ) -> DispatchResult {1032 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1033 properties.try_scoped_set(scope, property.key, property.value)1034 })1035 .map_err(<Error<T>>::from)?;10361037 Ok(())1038 }10391040 1041 1042 1043 1044 1045 pub fn set_scoped_collection_properties(1046 collection_id: CollectionId,1047 scope: PropertyScope,1048 properties: impl Iterator<Item = Property>,1049 ) -> DispatchResult {1050 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1051 stored_properties.try_scoped_set_from_iter(scope, properties)1052 })1053 .map_err(<Error<T>>::from)?;10541055 Ok(())1056 }10571058 1059 1060 1061 1062 1063 #[transactional]1064 pub fn set_collection_properties(1065 collection: &CollectionHandle<T>,1066 sender: &T::CrossAccountId,1067 properties: Vec<Property>,1068 ) -> DispatchResult {1069 for property in properties {1070 Self::set_collection_property(collection, sender, property)?;1071 }10721073 Ok(())1074 }10751076 1077 1078 1079 1080 1081 pub fn delete_collection_property(1082 collection: &CollectionHandle<T>,1083 sender: &T::CrossAccountId,1084 property_key: PropertyKey,1085 ) -> DispatchResult {1086 collection.check_is_owner_or_admin(sender)?;10871088 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1089 properties.remove(&property_key)1090 })1091 .map_err(<Error<T>>::from)?;10921093 Self::deposit_event(Event::CollectionPropertyDeleted(1094 collection.id,1095 property_key,1096 ));10971098 Ok(())1099 }11001101 1102 1103 1104 1105 1106 #[transactional]1107 pub fn delete_collection_properties(1108 collection: &CollectionHandle<T>,1109 sender: &T::CrossAccountId,1110 property_keys: Vec<PropertyKey>,1111 ) -> DispatchResult {1112 for key in property_keys {1113 Self::delete_collection_property(collection, sender, key)?;1114 }11151116 Ok(())1117 }11181119 1120 1121 1122 1123 1124 1125 pub fn set_property_permission_unchecked(1126 collection: CollectionId,1127 property_permission: PropertyKeyPermission,1128 ) -> DispatchResult {1129 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1130 permissions.try_set(property_permission.key, property_permission.permission)1131 })1132 .map_err(<Error<T>>::from)?;1133 Ok(())1134 }11351136 1137 1138 1139 1140 1141 pub fn set_property_permission(1142 collection: &CollectionHandle<T>,1143 sender: &T::CrossAccountId,1144 property_permission: PropertyKeyPermission,1145 ) -> DispatchResult {1146 Self::set_scoped_property_permission(1147 collection,1148 sender,1149 PropertyScope::None,1150 property_permission,1151 )1152 }11531154 1155 1156 1157 1158 1159 1160 pub fn set_scoped_property_permission(1161 collection: &CollectionHandle<T>,1162 sender: &T::CrossAccountId,1163 scope: PropertyScope,1164 property_permission: PropertyKeyPermission,1165 ) -> DispatchResult {1166 collection.check_is_owner_or_admin(sender)?;11671168 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1169 let current_permission = all_permissions.get(&property_permission.key);1170 if matches![1171 current_permission,1172 Some(PropertyPermission { mutable: false, .. })1173 ] {1174 return Err(<Error<T>>::NoPermission.into());1175 }11761177 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1178 let property_permission = property_permission.clone();1179 permissions.try_scoped_set(1180 scope,1181 property_permission.key,1182 property_permission.permission,1183 )1184 })1185 .map_err(<Error<T>>::from)?;11861187 Self::deposit_event(Event::PropertyPermissionSet(1188 collection.id,1189 property_permission.key,1190 ));11911192 Ok(())1193 }11941195 1196 1197 1198 1199 1200 #[transactional]1201 pub fn set_token_property_permissions(1202 collection: &CollectionHandle<T>,1203 sender: &T::CrossAccountId,1204 property_permissions: Vec<PropertyKeyPermission>,1205 ) -> DispatchResult {1206 Self::set_scoped_token_property_permissions(1207 collection,1208 sender,1209 PropertyScope::None,1210 property_permissions,1211 )1212 }12131214 1215 1216 1217 1218 1219 1220 #[transactional]1221 pub fn set_scoped_token_property_permissions(1222 collection: &CollectionHandle<T>,1223 sender: &T::CrossAccountId,1224 scope: PropertyScope,1225 property_permissions: Vec<PropertyKeyPermission>,1226 ) -> DispatchResult {1227 for prop_pemission in property_permissions {1228 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1229 }12301231 Ok(())1232 }12331234 1235 pub fn get_collection_property(1236 collection_id: CollectionId,1237 key: &PropertyKey,1238 ) -> Option<PropertyValue> {1239 Self::collection_properties(collection_id).get(key).cloned()1240 }12411242 1243 pub fn bytes_keys_to_property_keys(1244 keys: Vec<Vec<u8>>,1245 ) -> Result<Vec<PropertyKey>, DispatchError> {1246 keys.into_iter()1247 .map(|key| -> Result<PropertyKey, DispatchError> {1248 key.try_into()1249 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1250 })1251 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1252 }12531254 1255 pub fn filter_collection_properties(1256 collection_id: CollectionId,1257 keys: Option<Vec<PropertyKey>>,1258 ) -> Result<Vec<Property>, DispatchError> {1259 let properties = Self::collection_properties(collection_id);12601261 let properties = keys1262 .map(|keys| {1263 keys.into_iter()1264 .filter_map(|key| {1265 properties.get(&key).map(|value| Property {1266 key,1267 value: value.clone(),1268 })1269 })1270 .collect()1271 })1272 .unwrap_or_else(|| {1273 properties1274 .into_iter()1275 .map(|(key, value)| Property { key, value })1276 .collect()1277 });12781279 Ok(properties)1280 }12811282 1283 pub fn filter_property_permissions(1284 collection_id: CollectionId,1285 keys: Option<Vec<PropertyKey>>,1286 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1287 let permissions = Self::property_permissions(collection_id);12881289 let key_permissions = keys1290 .map(|keys| {1291 keys.into_iter()1292 .filter_map(|key| {1293 permissions1294 .get(&key)1295 .map(|permission| PropertyKeyPermission {1296 key,1297 permission: permission.clone(),1298 })1299 })1300 .collect()1301 })1302 .unwrap_or_else(|| {1303 permissions1304 .into_iter()1305 .map(|(key, permission)| PropertyKeyPermission { key, permission })1306 .collect()1307 });13081309 Ok(key_permissions)1310 }13111312 1313 1314 1315 pub fn toggle_allowlist(1316 collection: &CollectionHandle<T>,1317 sender: &T::CrossAccountId,1318 user: &T::CrossAccountId,1319 allowed: bool,1320 ) -> DispatchResult {1321 collection.check_is_owner_or_admin(sender)?;13221323 13241325 if allowed {1326 <Allowlist<T>>::insert((collection.id, user), true);1327 } else {1328 <Allowlist<T>>::remove((collection.id, user));1329 }13301331 Ok(())1332 }13331334 1335 1336 1337 pub fn toggle_admin(1338 collection: &CollectionHandle<T>,1339 sender: &T::CrossAccountId,1340 user: &T::CrossAccountId,1341 admin: bool,1342 ) -> DispatchResult {1343 collection.check_is_owner(sender)?;13441345 let was_admin = <IsAdmin<T>>::get((collection.id, user));1346 if was_admin == admin {1347 return Ok(());1348 }1349 let amount = <AdminAmount<T>>::get(collection.id);13501351 if admin {1352 let amount = amount1353 .checked_add(1)1354 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1355 ensure!(1356 amount <= Self::collection_admins_limit(),1357 <Error<T>>::CollectionAdminCountExceeded,1358 );13591360 13611362 <AdminAmount<T>>::insert(collection.id, amount);1363 <IsAdmin<T>>::insert((collection.id, user), true);1364 } else {1365 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1366 <IsAdmin<T>>::remove((collection.id, user));1367 }13681369 Ok(())1370 }13711372 1373 pub fn clamp_limits(1374 mode: CollectionMode,1375 old_limit: &CollectionLimits,1376 mut new_limit: CollectionLimits,1377 ) -> Result<CollectionLimits, DispatchError> {1378 let limits = old_limit;1379 limit_default!(old_limit, new_limit,1380 account_token_ownership_limit => ensure!(1381 new_limit <= MAX_TOKEN_OWNERSHIP,1382 <Error<T>>::CollectionLimitBoundsExceeded,1383 ),1384 sponsored_data_size => ensure!(1385 new_limit <= CUSTOM_DATA_LIMIT,1386 <Error<T>>::CollectionLimitBoundsExceeded,1387 ),13881389 sponsored_data_rate_limit => {},1390 token_limit => ensure!(1391 old_limit >= new_limit && new_limit > 0,1392 <Error<T>>::CollectionTokenLimitExceeded1393 ),13941395 sponsor_transfer_timeout(match mode {1396 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1397 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1398 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1399 }) => ensure!(1400 new_limit <= MAX_SPONSOR_TIMEOUT,1401 <Error<T>>::CollectionLimitBoundsExceeded,1402 ),1403 sponsor_approve_timeout => {},1404 owner_can_transfer => ensure!(1405 !limits.owner_can_transfer_instaled() ||1406 old_limit || !new_limit,1407 <Error<T>>::OwnerPermissionsCantBeReverted,1408 ),1409 owner_can_destroy => ensure!(1410 old_limit || !new_limit,1411 <Error<T>>::OwnerPermissionsCantBeReverted,1412 ),1413 transfers_enabled => {},1414 );1415 Ok(new_limit)1416 }14171418 1419 pub fn clamp_permissions(1420 _mode: CollectionMode,1421 old_permission: &CollectionPermissions,1422 mut new_permission: CollectionPermissions,1423 ) -> Result<CollectionPermissions, DispatchError> {1424 limit_default_clone!(old_permission, new_permission,1425 access => {},1426 mint_mode => {},1427 nesting => { },1428 );1429 Ok(new_permission)1430 }1431}143214331434#[macro_export]1435macro_rules! unsupported {1436 ($runtime:path) => {1437 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1438 };1439}144014411442pub trait CommonWeightInfo<CrossAccountId> {1443 1444 fn create_item() -> Weight;14451446 1447 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14481449 1450 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14511452 1453 fn burn_item() -> Weight;14541455 1456 1457 1458 fn set_collection_properties(amount: u32) -> Weight;14591460 1461 1462 1463 fn delete_collection_properties(amount: u32) -> Weight;14641465 1466 1467 1468 fn set_token_properties(amount: u32) -> Weight;14691470 1471 1472 1473 fn delete_token_properties(amount: u32) -> Weight;14741475 1476 1477 1478 fn set_token_property_permissions(amount: u32) -> Weight;14791480 1481 fn transfer() -> Weight;14821483 1484 fn approve() -> Weight;14851486 1487 fn transfer_from() -> Weight;14881489 1490 fn burn_from() -> Weight;14911492 1493 1494 1495 1496 fn burn_recursively_self_raw() -> Weight;14971498 1499 1500 1501 fn burn_recursively_breadth_raw(amount: u32) -> Weight;15021503 1504 1505 1506 1507 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1508 Self::burn_recursively_self_raw()1509 .saturating_mul(max_selfs.max(1) as u64)1510 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1511 }15121513 1514 fn token_owner() -> Weight;1515}151615171518pub trait RefungibleExtensionsWeightInfo {1519 1520 fn repartition() -> Weight;1521}152215231524152515261527pub trait CommonCollectionOperations<T: Config> {1528 1529 1530 1531 1532 1533 1534 fn create_item(1535 &self,1536 sender: T::CrossAccountId,1537 to: T::CrossAccountId,1538 data: CreateItemData,1539 nesting_budget: &dyn Budget,1540 ) -> DispatchResultWithPostInfo;15411542 1543 1544 1545 1546 1547 1548 fn create_multiple_items(1549 &self,1550 sender: T::CrossAccountId,1551 to: T::CrossAccountId,1552 data: Vec<CreateItemData>,1553 nesting_budget: &dyn Budget,1554 ) -> DispatchResultWithPostInfo;15551556 1557 1558 1559 1560 1561 1562 fn create_multiple_items_ex(1563 &self,1564 sender: T::CrossAccountId,1565 data: CreateItemExData<T::CrossAccountId>,1566 nesting_budget: &dyn Budget,1567 ) -> DispatchResultWithPostInfo;15681569 1570 1571 1572 1573 1574 fn burn_item(1575 &self,1576 sender: T::CrossAccountId,1577 token: TokenId,1578 amount: u128,1579 ) -> DispatchResultWithPostInfo;15801581 1582 1583 1584 1585 1586 1587 fn burn_item_recursively(1588 &self,1589 sender: T::CrossAccountId,1590 token: TokenId,1591 self_budget: &dyn Budget,1592 breadth_budget: &dyn Budget,1593 ) -> DispatchResultWithPostInfo;15941595 1596 1597 1598 1599 fn set_collection_properties(1600 &self,1601 sender: T::CrossAccountId,1602 properties: Vec<Property>,1603 ) -> DispatchResultWithPostInfo;16041605 1606 1607 1608 1609 fn delete_collection_properties(1610 &self,1611 sender: &T::CrossAccountId,1612 property_keys: Vec<PropertyKey>,1613 ) -> DispatchResultWithPostInfo;16141615 1616 1617 1618 1619 1620 1621 1622 1623 1624 fn set_token_properties(1625 &self,1626 sender: T::CrossAccountId,1627 token_id: TokenId,1628 properties: Vec<Property>,1629 budget: &dyn Budget,1630 ) -> DispatchResultWithPostInfo;16311632 1633 1634 1635 1636 1637 1638 1639 1640 1641 fn delete_token_properties(1642 &self,1643 sender: T::CrossAccountId,1644 token_id: TokenId,1645 property_keys: Vec<PropertyKey>,1646 budget: &dyn Budget,1647 ) -> DispatchResultWithPostInfo;16481649 1650 1651 1652 1653 1654 1655 fn set_token_property_permissions(1656 &self,1657 sender: &T::CrossAccountId,1658 property_permissions: Vec<PropertyKeyPermission>,1659 ) -> DispatchResultWithPostInfo;16601661 1662 1663 1664 1665 1666 1667 1668 fn transfer(1669 &self,1670 sender: T::CrossAccountId,1671 to: T::CrossAccountId,1672 token: TokenId,1673 amount: u128,1674 budget: &dyn Budget,1675 ) -> DispatchResultWithPostInfo;16761677 1678 1679 1680 1681 1682 1683 fn approve(1684 &self,1685 sender: T::CrossAccountId,1686 spender: T::CrossAccountId,1687 token: TokenId,1688 amount: u128,1689 ) -> DispatchResultWithPostInfo;16901691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 fn transfer_from(1702 &self,1703 sender: T::CrossAccountId,1704 from: T::CrossAccountId,1705 to: T::CrossAccountId,1706 token: TokenId,1707 amount: u128,1708 budget: &dyn Budget,1709 ) -> DispatchResultWithPostInfo;17101711 1712 1713 1714 1715 1716 1717 1718 1719 1720 fn burn_from(1721 &self,1722 sender: T::CrossAccountId,1723 from: T::CrossAccountId,1724 token: TokenId,1725 amount: u128,1726 budget: &dyn Budget,1727 ) -> DispatchResultWithPostInfo;17281729 1730 1731 1732 1733 1734 1735 fn check_nesting(1736 &self,1737 sender: T::CrossAccountId,1738 from: (CollectionId, TokenId),1739 under: TokenId,1740 budget: &dyn Budget,1741 ) -> DispatchResult;17421743 1744 1745 1746 1747 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17481749 1750 1751 1752 1753 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17541755 1756 1757 1758 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17591760 1761 fn collection_tokens(&self) -> Vec<TokenId>;17621763 1764 1765 1766 fn token_exists(&self, token: TokenId) -> bool;17671768 1769 fn last_token_id(&self) -> TokenId;17701771 1772 1773 1774 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17751776 1777 1778 1779 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17801781 1782 1783 1784 1785 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17861787 1788 1789 1790 1791 1792 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17931794 1795 fn total_supply(&self) -> u32;17961797 1798 1799 1800 fn account_balance(&self, account: T::CrossAccountId) -> u32;18011802 1803 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;18041805 1806 fn total_pieces(&self, token: TokenId) -> Option<u128>;18071808 1809 1810 1811 1812 1813 fn allowance(1814 &self,1815 sender: T::CrossAccountId,1816 spender: T::CrossAccountId,1817 token: TokenId,1818 ) -> u128;18191820 1821 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1822}182318241825pub trait RefungibleExtensions<T>1826where1827 T: Config,1828{1829 1830 1831 1832 1833 1834 1835 1836 fn repartition(1837 &self,1838 sender: &T::CrossAccountId,1839 token: TokenId,1840 amount: u128,1841 ) -> DispatchResultWithPostInfo;1842}18431844184518461847pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1848 let post_info = PostDispatchInfo {1849 actual_weight: Some(weight),1850 pays_fee: Pays::Yes,1851 };1852 match res {1853 Ok(()) => Ok(post_info),1854 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1855 }1856}18571858impl<T: Config> From<PropertiesError> for Error<T> {1859 fn from(error: PropertiesError) -> Self {1860 match error {1861 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1862 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1863 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1864 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1865 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1866 }1867 }1868}