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 <IsAdmin<T>>::remove_prefix((collection.id,), None);962 <Allowlist<T>>::remove_prefix((collection.id,), 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 collection.check_is_owner_or_admin(sender)?;11171118 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1119 let current_permission = all_permissions.get(&property_permission.key);1120 if matches![1121 current_permission,1122 Some(PropertyPermission { mutable: false, .. })1123 ] {1124 return Err(<Error<T>>::NoPermission.into());1125 }11261127 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1128 let property_permission = property_permission.clone();1129 permissions.try_set(property_permission.key, property_permission.permission)1130 })1131 .map_err(<Error<T>>::from)?;11321133 Self::deposit_event(Event::PropertyPermissionSet(1134 collection.id,1135 property_permission.key,1136 ));11371138 Ok(())1139 }11401141 1142 1143 1144 1145 1146 #[transactional]1147 pub fn set_token_property_permissions(1148 collection: &CollectionHandle<T>,1149 sender: &T::CrossAccountId,1150 property_permissions: Vec<PropertyKeyPermission>,1151 ) -> DispatchResult {1152 for prop_pemission in property_permissions {1153 Self::set_property_permission(collection, sender, prop_pemission)?;1154 }11551156 Ok(())1157 }11581159 1160 pub fn get_collection_property(1161 collection_id: CollectionId,1162 key: &PropertyKey,1163 ) -> Option<PropertyValue> {1164 Self::collection_properties(collection_id).get(key).cloned()1165 }11661167 1168 pub fn bytes_keys_to_property_keys(1169 keys: Vec<Vec<u8>>,1170 ) -> Result<Vec<PropertyKey>, DispatchError> {1171 keys.into_iter()1172 .map(|key| -> Result<PropertyKey, DispatchError> {1173 key.try_into()1174 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1175 })1176 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1177 }11781179 1180 pub fn filter_collection_properties(1181 collection_id: CollectionId,1182 keys: Option<Vec<PropertyKey>>,1183 ) -> Result<Vec<Property>, DispatchError> {1184 let properties = Self::collection_properties(collection_id);11851186 let properties = keys1187 .map(|keys| {1188 keys.into_iter()1189 .filter_map(|key| {1190 properties.get(&key).map(|value| Property {1191 key,1192 value: value.clone(),1193 })1194 })1195 .collect()1196 })1197 .unwrap_or_else(|| {1198 properties1199 .into_iter()1200 .map(|(key, value)| Property { key, value })1201 .collect()1202 });12031204 Ok(properties)1205 }12061207 1208 pub fn filter_property_permissions(1209 collection_id: CollectionId,1210 keys: Option<Vec<PropertyKey>>,1211 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1212 let permissions = Self::property_permissions(collection_id);12131214 let key_permissions = keys1215 .map(|keys| {1216 keys.into_iter()1217 .filter_map(|key| {1218 permissions1219 .get(&key)1220 .map(|permission| PropertyKeyPermission {1221 key,1222 permission: permission.clone(),1223 })1224 })1225 .collect()1226 })1227 .unwrap_or_else(|| {1228 permissions1229 .into_iter()1230 .map(|(key, permission)| PropertyKeyPermission { key, permission })1231 .collect()1232 });12331234 Ok(key_permissions)1235 }12361237 1238 pub fn toggle_allowlist(1239 collection: &CollectionHandle<T>,1240 sender: &T::CrossAccountId,1241 user: &T::CrossAccountId,1242 allowed: bool,1243 ) -> DispatchResult {1244 collection.check_is_owner_or_admin(sender)?;12451246 12471248 if allowed {1249 <Allowlist<T>>::insert((collection.id, user), true);1250 } else {1251 <Allowlist<T>>::remove((collection.id, user));1252 }12531254 Ok(())1255 }12561257 1258 pub fn toggle_admin(1259 collection: &CollectionHandle<T>,1260 sender: &T::CrossAccountId,1261 user: &T::CrossAccountId,1262 admin: bool,1263 ) -> DispatchResult {1264 collection.check_is_owner(sender)?;12651266 let was_admin = <IsAdmin<T>>::get((collection.id, user));1267 if was_admin == admin {1268 return Ok(());1269 }1270 let amount = <AdminAmount<T>>::get(collection.id);12711272 if admin {1273 let amount = amount1274 .checked_add(1)1275 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1276 ensure!(1277 amount <= Self::collection_admins_limit(),1278 <Error<T>>::CollectionAdminCountExceeded,1279 );12801281 12821283 <AdminAmount<T>>::insert(collection.id, amount);1284 <IsAdmin<T>>::insert((collection.id, user), true);1285 } else {1286 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1287 <IsAdmin<T>>::remove((collection.id, user));1288 }12891290 Ok(())1291 }12921293 1294 pub fn clamp_limits(1295 mode: CollectionMode,1296 old_limit: &CollectionLimits,1297 mut new_limit: CollectionLimits,1298 ) -> Result<CollectionLimits, DispatchError> {1299 let limits = old_limit;1300 limit_default!(old_limit, new_limit,1301 account_token_ownership_limit => ensure!(1302 new_limit <= MAX_TOKEN_OWNERSHIP,1303 <Error<T>>::CollectionLimitBoundsExceeded,1304 ),1305 sponsored_data_size => ensure!(1306 new_limit <= CUSTOM_DATA_LIMIT,1307 <Error<T>>::CollectionLimitBoundsExceeded,1308 ),13091310 sponsored_data_rate_limit => {},1311 token_limit => ensure!(1312 old_limit >= new_limit && new_limit > 0,1313 <Error<T>>::CollectionTokenLimitExceeded1314 ),13151316 sponsor_transfer_timeout(match mode {1317 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1318 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1319 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1320 }) => ensure!(1321 new_limit <= MAX_SPONSOR_TIMEOUT,1322 <Error<T>>::CollectionLimitBoundsExceeded,1323 ),1324 sponsor_approve_timeout => {},1325 owner_can_transfer => ensure!(1326 !limits.owner_can_transfer_instaled() ||1327 old_limit || !new_limit,1328 <Error<T>>::OwnerPermissionsCantBeReverted,1329 ),1330 owner_can_destroy => ensure!(1331 old_limit || !new_limit,1332 <Error<T>>::OwnerPermissionsCantBeReverted,1333 ),1334 transfers_enabled => {},1335 );1336 Ok(new_limit)1337 }13381339 1340 pub fn clamp_permissions(1341 _mode: CollectionMode,1342 old_permission: &CollectionPermissions,1343 mut new_permission: CollectionPermissions,1344 ) -> Result<CollectionPermissions, DispatchError> {1345 limit_default_clone!(old_permission, new_permission,1346 access => {},1347 mint_mode => {},1348 nesting => { },1349 );1350 Ok(new_permission)1351 }1352}135313541355#[macro_export]1356macro_rules! unsupported {1357 ($runtime:path) => {1358 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1359 };1360}136113621363pub trait CommonWeightInfo<CrossAccountId> {1364 1365 fn create_item() -> Weight;13661367 1368 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;13691370 1371 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;13721373 1374 fn burn_item() -> Weight;13751376 1377 1378 1379 fn set_collection_properties(amount: u32) -> Weight;13801381 1382 1383 1384 fn delete_collection_properties(amount: u32) -> Weight;13851386 1387 1388 1389 fn set_token_properties(amount: u32) -> Weight;13901391 1392 1393 1394 fn delete_token_properties(amount: u32) -> Weight;13951396 1397 1398 1399 fn set_token_property_permissions(amount: u32) -> Weight;14001401 1402 fn transfer() -> Weight;14031404 1405 fn approve() -> Weight;14061407 1408 fn transfer_from() -> Weight;14091410 1411 fn burn_from() -> Weight;14121413 1414 1415 1416 1417 fn burn_recursively_self_raw() -> Weight;14181419 1420 1421 1422 fn burn_recursively_breadth_raw(amount: u32) -> Weight;14231424 1425 1426 1427 1428 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1429 Self::burn_recursively_self_raw()1430 .saturating_mul(max_selfs.max(1) as u64)1431 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1432 }1433}143414351436pub trait RefungibleExtensionsWeightInfo {1437 1438 fn repartition() -> Weight;1439}144014411442144314441445pub trait CommonCollectionOperations<T: Config> {1446 1447 1448 1449 1450 1451 1452 fn create_item(1453 &self,1454 sender: T::CrossAccountId,1455 to: T::CrossAccountId,1456 data: CreateItemData,1457 nesting_budget: &dyn Budget,1458 ) -> DispatchResultWithPostInfo;14591460 1461 1462 1463 1464 1465 1466 fn create_multiple_items(1467 &self,1468 sender: T::CrossAccountId,1469 to: T::CrossAccountId,1470 data: Vec<CreateItemData>,1471 nesting_budget: &dyn Budget,1472 ) -> DispatchResultWithPostInfo;14731474 1475 1476 1477 1478 1479 1480 fn create_multiple_items_ex(1481 &self,1482 sender: T::CrossAccountId,1483 data: CreateItemExData<T::CrossAccountId>,1484 nesting_budget: &dyn Budget,1485 ) -> DispatchResultWithPostInfo;14861487 1488 1489 1490 1491 1492 fn burn_item(1493 &self,1494 sender: T::CrossAccountId,1495 token: TokenId,1496 amount: u128,1497 ) -> DispatchResultWithPostInfo;14981499 1500 1501 1502 1503 1504 1505 fn burn_item_recursively(1506 &self,1507 sender: T::CrossAccountId,1508 token: TokenId,1509 self_budget: &dyn Budget,1510 breadth_budget: &dyn Budget,1511 ) -> DispatchResultWithPostInfo;15121513 1514 1515 1516 1517 fn set_collection_properties(1518 &self,1519 sender: T::CrossAccountId,1520 properties: Vec<Property>,1521 ) -> DispatchResultWithPostInfo;15221523 1524 1525 1526 1527 fn delete_collection_properties(1528 &self,1529 sender: &T::CrossAccountId,1530 property_keys: Vec<PropertyKey>,1531 ) -> DispatchResultWithPostInfo;15321533 1534 1535 1536 1537 1538 1539 1540 1541 1542 fn set_token_properties(1543 &self,1544 sender: T::CrossAccountId,1545 token_id: TokenId,1546 properties: Vec<Property>,1547 budget: &dyn Budget,1548 ) -> DispatchResultWithPostInfo;15491550 1551 1552 1553 1554 1555 1556 1557 1558 1559 fn delete_token_properties(1560 &self,1561 sender: T::CrossAccountId,1562 token_id: TokenId,1563 property_keys: Vec<PropertyKey>,1564 budget: &dyn Budget,1565 ) -> DispatchResultWithPostInfo;15661567 1568 1569 1570 1571 1572 1573 fn set_token_property_permissions(1574 &self,1575 sender: &T::CrossAccountId,1576 property_permissions: Vec<PropertyKeyPermission>,1577 ) -> DispatchResultWithPostInfo;15781579 1580 1581 1582 1583 1584 1585 1586 fn transfer(1587 &self,1588 sender: T::CrossAccountId,1589 to: T::CrossAccountId,1590 token: TokenId,1591 amount: u128,1592 budget: &dyn Budget,1593 ) -> DispatchResultWithPostInfo;15941595 1596 1597 1598 1599 1600 1601 fn approve(1602 &self,1603 sender: T::CrossAccountId,1604 spender: T::CrossAccountId,1605 token: TokenId,1606 amount: u128,1607 ) -> DispatchResultWithPostInfo;16081609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 fn transfer_from(1620 &self,1621 sender: T::CrossAccountId,1622 from: T::CrossAccountId,1623 to: T::CrossAccountId,1624 token: TokenId,1625 amount: u128,1626 budget: &dyn Budget,1627 ) -> DispatchResultWithPostInfo;16281629 1630 1631 1632 1633 1634 1635 1636 1637 1638 fn burn_from(1639 &self,1640 sender: T::CrossAccountId,1641 from: T::CrossAccountId,1642 token: TokenId,1643 amount: u128,1644 budget: &dyn Budget,1645 ) -> DispatchResultWithPostInfo;16461647 1648 1649 1650 1651 1652 1653 fn check_nesting(1654 &self,1655 sender: T::CrossAccountId,1656 from: (CollectionId, TokenId),1657 under: TokenId,1658 budget: &dyn Budget,1659 ) -> DispatchResult;16601661 1662 1663 1664 1665 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));16661667 1668 1669 1670 1671 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));16721673 1674 1675 1676 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;16771678 1679 fn collection_tokens(&self) -> Vec<TokenId>;16801681 1682 1683 1684 fn token_exists(&self, token: TokenId) -> bool;16851686 1687 fn last_token_id(&self) -> TokenId;16881689 1690 1691 1692 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;16931694 1695 1696 1697 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;16981699 1700 1701 1702 1703 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17041705 1706 1707 1708 1709 1710 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17111712 1713 fn total_supply(&self) -> u32;17141715 1716 1717 1718 fn account_balance(&self, account: T::CrossAccountId) -> u32;17191720 1721 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;17221723 1724 fn total_pieces(&self, token: TokenId) -> Option<u128>;17251726 1727 1728 1729 1730 1731 fn allowance(1732 &self,1733 sender: T::CrossAccountId,1734 spender: T::CrossAccountId,1735 token: TokenId,1736 ) -> u128;17371738 1739 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1740}174117421743pub trait RefungibleExtensions<T>1744where1745 T: Config,1746{1747 1748 1749 1750 1751 1752 1753 1754 fn repartition(1755 &self,1756 sender: &T::CrossAccountId,1757 token: TokenId,1758 amount: u128,1759 ) -> DispatchResultWithPostInfo;1760}17611762176317641765pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1766 let post_info = PostDispatchInfo {1767 actual_weight: Some(weight),1768 pays_fee: Pays::Yes,1769 };1770 match res {1771 Ok(()) => Ok(post_info),1772 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1773 }1774}17751776impl<T: Config> From<PropertiesError> for Error<T> {1777 fn from(error: PropertiesError) -> Self {1778 match error {1779 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1780 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1781 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1782 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1783 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1784 }1785 }1786}