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 collection.check_is_owner_or_admin(sender)?;11161117 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1118 let current_permission = all_permissions.get(&property_permission.key);1119 if matches![1120 current_permission,1121 Some(PropertyPermission { mutable: false, .. })1122 ] {1123 return Err(<Error<T>>::NoPermission.into());1124 }11251126 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1127 let property_permission = property_permission.clone();1128 permissions.try_set(property_permission.key, property_permission.permission)1129 })1130 .map_err(<Error<T>>::from)?;11311132 Self::deposit_event(Event::PropertyPermissionSet(1133 collection.id,1134 property_permission.key,1135 ));11361137 Ok(())1138 }11391140 1141 1142 1143 1144 1145 #[transactional]1146 pub fn set_token_property_permissions(1147 collection: &CollectionHandle<T>,1148 sender: &T::CrossAccountId,1149 property_permissions: Vec<PropertyKeyPermission>,1150 ) -> DispatchResult {1151 for prop_pemission in property_permissions {1152 Self::set_property_permission(collection, sender, prop_pemission)?;1153 }11541155 Ok(())1156 }11571158 1159 pub fn get_collection_property(1160 collection_id: CollectionId,1161 key: &PropertyKey,1162 ) -> Option<PropertyValue> {1163 Self::collection_properties(collection_id).get(key).cloned()1164 }11651166 1167 pub fn bytes_keys_to_property_keys(1168 keys: Vec<Vec<u8>>,1169 ) -> Result<Vec<PropertyKey>, DispatchError> {1170 keys.into_iter()1171 .map(|key| -> Result<PropertyKey, DispatchError> {1172 key.try_into()1173 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1174 })1175 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1176 }11771178 1179 pub fn filter_collection_properties(1180 collection_id: CollectionId,1181 keys: Option<Vec<PropertyKey>>,1182 ) -> Result<Vec<Property>, DispatchError> {1183 let properties = Self::collection_properties(collection_id);11841185 let properties = keys1186 .map(|keys| {1187 keys.into_iter()1188 .filter_map(|key| {1189 properties.get(&key).map(|value| Property {1190 key,1191 value: value.clone(),1192 })1193 })1194 .collect()1195 })1196 .unwrap_or_else(|| {1197 properties1198 .into_iter()1199 .map(|(key, value)| Property { key, value })1200 .collect()1201 });12021203 Ok(properties)1204 }12051206 1207 pub fn filter_property_permissions(1208 collection_id: CollectionId,1209 keys: Option<Vec<PropertyKey>>,1210 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1211 let permissions = Self::property_permissions(collection_id);12121213 let key_permissions = keys1214 .map(|keys| {1215 keys.into_iter()1216 .filter_map(|key| {1217 permissions1218 .get(&key)1219 .map(|permission| PropertyKeyPermission {1220 key,1221 permission: permission.clone(),1222 })1223 })1224 .collect()1225 })1226 .unwrap_or_else(|| {1227 permissions1228 .into_iter()1229 .map(|(key, permission)| PropertyKeyPermission { key, permission })1230 .collect()1231 });12321233 Ok(key_permissions)1234 }12351236 1237 pub fn toggle_allowlist(1238 collection: &CollectionHandle<T>,1239 sender: &T::CrossAccountId,1240 user: &T::CrossAccountId,1241 allowed: bool,1242 ) -> DispatchResult {1243 collection.check_is_owner_or_admin(sender)?;12441245 12461247 if allowed {1248 <Allowlist<T>>::insert((collection.id, user), true);1249 } else {1250 <Allowlist<T>>::remove((collection.id, user));1251 }12521253 Ok(())1254 }12551256 1257 pub fn toggle_admin(1258 collection: &CollectionHandle<T>,1259 sender: &T::CrossAccountId,1260 user: &T::CrossAccountId,1261 admin: bool,1262 ) -> DispatchResult {1263 collection.check_is_owner(sender)?;12641265 let was_admin = <IsAdmin<T>>::get((collection.id, user));1266 if was_admin == admin {1267 return Ok(());1268 }1269 let amount = <AdminAmount<T>>::get(collection.id);12701271 if admin {1272 let amount = amount1273 .checked_add(1)1274 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1275 ensure!(1276 amount <= Self::collection_admins_limit(),1277 <Error<T>>::CollectionAdminCountExceeded,1278 );12791280 12811282 <AdminAmount<T>>::insert(collection.id, amount);1283 <IsAdmin<T>>::insert((collection.id, user), true);1284 } else {1285 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1286 <IsAdmin<T>>::remove((collection.id, user));1287 }12881289 Ok(())1290 }12911292 1293 pub fn clamp_limits(1294 mode: CollectionMode,1295 old_limit: &CollectionLimits,1296 mut new_limit: CollectionLimits,1297 ) -> Result<CollectionLimits, DispatchError> {1298 let limits = old_limit;1299 limit_default!(old_limit, new_limit,1300 account_token_ownership_limit => ensure!(1301 new_limit <= MAX_TOKEN_OWNERSHIP,1302 <Error<T>>::CollectionLimitBoundsExceeded,1303 ),1304 sponsored_data_size => ensure!(1305 new_limit <= CUSTOM_DATA_LIMIT,1306 <Error<T>>::CollectionLimitBoundsExceeded,1307 ),13081309 sponsored_data_rate_limit => {},1310 token_limit => ensure!(1311 old_limit >= new_limit && new_limit > 0,1312 <Error<T>>::CollectionTokenLimitExceeded1313 ),13141315 sponsor_transfer_timeout(match mode {1316 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1317 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1318 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1319 }) => ensure!(1320 new_limit <= MAX_SPONSOR_TIMEOUT,1321 <Error<T>>::CollectionLimitBoundsExceeded,1322 ),1323 sponsor_approve_timeout => {},1324 owner_can_transfer => ensure!(1325 !limits.owner_can_transfer_instaled() ||1326 old_limit || !new_limit,1327 <Error<T>>::OwnerPermissionsCantBeReverted,1328 ),1329 owner_can_destroy => ensure!(1330 old_limit || !new_limit,1331 <Error<T>>::OwnerPermissionsCantBeReverted,1332 ),1333 transfers_enabled => {},1334 );1335 Ok(new_limit)1336 }13371338 1339 pub fn clamp_permissions(1340 _mode: CollectionMode,1341 old_permission: &CollectionPermissions,1342 mut new_permission: CollectionPermissions,1343 ) -> Result<CollectionPermissions, DispatchError> {1344 limit_default_clone!(old_permission, new_permission,1345 access => {},1346 mint_mode => {},1347 nesting => { },1348 );1349 Ok(new_permission)1350 }1351}135213531354#[macro_export]1355macro_rules! unsupported {1356 () => {1357 Err(<Error<T>>::UnsupportedOperation.into())1358 };1359}136013611362pub trait CommonWeightInfo<CrossAccountId> {1363 1364 fn create_item() -> Weight;13651366 1367 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;13681369 1370 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;13711372 1373 fn burn_item() -> Weight;13741375 1376 1377 1378 fn set_collection_properties(amount: u32) -> Weight;13791380 1381 1382 1383 fn delete_collection_properties(amount: u32) -> Weight;13841385 1386 1387 1388 fn set_token_properties(amount: u32) -> Weight;13891390 1391 1392 1393 fn delete_token_properties(amount: u32) -> Weight;13941395 1396 1397 1398 fn set_token_property_permissions(amount: u32) -> Weight;13991400 1401 fn transfer() -> Weight;14021403 1404 fn approve() -> Weight;14051406 1407 fn transfer_from() -> Weight;14081409 1410 fn burn_from() -> Weight;14111412 1413 1414 1415 1416 fn burn_recursively_self_raw() -> Weight;14171418 1419 1420 1421 fn burn_recursively_breadth_raw(amount: u32) -> Weight;14221423 1424 1425 1426 1427 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1428 Self::burn_recursively_self_raw()1429 .saturating_mul(max_selfs.max(1) as u64)1430 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1431 }1432}143314341435pub trait RefungibleExtensionsWeightInfo {1436 1437 fn repartition() -> Weight;1438}143914401441144214431444pub trait CommonCollectionOperations<T: Config> {1445 1446 1447 1448 1449 1450 1451 fn create_item(1452 &self,1453 sender: T::CrossAccountId,1454 to: T::CrossAccountId,1455 data: CreateItemData,1456 nesting_budget: &dyn Budget,1457 ) -> DispatchResultWithPostInfo;14581459 1460 1461 1462 1463 1464 1465 fn create_multiple_items(1466 &self,1467 sender: T::CrossAccountId,1468 to: T::CrossAccountId,1469 data: Vec<CreateItemData>,1470 nesting_budget: &dyn Budget,1471 ) -> DispatchResultWithPostInfo;14721473 1474 1475 1476 1477 1478 1479 fn create_multiple_items_ex(1480 &self,1481 sender: T::CrossAccountId,1482 data: CreateItemExData<T::CrossAccountId>,1483 nesting_budget: &dyn Budget,1484 ) -> DispatchResultWithPostInfo;14851486 1487 1488 1489 1490 1491 fn burn_item(1492 &self,1493 sender: T::CrossAccountId,1494 token: TokenId,1495 amount: u128,1496 ) -> DispatchResultWithPostInfo;14971498 1499 1500 1501 1502 1503 1504 fn burn_item_recursively(1505 &self,1506 sender: T::CrossAccountId,1507 token: TokenId,1508 self_budget: &dyn Budget,1509 breadth_budget: &dyn Budget,1510 ) -> DispatchResultWithPostInfo;15111512 1513 1514 1515 1516 fn set_collection_properties(1517 &self,1518 sender: T::CrossAccountId,1519 properties: Vec<Property>,1520 ) -> DispatchResultWithPostInfo;15211522 1523 1524 1525 1526 fn delete_collection_properties(1527 &self,1528 sender: &T::CrossAccountId,1529 property_keys: Vec<PropertyKey>,1530 ) -> DispatchResultWithPostInfo;15311532 1533 1534 1535 1536 1537 1538 1539 1540 1541 fn set_token_properties(1542 &self,1543 sender: T::CrossAccountId,1544 token_id: TokenId,1545 properties: Vec<Property>,1546 budget: &dyn Budget,1547 ) -> DispatchResultWithPostInfo;15481549 1550 1551 1552 1553 1554 1555 1556 1557 1558 fn delete_token_properties(1559 &self,1560 sender: T::CrossAccountId,1561 token_id: TokenId,1562 property_keys: Vec<PropertyKey>,1563 budget: &dyn Budget,1564 ) -> DispatchResultWithPostInfo;15651566 1567 1568 1569 1570 1571 1572 fn set_token_property_permissions(1573 &self,1574 sender: &T::CrossAccountId,1575 property_permissions: Vec<PropertyKeyPermission>,1576 ) -> DispatchResultWithPostInfo;15771578 1579 1580 1581 1582 1583 1584 1585 fn transfer(1586 &self,1587 sender: T::CrossAccountId,1588 to: T::CrossAccountId,1589 token: TokenId,1590 amount: u128,1591 budget: &dyn Budget,1592 ) -> DispatchResultWithPostInfo;15931594 1595 1596 1597 1598 1599 1600 fn approve(1601 &self,1602 sender: T::CrossAccountId,1603 spender: T::CrossAccountId,1604 token: TokenId,1605 amount: u128,1606 ) -> DispatchResultWithPostInfo;16071608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 fn transfer_from(1619 &self,1620 sender: T::CrossAccountId,1621 from: T::CrossAccountId,1622 to: T::CrossAccountId,1623 token: TokenId,1624 amount: u128,1625 budget: &dyn Budget,1626 ) -> DispatchResultWithPostInfo;16271628 1629 1630 1631 1632 1633 1634 1635 1636 1637 fn burn_from(1638 &self,1639 sender: T::CrossAccountId,1640 from: T::CrossAccountId,1641 token: TokenId,1642 amount: u128,1643 budget: &dyn Budget,1644 ) -> DispatchResultWithPostInfo;16451646 1647 1648 1649 1650 1651 1652 fn check_nesting(1653 &self,1654 sender: T::CrossAccountId,1655 from: (CollectionId, TokenId),1656 under: TokenId,1657 budget: &dyn Budget,1658 ) -> DispatchResult;16591660 1661 1662 1663 1664 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));16651666 1667 1668 1669 1670 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));16711672 1673 1674 1675 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;16761677 1678 fn collection_tokens(&self) -> Vec<TokenId>;16791680 1681 1682 1683 fn token_exists(&self, token: TokenId) -> bool;16841685 1686 fn last_token_id(&self) -> TokenId;16871688 1689 1690 1691 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;16921693 1694 1695 1696 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;16971698 1699 1700 1701 1702 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17031704 1705 1706 1707 1708 1709 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17101711 1712 fn total_supply(&self) -> u32;17131714 1715 1716 1717 fn account_balance(&self, account: T::CrossAccountId) -> u32;17181719 1720 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;17211722 1723 fn total_pieces(&self, token: TokenId) -> Option<u128>;17241725 1726 1727 1728 1729 1730 fn allowance(1731 &self,1732 sender: T::CrossAccountId,1733 spender: T::CrossAccountId,1734 token: TokenId,1735 ) -> u128;17361737 1738 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1739}174017411742pub trait RefungibleExtensions<T>1743where1744 T: Config,1745{1746 1747 1748 1749 1750 1751 1752 1753 fn repartition(1754 &self,1755 sender: &T::CrossAccountId,1756 token: TokenId,1757 amount: u128,1758 ) -> DispatchResultWithPostInfo;1759}17601761176217631764pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1765 let post_info = PostDispatchInfo {1766 actual_weight: Some(weight),1767 pays_fee: Pays::Yes,1768 };1769 match res {1770 Ok(()) => Ok(post_info),1771 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1772 }1773}17741775impl<T: Config> From<PropertiesError> for Error<T> {1776 fn from(error: PropertiesError) -> Self {1777 match error {1778 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1779 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1780 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1781 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1782 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1783 }1784 }1785}