12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::{57 ops::{Deref, DerefMut},58 slice::from_ref,59};60use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};61use sp_std::vec::Vec;62use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};63use evm_coder::ToLog;64use frame_support::{65 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},66 ensure,67 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},68 dispatch::Pays,69 transactional, fail,70};71use pallet_evm::GasWeightMapping;72use up_data_structs::{73 AccessMode,74 COLLECTION_NUMBER_LIMIT,75 Collection,76 RpcCollection,77 CollectionFlags,78 RpcCollectionFlags,79 CollectionId,80 CreateItemData,81 MAX_TOKEN_PREFIX_LENGTH,82 COLLECTION_ADMINS_LIMIT,83 TokenId,84 TokenChild,85 CollectionStats,86 MAX_TOKEN_OWNERSHIP,87 CollectionMode,88 NFT_SPONSOR_TRANSFER_TIMEOUT,89 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,90 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,91 MAX_SPONSOR_TIMEOUT,92 CUSTOM_DATA_LIMIT,93 CollectionLimits,94 CreateCollectionData,95 SponsorshipState,96 CreateItemExData,97 SponsoringRateLimit,98 budget::Budget,99 PhantomType,100 Property,101 Properties,102 PropertiesPermissionMap,103 PropertyKey,104 PropertyValue,105 PropertyPermission,106 PropertiesError,107 TokenOwnerError,108 PropertyKeyPermission,109 TokenData,110 TrySetProperty,111 PropertyScope,112 113 RmrkCollectionInfo,114 RmrkInstanceInfo,115 RmrkResourceInfo,116 RmrkPropertyInfo,117 RmrkBaseInfo,118 RmrkPartType,119 RmrkBoundedTheme,120 RmrkNftChild,121 CollectionPermissions,122};123use up_pov_estimate_rpc::PovInfo;124125pub use pallet::*;126use sp_core::H160;127use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};128129use crate::erc::CollectionHelpersEvents;130#[cfg(feature = "runtime-benchmarks")]131pub mod benchmarking;132pub mod dispatch;133pub mod erc;134pub mod eth;135pub mod weights;136137138pub type SelfWeightOf<T> = <T as Config>::WeightInfo;139140141142143144145146#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]147pub struct CollectionHandle<T: Config> {148 149 pub id: CollectionId,150 collection: Collection<T::AccountId>,151 152 pub recorder: SubstrateRecorder<T>,153}154155impl<T: Config> WithRecorder<T> for CollectionHandle<T> {156 fn recorder(&self) -> &SubstrateRecorder<T> {157 &self.recorder158 }159 fn into_recorder(self) -> SubstrateRecorder<T> {160 self.recorder161 }162}163164impl<T: Config> CollectionHandle<T> {165 166 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {167 <CollectionById<T>>::get(id).map(|collection| Self {168 id,169 collection,170 recorder: SubstrateRecorder::new(gas_limit),171 })172 }173174 175 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {176 <CollectionById<T>>::get(id).map(|collection| Self {177 id,178 collection,179 recorder,180 })181 }182183 184 185 pub fn new(id: CollectionId) -> Option<Self> {186 Self::new_with_gas_limit(id, u64::MAX)187 }188189 190 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {191 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)192 }193194 195 pub fn consume_store_reads(196 &self,197 reads: u64,198 ) -> pallet_evm_coder_substrate::execution::Result<()> {199 self.recorder200 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(201 <T as frame_system::Config>::DbWeight::get()202 .read203 .saturating_mul(reads),204 )))205 }206207 208 pub fn consume_store_writes(209 &self,210 writes: u64,211 ) -> pallet_evm_coder_substrate::execution::Result<()> {212 self.recorder213 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(214 <T as frame_system::Config>::DbWeight::get()215 .write216 .saturating_mul(writes),217 )))218 }219220 221 pub fn consume_store_reads_and_writes(222 &self,223 reads: u64,224 writes: u64,225 ) -> pallet_evm_coder_substrate::execution::Result<()> {226 let weight = <T as frame_system::Config>::DbWeight::get();227 let reads = weight.read.saturating_mul(reads);228 let writes = weight.read.saturating_mul(writes);229 self.recorder230 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(231 reads.saturating_add(writes),232 )))233 }234235 236 pub fn save(&self) -> DispatchResult {237 <CollectionById<T>>::insert(self.id, &self.collection);238 Ok(())239 }240241 242 243 244 245 246 pub fn set_sponsor(247 &mut self,248 sender: &T::CrossAccountId,249 sponsor: T::AccountId,250 ) -> DispatchResult {251 self.check_is_internal()?;252 self.check_is_owner_or_admin(sender)?;253254 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());255256 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));257 <PalletEvm<T>>::deposit_log(258 erc::CollectionHelpersEvents::CollectionChanged {259 collection_id: eth::collection_id_to_address(self.id),260 }261 .to_log(T::ContractAddress::get()),262 );263264 self.save()265 }266267 268 269 270 271 272 273 274 275 276 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {277 self.check_is_internal()?;278279 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());280281 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));282 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));283 <PalletEvm<T>>::deposit_log(284 erc::CollectionHelpersEvents::CollectionChanged {285 collection_id: eth::collection_id_to_address(self.id),286 }287 .to_log(T::ContractAddress::get()),288 );289290 self.save()291 }292293 294 295 296 297 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {298 self.check_is_internal()?;299 ensure!(300 self.collection.sponsorship.pending_sponsor() == Some(sender),301 Error::<T>::ConfirmSponsorshipFail302 );303304 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());305306 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));307 <PalletEvm<T>>::deposit_log(308 erc::CollectionHelpersEvents::CollectionChanged {309 collection_id: eth::collection_id_to_address(self.id),310 }311 .to_log(T::ContractAddress::get()),312 );313314 self.save()315 }316317 318 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {319 self.check_is_internal()?;320 self.check_is_owner_or_admin(sender)?;321322 self.collection.sponsorship = SponsorshipState::Disabled;323324 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));325 <PalletEvm<T>>::deposit_log(326 erc::CollectionHelpersEvents::CollectionChanged {327 collection_id: eth::collection_id_to_address(self.id),328 }329 .to_log(T::ContractAddress::get()),330 );331 self.save()332 }333334 335 336 337 338 pub fn force_remove_sponsor(&mut self) -> DispatchResult {339 self.check_is_internal()?;340341 self.collection.sponsorship = SponsorshipState::Disabled;342343 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));344 <PalletEvm<T>>::deposit_log(345 erc::CollectionHelpersEvents::CollectionChanged {346 collection_id: eth::collection_id_to_address(self.id),347 }348 .to_log(T::ContractAddress::get()),349 );350 self.save()351 }352353 354 355 pub fn check_is_internal(&self) -> DispatchResult {356 if self.flags.external {357 return Err(<Error<T>>::CollectionIsExternal)?;358 }359360 Ok(())361 }362363 364 365 pub fn check_is_external(&self) -> DispatchResult {366 if !self.flags.external {367 return Err(<Error<T>>::CollectionIsInternal)?;368 }369370 Ok(())371 }372}373374impl<T: Config> Deref for CollectionHandle<T> {375 type Target = Collection<T::AccountId>;376377 fn deref(&self) -> &Self::Target {378 &self.collection379 }380}381382impl<T: Config> DerefMut for CollectionHandle<T> {383 fn deref_mut(&mut self) -> &mut Self::Target {384 &mut self.collection385 }386}387388impl<T: Config> CollectionHandle<T> {389 390 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {391 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);392 Ok(())393 }394395 396 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {397 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))398 }399400 401 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {402 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);403 Ok(())404 }405406 407 408 409 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {410 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)411 }412413 414 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {415 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)416 }417418 419 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {420 ensure!(421 <Allowlist<T>>::get((self.id, user)),422 <Error<T>>::AddressNotInAllowlist423 );424 Ok(())425 }426427 428 429 430 pub fn change_owner(431 &mut self,432 caller: T::CrossAccountId,433 new_owner: T::CrossAccountId,434 ) -> DispatchResult {435 self.check_is_internal()?;436 self.check_is_owner(&caller)?;437 self.collection.owner = new_owner.as_sub().clone();438439 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(440 self.id,441 new_owner.as_sub().clone(),442 ));443 <PalletEvm<T>>::deposit_log(444 erc::CollectionHelpersEvents::CollectionChanged {445 collection_id: eth::collection_id_to_address(self.id),446 }447 .to_log(T::ContractAddress::get()),448 );449450 self.save()451 }452}453454#[frame_support::pallet]455pub mod pallet {456 use super::*;457 use dispatch::CollectionDispatch;458 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};459 use frame_system::pallet_prelude::*;460 use frame_support::traits::Currency;461 use up_data_structs::{TokenId, mapping::TokenAddressMapping};462 use scale_info::TypeInfo;463 use weights::WeightInfo;464465 #[pallet::config]466 pub trait Config:467 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo468 {469 470 type WeightInfo: WeightInfo;471472 473 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;474475 476 type Currency: Currency<Self::AccountId>;477478 479 #[pallet::constant]480 type CollectionCreationPrice: Get<481 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,482 >;483484 485 type CollectionDispatch: CollectionDispatch<Self>;486487 488 type TreasuryAccountId: Get<Self::AccountId>;489490 491 #[pallet::constant]492 type ContractAddress: Get<H160>;493494 495 type EvmTokenAddressMapping: TokenAddressMapping<H160>;496497 498 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;499 }500501 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);502503 #[pallet::pallet]504 #[pallet::storage_version(STORAGE_VERSION)]505 #[pallet::generate_store(pub(super) trait Store)]506 pub struct Pallet<T>(_);507508 #[pallet::extra_constants]509 impl<T: Config> Pallet<T> {510 511 pub fn collection_admins_limit() -> u32 {512 COLLECTION_ADMINS_LIMIT513 }514 }515516 impl<T: Config> Pallet<T> {517 518 pub fn deposit_event(event: Event<T>) {519 let event = <T as Config>::RuntimeEvent::from(event);520 let event = event.into();521 <frame_system::Pallet<T>>::deposit_event(event)522 }523 }524525 #[pallet::event]526 pub enum Event<T: Config> {527 528 CollectionCreated(529 530 CollectionId,531 532 u8,533 534 T::AccountId,535 ),536537 538 CollectionDestroyed(539 540 CollectionId,541 ),542543 544 ItemCreated(545 546 CollectionId,547 548 TokenId,549 550 T::CrossAccountId,551 552 u128,553 ),554555 556 ItemDestroyed(557 558 CollectionId,559 560 TokenId,561 562 T::CrossAccountId,563 564 u128,565 ),566567 568 Transfer(569 570 CollectionId,571 572 TokenId,573 574 T::CrossAccountId,575 576 T::CrossAccountId,577 578 u128,579 ),580581 582 Approved(583 584 CollectionId,585 586 TokenId,587 588 T::CrossAccountId,589 590 T::CrossAccountId,591 592 u128,593 ),594595 596 ApprovedForAll(597 598 CollectionId,599 600 T::CrossAccountId,601 602 T::CrossAccountId,603 604 bool,605 ),606607 608 CollectionPropertySet(609 610 CollectionId,611 612 PropertyKey,613 ),614615 616 CollectionPropertyDeleted(617 618 CollectionId,619 620 PropertyKey,621 ),622623 624 TokenPropertySet(625 626 CollectionId,627 628 TokenId,629 630 PropertyKey,631 ),632633 634 TokenPropertyDeleted(635 636 CollectionId,637 638 TokenId,639 640 PropertyKey,641 ),642643 644 PropertyPermissionSet(645 646 CollectionId,647 648 PropertyKey,649 ),650651 652 AllowListAddressAdded(653 654 CollectionId,655 656 T::CrossAccountId,657 ),658659 660 AllowListAddressRemoved(661 662 CollectionId,663 664 T::CrossAccountId,665 ),666667 668 CollectionAdminAdded(669 670 CollectionId,671 672 T::CrossAccountId,673 ),674675 676 CollectionAdminRemoved(677 678 CollectionId,679 680 T::CrossAccountId,681 ),682683 684 CollectionLimitSet(685 686 CollectionId,687 ),688689 690 CollectionOwnerChanged(691 692 CollectionId,693 694 T::AccountId,695 ),696697 698 CollectionPermissionSet(699 700 CollectionId,701 ),702703 704 CollectionSponsorSet(705 706 CollectionId,707 708 T::AccountId,709 ),710711 712 SponsorshipConfirmed(713 714 CollectionId,715 716 T::AccountId,717 ),718719 720 CollectionSponsorRemoved(721 722 CollectionId,723 ),724 }725726 #[pallet::error]727 pub enum Error<T> {728 729 CollectionNotFound,730 731 MustBeTokenOwner,732 733 NoPermission,734 735 CantDestroyNotEmptyCollection,736 737 PublicMintingNotAllowed,738 739 AddressNotInAllowlist,740741 742 CollectionNameLimitExceeded,743 744 CollectionDescriptionLimitExceeded,745 746 CollectionTokenPrefixLimitExceeded,747 748 TotalCollectionsLimitExceeded,749 750 CollectionAdminCountExceeded,751 752 CollectionLimitBoundsExceeded,753 754 OwnerPermissionsCantBeReverted,755 756 TransferNotAllowed,757 758 AccountTokenLimitExceeded,759 760 CollectionTokenLimitExceeded,761 762 MetadataFlagFrozen,763764 765 TokenNotFound,766 767 TokenValueTooLow,768 769 ApprovedValueTooLow,770 771 CantApproveMoreThanOwned,772 773 AddressIsNotEthMirror,774775 776 AddressIsZero,777778 779 UnsupportedOperation,780781 782 NotSufficientFounds,783784 785 UserIsNotAllowedToNest,786 787 SourceCollectionIsNotAllowedToNest,788789 790 CollectionFieldSizeExceeded,791792 793 NoSpaceForProperty,794795 796 PropertyLimitReached,797798 799 PropertyKeyIsTooLong,800801 802 InvalidCharacterInPropertyKey,803804 805 EmptyPropertyKey,806807 808 CollectionIsExternal,809810 811 CollectionIsInternal,812813 814 ConfirmSponsorshipFail,815816 817 UserIsNotCollectionAdmin,818 }819820 821 #[pallet::storage]822 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;823824 825 #[pallet::storage]826 pub type DestroyedCollectionCount<T> =827 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;828829 830 #[pallet::storage]831 pub type CollectionById<T> = StorageMap<832 Hasher = Blake2_128Concat,833 Key = CollectionId,834 Value = Collection<<T as frame_system::Config>::AccountId>,835 QueryKind = OptionQuery,836 >;837838 839 #[pallet::storage]840 #[pallet::getter(fn collection_properties)]841 pub type CollectionProperties<T> = StorageMap<842 Hasher = Blake2_128Concat,843 Key = CollectionId,844 Value = Properties,845 QueryKind = ValueQuery,846 OnEmpty = up_data_structs::CollectionProperties,847 >;848849 850 #[pallet::storage]851 #[pallet::getter(fn property_permissions)]852 pub type CollectionPropertyPermissions<T> = StorageMap<853 Hasher = Blake2_128Concat,854 Key = CollectionId,855 Value = PropertiesPermissionMap,856 QueryKind = ValueQuery,857 >;858859 860 #[pallet::storage]861 pub type AdminAmount<T> = StorageMap<862 Hasher = Blake2_128Concat,863 Key = CollectionId,864 Value = u32,865 QueryKind = ValueQuery,866 >;867868 869 #[pallet::storage]870 pub type IsAdmin<T: Config> = StorageNMap<871 Key = (872 Key<Blake2_128Concat, CollectionId>,873 Key<Blake2_128Concat, T::CrossAccountId>,874 ),875 Value = bool,876 QueryKind = ValueQuery,877 >;878879 880 #[pallet::storage]881 pub type Allowlist<T: Config> = StorageNMap<882 Key = (883 Key<Blake2_128Concat, CollectionId>,884 Key<Blake2_128Concat, T::CrossAccountId>,885 ),886 Value = bool,887 QueryKind = ValueQuery,888 >;889890 891 #[pallet::storage]892 pub type DummyStorageValue<T: Config> = StorageValue<893 Value = (894 CollectionStats,895 CollectionId,896 TokenId,897 TokenChild,898 PhantomType<(899 TokenData<T::CrossAccountId>,900 RpcCollection<T::AccountId>,901 902 RmrkCollectionInfo<T::AccountId>,903 RmrkInstanceInfo<T::AccountId>,904 RmrkResourceInfo,905 RmrkPropertyInfo,906 RmrkBaseInfo<T::AccountId>,907 RmrkPartType,908 RmrkBoundedTheme,909 RmrkNftChild,910 911 PovInfo,912 )>,913 ),914 QueryKind = OptionQuery,915 >;916917 #[pallet::hooks]918 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {919 fn on_runtime_upgrade() -> Weight {920 StorageVersion::new(1).put::<Pallet<T>>();921922 Weight::zero()923 }924 }925}926927impl<T: Config> Pallet<T> {928 929 930 931 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {932 ensure!(933 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,934 <Error<T>>::AddressIsZero935 );936 Ok(())937 }938939 940 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {941 <IsAdmin<T>>::iter_prefix((collection,))942 .map(|(a, _)| a)943 .collect()944 }945946 947 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {948 <Allowlist<T>>::iter_prefix((collection,))949 .map(|(a, _)| a)950 .collect()951 }952953 954 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {955 <Allowlist<T>>::get((collection, user))956 }957958 959 pub fn collection_stats() -> CollectionStats {960 let created = <CreatedCollectionCount<T>>::get();961 let destroyed = <DestroyedCollectionCount<T>>::get();962 CollectionStats {963 created: created.0,964 destroyed: destroyed.0,965 alive: created.0 - destroyed.0,966 }967 }968969 970 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {971 let collection = <CollectionById<T>>::get(collection)?;972 let limits = collection.limits;973 let effective_limits = CollectionLimits {974 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),975 sponsored_data_size: Some(limits.sponsored_data_size()),976 sponsored_data_rate_limit: Some(977 limits978 .sponsored_data_rate_limit979 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),980 ),981 token_limit: Some(limits.token_limit()),982 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(983 match collection.mode {984 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,985 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,986 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,987 },988 )),989 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),990 owner_can_transfer: Some(limits.owner_can_transfer()),991 owner_can_destroy: Some(limits.owner_can_destroy()),992 transfers_enabled: Some(limits.transfers_enabled()),993 };994995 Some(effective_limits)996 }997998 999 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1000 let Collection {1001 name,1002 description,1003 owner,1004 mode,1005 token_prefix,1006 sponsorship,1007 limits,1008 permissions,1009 flags,1010 } = <CollectionById<T>>::get(collection)?;10111012 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1013 .into_iter()1014 .map(|(key, permission)| PropertyKeyPermission { key, permission })1015 .collect();10161017 let properties = <CollectionProperties<T>>::get(collection)1018 .into_iter()1019 .map(|(key, value)| Property { key, value })1020 .collect();10211022 let permissions = CollectionPermissions {1023 access: Some(permissions.access()),1024 mint_mode: Some(permissions.mint_mode()),1025 nesting: Some(permissions.nesting().clone()),1026 };10271028 Some(RpcCollection {1029 name: name.into_inner(),1030 description: description.into_inner(),1031 owner,1032 mode,1033 token_prefix: token_prefix.into_inner(),1034 sponsorship,1035 limits,1036 permissions,1037 token_property_permissions,1038 properties,1039 read_only: flags.external,10401041 flags: RpcCollectionFlags {1042 foreign: flags.foreign,1043 erc721metadata: flags.erc721metadata,1044 },1045 })1046 }1047}10481049macro_rules! limit_default {1050 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1051 $(1052 if let Some($new) = $new.$field {1053 let $old = $old.$field($($arg)?);1054 let _ = $new;1055 let _ = $old;1056 $check1057 } else {1058 $new.$field = $old.$field1059 }1060 )*1061 }};1062}1063macro_rules! limit_default_clone {1064 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1065 $(1066 if let Some($new) = $new.$field.clone() {1067 let $old = $old.$field($($arg)?);1068 let _ = $new;1069 let _ = $old;1070 $check1071 } else {1072 $new.$field = $old.$field.clone()1073 }1074 )*1075 }};1076}10771078impl<T: Config> Pallet<T> {1079 1080 1081 1082 1083 1084 pub fn init_collection(1085 owner: T::CrossAccountId,1086 payer: T::CrossAccountId,1087 data: CreateCollectionData<T::AccountId>,1088 flags: CollectionFlags,1089 ) -> Result<CollectionId, DispatchError> {1090 {1091 ensure!(1092 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1093 Error::<T>::CollectionTokenPrefixLimitExceeded1094 );1095 }10961097 let created_count = <CreatedCollectionCount<T>>::get()1098 .01099 .checked_add(1)1100 .ok_or(ArithmeticError::Overflow)?;1101 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1102 let id = CollectionId(created_count);11031104 1105 ensure!(1106 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1107 <Error<T>>::TotalCollectionsLimitExceeded1108 );11091110 11111112 let collection = Collection {1113 owner: owner.as_sub().clone(),1114 name: data.name,1115 mode: data.mode.clone(),1116 description: data.description,1117 token_prefix: data.token_prefix,1118 sponsorship: data1119 .pending_sponsor1120 .map(SponsorshipState::Unconfirmed)1121 .unwrap_or_default(),1122 limits: data1123 .limits1124 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1125 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1126 permissions: data1127 .permissions1128 .map(|permissions| {1129 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1130 })1131 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1132 flags,1133 };11341135 let mut collection_properties = up_data_structs::CollectionProperties::get();1136 collection_properties1137 .try_set_from_iter(data.properties.into_iter())1138 .map_err(<Error<T>>::from)?;11391140 CollectionProperties::<T>::insert(id, collection_properties);11411142 let mut token_props_permissions = PropertiesPermissionMap::new();1143 token_props_permissions1144 .try_set_from_iter(data.token_property_permissions.into_iter())1145 .map_err(<Error<T>>::from)?;11461147 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11481149 1150 {1151 let mut imbalance =1152 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1153 imbalance.subsume(1154 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1155 &T::TreasuryAccountId::get(),1156 T::CollectionCreationPrice::get(),1157 ),1158 );1159 <T as Config>::Currency::settle(1160 payer.as_sub(),1161 imbalance,1162 WithdrawReasons::TRANSFER,1163 ExistenceRequirement::KeepAlive,1164 )1165 .map_err(|_| Error::<T>::NotSufficientFounds)?;1166 }11671168 <CreatedCollectionCount<T>>::put(created_count);1169 <Pallet<T>>::deposit_event(Event::CollectionCreated(1170 id,1171 data.mode.id(),1172 owner.as_sub().clone(),1173 ));1174 <PalletEvm<T>>::deposit_log(1175 erc::CollectionHelpersEvents::CollectionCreated {1176 owner: *owner.as_eth(),1177 collection_id: eth::collection_id_to_address(id),1178 }1179 .to_log(T::ContractAddress::get()),1180 );1181 <CollectionById<T>>::insert(id, collection);1182 Ok(id)1183 }11841185 1186 1187 1188 1189 pub fn destroy_collection(1190 collection: CollectionHandle<T>,1191 sender: &T::CrossAccountId,1192 ) -> DispatchResult {1193 ensure!(1194 collection.limits.owner_can_destroy(),1195 <Error<T>>::NoPermission,1196 );1197 collection.check_is_owner(sender)?;11981199 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1200 .01201 .checked_add(1)1202 .ok_or(ArithmeticError::Overflow)?;12031204 12051206 <DestroyedCollectionCount<T>>::put(destroyed_collections);1207 <CollectionById<T>>::remove(collection.id);1208 <AdminAmount<T>>::remove(collection.id);1209 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1210 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1211 <CollectionProperties<T>>::remove(collection.id);12121213 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12141215 <PalletEvm<T>>::deposit_log(1216 erc::CollectionHelpersEvents::CollectionDestroyed {1217 collection_id: eth::collection_id_to_address(collection.id),1218 }1219 .to_log(T::ContractAddress::get()),1220 );1221 Ok(())1222 }12231224 1225 1226 1227 1228 1229 1230 1231 1232 #[transactional]1233 fn modify_collection_properties(1234 collection: &CollectionHandle<T>,1235 sender: &T::CrossAccountId,1236 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1237 ) -> DispatchResult {1238 collection.check_is_owner_or_admin(sender)?;12391240 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12411242 for (key, value) in properties_updates {1243 match value {1244 Some(value) => {1245 stored_properties1246 .try_set(key.clone(), value)1247 .map_err(<Error<T>>::from)?;12481249 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1250 <PalletEvm<T>>::deposit_log(1251 erc::CollectionHelpersEvents::CollectionChanged {1252 collection_id: eth::collection_id_to_address(collection.id),1253 }1254 .to_log(T::ContractAddress::get()),1255 );1256 }1257 None => {1258 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12591260 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1261 <PalletEvm<T>>::deposit_log(1262 erc::CollectionHelpersEvents::CollectionChanged {1263 collection_id: eth::collection_id_to_address(collection.id),1264 }1265 .to_log(T::ContractAddress::get()),1266 );1267 }1268 }1269 }12701271 <CollectionProperties<T>>::set(collection.id, stored_properties);12721273 Ok(())1274 }12751276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 pub fn modify_token_properties(1294 collection: &CollectionHandle<T>,1295 sender: &T::CrossAccountId,1296 token_id: TokenId,1297 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1298 is_token_create: bool,1299 mut stored_properties: Properties,1300 is_token_owner: impl Fn() -> Result<bool, DispatchError>,1301 set_token_properties: impl FnOnce(Properties),1302 ) -> DispatchResult {1303 let is_collection_admin = collection.is_owner_or_admin(sender);1304 let permissions = Self::property_permissions(collection.id);13051306 let mut token_owner_result = None;1307 let mut is_token_owner = || -> Result<bool, DispatchError> {1308 *token_owner_result.get_or_insert_with(&is_token_owner)1309 };13101311 for (key, value) in properties_updates {1312 let permission = permissions1313 .get(&key)1314 .cloned()1315 .unwrap_or_else(PropertyPermission::none);13161317 let is_property_exists = stored_properties.get(&key).is_some();13181319 match permission {1320 PropertyPermission { mutable: false, .. } if is_property_exists => {1321 return Err(<Error<T>>::NoPermission.into());1322 }13231324 PropertyPermission {1325 collection_admin,1326 token_owner,1327 ..1328 } => {1329 1330 let is_token_create =1331 is_token_create && (collection_admin || token_owner) && value.is_some();1332 if !(is_token_create1333 || (collection_admin && is_collection_admin)1334 || (token_owner && is_token_owner()?))1335 {1336 fail!(<Error<T>>::NoPermission);1337 }1338 }1339 }13401341 match value {1342 Some(value) => {1343 stored_properties1344 .try_set(key.clone(), value)1345 .map_err(<Error<T>>::from)?;13461347 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1348 }1349 None => {1350 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13511352 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1353 }1354 }13551356 <PalletEvm<T>>::deposit_log(1357 CollectionHelpersEvents::TokenChanged {1358 collection_id: eth::collection_id_to_address(collection.id),1359 token_id: token_id.into(),1360 }1361 .to_log(T::ContractAddress::get()),1362 );1363 }13641365 set_token_properties(stored_properties);13661367 Ok(())1368 }13691370 1371 1372 1373 1374 1375 1376 pub fn set_allowance_for_all(1377 collection: &CollectionHandle<T>,1378 owner: &T::CrossAccountId,1379 operator: &T::CrossAccountId,1380 approve: bool,1381 set_allowance: impl FnOnce(),1382 log: evm_coder::ethereum::Log,1383 ) -> DispatchResult {1384 if collection.permissions.access() == AccessMode::AllowList {1385 collection.check_allowlist(owner)?;1386 collection.check_allowlist(operator)?;1387 }13881389 Self::ensure_correct_receiver(operator)?;13901391 set_allowance();13921393 <PalletEvm<T>>::deposit_log(log);1394 Self::deposit_event(Event::ApprovedForAll(1395 collection.id,1396 owner.clone(),1397 operator.clone(),1398 approve,1399 ));1400 Ok(())1401 }14021403 1404 1405 1406 1407 1408 pub fn set_collection_property(1409 collection: &CollectionHandle<T>,1410 sender: &T::CrossAccountId,1411 property: Property,1412 ) -> DispatchResult {1413 Self::set_collection_properties(collection, sender, [property].into_iter())1414 }14151416 1417 1418 1419 1420 1421 1422 pub fn set_scoped_collection_property(1423 collection_id: CollectionId,1424 scope: PropertyScope,1425 property: Property,1426 ) -> DispatchResult {1427 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1428 properties.try_scoped_set(scope, property.key, property.value)1429 })1430 .map_err(<Error<T>>::from)?;14311432 Ok(())1433 }14341435 1436 1437 1438 1439 1440 1441 pub fn set_scoped_collection_properties(1442 collection_id: CollectionId,1443 scope: PropertyScope,1444 properties: impl Iterator<Item = Property>,1445 ) -> DispatchResult {1446 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1447 stored_properties.try_scoped_set_from_iter(scope, properties)1448 })1449 .map_err(<Error<T>>::from)?;14501451 Ok(())1452 }14531454 1455 1456 1457 1458 1459 pub fn set_collection_properties(1460 collection: &CollectionHandle<T>,1461 sender: &T::CrossAccountId,1462 properties: impl Iterator<Item = Property>,1463 ) -> DispatchResult {1464 Self::modify_collection_properties(1465 collection,1466 sender,1467 properties.map(|property| (property.key, Some(property.value))),1468 )1469 }14701471 1472 1473 1474 1475 1476 pub fn delete_collection_property(1477 collection: &CollectionHandle<T>,1478 sender: &T::CrossAccountId,1479 property_key: PropertyKey,1480 ) -> DispatchResult {1481 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1482 }14831484 1485 1486 1487 1488 1489 pub fn delete_collection_properties(1490 collection: &CollectionHandle<T>,1491 sender: &T::CrossAccountId,1492 property_keys: impl Iterator<Item = PropertyKey>,1493 ) -> DispatchResult {1494 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1495 }14961497 1498 1499 1500 1501 1502 1503 pub fn set_property_permission_unchecked(1504 collection: CollectionId,1505 property_permission: PropertyKeyPermission,1506 ) -> DispatchResult {1507 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1508 permissions.try_set(property_permission.key, property_permission.permission)1509 })1510 .map_err(<Error<T>>::from)?;1511 Ok(())1512 }15131514 1515 1516 1517 1518 1519 pub fn set_property_permission(1520 collection: &CollectionHandle<T>,1521 sender: &T::CrossAccountId,1522 property_permission: PropertyKeyPermission,1523 ) -> DispatchResult {1524 Self::set_scoped_property_permission(1525 collection,1526 sender,1527 PropertyScope::None,1528 property_permission,1529 )1530 }15311532 1533 1534 1535 1536 1537 1538 pub fn set_scoped_property_permission(1539 collection: &CollectionHandle<T>,1540 sender: &T::CrossAccountId,1541 scope: PropertyScope,1542 property_permission: PropertyKeyPermission,1543 ) -> DispatchResult {1544 collection.check_is_owner_or_admin(sender)?;15451546 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1547 let current_permission = all_permissions.get(&property_permission.key);1548 if matches![1549 current_permission,1550 Some(PropertyPermission { mutable: false, .. })1551 ] {1552 return Err(<Error<T>>::NoPermission.into());1553 }15541555 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1556 let property_permission = property_permission.clone();1557 permissions.try_scoped_set(1558 scope,1559 property_permission.key,1560 property_permission.permission,1561 )1562 })1563 .map_err(<Error<T>>::from)?;15641565 Self::deposit_event(Event::PropertyPermissionSet(1566 collection.id,1567 property_permission.key,1568 ));1569 <PalletEvm<T>>::deposit_log(1570 erc::CollectionHelpersEvents::CollectionChanged {1571 collection_id: eth::collection_id_to_address(collection.id),1572 }1573 .to_log(T::ContractAddress::get()),1574 );15751576 Ok(())1577 }15781579 1580 1581 1582 1583 1584 #[transactional]1585 pub fn set_token_property_permissions(1586 collection: &CollectionHandle<T>,1587 sender: &T::CrossAccountId,1588 property_permissions: Vec<PropertyKeyPermission>,1589 ) -> DispatchResult {1590 Self::set_scoped_token_property_permissions(1591 collection,1592 sender,1593 PropertyScope::None,1594 property_permissions,1595 )1596 }15971598 1599 1600 1601 1602 1603 1604 #[transactional]1605 pub fn set_scoped_token_property_permissions(1606 collection: &CollectionHandle<T>,1607 sender: &T::CrossAccountId,1608 scope: PropertyScope,1609 property_permissions: Vec<PropertyKeyPermission>,1610 ) -> DispatchResult {1611 for prop_pemission in property_permissions {1612 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1613 }16141615 Ok(())1616 }16171618 1619 pub fn get_collection_property(1620 collection_id: CollectionId,1621 key: &PropertyKey,1622 ) -> Option<PropertyValue> {1623 Self::collection_properties(collection_id).get(key).cloned()1624 }16251626 1627 pub fn bytes_keys_to_property_keys(1628 keys: Vec<Vec<u8>>,1629 ) -> Result<Vec<PropertyKey>, DispatchError> {1630 keys.into_iter()1631 .map(|key| -> Result<PropertyKey, DispatchError> {1632 key.try_into()1633 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1634 })1635 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1636 }16371638 1639 pub fn filter_collection_properties(1640 collection_id: CollectionId,1641 keys: Option<Vec<PropertyKey>>,1642 ) -> Result<Vec<Property>, DispatchError> {1643 let properties = Self::collection_properties(collection_id);16441645 let properties = keys1646 .map(|keys| {1647 keys.into_iter()1648 .filter_map(|key| {1649 properties.get(&key).map(|value| Property {1650 key,1651 value: value.clone(),1652 })1653 })1654 .collect()1655 })1656 .unwrap_or_else(|| {1657 properties1658 .into_iter()1659 .map(|(key, value)| Property { key, value })1660 .collect()1661 });16621663 Ok(properties)1664 }16651666 1667 pub fn filter_property_permissions(1668 collection_id: CollectionId,1669 keys: Option<Vec<PropertyKey>>,1670 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1671 let permissions = Self::property_permissions(collection_id);16721673 let key_permissions = keys1674 .map(|keys| {1675 keys.into_iter()1676 .filter_map(|key| {1677 permissions1678 .get(&key)1679 .map(|permission| PropertyKeyPermission {1680 key,1681 permission: permission.clone(),1682 })1683 })1684 .collect()1685 })1686 .unwrap_or_else(|| {1687 permissions1688 .into_iter()1689 .map(|(key, permission)| PropertyKeyPermission { key, permission })1690 .collect()1691 });16921693 Ok(key_permissions)1694 }16951696 1697 1698 1699 pub fn toggle_allowlist(1700 collection: &CollectionHandle<T>,1701 sender: &T::CrossAccountId,1702 user: &T::CrossAccountId,1703 allowed: bool,1704 ) -> DispatchResult {1705 collection.check_is_owner_or_admin(sender)?;17061707 17081709 if allowed {1710 <Allowlist<T>>::insert((collection.id, user), true);1711 Self::deposit_event(Event::<T>::AllowListAddressAdded(1712 collection.id,1713 user.clone(),1714 ));1715 } else {1716 <Allowlist<T>>::remove((collection.id, user));1717 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1718 collection.id,1719 user.clone(),1720 ));1721 }17221723 <PalletEvm<T>>::deposit_log(1724 erc::CollectionHelpersEvents::CollectionChanged {1725 collection_id: eth::collection_id_to_address(collection.id),1726 }1727 .to_log(T::ContractAddress::get()),1728 );17291730 Ok(())1731 }17321733 1734 1735 1736 pub fn toggle_admin(1737 collection: &CollectionHandle<T>,1738 sender: &T::CrossAccountId,1739 user: &T::CrossAccountId,1740 admin: bool,1741 ) -> DispatchResult {1742 collection.check_is_internal()?;1743 collection.check_is_owner(sender)?;17441745 let is_admin = <IsAdmin<T>>::get((collection.id, user));1746 if is_admin == admin {1747 if admin {1748 return Ok(());1749 } else {1750 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1751 }1752 }1753 let amount = <AdminAmount<T>>::get(collection.id);17541755 17561757 if admin {1758 let amount = amount1759 .checked_add(1)1760 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1761 ensure!(1762 amount <= Self::collection_admins_limit(),1763 <Error<T>>::CollectionAdminCountExceeded,1764 );17651766 <AdminAmount<T>>::insert(collection.id, amount);1767 <IsAdmin<T>>::insert((collection.id, user), true);17681769 Self::deposit_event(Event::<T>::CollectionAdminAdded(1770 collection.id,1771 user.clone(),1772 ));1773 } else {1774 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1775 <IsAdmin<T>>::remove((collection.id, user));17761777 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1778 collection.id,1779 user.clone(),1780 ));1781 }17821783 <PalletEvm<T>>::deposit_log(1784 erc::CollectionHelpersEvents::CollectionChanged {1785 collection_id: eth::collection_id_to_address(collection.id),1786 }1787 .to_log(T::ContractAddress::get()),1788 );17891790 Ok(())1791 }17921793 1794 pub fn update_limits(1795 user: &T::CrossAccountId,1796 collection: &mut CollectionHandle<T>,1797 new_limit: CollectionLimits,1798 ) -> DispatchResult {1799 collection.check_is_internal()?;1800 collection.check_is_owner_or_admin(user)?;18011802 collection.limits =1803 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;18041805 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1806 <PalletEvm<T>>::deposit_log(1807 erc::CollectionHelpersEvents::CollectionChanged {1808 collection_id: eth::collection_id_to_address(collection.id),1809 }1810 .to_log(T::ContractAddress::get()),1811 );18121813 collection.save()1814 }18151816 1817 fn clamp_limits(1818 mode: CollectionMode,1819 old_limit: &CollectionLimits,1820 mut new_limit: CollectionLimits,1821 ) -> Result<CollectionLimits, DispatchError> {1822 let limits = old_limit;1823 limit_default!(old_limit, new_limit,1824 account_token_ownership_limit => ensure!(1825 new_limit <= MAX_TOKEN_OWNERSHIP,1826 <Error<T>>::CollectionLimitBoundsExceeded,1827 ),1828 sponsored_data_size => ensure!(1829 new_limit <= CUSTOM_DATA_LIMIT,1830 <Error<T>>::CollectionLimitBoundsExceeded,1831 ),18321833 sponsored_data_rate_limit => {},1834 token_limit => ensure!(1835 old_limit >= new_limit && new_limit > 0,1836 <Error<T>>::CollectionTokenLimitExceeded1837 ),18381839 sponsor_transfer_timeout(match mode {1840 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1841 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1842 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1843 }) => ensure!(1844 new_limit <= MAX_SPONSOR_TIMEOUT,1845 <Error<T>>::CollectionLimitBoundsExceeded,1846 ),1847 sponsor_approve_timeout => {},1848 owner_can_transfer => ensure!(1849 !limits.owner_can_transfer_instaled() ||1850 old_limit || !new_limit,1851 <Error<T>>::OwnerPermissionsCantBeReverted,1852 ),1853 owner_can_destroy => ensure!(1854 old_limit || !new_limit,1855 <Error<T>>::OwnerPermissionsCantBeReverted,1856 ),1857 transfers_enabled => {},1858 );1859 Ok(new_limit)1860 }18611862 1863 pub fn update_permissions(1864 user: &T::CrossAccountId,1865 collection: &mut CollectionHandle<T>,1866 new_permission: CollectionPermissions,1867 ) -> DispatchResult {1868 collection.check_is_internal()?;1869 collection.check_is_owner_or_admin(user)?;1870 collection.permissions = Self::clamp_permissions(1871 collection.mode.clone(),1872 &collection.permissions,1873 new_permission,1874 )?;18751876 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1877 <PalletEvm<T>>::deposit_log(1878 erc::CollectionHelpersEvents::CollectionChanged {1879 collection_id: eth::collection_id_to_address(collection.id),1880 }1881 .to_log(T::ContractAddress::get()),1882 );18831884 collection.save()1885 }18861887 1888 fn clamp_permissions(1889 _mode: CollectionMode,1890 old_permission: &CollectionPermissions,1891 mut new_permission: CollectionPermissions,1892 ) -> Result<CollectionPermissions, DispatchError> {1893 limit_default_clone!(old_permission, new_permission,1894 access => {},1895 mint_mode => {},1896 nesting => { },1897 );1898 Ok(new_permission)1899 }19001901 1902 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1903 CollectionProperties::<T>::mutate(collection_id, |properties| {1904 properties.recompute_consumed_space();1905 });19061907 Ok(())1908 }1909}191019111912#[macro_export]1913macro_rules! unsupported {1914 ($runtime:path) => {1915 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1916 };1917}191819191920pub trait CommonWeightInfo<CrossAccountId> {1921 1922 fn create_item(data: &CreateItemData) -> Weight {1923 Self::create_multiple_items(from_ref(data))1924 }19251926 1927 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19281929 1930 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19311932 1933 fn burn_item() -> Weight;19341935 1936 1937 1938 fn set_collection_properties(amount: u32) -> Weight;19391940 1941 1942 1943 fn delete_collection_properties(amount: u32) -> Weight;19441945 1946 1947 1948 fn set_token_properties(amount: u32) -> Weight;19491950 1951 1952 1953 fn delete_token_properties(amount: u32) -> Weight;19541955 1956 1957 1958 fn set_token_property_permissions(amount: u32) -> Weight;19591960 1961 fn transfer() -> Weight;19621963 1964 fn approve() -> Weight;19651966 1967 fn approve_from() -> Weight;19681969 1970 fn transfer_from() -> Weight;19711972 1973 fn burn_from() -> Weight;19741975 1976 1977 1978 1979 fn burn_recursively_self_raw() -> Weight;19801981 1982 1983 1984 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19851986 1987 1988 1989 1990 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1991 Self::burn_recursively_self_raw()1992 .saturating_mul(max_selfs.max(1) as u64)1993 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1994 }19951996 1997 fn token_owner() -> Weight;19981999 2000 fn set_allowance_for_all() -> Weight;20012002 2003 fn force_repair_item() -> Weight;2004}200520062007pub trait RefungibleExtensionsWeightInfo {2008 2009 fn repartition() -> Weight;2010}201120122013201420152016pub trait CommonCollectionOperations<T: Config> {2017 2018 2019 2020 2021 2022 2023 fn create_item(2024 &self,2025 sender: T::CrossAccountId,2026 to: T::CrossAccountId,2027 data: CreateItemData,2028 nesting_budget: &dyn Budget,2029 ) -> DispatchResultWithPostInfo;20302031 2032 2033 2034 2035 2036 2037 fn create_multiple_items(2038 &self,2039 sender: T::CrossAccountId,2040 to: T::CrossAccountId,2041 data: Vec<CreateItemData>,2042 nesting_budget: &dyn Budget,2043 ) -> DispatchResultWithPostInfo;20442045 2046 2047 2048 2049 2050 2051 fn create_multiple_items_ex(2052 &self,2053 sender: T::CrossAccountId,2054 data: CreateItemExData<T::CrossAccountId>,2055 nesting_budget: &dyn Budget,2056 ) -> DispatchResultWithPostInfo;20572058 2059 2060 2061 2062 2063 fn burn_item(2064 &self,2065 sender: T::CrossAccountId,2066 token: TokenId,2067 amount: u128,2068 ) -> DispatchResultWithPostInfo;20692070 2071 2072 2073 2074 2075 2076 fn burn_item_recursively(2077 &self,2078 sender: T::CrossAccountId,2079 token: TokenId,2080 self_budget: &dyn Budget,2081 breadth_budget: &dyn Budget,2082 ) -> DispatchResultWithPostInfo;20832084 2085 2086 2087 2088 fn set_collection_properties(2089 &self,2090 sender: T::CrossAccountId,2091 properties: Vec<Property>,2092 ) -> DispatchResultWithPostInfo;20932094 2095 2096 2097 2098 fn delete_collection_properties(2099 &self,2100 sender: &T::CrossAccountId,2101 property_keys: Vec<PropertyKey>,2102 ) -> DispatchResultWithPostInfo;21032104 2105 2106 2107 2108 2109 2110 2111 2112 2113 fn set_token_properties(2114 &self,2115 sender: T::CrossAccountId,2116 token_id: TokenId,2117 properties: Vec<Property>,2118 budget: &dyn Budget,2119 ) -> DispatchResultWithPostInfo;21202121 2122 2123 2124 2125 2126 2127 2128 2129 2130 fn delete_token_properties(2131 &self,2132 sender: T::CrossAccountId,2133 token_id: TokenId,2134 property_keys: Vec<PropertyKey>,2135 budget: &dyn Budget,2136 ) -> DispatchResultWithPostInfo;21372138 2139 2140 2141 2142 2143 2144 fn set_token_property_permissions(2145 &self,2146 sender: &T::CrossAccountId,2147 property_permissions: Vec<PropertyKeyPermission>,2148 ) -> DispatchResultWithPostInfo;21492150 2151 2152 2153 2154 2155 2156 2157 fn transfer(2158 &self,2159 sender: T::CrossAccountId,2160 to: T::CrossAccountId,2161 token: TokenId,2162 amount: u128,2163 budget: &dyn Budget,2164 ) -> DispatchResultWithPostInfo;21652166 2167 2168 2169 2170 2171 2172 fn approve(2173 &self,2174 sender: T::CrossAccountId,2175 spender: T::CrossAccountId,2176 token: TokenId,2177 amount: u128,2178 ) -> DispatchResultWithPostInfo;21792180 2181 2182 2183 2184 2185 2186 2187 fn approve_from(2188 &self,2189 sender: T::CrossAccountId,2190 from: T::CrossAccountId,2191 to: T::CrossAccountId,2192 token: TokenId,2193 amount: u128,2194 ) -> DispatchResultWithPostInfo;21952196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 fn transfer_from(2207 &self,2208 sender: T::CrossAccountId,2209 from: T::CrossAccountId,2210 to: T::CrossAccountId,2211 token: TokenId,2212 amount: u128,2213 budget: &dyn Budget,2214 ) -> DispatchResultWithPostInfo;22152216 2217 2218 2219 2220 2221 2222 2223 2224 2225 fn burn_from(2226 &self,2227 sender: T::CrossAccountId,2228 from: T::CrossAccountId,2229 token: TokenId,2230 amount: u128,2231 budget: &dyn Budget,2232 ) -> DispatchResultWithPostInfo;22332234 2235 2236 2237 2238 2239 2240 fn check_nesting(2241 &self,2242 sender: T::CrossAccountId,2243 from: (CollectionId, TokenId),2244 under: TokenId,2245 budget: &dyn Budget,2246 ) -> DispatchResult;22472248 2249 2250 2251 2252 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22532254 2255 2256 2257 2258 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22592260 2261 2262 2263 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22642265 2266 fn collection_tokens(&self) -> Vec<TokenId>;22672268 2269 2270 2271 fn token_exists(&self, token: TokenId) -> bool;22722273 2274 fn last_token_id(&self) -> TokenId;22752276 2277 2278 2279 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22802281 2282 2283 2284 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22852286 2287 2288 2289 2290 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22912292 2293 2294 2295 2296 2297 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22982299 2300 fn total_supply(&self) -> u32;23012302 2303 2304 2305 fn account_balance(&self, account: T::CrossAccountId) -> u32;23062307 2308 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;23092310 2311 fn total_pieces(&self, token: TokenId) -> Option<u128>;23122313 2314 2315 2316 2317 2318 fn allowance(2319 &self,2320 sender: T::CrossAccountId,2321 spender: T::CrossAccountId,2322 token: TokenId,2323 ) -> u128;23242325 2326 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;23272328 2329 2330 2331 2332 fn set_allowance_for_all(2333 &self,2334 owner: T::CrossAccountId,2335 operator: T::CrossAccountId,2336 approve: bool,2337 ) -> DispatchResultWithPostInfo;23382339 2340 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23412342 2343 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2344}234523462347pub trait RefungibleExtensions<T>2348where2349 T: Config,2350{2351 2352 2353 2354 2355 2356 2357 2358 fn repartition(2359 &self,2360 sender: &T::CrossAccountId,2361 token: TokenId,2362 amount: u128,2363 ) -> DispatchResultWithPostInfo;2364}23652366236723682369pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2370 let post_info = PostDispatchInfo {2371 actual_weight: Some(weight),2372 pays_fee: Pays::Yes,2373 };2374 match res {2375 Ok(()) => Ok(post_info),2376 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2377 }2378}23792380impl<T: Config> From<PropertiesError> for Error<T> {2381 fn from(error: PropertiesError) -> Self {2382 match error {2383 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2384 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2385 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2386 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2387 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2388 }2389 }2390}