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,572573 574 UnsupportedRuntimeApi,575 }576577 578 #[pallet::storage]579 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;580581 582 #[pallet::storage]583 pub type DestroyedCollectionCount<T> =584 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;585586 587 #[pallet::storage]588 pub type CollectionById<T> = StorageMap<589 Hasher = Blake2_128Concat,590 Key = CollectionId,591 Value = Collection<<T as frame_system::Config>::AccountId>,592 QueryKind = OptionQuery,593 >;594595 596 #[pallet::storage]597 #[pallet::getter(fn collection_properties)]598 pub type CollectionProperties<T> = StorageMap<599 Hasher = Blake2_128Concat,600 Key = CollectionId,601 Value = Properties,602 QueryKind = ValueQuery,603 OnEmpty = up_data_structs::CollectionProperties,604 >;605606 607 #[pallet::storage]608 #[pallet::getter(fn property_permissions)]609 pub type CollectionPropertyPermissions<T> = StorageMap<610 Hasher = Blake2_128Concat,611 Key = CollectionId,612 Value = PropertiesPermissionMap,613 QueryKind = ValueQuery,614 >;615616 617 #[pallet::storage]618 pub type AdminAmount<T> = StorageMap<619 Hasher = Blake2_128Concat,620 Key = CollectionId,621 Value = u32,622 QueryKind = ValueQuery,623 >;624625 626 #[pallet::storage]627 pub type IsAdmin<T: Config> = StorageNMap<628 Key = (629 Key<Blake2_128Concat, CollectionId>,630 Key<Blake2_128Concat, T::CrossAccountId>,631 ),632 Value = bool,633 QueryKind = ValueQuery,634 >;635636 637 #[pallet::storage]638 pub type Allowlist<T: Config> = StorageNMap<639 Key = (640 Key<Blake2_128Concat, CollectionId>,641 Key<Blake2_128Concat, T::CrossAccountId>,642 ),643 Value = bool,644 QueryKind = ValueQuery,645 >;646647 648 #[pallet::storage]649 pub type DummyStorageValue<T: Config> = StorageValue<650 Value = (651 CollectionStats,652 CollectionId,653 TokenId,654 TokenChild,655 PhantomType<(656 TokenData<T::CrossAccountId>,657 RpcCollection<T::AccountId>,658 659 RmrkCollectionInfo<T::AccountId>,660 RmrkInstanceInfo<T::AccountId>,661 RmrkResourceInfo,662 RmrkPropertyInfo,663 RmrkBaseInfo<T::AccountId>,664 RmrkPartType,665 RmrkBoundedTheme,666 RmrkNftChild,667 )>,668 ),669 QueryKind = OptionQuery,670 >;671672 #[pallet::hooks]673 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {674 fn on_runtime_upgrade() -> Weight {675 StorageVersion::new(1).put::<Pallet<T>>();676677 0678 }679 }680}681682impl<T: Config> Pallet<T> {683 684 685 686 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {687 ensure!(688 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,689 <Error<T>>::AddressIsZero690 );691 Ok(())692 }693694 695 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {696 <IsAdmin<T>>::iter_prefix((collection,))697 .map(|(a, _)| a)698 .collect()699 }700701 702 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {703 <Allowlist<T>>::iter_prefix((collection,))704 .map(|(a, _)| a)705 .collect()706 }707708 709 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {710 <Allowlist<T>>::get((collection, user))711 }712713 714 pub fn collection_stats() -> CollectionStats {715 let created = <CreatedCollectionCount<T>>::get();716 let destroyed = <DestroyedCollectionCount<T>>::get();717 CollectionStats {718 created: created.0,719 destroyed: destroyed.0,720 alive: created.0 - destroyed.0,721 }722 }723724 725 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {726 let collection = <CollectionById<T>>::get(collection);727 if collection.is_none() {728 return None;729 }730731 let collection = collection.unwrap();732 let limits = collection.limits;733 let effective_limits = CollectionLimits {734 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),735 sponsored_data_size: Some(limits.sponsored_data_size()),736 sponsored_data_rate_limit: Some(737 limits738 .sponsored_data_rate_limit739 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),740 ),741 token_limit: Some(limits.token_limit()),742 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(743 match collection.mode {744 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,745 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,746 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,747 },748 )),749 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),750 owner_can_transfer: Some(limits.owner_can_transfer()),751 owner_can_destroy: Some(limits.owner_can_destroy()),752 transfers_enabled: Some(limits.transfers_enabled()),753 };754755 Some(effective_limits)756 }757758 759 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {760 let Collection {761 name,762 description,763 owner,764 mode,765 token_prefix,766 sponsorship,767 limits,768 permissions,769 external_collection,770 } = <CollectionById<T>>::get(collection)?;771772 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)773 .into_iter()774 .map(|(key, permission)| PropertyKeyPermission { key, permission })775 .collect();776777 let properties = <CollectionProperties<T>>::get(collection)778 .into_iter()779 .map(|(key, value)| Property { key, value })780 .collect();781782 let permissions = CollectionPermissions {783 access: Some(permissions.access()),784 mint_mode: Some(permissions.mint_mode()),785 nesting: Some(permissions.nesting().clone()),786 };787788 Some(RpcCollection {789 name: name.into_inner(),790 description: description.into_inner(),791 owner,792 mode,793 token_prefix: token_prefix.into_inner(),794 sponsorship,795 limits,796 permissions,797 token_property_permissions,798 properties,799 read_only: external_collection,800 })801 }802}803804macro_rules! limit_default {805 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{806 $(807 if let Some($new) = $new.$field {808 let $old = $old.$field($($arg)?);809 let _ = $new;810 let _ = $old;811 $check812 } else {813 $new.$field = $old.$field814 }815 )*816 }};817}818macro_rules! limit_default_clone {819 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{820 $(821 if let Some($new) = $new.$field.clone() {822 let $old = $old.$field($($arg)?);823 let _ = $new;824 let _ = $old;825 $check826 } else {827 $new.$field = $old.$field.clone()828 }829 )*830 }};831}832833impl<T: Config> Pallet<T> {834 835 836 837 838 839 pub fn init_collection(840 owner: T::CrossAccountId,841 data: CreateCollectionData<T::AccountId>,842 is_external: bool,843 ) -> Result<CollectionId, DispatchError> {844 {845 ensure!(846 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,847 Error::<T>::CollectionTokenPrefixLimitExceeded848 );849 }850851 let created_count = <CreatedCollectionCount<T>>::get()852 .0853 .checked_add(1)854 .ok_or(ArithmeticError::Overflow)?;855 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;856 let id = CollectionId(created_count);857858 859 ensure!(860 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,861 <Error<T>>::TotalCollectionsLimitExceeded862 );863864 865866 let collection = Collection {867 owner: owner.as_sub().clone(),868 name: data.name,869 mode: data.mode.clone(),870 description: data.description,871 token_prefix: data.token_prefix,872 sponsorship: data873 .pending_sponsor874 .map(SponsorshipState::Unconfirmed)875 .unwrap_or_default(),876 limits: data877 .limits878 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))879 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,880 permissions: data881 .permissions882 .map(|permissions| {883 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)884 })885 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,886 external_collection: is_external,887 };888889 let mut collection_properties = up_data_structs::CollectionProperties::get();890 collection_properties891 .try_set_from_iter(data.properties.into_iter())892 .map_err(<Error<T>>::from)?;893894 CollectionProperties::<T>::insert(id, collection_properties);895896 let mut token_props_permissions = PropertiesPermissionMap::new();897 token_props_permissions898 .try_set_from_iter(data.token_property_permissions.into_iter())899 .map_err(<Error<T>>::from)?;900901 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);902903 904 {905 let mut imbalance =906 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();907 imbalance.subsume(908 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(909 &T::TreasuryAccountId::get(),910 T::CollectionCreationPrice::get(),911 ),912 );913 <T as Config>::Currency::settle(914 owner.as_sub(),915 imbalance,916 WithdrawReasons::TRANSFER,917 ExistenceRequirement::KeepAlive,918 )919 .map_err(|_| Error::<T>::NotSufficientFounds)?;920 }921922 <CreatedCollectionCount<T>>::put(created_count);923 <Pallet<T>>::deposit_event(Event::CollectionCreated(924 id,925 data.mode.id(),926 owner.as_sub().clone(),927 ));928 <PalletEvm<T>>::deposit_log(929 erc::CollectionHelpersEvents::CollectionCreated {930 owner: *owner.as_eth(),931 collection_id: eth::collection_id_to_address(id),932 }933 .to_log(T::ContractAddress::get()),934 );935 <CollectionById<T>>::insert(id, collection);936 Ok(id)937 }938939 940 941 942 943 pub fn destroy_collection(944 collection: CollectionHandle<T>,945 sender: &T::CrossAccountId,946 ) -> DispatchResult {947 ensure!(948 collection.limits.owner_can_destroy(),949 <Error<T>>::NoPermission,950 );951 collection.check_is_owner(sender)?;952953 let destroyed_collections = <DestroyedCollectionCount<T>>::get()954 .0955 .checked_add(1)956 .ok_or(ArithmeticError::Overflow)?;957958 959960 <DestroyedCollectionCount<T>>::put(destroyed_collections);961 <CollectionById<T>>::remove(collection.id);962 <AdminAmount<T>>::remove(collection.id);963 <IsAdmin<T>>::remove_prefix((collection.id,), None);964 <Allowlist<T>>::remove_prefix((collection.id,), None);965 <CollectionProperties<T>>::remove(collection.id);966967 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));968 Ok(())969 }970971 972 973 974 975 976 pub fn set_collection_property(977 collection: &CollectionHandle<T>,978 sender: &T::CrossAccountId,979 property: Property,980 ) -> DispatchResult {981 collection.check_is_owner_or_admin(sender)?;982983 CollectionProperties::<T>::try_mutate(collection.id, |properties| {984 let property = property.clone();985 properties.try_set(property.key, property.value)986 })987 .map_err(<Error<T>>::from)?;988989 Self::deposit_event(Event::CollectionPropertySet(collection.id, property.key));990991 Ok(())992 }993994 995 996 997 998 999 pub fn set_scoped_collection_property(1000 collection_id: CollectionId,1001 scope: PropertyScope,1002 property: Property,1003 ) -> DispatchResult {1004 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1005 properties.try_scoped_set(scope, property.key, property.value)1006 })1007 .map_err(<Error<T>>::from)?;10081009 Ok(())1010 }10111012 1013 1014 1015 1016 1017 pub fn set_scoped_collection_properties(1018 collection_id: CollectionId,1019 scope: PropertyScope,1020 properties: impl Iterator<Item = Property>,1021 ) -> DispatchResult {1022 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1023 stored_properties.try_scoped_set_from_iter(scope, properties)1024 })1025 .map_err(<Error<T>>::from)?;10261027 Ok(())1028 }10291030 1031 1032 1033 1034 1035 #[transactional]1036 pub fn set_collection_properties(1037 collection: &CollectionHandle<T>,1038 sender: &T::CrossAccountId,1039 properties: Vec<Property>,1040 ) -> DispatchResult {1041 for property in properties {1042 Self::set_collection_property(collection, sender, property)?;1043 }10441045 Ok(())1046 }10471048 1049 1050 1051 1052 1053 pub fn delete_collection_property(1054 collection: &CollectionHandle<T>,1055 sender: &T::CrossAccountId,1056 property_key: PropertyKey,1057 ) -> DispatchResult {1058 collection.check_is_owner_or_admin(sender)?;10591060 CollectionProperties::<T>::try_mutate(collection.id, |properties| {1061 properties.remove(&property_key)1062 })1063 .map_err(<Error<T>>::from)?;10641065 Self::deposit_event(Event::CollectionPropertyDeleted(1066 collection.id,1067 property_key,1068 ));10691070 Ok(())1071 }10721073 1074 1075 1076 1077 1078 #[transactional]1079 pub fn delete_collection_properties(1080 collection: &CollectionHandle<T>,1081 sender: &T::CrossAccountId,1082 property_keys: Vec<PropertyKey>,1083 ) -> DispatchResult {1084 for key in property_keys {1085 Self::delete_collection_property(collection, sender, key)?;1086 }10871088 Ok(())1089 }10901091 1092 1093 1094 1095 1096 1097 pub fn set_property_permission_unchecked(1098 collection: CollectionId,1099 property_permission: PropertyKeyPermission,1100 ) -> DispatchResult {1101 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1102 permissions.try_set(property_permission.key, property_permission.permission)1103 })1104 .map_err(<Error<T>>::from)?;1105 Ok(())1106 }11071108 1109 1110 1111 1112 1113 pub fn set_property_permission(1114 collection: &CollectionHandle<T>,1115 sender: &T::CrossAccountId,1116 property_permission: PropertyKeyPermission,1117 ) -> DispatchResult {1118 collection.check_is_owner_or_admin(sender)?;11191120 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1121 let current_permission = all_permissions.get(&property_permission.key);1122 if matches![1123 current_permission,1124 Some(PropertyPermission { mutable: false, .. })1125 ] {1126 return Err(<Error<T>>::NoPermission.into());1127 }11281129 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1130 let property_permission = property_permission.clone();1131 permissions.try_set(property_permission.key, property_permission.permission)1132 })1133 .map_err(<Error<T>>::from)?;11341135 Self::deposit_event(Event::PropertyPermissionSet(1136 collection.id,1137 property_permission.key,1138 ));11391140 Ok(())1141 }11421143 1144 1145 1146 1147 1148 #[transactional]1149 pub fn set_token_property_permissions(1150 collection: &CollectionHandle<T>,1151 sender: &T::CrossAccountId,1152 property_permissions: Vec<PropertyKeyPermission>,1153 ) -> DispatchResult {1154 for prop_pemission in property_permissions {1155 Self::set_property_permission(collection, sender, prop_pemission)?;1156 }11571158 Ok(())1159 }11601161 1162 pub fn get_collection_property(1163 collection_id: CollectionId,1164 key: &PropertyKey,1165 ) -> Option<PropertyValue> {1166 Self::collection_properties(collection_id).get(key).cloned()1167 }11681169 1170 pub fn bytes_keys_to_property_keys(1171 keys: Vec<Vec<u8>>,1172 ) -> Result<Vec<PropertyKey>, DispatchError> {1173 keys.into_iter()1174 .map(|key| -> Result<PropertyKey, DispatchError> {1175 key.try_into()1176 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1177 })1178 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1179 }11801181 1182 pub fn filter_collection_properties(1183 collection_id: CollectionId,1184 keys: Option<Vec<PropertyKey>>,1185 ) -> Result<Vec<Property>, DispatchError> {1186 let properties = Self::collection_properties(collection_id);11871188 let properties = keys1189 .map(|keys| {1190 keys.into_iter()1191 .filter_map(|key| {1192 properties.get(&key).map(|value| Property {1193 key,1194 value: value.clone(),1195 })1196 })1197 .collect()1198 })1199 .unwrap_or_else(|| {1200 properties1201 .into_iter()1202 .map(|(key, value)| Property { key, value })1203 .collect()1204 });12051206 Ok(properties)1207 }12081209 1210 pub fn filter_property_permissions(1211 collection_id: CollectionId,1212 keys: Option<Vec<PropertyKey>>,1213 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1214 let permissions = Self::property_permissions(collection_id);12151216 let key_permissions = keys1217 .map(|keys| {1218 keys.into_iter()1219 .filter_map(|key| {1220 permissions1221 .get(&key)1222 .map(|permission| PropertyKeyPermission {1223 key,1224 permission: permission.clone(),1225 })1226 })1227 .collect()1228 })1229 .unwrap_or_else(|| {1230 permissions1231 .into_iter()1232 .map(|(key, permission)| PropertyKeyPermission { key, permission })1233 .collect()1234 });12351236 Ok(key_permissions)1237 }12381239 1240 pub fn toggle_allowlist(1241 collection: &CollectionHandle<T>,1242 sender: &T::CrossAccountId,1243 user: &T::CrossAccountId,1244 allowed: bool,1245 ) -> DispatchResult {1246 collection.check_is_owner_or_admin(sender)?;12471248 12491250 if allowed {1251 <Allowlist<T>>::insert((collection.id, user), true);1252 } else {1253 <Allowlist<T>>::remove((collection.id, user));1254 }12551256 Ok(())1257 }12581259 1260 pub fn toggle_admin(1261 collection: &CollectionHandle<T>,1262 sender: &T::CrossAccountId,1263 user: &T::CrossAccountId,1264 admin: bool,1265 ) -> DispatchResult {1266 collection.check_is_owner(sender)?;12671268 let was_admin = <IsAdmin<T>>::get((collection.id, user));1269 if was_admin == admin {1270 return Ok(());1271 }1272 let amount = <AdminAmount<T>>::get(collection.id);12731274 if admin {1275 let amount = amount1276 .checked_add(1)1277 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1278 ensure!(1279 amount <= Self::collection_admins_limit(),1280 <Error<T>>::CollectionAdminCountExceeded,1281 );12821283 12841285 <AdminAmount<T>>::insert(collection.id, amount);1286 <IsAdmin<T>>::insert((collection.id, user), true);1287 } else {1288 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1289 <IsAdmin<T>>::remove((collection.id, user));1290 }12911292 Ok(())1293 }12941295 1296 pub fn clamp_limits(1297 mode: CollectionMode,1298 old_limit: &CollectionLimits,1299 mut new_limit: CollectionLimits,1300 ) -> Result<CollectionLimits, DispatchError> {1301 let limits = old_limit;1302 limit_default!(old_limit, new_limit,1303 account_token_ownership_limit => ensure!(1304 new_limit <= MAX_TOKEN_OWNERSHIP,1305 <Error<T>>::CollectionLimitBoundsExceeded,1306 ),1307 sponsored_data_size => ensure!(1308 new_limit <= CUSTOM_DATA_LIMIT,1309 <Error<T>>::CollectionLimitBoundsExceeded,1310 ),13111312 sponsored_data_rate_limit => {},1313 token_limit => ensure!(1314 old_limit >= new_limit && new_limit > 0,1315 <Error<T>>::CollectionTokenLimitExceeded1316 ),13171318 sponsor_transfer_timeout(match mode {1319 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1320 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1321 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1322 }) => ensure!(1323 new_limit <= MAX_SPONSOR_TIMEOUT,1324 <Error<T>>::CollectionLimitBoundsExceeded,1325 ),1326 sponsor_approve_timeout => {},1327 owner_can_transfer => ensure!(1328 !limits.owner_can_transfer_instaled() ||1329 old_limit || !new_limit,1330 <Error<T>>::OwnerPermissionsCantBeReverted,1331 ),1332 owner_can_destroy => ensure!(1333 old_limit || !new_limit,1334 <Error<T>>::OwnerPermissionsCantBeReverted,1335 ),1336 transfers_enabled => {},1337 );1338 Ok(new_limit)1339 }13401341 1342 pub fn clamp_permissions(1343 _mode: CollectionMode,1344 old_permission: &CollectionPermissions,1345 mut new_permission: CollectionPermissions,1346 ) -> Result<CollectionPermissions, DispatchError> {1347 limit_default_clone!(old_permission, new_permission,1348 access => {},1349 mint_mode => {},1350 nesting => { },1351 );1352 Ok(new_permission)1353 }1354}135513561357#[macro_export]1358macro_rules! unsupported {1359 () => {1360 Err(<Error<T>>::UnsupportedOperation.into())1361 };1362}136313641365pub trait CommonWeightInfo<CrossAccountId> {1366 1367 fn create_item() -> Weight;13681369 1370 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;13711372 1373 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;13741375 1376 fn burn_item() -> Weight;13771378 1379 1380 1381 fn set_collection_properties(amount: u32) -> Weight;13821383 1384 1385 1386 fn delete_collection_properties(amount: u32) -> Weight;13871388 1389 1390 1391 fn set_token_properties(amount: u32) -> Weight;13921393 1394 1395 1396 fn delete_token_properties(amount: u32) -> Weight;13971398 1399 1400 1401 fn set_token_property_permissions(amount: u32) -> Weight;14021403 1404 fn transfer() -> Weight;14051406 1407 fn approve() -> Weight;14081409 1410 fn transfer_from() -> Weight;14111412 1413 fn burn_from() -> Weight;14141415 1416 1417 1418 1419 fn burn_recursively_self_raw() -> Weight;14201421 1422 1423 1424 fn burn_recursively_breadth_raw(amount: u32) -> Weight;14251426 1427 1428 1429 1430 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1431 Self::burn_recursively_self_raw()1432 .saturating_mul(max_selfs.max(1) as u64)1433 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1434 }1435}143614371438pub trait RefungibleExtensionsWeightInfo {1439 1440 fn repartition() -> Weight;1441}144214431444144514461447pub trait CommonCollectionOperations<T: Config> {1448 1449 1450 1451 1452 1453 1454 fn create_item(1455 &self,1456 sender: T::CrossAccountId,1457 to: T::CrossAccountId,1458 data: CreateItemData,1459 nesting_budget: &dyn Budget,1460 ) -> DispatchResultWithPostInfo;14611462 1463 1464 1465 1466 1467 1468 fn create_multiple_items(1469 &self,1470 sender: T::CrossAccountId,1471 to: T::CrossAccountId,1472 data: Vec<CreateItemData>,1473 nesting_budget: &dyn Budget,1474 ) -> DispatchResultWithPostInfo;14751476 1477 1478 1479 1480 1481 1482 fn create_multiple_items_ex(1483 &self,1484 sender: T::CrossAccountId,1485 data: CreateItemExData<T::CrossAccountId>,1486 nesting_budget: &dyn Budget,1487 ) -> DispatchResultWithPostInfo;14881489 1490 1491 1492 1493 1494 fn burn_item(1495 &self,1496 sender: T::CrossAccountId,1497 token: TokenId,1498 amount: u128,1499 ) -> DispatchResultWithPostInfo;15001501 1502 1503 1504 1505 1506 1507 fn burn_item_recursively(1508 &self,1509 sender: T::CrossAccountId,1510 token: TokenId,1511 self_budget: &dyn Budget,1512 breadth_budget: &dyn Budget,1513 ) -> DispatchResultWithPostInfo;15141515 1516 1517 1518 1519 fn set_collection_properties(1520 &self,1521 sender: T::CrossAccountId,1522 properties: Vec<Property>,1523 ) -> DispatchResultWithPostInfo;15241525 1526 1527 1528 1529 fn delete_collection_properties(1530 &self,1531 sender: &T::CrossAccountId,1532 property_keys: Vec<PropertyKey>,1533 ) -> DispatchResultWithPostInfo;15341535 1536 1537 1538 1539 1540 1541 1542 1543 1544 fn set_token_properties(1545 &self,1546 sender: T::CrossAccountId,1547 token_id: TokenId,1548 properties: Vec<Property>,1549 budget: &dyn Budget,1550 ) -> DispatchResultWithPostInfo;15511552 1553 1554 1555 1556 1557 1558 1559 1560 1561 fn delete_token_properties(1562 &self,1563 sender: T::CrossAccountId,1564 token_id: TokenId,1565 property_keys: Vec<PropertyKey>,1566 budget: &dyn Budget,1567 ) -> DispatchResultWithPostInfo;15681569 1570 1571 1572 1573 1574 1575 fn set_token_property_permissions(1576 &self,1577 sender: &T::CrossAccountId,1578 property_permissions: Vec<PropertyKeyPermission>,1579 ) -> DispatchResultWithPostInfo;15801581 1582 1583 1584 1585 1586 1587 1588 fn transfer(1589 &self,1590 sender: T::CrossAccountId,1591 to: T::CrossAccountId,1592 token: TokenId,1593 amount: u128,1594 budget: &dyn Budget,1595 ) -> DispatchResultWithPostInfo;15961597 1598 1599 1600 1601 1602 1603 fn approve(1604 &self,1605 sender: T::CrossAccountId,1606 spender: T::CrossAccountId,1607 token: TokenId,1608 amount: u128,1609 ) -> DispatchResultWithPostInfo;16101611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 fn transfer_from(1622 &self,1623 sender: T::CrossAccountId,1624 from: T::CrossAccountId,1625 to: T::CrossAccountId,1626 token: TokenId,1627 amount: u128,1628 budget: &dyn Budget,1629 ) -> DispatchResultWithPostInfo;16301631 1632 1633 1634 1635 1636 1637 1638 1639 1640 fn burn_from(1641 &self,1642 sender: T::CrossAccountId,1643 from: T::CrossAccountId,1644 token: TokenId,1645 amount: u128,1646 budget: &dyn Budget,1647 ) -> DispatchResultWithPostInfo;16481649 1650 1651 1652 1653 1654 1655 fn check_nesting(1656 &self,1657 sender: T::CrossAccountId,1658 from: (CollectionId, TokenId),1659 under: TokenId,1660 budget: &dyn Budget,1661 ) -> DispatchResult;16621663 1664 1665 1666 1667 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));16681669 1670 1671 1672 1673 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));16741675 1676 1677 1678 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;16791680 1681 fn collection_tokens(&self) -> Vec<TokenId>;16821683 1684 1685 1686 fn token_exists(&self, token: TokenId) -> bool;16871688 1689 fn last_token_id(&self) -> TokenId;16901691 1692 1693 1694 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;16951696 1697 1698 1699 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;17001701 1702 1703 1704 1705 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;17061707 1708 1709 1710 1711 1712 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;17131714 1715 fn total_supply(&self) -> u32;17161717 1718 1719 1720 fn account_balance(&self, account: T::CrossAccountId) -> u32;17211722 1723 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;17241725 1726 fn total_pieces(&self, token: TokenId) -> Option<u128>;17271728 1729 1730 1731 1732 1733 fn allowance(1734 &self,1735 sender: T::CrossAccountId,1736 spender: T::CrossAccountId,1737 token: TokenId,1738 ) -> u128;17391740 1741 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;1742}174317441745pub trait RefungibleExtensions<T>1746where1747 T: Config,1748{1749 1750 1751 1752 1753 1754 1755 1756 fn repartition(1757 &self,1758 sender: &T::CrossAccountId,1759 token: TokenId,1760 amount: u128,1761 ) -> DispatchResultWithPostInfo;1762}17631764176517661767pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {1768 let post_info = PostDispatchInfo {1769 actual_weight: Some(weight),1770 pays_fee: Pays::Yes,1771 };1772 match res {1773 Ok(()) => Ok(post_info),1774 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),1775 }1776}17771778impl<T: Config> From<PropertiesError> for Error<T> {1779 fn from(error: PropertiesError) -> Self {1780 match error {1781 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,1782 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,1783 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,1784 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,1785 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,1786 }1787 }1788}