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 }305}306307#[frame_support::pallet]308pub mod pallet {309 use super::*;310 use pallet_evm::account;311 use dispatch::CollectionDispatch;312 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};313 use frame_system::pallet_prelude::*;314 use frame_support::traits::Currency;315 use up_data_structs::{TokenId, mapping::TokenAddressMapping};316 use scale_info::TypeInfo;317 use weights::WeightInfo;318319 #[pallet::config]320 pub trait Config:321 frame_system::Config322 + pallet_evm_coder_substrate::Config323 + pallet_evm::Config324 + TypeInfo325 + account::Config326 {327 328 type WeightInfo: WeightInfo;329330 331 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;332333 334 type Currency: Currency<Self::AccountId>;335336 337 #[pallet::constant]338 type CollectionCreationPrice: Get<339 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,340 >;341342 343 type CollectionDispatch: CollectionDispatch<Self>;344345 346 type TreasuryAccountId: Get<Self::AccountId>;347348 349 type ContractAddress: Get<H160>;350351 352 type EvmTokenAddressMapping: TokenAddressMapping<H160>;353354 355 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;356 }357358 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);359360 #[pallet::pallet]361 #[pallet::storage_version(STORAGE_VERSION)]362 #[pallet::generate_store(pub(super) trait Store)]363 pub struct Pallet<T>(_);364365 #[pallet::extra_constants]366 impl<T: Config> Pallet<T> {367 368 pub fn collection_admins_limit() -> u32 {369 COLLECTION_ADMINS_LIMIT370 }371 }372373 #[pallet::event]374 #[pallet::generate_deposit(pub fn deposit_event)]375 pub enum Event<T: Config> {376 377 CollectionCreated(378 379 CollectionId,380 381 u8,382 383 T::AccountId,384 ),385386 387 CollectionDestroyed(388 389 CollectionId,390 ),391392 393 ItemCreated(394 395 CollectionId,396 397 TokenId,398 399 T::CrossAccountId,400 401 u128,402 ),403404 405 ItemDestroyed(406 407 CollectionId,408 409 TokenId,410 411 T::CrossAccountId,412 413 u128,414 ),415416 417 Transfer(418 419 CollectionId,420 421 TokenId,422 423 T::CrossAccountId,424 425 T::CrossAccountId,426 427 u128,428 ),429430 431 Approved(432 433 CollectionId,434 435 TokenId,436 437 T::CrossAccountId,438 439 T::CrossAccountId,440 441 u128,442 ),443444 445 CollectionPropertySet(446 447 CollectionId,448 449 PropertyKey,450 ),451452 453 CollectionPropertyDeleted(454 455 CollectionId,456 457 PropertyKey,458 ),459460 461 TokenPropertySet(462 463 CollectionId,464 465 TokenId,466 467 PropertyKey,468 ),469470 471 TokenPropertyDeleted(472 473 CollectionId,474 475 TokenId,476 477 PropertyKey,478 ),479480 481 PropertyPermissionSet(482 483 CollectionId,484 485 PropertyKey,486 ),487 }488489 #[pallet::error]490 pub enum Error<T> {491 492 CollectionNotFound,493 494 MustBeTokenOwner,495 496 NoPermission,497 498 CantDestroyNotEmptyCollection,499 500 PublicMintingNotAllowed,501 502 AddressNotInAllowlist,503504 505 CollectionNameLimitExceeded,506 507 CollectionDescriptionLimitExceeded,508 509 CollectionTokenPrefixLimitExceeded,510 511 TotalCollectionsLimitExceeded,512 513 CollectionAdminCountExceeded,514 515 CollectionLimitBoundsExceeded,516 517 OwnerPermissionsCantBeReverted,518 519 TransferNotAllowed,520 521 AccountTokenLimitExceeded,522 523 CollectionTokenLimitExceeded,524 525 MetadataFlagFrozen,526527 528 TokenNotFound,529 530 TokenValueTooLow,531 532 ApprovedValueTooLow,533 534 CantApproveMoreThanOwned,535536 537 AddressIsZero,538539 540 UnsupportedOperation,541542 543 NotSufficientFounds,544545 546 UserIsNotAllowedToNest,547 548 SourceCollectionIsNotAllowedToNest,549550 551 CollectionFieldSizeExceeded,552553 554 NoSpaceForProperty,555556 557 PropertyLimitReached,558559 560 PropertyKeyIsTooLong,561562 563 InvalidCharacterInPropertyKey,564565 566 EmptyPropertyKey,567568 569 CollectionIsExternal,570571 572 CollectionIsInternal,573 }574575 576 #[pallet::storage]577 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;578579 580 #[pallet::storage]581 pub type DestroyedCollectionCount<T> =582 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;583584 585 #[pallet::storage]586 pub type CollectionById<T> = StorageMap<587 Hasher = Blake2_128Concat,588 Key = CollectionId,589 Value = Collection<<T as frame_system::Config>::AccountId>,590 QueryKind = OptionQuery,591 >;592593 594 #[pallet::storage]595 #[pallet::getter(fn collection_properties)]596 pub type CollectionProperties<T> = StorageMap<597 Hasher = Blake2_128Concat,598 Key = CollectionId,599 Value = Properties,600 QueryKind = ValueQuery,601 OnEmpty = up_data_structs::CollectionProperties,602 >;603604 605 #[pallet::storage]606 #[pallet::getter(fn property_permissions)]607 pub type CollectionPropertyPermissions<T> = StorageMap<608 Hasher = Blake2_128Concat,609 Key = CollectionId,610 Value = PropertiesPermissionMap,611 QueryKind = ValueQuery,612 >;613614 615 #[pallet::storage]616 pub type AdminAmount<T> = StorageMap<617 Hasher = Blake2_128Concat,618 Key = CollectionId,619 Value = u32,620 QueryKind = ValueQuery,621 >;622623 624 #[pallet::storage]625 pub type IsAdmin<T: Config> = StorageNMap<626 Key = (627 Key<Blake2_128Concat, CollectionId>,628 Key<Blake2_128Concat, T::CrossAccountId>,629 ),630 Value = bool,631 QueryKind = ValueQuery,632 >;633634 635 #[pallet::storage]636 pub type Allowlist<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 DummyStorageValue<T: Config> = StorageValue<648 Value = (649 CollectionStats,650 CollectionId,651 TokenId,652 TokenChild,653 PhantomType<(654 TokenData<T::CrossAccountId>,655 RpcCollection<T::AccountId>,656 657 RmrkCollectionInfo<T::AccountId>,658 RmrkInstanceInfo<T::AccountId>,659 RmrkResourceInfo,660 RmrkPropertyInfo,661 RmrkBaseInfo<T::AccountId>,662 RmrkPartType,663 RmrkBoundedTheme,664 RmrkNftChild,665 )>,666 ),667 QueryKind = OptionQuery,668 >;669670 #[pallet::hooks]671 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {672 fn on_runtime_upgrade() -> Weight {673 StorageVersion::new(1).put::<Pallet<T>>();674675 0676 }677 }678}679680impl<T: Config> Pallet<T> {681 682 683 684 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {685 ensure!(686 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,687 <Error<T>>::AddressIsZero688 );689 Ok(())690 }691692 693 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {694 <IsAdmin<T>>::iter_prefix((collection,))695 .map(|(a, _)| a)696 .collect()697 }698699 700 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {701 <Allowlist<T>>::iter_prefix((collection,))702 .map(|(a, _)| a)703 .collect()704 }705706 707 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {708 <Allowlist<T>>::get((collection, user))709 }710711 712 pub fn collection_stats() -> CollectionStats {713 let created = <CreatedCollectionCount<T>>::get();714 let destroyed = <DestroyedCollectionCount<T>>::get();715 CollectionStats {716 created: created.0,717 destroyed: destroyed.0,718 alive: created.0 - destroyed.0,719 }720 }721722 723 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {724 let collection = <CollectionById<T>>::get(collection);725 if collection.is_none() {726 return None;727 }728729 let collection = collection.unwrap();730 let limits = collection.limits;731 let effective_limits = CollectionLimits {732 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),733 sponsored_data_size: Some(limits.sponsored_data_size()),734 sponsored_data_rate_limit: Some(735 limits736 .sponsored_data_rate_limit737 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),738 ),739 token_limit: Some(limits.token_limit()),740 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(741 match collection.mode {742 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,743 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,744 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,745 },746 )),747 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),748 owner_can_transfer: Some(limits.owner_can_transfer()),749 owner_can_destroy: Some(limits.owner_can_destroy()),750 transfers_enabled: Some(limits.transfers_enabled()),751 };752753 Some(effective_limits)754 }755756 757 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {758 let Collection {759 name,760 description,761 owner,762 mode,763 token_prefix,764 sponsorship,765 limits,766 permissions,767 external_collection,768 } = <CollectionById<T>>::get(collection)?;769770 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)771 .into_iter()772 .map(|(key, permission)| PropertyKeyPermission { key, permission })773 .collect();774775 let properties = <CollectionProperties<T>>::get(collection)776 .into_iter()777 .map(|(key, value)| Property { key, value })778 .collect();779780 let permissions = CollectionPermissions {781 access: Some(permissions.access()),782 mint_mode: Some(permissions.mint_mode()),783 nesting: Some(permissions.nesting().clone()),784 };785786 Some(RpcCollection {787 name: name.into_inner(),788 description: description.into_inner(),789 owner,790 mode,791 token_prefix: token_prefix.into_inner(),792 sponsorship,793 limits,794 permissions,795 token_property_permissions,796 properties,797 read_only: external_collection,798 })799 }800}801802macro_rules! limit_default {803 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{804 $(805 if let Some($new) = $new.$field {806 let $old = $old.$field($($arg)?);807 let _ = $new;808 let _ = $old;809 $check810 } else {811 $new.$field = $old.$field812 }813 )*814 }};815}816macro_rules! limit_default_clone {817 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{818 $(819 if let Some($new) = $new.$field.clone() {820 let $old = $old.$field($($arg)?);821 let _ = $new;822 let _ = $old;823 $check824 } else {825 $new.$field = $old.$field.clone()826 }827 )*828 }};829}830831impl<T: Config> Pallet<T> {832 833 834 835 836 837 pub fn init_collection(838 owner: T::CrossAccountId,839 data: CreateCollectionData<T::AccountId>,840 is_external: bool,841 ) -> Result<CollectionId, DispatchError> {842 {843 ensure!(844 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,845 Error::<T>::CollectionTokenPrefixLimitExceeded846 );847 }848849 let created_count = <CreatedCollectionCount<T>>::get()850 .0851 .checked_add(1)852 .ok_or(ArithmeticError::Overflow)?;853 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;854 let id = CollectionId(created_count);855856 857 ensure!(858 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,859 <Error<T>>::TotalCollectionsLimitExceeded860 );861862 863864 let collection = Collection {865 owner: owner.as_sub().clone(),866 name: data.name,867 mode: data.mode.clone(),868 description: data.description,869 token_prefix: data.token_prefix,870 sponsorship: data871 .pending_sponsor872 .map(SponsorshipState::Unconfirmed)873 .unwrap_or_default(),874 limits: data875 .limits876 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))877 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,878 permissions: data879 .permissions880 .map(|permissions| {881 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)882 })883 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,884 external_collection: is_external,885 };886887 let mut collection_properties = up_data_structs::CollectionProperties::get();888 collection_properties889 .try_set_from_iter(data.properties.into_iter())890 .map_err(<Error<T>>::from)?;891892 CollectionProperties::<T>::insert(id, collection_properties);893894 let mut token_props_permissions = PropertiesPermissionMap::new();895 token_props_permissions896 .try_set_from_iter(data.token_property_permissions.into_iter())897 .map_err(<Error<T>>::from)?;898899 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);900901 902 {903 let mut imbalance =904 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();905 imbalance.subsume(906 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(907 &T::TreasuryAccountId::get(),908 T::CollectionCreationPrice::get(),909 ),910 );911 <T as Config>::Currency::settle(912 owner.as_sub(),913 imbalance,914 WithdrawReasons::TRANSFER,915 ExistenceRequirement::KeepAlive,916 )917 .map_err(|_| Error::<T>::NotSufficientFounds)?;918 }919920 <CreatedCollectionCount<T>>::put(created_count);921 <Pallet<T>>::deposit_event(Event::CollectionCreated(922 id,923 data.mode.id(),924 owner.as_sub().clone(),925 ));926 <PalletEvm<T>>::deposit_log(927 erc::CollectionHelpersEvents::CollectionCreated {928 owner: *owner.as_eth(),929 collection_id: eth::collection_id_to_address(id),930 }931 .to_log(T::ContractAddress::get()),932 );933 <CollectionById<T>>::insert(id, collection);934 Ok(id)935 }936937 938 939 940 941 pub fn destroy_collection(942 collection: CollectionHandle<T>,943 sender: &T::CrossAccountId,944 ) -> DispatchResult {945 ensure!(946 collection.limits.owner_can_destroy(),947 <Error<T>>::NoPermission,948 );949 collection.check_is_owner(sender)?;950951 let destroyed_collections = <DestroyedCollectionCount<T>>::get()952 .0953 .checked_add(1)954 .ok_or(ArithmeticError::Overflow)?;955956 957958 <DestroyedCollectionCount<T>>::put(destroyed_collections);959 <CollectionById<T>>::remove(collection.id);960 <AdminAmount<T>>::remove(collection.id);961 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);962 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);963 <CollectionProperties<T>>::remove(collection.id);964965 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));966 Ok(())967 }968969 970 971 972 973 974 pub fn set_collection_property(975 collection: &CollectionHandle<T>,976 sender: &T::CrossAccountId,977 property: Property,978 ) -> DispatchResult {979 collection.check_is_owner_or_admin(sender)?;980981 CollectionProperties::<T>::try_mutate(collection.id, |properties| {982 let property = property.clone();983 properties.try_set(property.key, property.value)984 })985 .map_err(<Error<T>>::from)?;986987 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));988989 Ok(())990 }991992 993 994 995 996 997 pub fn set_scoped_collection_property(998 collection_id: CollectionId,999 scope: PropertyScope,1000 property: Property,1001 ) -> DispatchResult {1002 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1003 properties.try_scoped_set(scope, property.key, property.value)1004 })1005 .map_err(<Error<T>>::from)?;10061007 Ok(())1008 }10091010 1011 1012 1013 1014 1015 pub fn set_scoped_collection_properties(1016 collection_id: CollectionId,1017 scope: PropertyScope,1018 properties: impl Iterator<Item = Property>,1019 ) -> DispatchResult {1020 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1021 stored_properties.try_scoped_set_from_iter(scope, properties)1022 })1023 .map_err(<Error<T>>::from)?;10241025 Ok(())1026 }10271028 1029 1030 1031 1032 1033 #[transactional]1034 pub fn set_collection_properties(1035 collection: &CollectionHandle<T>,1036 sender: &T::CrossAccountId,1037 properties: Vec<Property>,1038 ) -> DispatchResult {1039 for property in properties {1040 Self::set_collection_property(collection, sender, property)?;1041 }10421043 Ok(())1044 }10451046 1047 1048 1049 1050 1051 pub fn delete_collection_property(1052 collection: &CollectionHandle<T>,1053 sender: &T::CrossAccountId,1054 property_key: PropertyKey,1055 ) -> DispatchResult {1056 collection.check_is_owner_or_admin(sender)?;10571058 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1059 properties.remove(&property_key)1060 })1061 .map_err(<Error<T>>::from)?;10621063 Self::deposit_event(Event::CollectionPropertyDeleted(1064 collection.id,1065 property_key,1066 ));10671068 Ok(())1069 }10701071 1072 1073 1074 1075 1076 #[transactional]1077 pub fn delete_collection_properties(1078 collection: &CollectionHandle<T>,1079 sender: &T::CrossAccountId,1080 property_keys: Vec<PropertyKey>,1081 ) -> DispatchResult {1082 for key in property_keys {1083 Self::delete_collection_property(collection, sender, key)?;1084 }10851086 Ok(())1087 }10881089 1090 1091 1092 1093 1094 1095 pub fn set_property_permission_unchecked(1096 collection: CollectionId,1097 property_permission: PropertyKeyPermission,1098 ) -> DispatchResult {1099 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1100 permissions.try_set(property_permission.key, property_permission.permission)1101 })1102 .map_err(<Error<T>>::from)?;1103 Ok(())1104 }11051106 1107 1108 1109 1110 1111 pub fn set_property_permission(1112 collection: &CollectionHandle<T>,1113 sender: &T::CrossAccountId,1114 property_permission: PropertyKeyPermission,1115 ) -> DispatchResult {1116 Self::set_scoped_property_permission(1117 collection,1118 sender,1119 PropertyScope::None,1120 property_permission,1121 )1122 }11231124 1125 1126 1127 1128 1129 1130 pub fn set_scoped_property_permission(1131 collection: &CollectionHandle<T>,1132 sender: &T::CrossAccountId,1133 scope: PropertyScope,1134 property_permission: PropertyKeyPermission,1135 ) -> DispatchResult {1136 collection.check_is_owner_or_admin(sender)?;11371138 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1139 let current_permission = all_permissions.get(&property_permission.key);1140 if matches![1141 current_permission,1142 Some(PropertyPermission { mutable: false, .. })1143 ] {1144 return Err(<Error<T>>::NoPermission.into());1145 }11461147 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1148 let property_permission = property_permission.clone();1149 permissions.try_scoped_set(1150 scope,1151 property_permission.key,1152 property_permission.permission,1153 )1154 })1155 .map_err(<Error<T>>::from)?;11561157 Self::deposit_event(Event::PropertyPermissionSet(1158 collection.id,1159 property_permission.key,1160 ));11611162 Ok(())1163 }11641165 1166 1167 1168 1169 1170 #[transactional]1171 pub fn set_token_property_permissions(1172 collection: &CollectionHandle<T>,1173 sender: &T::CrossAccountId,1174 property_permissions: Vec<PropertyKeyPermission>,1175 ) -> DispatchResult {1176 Self::set_scoped_token_property_permissions(1177 collection,1178 sender,1179 PropertyScope::None,1180 property_permissions,1181 )1182 }11831184 1185 1186 1187 1188 1189 1190 #[transactional]1191 pub fn set_scoped_token_property_permissions(1192 collection: &CollectionHandle<T>,1193 sender: &T::CrossAccountId,1194 scope: PropertyScope,1195 property_permissions: Vec<PropertyKeyPermission>,1196 ) -> DispatchResult {1197 for prop_pemission in property_permissions {1198 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1199 }12001201 Ok(())1202 }12031204 1205 pub fn get_collection_property(1206 collection_id: CollectionId,1207 key: &PropertyKey,1208 ) -> Option<PropertyValue> {1209 Self::collection_properties(collection_id).get(key).cloned()1210 }12111212 1213 pub fn bytes_keys_to_property_keys(1214 keys: Vec<Vec<u8>>,1215 ) -> Result<Vec<PropertyKey>, DispatchError> {1216 keys.into_iter()1217 .map(|key| -> Result<PropertyKey, DispatchError> {1218 key.try_into()1219 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1220 })1221 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1222 }12231224 1225 pub fn filter_collection_properties(1226 collection_id: CollectionId,1227 keys: Option<Vec<PropertyKey>>,1228 ) -> Result<Vec<Property>, DispatchError> {1229 let properties = Self::collection_properties(collection_id);12301231 let properties = keys1232 .map(|keys| {1233 keys.into_iter()1234 .filter_map(|key| {1235 properties.get(&key).map(|value| Property {1236 key,1237 value: value.clone(),1238 })1239 })1240 .collect()1241 })1242 .unwrap_or_else(|| {1243 properties1244 .into_iter()1245 .map(|(key, value)| Property { key, value })1246 .collect()1247 });12481249 Ok(properties)1250 }12511252 1253 pub fn filter_property_permissions(1254 collection_id: CollectionId,1255 keys: Option<Vec<PropertyKey>>,1256 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1257 let permissions = Self::property_permissions(collection_id);12581259 let key_permissions = keys1260 .map(|keys| {1261 keys.into_iter()1262 .filter_map(|key| {1263 permissions1264 .get(&key)1265 .map(|permission| PropertyKeyPermission {1266 key,1267 permission: permission.clone(),1268 })1269 })1270 .collect()1271 })1272 .unwrap_or_else(|| {1273 permissions1274 .into_iter()1275 .map(|(key, permission)| PropertyKeyPermission { key, permission })1276 .collect()1277 });12781279 Ok(key_permissions)1280 }12811282 1283 pub fn toggle_allowlist(1284 collection: &CollectionHandle<T>,1285 sender: &T::CrossAccountId,1286 user: &T::CrossAccountId,1287 allowed: bool,1288 ) -> DispatchResult {1289 collection.check_is_owner_or_admin(sender)?;12901291 12921293 if allowed {1294 <Allowlist<T>>::insert((collection.id, user), true);1295 } else {1296 <Allowlist<T>>::remove((collection.id, user));1297 }12981299 Ok(())1300 }13011302 1303 pub fn toggle_admin(1304 collection: &CollectionHandle<T>,1305 sender: &T::CrossAccountId,1306 user: &T::CrossAccountId,1307 admin: bool,1308 ) -> DispatchResult {1309 collection.check_is_owner(sender)?;13101311 let was_admin = <IsAdmin<T>>::get((collection.id, user));1312 if was_admin == admin {1313 return Ok(());1314 }1315 let amount = <AdminAmount<T>>::get(collection.id);13161317 if admin {1318 let amount = amount1319 .checked_add(1)1320 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1321 ensure!(1322 amount <= Self::collection_admins_limit(),1323 <Error<T>>::CollectionAdminCountExceeded,1324 );13251326 13271328 <AdminAmount<T>>::insert(collection.id, amount);1329 <IsAdmin<T>>::insert((collection.id, user), true);1330 } else {1331 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1332 <IsAdmin<T>>::remove((collection.id, user));1333 }13341335 Ok(())1336 }13371338 1339 pub fn clamp_limits(1340 mode: CollectionMode,1341 old_limit: &CollectionLimits,1342 mut new_limit: CollectionLimits,1343 ) -> Result<CollectionLimits, DispatchError> {1344 let limits = old_limit;1345 limit_default!(old_limit, new_limit,1346 account_token_ownership_limit => ensure!(1347 new_limit <= MAX_TOKEN_OWNERSHIP,1348 <Error<T>>::CollectionLimitBoundsExceeded,1349 ),1350 sponsored_data_size => ensure!(1351 new_limit <= CUSTOM_DATA_LIMIT,1352 <Error<T>>::CollectionLimitBoundsExceeded,1353 ),13541355 sponsored_data_rate_limit => {},1356 token_limit => ensure!(1357 old_limit >= new_limit && new_limit > 0,1358 <Error<T>>::CollectionTokenLimitExceeded1359 ),13601361 sponsor_transfer_timeout(match mode {1362 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1363 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1364 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1365 }) => ensure!(1366 new_limit <= MAX_SPONSOR_TIMEOUT,1367 <Error<T>>::CollectionLimitBoundsExceeded,1368 ),1369 sponsor_approve_timeout => {},1370 owner_can_transfer => ensure!(1371 !limits.owner_can_transfer_instaled() ||1372 old_limit || !new_limit,1373 <Error<T>>::OwnerPermissionsCantBeReverted,1374 ),1375 owner_can_destroy => ensure!(1376 old_limit || !new_limit,1377 <Error<T>>::OwnerPermissionsCantBeReverted,1378 ),1379 transfers_enabled => {},1380 );1381 Ok(new_limit)1382 }13831384 1385 pub fn clamp_permissions(1386 _mode: CollectionMode,1387 old_permission: &CollectionPermissions,1388 mut new_permission: CollectionPermissions,1389 ) -> Result<CollectionPermissions, DispatchError> {1390 limit_default_clone!(old_permission, new_permission,1391 access => {},1392 mint_mode => {},1393 nesting => { },1394 );1395 Ok(new_permission)1396 }1397}139813991400#[macro_export]1401macro_rules! unsupported {1402 ($runtime:path) => {1403 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1404 };1405}140614071408pub trait CommonWeightInfo<CrossAccountId> {1409 1410 fn create_item() -> Weight;14111412 1413 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14141415 1416 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14171418 1419 fn burn_item() -> Weight;14201421 1422 1423 1424 fn set_collection_properties(amount: u32) -> Weight;14251426 1427 1428 1429 fn delete_collection_properties(amount: u32) -> Weight;14301431 1432 1433 1434 fn set_token_properties(amount: u32) -> Weight;14351436 1437 1438 1439 fn delete_token_properties(amount: u32) -> Weight;14401441 1442 1443 1444 fn set_token_property_permissions(amount: u32) -> Weight;14451446 1447 fn transfer() -> Weight;14481449 1450 fn approve() -> Weight;14511452 1453 fn transfer_from() -> Weight;14541455 1456 fn burn_from() -> Weight;14571458 1459 1460 1461 1462 fn burn_recursively_self_raw() -> Weight;14631464 1465 1466 1467 fn burn_recursively_breadth_raw(amount: u32) -> Weight;14681469 1470 1471 1472 1473 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1474 Self::burn_recursively_self_raw()1475 .saturating_mul(max_selfs.max(1) as u64)1476 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1477 }14781479 1480 fn token_owner() -> Weight;1481}148214831484pub trait RefungibleExtensionsWeightInfo {1485 1486 fn repartition() -> Weight;1487}148814891490149114921493pub trait CommonCollectionOperations<T: Config> {1494 1495 1496 1497 1498 1499 1500 fn create_item(1501 &self,1502 sender: T::CrossAccountId,1503 to: T::CrossAccountId,1504 data: CreateItemData,1505 nesting_budget: &dyn Budget,1506 ) -> DispatchResultWithPostInfo;15071508 1509 1510 1511 1512 1513 1514 fn create_multiple_items(1515 &self,1516 sender: T::CrossAccountId,1517 to: T::CrossAccountId,1518 data: Vec<CreateItemData>,1519 nesting_budget: &dyn Budget,1520 ) -> DispatchResultWithPostInfo;15211522 1523 1524 1525 1526 1527 1528 fn create_multiple_items_ex(1529 &self,1530 sender: T::CrossAccountId,1531 data: CreateItemExData<T::CrossAccountId>,1532 nesting_budget: &dyn Budget,1533 ) -> DispatchResultWithPostInfo;15341535 1536 1537 1538 1539 1540 fn burn_item(1541 &self,1542 sender: T::CrossAccountId,1543 token: TokenId,1544 amount: u128,1545 ) -> DispatchResultWithPostInfo;15461547 1548 1549 1550 1551 1552 1553 fn burn_item_recursively(1554 &self,1555 sender: T::CrossAccountId,1556 token: TokenId,1557 self_budget: &dyn Budget,1558 breadth_budget: &dyn Budget,1559 ) -> DispatchResultWithPostInfo;15601561 1562 1563 1564 1565 fn set_collection_properties(1566 &self,1567 sender: T::CrossAccountId,1568 properties: Vec<Property>,1569 ) -> DispatchResultWithPostInfo;15701571 1572 1573 1574 1575 fn delete_collection_properties(1576 &self,1577 sender: &T::CrossAccountId,1578 property_keys: Vec<PropertyKey>,1579 ) -> DispatchResultWithPostInfo;15801581 1582 1583 1584 1585 1586 1587 1588 1589 1590 fn set_token_properties(1591 &self,1592 sender: T::CrossAccountId,1593 token_id: TokenId,1594 properties: Vec<Property>,1595 budget: &dyn Budget,1596 ) -> DispatchResultWithPostInfo;15971598 1599 1600 1601 1602 1603 1604 1605 1606 1607 fn delete_token_properties(1608 &self,1609 sender: T::CrossAccountId,1610 token_id: TokenId,1611 property_keys: Vec<PropertyKey>,1612 budget: &dyn Budget,1613 ) -> DispatchResultWithPostInfo;16141615 1616 1617 1618 1619 1620 1621 fn set_token_property_permissions(1622 &self,1623 sender: &T::CrossAccountId,1624 property_permissions: Vec<PropertyKeyPermission>,1625 ) -> DispatchResultWithPostInfo;16261627 1628 1629 1630 1631 1632 1633 1634 fn transfer(1635 &self,1636 sender: T::CrossAccountId,1637 to: T::CrossAccountId,1638 token: TokenId,1639 amount: u128,1640 budget: &dyn Budget,1641 ) -> DispatchResultWithPostInfo;16421643 1644 1645 1646 1647 1648 1649 fn approve(1650 &self,1651 sender: T::CrossAccountId,1652 spender: T::CrossAccountId,1653 token: TokenId,1654 amount: u128,1655 ) -> DispatchResultWithPostInfo;16561657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 fn transfer_from(1668 &self,1669 sender: T::CrossAccountId,1670 from: 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 1684 1685 1686 fn burn_from(1687 &self,1688 sender: T::CrossAccountId,1689 from: T::CrossAccountId,1690 token: TokenId,1691 amount: u128,1692 budget: &dyn Budget,1693 ) -> DispatchResultWithPostInfo;16941695 1696 1697 1698 1699 1700 1701 fn check_nesting(1702 &self,1703 sender: T::CrossAccountId,1704 from: (CollectionId, TokenId),1705 under: TokenId,1706 budget: &dyn Budget,1707 ) -> DispatchResult;17081709 1710 1711 1712 1713 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17141715 1716 1717 1718 1719 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17201721 1722 1723 1724 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17251726 1727 fn collection_tokens(&self) -> Vec<TokenId>;17281729 1730 1731 1732 fn token_exists(&self, token: TokenId) -> bool;17331734 1735 fn last_token_id(&self) -> TokenId;17361737 1738 1739 1740 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17411742 1743 1744 1745 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17461747 1748 1749 1750 1751 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17521753 1754 1755 1756 1757 1758 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17591760 1761 fn total_supply(&self) -> u32;17621763 1764 1765 1766 fn account_balance(&self, account: T::CrossAccountId) -> u32;17671768 1769 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;17701771 1772 fn total_pieces(&self, token: TokenId) -> Option<u128>;17731774 1775 1776 1777 1778 1779 fn allowance(1780 &self,1781 sender: T::CrossAccountId,1782 spender: T::CrossAccountId,1783 token: TokenId,1784 ) -> u128;17851786 1787 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1788}178917901791pub trait RefungibleExtensions<T>1792where1793 T: Config,1794{1795 1796 1797 1798 1799 1800 1801 1802 fn repartition(1803 &self,1804 sender: &T::CrossAccountId,1805 token: TokenId,1806 amount: u128,1807 ) -> DispatchResultWithPostInfo;1808}18091810181118121813pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1814 let post_info = PostDispatchInfo {1815 actual_weight: Some(weight),1816 pays_fee: Pays::Yes,1817 };1818 match res {1819 Ok(()) => Ok(post_info),1820 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1821 }1822}18231824impl<T: Config> From<PropertiesError> for Error<T> {1825 fn from(error: PropertiesError) -> Self {1826 match error {1827 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1828 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1829 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1830 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1831 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1832 }1833 }1834}