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,538 539 UnsupportedOperation,540541 542 NotSufficientFounds,543544 545 UserIsNotAllowedToNest,546 547 SourceCollectionIsNotAllowedToNest,548549 550 CollectionFieldSizeExceeded,551552 553 NoSpaceForProperty,554555 556 PropertyLimitReached,557558 559 PropertyKeyIsTooLong,560561 562 InvalidCharacterInPropertyKey,563564 565 EmptyPropertyKey,566567 568 CollectionIsExternal,569570 571 CollectionIsInternal,572 }573574 575 #[pallet::storage]576 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;577578 579 #[pallet::storage]580 pub type DestroyedCollectionCount<T> =581 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;582583 584 #[pallet::storage]585 pub type CollectionById<T> = StorageMap<586 Hasher = Blake2_128Concat,587 Key = CollectionId,588 Value = Collection<<T as frame_system::Config>::AccountId>,589 QueryKind = OptionQuery,590 >;591592 593 #[pallet::storage]594 #[pallet::getter(fn collection_properties)]595 pub type CollectionProperties<T> = StorageMap<596 Hasher = Blake2_128Concat,597 Key = CollectionId,598 Value = Properties,599 QueryKind = ValueQuery,600 OnEmpty = up_data_structs::CollectionProperties,601 >;602603 604 #[pallet::storage]605 #[pallet::getter(fn property_permissions)]606 pub type CollectionPropertyPermissions<T> = StorageMap<607 Hasher = Blake2_128Concat,608 Key = CollectionId,609 Value = PropertiesPermissionMap,610 QueryKind = ValueQuery,611 >;612613 614 #[pallet::storage]615 pub type AdminAmount<T> = StorageMap<616 Hasher = Blake2_128Concat,617 Key = CollectionId,618 Value = u32,619 QueryKind = ValueQuery,620 >;621622 623 #[pallet::storage]624 pub type IsAdmin<T: Config> = StorageNMap<625 Key = (626 Key<Blake2_128Concat, CollectionId>,627 Key<Blake2_128Concat, T::CrossAccountId>,628 ),629 Value = bool,630 QueryKind = ValueQuery,631 >;632633 634 #[pallet::storage]635 pub type Allowlist<T: Config> = StorageNMap<636 Key = (637 Key<Blake2_128Concat, CollectionId>,638 Key<Blake2_128Concat, T::CrossAccountId>,639 ),640 Value = bool,641 QueryKind = ValueQuery,642 >;643644 645 #[pallet::storage]646 pub type DummyStorageValue<T: Config> = StorageValue<647 Value = (648 CollectionStats,649 CollectionId,650 TokenId,651 TokenChild,652 PhantomType<(653 TokenData<T::CrossAccountId>,654 RpcCollection<T::AccountId>,655 656 RmrkCollectionInfo<T::AccountId>,657 RmrkInstanceInfo<T::AccountId>,658 RmrkResourceInfo,659 RmrkPropertyInfo,660 RmrkBaseInfo<T::AccountId>,661 RmrkPartType,662 RmrkBoundedTheme,663 RmrkNftChild,664 )>,665 ),666 QueryKind = OptionQuery,667 >;668669 #[pallet::hooks]670 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {671 fn on_runtime_upgrade() -> Weight {672 StorageVersion::new(1).put::<Pallet<T>>();673674 0675 }676 }677}678679impl<T: Config> Pallet<T> {680 681 682 683 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {684 ensure!(685 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,686 <Error<T>>::AddressIsZero687 );688 Ok(())689 }690691 692 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {693 <IsAdmin<T>>::iter_prefix((collection,))694 .map(|(a, _)| a)695 .collect()696 }697698 699 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {700 <Allowlist<T>>::iter_prefix((collection,))701 .map(|(a, _)| a)702 .collect()703 }704705 706 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {707 <Allowlist<T>>::get((collection, user))708 }709710 711 pub fn collection_stats() -> CollectionStats {712 let created = <CreatedCollectionCount<T>>::get();713 let destroyed = <DestroyedCollectionCount<T>>::get();714 CollectionStats {715 created: created.0,716 destroyed: destroyed.0,717 alive: created.0 - destroyed.0,718 }719 }720721 722 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {723 let collection = <CollectionById<T>>::get(collection);724 if collection.is_none() {725 return None;726 }727728 let collection = collection.unwrap();729 let limits = collection.limits;730 let effective_limits = CollectionLimits {731 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),732 sponsored_data_size: Some(limits.sponsored_data_size()),733 sponsored_data_rate_limit: Some(734 limits735 .sponsored_data_rate_limit736 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),737 ),738 token_limit: Some(limits.token_limit()),739 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(740 match collection.mode {741 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,742 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,743 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,744 },745 )),746 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),747 owner_can_transfer: Some(limits.owner_can_transfer()),748 owner_can_destroy: Some(limits.owner_can_destroy()),749 transfers_enabled: Some(limits.transfers_enabled()),750 };751752 Some(effective_limits)753 }754755 756 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {757 let Collection {758 name,759 description,760 owner,761 mode,762 token_prefix,763 sponsorship,764 limits,765 permissions,766 external_collection,767 } = <CollectionById<T>>::get(collection)?;768769 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)770 .into_iter()771 .map(|(key, permission)| PropertyKeyPermission { key, permission })772 .collect();773774 let properties = <CollectionProperties<T>>::get(collection)775 .into_iter()776 .map(|(key, value)| Property { key, value })777 .collect();778779 let permissions = CollectionPermissions {780 access: Some(permissions.access()),781 mint_mode: Some(permissions.mint_mode()),782 nesting: Some(permissions.nesting().clone()),783 };784785 Some(RpcCollection {786 name: name.into_inner(),787 description: description.into_inner(),788 owner,789 mode,790 token_prefix: token_prefix.into_inner(),791 sponsorship,792 limits,793 permissions,794 token_property_permissions,795 properties,796 read_only: external_collection,797 })798 }799}800801macro_rules! limit_default {802 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{803 $(804 if let Some($new) = $new.$field {805 let $old = $old.$field($($arg)?);806 let _ = $new;807 let _ = $old;808 $check809 } else {810 $new.$field = $old.$field811 }812 )*813 }};814}815macro_rules! limit_default_clone {816 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{817 $(818 if let Some($new) = $new.$field.clone() {819 let $old = $old.$field($($arg)?);820 let _ = $new;821 let _ = $old;822 $check823 } else {824 $new.$field = $old.$field.clone()825 }826 )*827 }};828}829830impl<T: Config> Pallet<T> {831 832 833 834 835 836 pub fn init_collection(837 owner: T::CrossAccountId,838 data: CreateCollectionData<T::AccountId>,839 is_external: bool,840 ) -> Result<CollectionId, DispatchError> {841 {842 ensure!(843 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,844 Error::<T>::CollectionTokenPrefixLimitExceeded845 );846 }847848 let created_count = <CreatedCollectionCount<T>>::get()849 .0850 .checked_add(1)851 .ok_or(ArithmeticError::Overflow)?;852 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;853 let id = CollectionId(created_count);854855 856 ensure!(857 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,858 <Error<T>>::TotalCollectionsLimitExceeded859 );860861 862863 let collection = Collection {864 owner: owner.as_sub().clone(),865 name: data.name,866 mode: data.mode.clone(),867 description: data.description,868 token_prefix: data.token_prefix,869 sponsorship: data870 .pending_sponsor871 .map(SponsorshipState::Unconfirmed)872 .unwrap_or_default(),873 limits: data874 .limits875 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))876 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,877 permissions: data878 .permissions879 .map(|permissions| {880 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)881 })882 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,883 external_collection: is_external,884 };885886 let mut collection_properties = up_data_structs::CollectionProperties::get();887 collection_properties888 .try_set_from_iter(data.properties.into_iter())889 .map_err(<Error<T>>::from)?;890891 CollectionProperties::<T>::insert(id, collection_properties);892893 let mut token_props_permissions = PropertiesPermissionMap::new();894 token_props_permissions895 .try_set_from_iter(data.token_property_permissions.into_iter())896 .map_err(<Error<T>>::from)?;897898 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);899900 901 {902 let mut imbalance =903 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();904 imbalance.subsume(905 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(906 &T::TreasuryAccountId::get(),907 T::CollectionCreationPrice::get(),908 ),909 );910 <T as Config>::Currency::settle(911 owner.as_sub(),912 imbalance,913 WithdrawReasons::TRANSFER,914 ExistenceRequirement::KeepAlive,915 )916 .map_err(|_| Error::<T>::NotSufficientFounds)?;917 }918919 <CreatedCollectionCount<T>>::put(created_count);920 <Pallet<T>>::deposit_event(Event::CollectionCreated(921 id,922 data.mode.id(),923 owner.as_sub().clone(),924 ));925 <PalletEvm<T>>::deposit_log(926 erc::CollectionHelpersEvents::CollectionCreated {927 owner: *owner.as_eth(),928 collection_id: eth::collection_id_to_address(id),929 }930 .to_log(T::ContractAddress::get()),931 );932 <CollectionById<T>>::insert(id, collection);933 Ok(id)934 }935936 937 938 939 940 pub fn destroy_collection(941 collection: CollectionHandle<T>,942 sender: &T::CrossAccountId,943 ) -> DispatchResult {944 ensure!(945 collection.limits.owner_can_destroy(),946 <Error<T>>::NoPermission,947 );948 collection.check_is_owner(sender)?;949950 let destroyed_collections = <DestroyedCollectionCount<T>>::get()951 .0952 .checked_add(1)953 .ok_or(ArithmeticError::Overflow)?;954955 956957 <DestroyedCollectionCount<T>>::put(destroyed_collections);958 <CollectionById<T>>::remove(collection.id);959 <AdminAmount<T>>::remove(collection.id);960 <IsAdmin<T>>::remove_prefix((collection.id,), None);961 <Allowlist<T>>::remove_prefix((collection.id,), None);962 <CollectionProperties<T>>::remove(collection.id);963964 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));965 Ok(())966 }967968 969 970 971 972 973 pub fn set_collection_property(974 collection: &CollectionHandle<T>,975 sender: &T::CrossAccountId,976 property: Property,977 ) -> DispatchResult {978 collection.check_is_owner_or_admin(sender)?;979980 CollectionProperties::<T>::try_mutate(collection.id, |properties| {981 let property = property.clone();982 properties.try_set(property.key, property.value)983 })984 .map_err(<Error<T>>::from)?;985986 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));987988 Ok(())989 }990991 992 993 994 995 996 pub fn set_scoped_collection_property(997 collection_id: CollectionId,998 scope: PropertyScope,999 property: Property,1000 ) -> DispatchResult {1001 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1002 properties.try_scoped_set(scope, property.key, property.value)1003 })1004 .map_err(<Error<T>>::from)?;10051006 Ok(())1007 }10081009 1010 1011 1012 1013 1014 pub fn set_scoped_collection_properties(1015 collection_id: CollectionId,1016 scope: PropertyScope,1017 properties: impl Iterator<Item = Property>,1018 ) -> DispatchResult {1019 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1020 stored_properties.try_scoped_set_from_iter(scope, properties)1021 })1022 .map_err(<Error<T>>::from)?;10231024 Ok(())1025 }10261027 1028 1029 1030 1031 1032 #[transactional]1033 pub fn set_collection_properties(1034 collection: &CollectionHandle<T>,1035 sender: &T::CrossAccountId,1036 properties: Vec<Property>,1037 ) -> DispatchResult {1038 for property in properties {1039 Self::set_collection_property(collection, sender, property)?;1040 }10411042 Ok(())1043 }10441045 1046 1047 1048 1049 1050 pub fn delete_collection_property(1051 collection: &CollectionHandle<T>,1052 sender: &T::CrossAccountId,1053 property_key: PropertyKey,1054 ) -> DispatchResult {1055 collection.check_is_owner_or_admin(sender)?;10561057 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1058 properties.remove(&property_key)1059 })1060 .map_err(<Error<T>>::from)?;10611062 Self::deposit_event(Event::CollectionPropertyDeleted(1063 collection.id,1064 property_key,1065 ));10661067 Ok(())1068 }10691070 1071 1072 1073 1074 1075 #[transactional]1076 pub fn delete_collection_properties(1077 collection: &CollectionHandle<T>,1078 sender: &T::CrossAccountId,1079 property_keys: Vec<PropertyKey>,1080 ) -> DispatchResult {1081 for key in property_keys {1082 Self::delete_collection_property(collection, sender, key)?;1083 }10841085 Ok(())1086 }10871088 1089 1090 1091 1092 1093 1094 pub fn set_property_permission_unchecked(1095 collection: CollectionId,1096 property_permission: PropertyKeyPermission,1097 ) -> DispatchResult {1098 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1099 permissions.try_set(property_permission.key, property_permission.permission)1100 })1101 .map_err(<Error<T>>::from)?;1102 Ok(())1103 }11041105 1106 1107 1108 1109 1110 pub fn set_property_permission(1111 collection: &CollectionHandle<T>,1112 sender: &T::CrossAccountId,1113 property_permission: PropertyKeyPermission,1114 ) -> DispatchResult {1115 Self::set_scoped_property_permission(1116 collection,1117 sender,1118 PropertyScope::None,1119 property_permission,1120 )1121 }11221123 1124 1125 1126 1127 1128 1129 pub fn set_scoped_property_permission(1130 collection: &CollectionHandle<T>,1131 sender: &T::CrossAccountId,1132 scope: PropertyScope,1133 property_permission: PropertyKeyPermission,1134 ) -> DispatchResult {1135 collection.check_is_owner_or_admin(sender)?;11361137 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1138 let current_permission = all_permissions.get(&property_permission.key);1139 if matches![1140 current_permission,1141 Some(PropertyPermission { mutable: false, .. })1142 ] {1143 return Err(<Error<T>>::NoPermission.into());1144 }11451146 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1147 let property_permission = property_permission.clone();1148 permissions.try_scoped_set(1149 scope,1150 property_permission.key,1151 property_permission.permission,1152 )1153 })1154 .map_err(<Error<T>>::from)?;11551156 Self::deposit_event(Event::PropertyPermissionSet(1157 collection.id,1158 property_permission.key,1159 ));11601161 Ok(())1162 }11631164 1165 1166 1167 1168 1169 #[transactional]1170 pub fn set_token_property_permissions(1171 collection: &CollectionHandle<T>,1172 sender: &T::CrossAccountId,1173 property_permissions: Vec<PropertyKeyPermission>,1174 ) -> DispatchResult {1175 Self::set_scoped_token_property_permissions(1176 collection,1177 sender,1178 PropertyScope::None,1179 property_permissions,1180 )1181 }11821183 1184 1185 1186 1187 1188 1189 #[transactional]1190 pub fn set_scoped_token_property_permissions(1191 collection: &CollectionHandle<T>,1192 sender: &T::CrossAccountId,1193 scope: PropertyScope,1194 property_permissions: Vec<PropertyKeyPermission>,1195 ) -> DispatchResult {1196 for prop_pemission in property_permissions {1197 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1198 }11991200 Ok(())1201 }12021203 1204 pub fn get_collection_property(1205 collection_id: CollectionId,1206 key: &PropertyKey,1207 ) -> Option<PropertyValue> {1208 Self::collection_properties(collection_id).get(key).cloned()1209 }12101211 1212 pub fn bytes_keys_to_property_keys(1213 keys: Vec<Vec<u8>>,1214 ) -> Result<Vec<PropertyKey>, DispatchError> {1215 keys.into_iter()1216 .map(|key| -> Result<PropertyKey, DispatchError> {1217 key.try_into()1218 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1219 })1220 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1221 }12221223 1224 pub fn filter_collection_properties(1225 collection_id: CollectionId,1226 keys: Option<Vec<PropertyKey>>,1227 ) -> Result<Vec<Property>, DispatchError> {1228 let properties = Self::collection_properties(collection_id);12291230 let properties = keys1231 .map(|keys| {1232 keys.into_iter()1233 .filter_map(|key| {1234 properties.get(&key).map(|value| Property {1235 key,1236 value: value.clone(),1237 })1238 })1239 .collect()1240 })1241 .unwrap_or_else(|| {1242 properties1243 .into_iter()1244 .map(|(key, value)| Property { key, value })1245 .collect()1246 });12471248 Ok(properties)1249 }12501251 1252 pub fn filter_property_permissions(1253 collection_id: CollectionId,1254 keys: Option<Vec<PropertyKey>>,1255 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1256 let permissions = Self::property_permissions(collection_id);12571258 let key_permissions = keys1259 .map(|keys| {1260 keys.into_iter()1261 .filter_map(|key| {1262 permissions1263 .get(&key)1264 .map(|permission| PropertyKeyPermission {1265 key,1266 permission: permission.clone(),1267 })1268 })1269 .collect()1270 })1271 .unwrap_or_else(|| {1272 permissions1273 .into_iter()1274 .map(|(key, permission)| PropertyKeyPermission { key, permission })1275 .collect()1276 });12771278 Ok(key_permissions)1279 }12801281 1282 pub fn toggle_allowlist(1283 collection: &CollectionHandle<T>,1284 sender: &T::CrossAccountId,1285 user: &T::CrossAccountId,1286 allowed: bool,1287 ) -> DispatchResult {1288 collection.check_is_owner_or_admin(sender)?;12891290 12911292 if allowed {1293 <Allowlist<T>>::insert((collection.id, user), true);1294 } else {1295 <Allowlist<T>>::remove((collection.id, user));1296 }12971298 Ok(())1299 }13001301 1302 pub fn toggle_admin(1303 collection: &CollectionHandle<T>,1304 sender: &T::CrossAccountId,1305 user: &T::CrossAccountId,1306 admin: bool,1307 ) -> DispatchResult {1308 collection.check_is_owner(sender)?;13091310 let was_admin = <IsAdmin<T>>::get((collection.id, user));1311 if was_admin == admin {1312 return Ok(());1313 }1314 let amount = <AdminAmount<T>>::get(collection.id);13151316 if admin {1317 let amount = amount1318 .checked_add(1)1319 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1320 ensure!(1321 amount <= Self::collection_admins_limit(),1322 <Error<T>>::CollectionAdminCountExceeded,1323 );13241325 13261327 <AdminAmount<T>>::insert(collection.id, amount);1328 <IsAdmin<T>>::insert((collection.id, user), true);1329 } else {1330 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1331 <IsAdmin<T>>::remove((collection.id, user));1332 }13331334 Ok(())1335 }13361337 1338 pub fn clamp_limits(1339 mode: CollectionMode,1340 old_limit: &CollectionLimits,1341 mut new_limit: CollectionLimits,1342 ) -> Result<CollectionLimits, DispatchError> {1343 let limits = old_limit;1344 limit_default!(old_limit, new_limit,1345 account_token_ownership_limit => ensure!(1346 new_limit <= MAX_TOKEN_OWNERSHIP,1347 <Error<T>>::CollectionLimitBoundsExceeded,1348 ),1349 sponsored_data_size => ensure!(1350 new_limit <= CUSTOM_DATA_LIMIT,1351 <Error<T>>::CollectionLimitBoundsExceeded,1352 ),13531354 sponsored_data_rate_limit => {},1355 token_limit => ensure!(1356 old_limit >= new_limit && new_limit > 0,1357 <Error<T>>::CollectionTokenLimitExceeded1358 ),13591360 sponsor_transfer_timeout(match mode {1361 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1362 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1363 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1364 }) => ensure!(1365 new_limit <= MAX_SPONSOR_TIMEOUT,1366 <Error<T>>::CollectionLimitBoundsExceeded,1367 ),1368 sponsor_approve_timeout => {},1369 owner_can_transfer => ensure!(1370 !limits.owner_can_transfer_instaled() ||1371 old_limit || !new_limit,1372 <Error<T>>::OwnerPermissionsCantBeReverted,1373 ),1374 owner_can_destroy => ensure!(1375 old_limit || !new_limit,1376 <Error<T>>::OwnerPermissionsCantBeReverted,1377 ),1378 transfers_enabled => {},1379 );1380 Ok(new_limit)1381 }13821383 1384 pub fn clamp_permissions(1385 _mode: CollectionMode,1386 old_permission: &CollectionPermissions,1387 mut new_permission: CollectionPermissions,1388 ) -> Result<CollectionPermissions, DispatchError> {1389 limit_default_clone!(old_permission, new_permission,1390 access => {},1391 mint_mode => {},1392 nesting => { },1393 );1394 Ok(new_permission)1395 }1396}139713981399#[macro_export]1400macro_rules! unsupported {1401 () => {1402 Err(<Error<T>>::UnsupportedOperation.into())1403 };1404}140514061407pub trait CommonWeightInfo<CrossAccountId> {1408 1409 fn create_item() -> Weight;14101411 1412 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;14131414 1415 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;14161417 1418 fn burn_item() -> Weight;14191420 1421 1422 1423 fn set_collection_properties(amount: u32) -> Weight;14241425 1426 1427 1428 fn delete_collection_properties(amount: u32) -> Weight;14291430 1431 1432 1433 fn set_token_properties(amount: u32) -> Weight;14341435 1436 1437 1438 fn delete_token_properties(amount: u32) -> Weight;14391440 1441 1442 1443 fn set_token_property_permissions(amount: u32) -> Weight;14441445 1446 fn transfer() -> Weight;14471448 1449 fn approve() -> Weight;14501451 1452 fn transfer_from() -> Weight;14531454 1455 fn burn_from() -> Weight;14561457 1458 1459 1460 1461 fn burn_recursively_self_raw() -> Weight;14621463 1464 1465 1466 fn burn_recursively_breadth_raw(amount: u32) -> Weight;14671468 1469 1470 1471 1472 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1473 Self::burn_recursively_self_raw()1474 .saturating_mul(max_selfs.max(1) as u64)1475 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1476 }14771478 1479 fn token_owner() -> Weight;1480}148114821483pub trait RefungibleExtensionsWeightInfo {1484 1485 fn repartition() -> Weight;1486}148714881489149014911492pub trait CommonCollectionOperations<T: Config> {1493 1494 1495 1496 1497 1498 1499 fn create_item(1500 &self,1501 sender: T::CrossAccountId,1502 to: T::CrossAccountId,1503 data: CreateItemData,1504 nesting_budget: &dyn Budget,1505 ) -> DispatchResultWithPostInfo;15061507 1508 1509 1510 1511 1512 1513 fn create_multiple_items(1514 &self,1515 sender: T::CrossAccountId,1516 to: T::CrossAccountId,1517 data: Vec<CreateItemData>,1518 nesting_budget: &dyn Budget,1519 ) -> DispatchResultWithPostInfo;15201521 1522 1523 1524 1525 1526 1527 fn create_multiple_items_ex(1528 &self,1529 sender: T::CrossAccountId,1530 data: CreateItemExData<T::CrossAccountId>,1531 nesting_budget: &dyn Budget,1532 ) -> DispatchResultWithPostInfo;15331534 1535 1536 1537 1538 1539 fn burn_item(1540 &self,1541 sender: T::CrossAccountId,1542 token: TokenId,1543 amount: u128,1544 ) -> DispatchResultWithPostInfo;15451546 1547 1548 1549 1550 1551 1552 fn burn_item_recursively(1553 &self,1554 sender: T::CrossAccountId,1555 token: TokenId,1556 self_budget: &dyn Budget,1557 breadth_budget: &dyn Budget,1558 ) -> DispatchResultWithPostInfo;15591560 1561 1562 1563 1564 fn set_collection_properties(1565 &self,1566 sender: T::CrossAccountId,1567 properties: Vec<Property>,1568 ) -> DispatchResultWithPostInfo;15691570 1571 1572 1573 1574 fn delete_collection_properties(1575 &self,1576 sender: &T::CrossAccountId,1577 property_keys: Vec<PropertyKey>,1578 ) -> DispatchResultWithPostInfo;15791580 1581 1582 1583 1584 1585 1586 1587 1588 1589 fn set_token_properties(1590 &self,1591 sender: T::CrossAccountId,1592 token_id: TokenId,1593 properties: Vec<Property>,1594 budget: &dyn Budget,1595 ) -> DispatchResultWithPostInfo;15961597 1598 1599 1600 1601 1602 1603 1604 1605 1606 fn delete_token_properties(1607 &self,1608 sender: T::CrossAccountId,1609 token_id: TokenId,1610 property_keys: Vec<PropertyKey>,1611 budget: &dyn Budget,1612 ) -> DispatchResultWithPostInfo;16131614 1615 1616 1617 1618 1619 1620 fn set_token_property_permissions(1621 &self,1622 sender: &T::CrossAccountId,1623 property_permissions: Vec<PropertyKeyPermission>,1624 ) -> DispatchResultWithPostInfo;16251626 1627 1628 1629 1630 1631 1632 1633 fn transfer(1634 &self,1635 sender: T::CrossAccountId,1636 to: T::CrossAccountId,1637 token: TokenId,1638 amount: u128,1639 budget: &dyn Budget,1640 ) -> DispatchResultWithPostInfo;16411642 1643 1644 1645 1646 1647 1648 fn approve(1649 &self,1650 sender: T::CrossAccountId,1651 spender: T::CrossAccountId,1652 token: TokenId,1653 amount: u128,1654 ) -> DispatchResultWithPostInfo;16551656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 fn transfer_from(1667 &self,1668 sender: T::CrossAccountId,1669 from: T::CrossAccountId,1670 to: T::CrossAccountId,1671 token: TokenId,1672 amount: u128,1673 budget: &dyn Budget,1674 ) -> DispatchResultWithPostInfo;16751676 1677 1678 1679 1680 1681 1682 1683 1684 1685 fn burn_from(1686 &self,1687 sender: T::CrossAccountId,1688 from: T::CrossAccountId,1689 token: TokenId,1690 amount: u128,1691 budget: &dyn Budget,1692 ) -> DispatchResultWithPostInfo;16931694 1695 1696 1697 1698 1699 1700 fn check_nesting(1701 &self,1702 sender: T::CrossAccountId,1703 from: (CollectionId, TokenId),1704 under: TokenId,1705 budget: &dyn Budget,1706 ) -> DispatchResult;17071708 1709 1710 1711 1712 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17131714 1715 1716 1717 1718 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));17191720 1721 1722 1723 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;17241725 1726 fn collection_tokens(&self) -> Vec<TokenId>;17271728 1729 1730 1731 fn token_exists(&self, token: TokenId) -> bool;17321733 1734 fn last_token_id(&self) -> TokenId;17351736 1737 1738 1739 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;17401741 1742 1743 1744 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17451746 1747 1748 1749 1750 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17511752 1753 1754 1755 1756 1757 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17581759 1760 fn total_supply(&self) -> u32;17611762 1763 1764 1765 fn account_balance(&self, account: T::CrossAccountId) -> u32;17661767 1768 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;17691770 1771 fn total_pieces(&self, token: TokenId) -> Option<u128>;17721773 1774 1775 1776 1777 1778 fn allowance(1779 &self,1780 sender: T::CrossAccountId,1781 spender: T::CrossAccountId,1782 token: TokenId,1783 ) -> u128;17841785 1786 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1787}178817891790pub trait RefungibleExtensions<T>1791where1792 T: Config,1793{1794 1795 1796 1797 1798 1799 1800 1801 fn repartition(1802 &self,1803 sender: &T::CrossAccountId,1804 token: TokenId,1805 amount: u128,1806 ) -> DispatchResultWithPostInfo;1807}18081809181018111812pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1813 let post_info = PostDispatchInfo {1814 actual_weight: Some(weight),1815 pays_fee: Pays::Yes,1816 };1817 match res {1818 Ok(()) => Ok(post_info),1819 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1820 }1821}18221823impl<T: Config> From<PropertiesError> for Error<T> {1824 fn from(error: PropertiesError) -> Self {1825 match error {1826 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1827 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1828 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1829 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1830 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1831 }1832 }1833}