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, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,74 RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,75 COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP,76 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,77 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,78 CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,79 PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties,80 PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError,81 TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope,82 CollectionPermissions,83};84use up_pov_estimate_rpc::PovInfo;8586pub use pallet::*;87use sp_core::H160;88use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};8990#[cfg(feature = "runtime-benchmarks")]91pub mod benchmarking;92pub mod dispatch;93pub mod erc;94pub mod eth;95pub mod helpers;96#[allow(missing_docs)]97pub mod weights;9899pub type SelfWeightOf<T> = <T as Config>::WeightInfo;100101102103104105106107#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]108pub struct CollectionHandle<T: Config> {109 110 pub id: CollectionId,111 collection: Collection<T::AccountId>,112 113 pub recorder: SubstrateRecorder<T>,114}115116impl<T: Config> WithRecorder<T> for CollectionHandle<T> {117 fn recorder(&self) -> &SubstrateRecorder<T> {118 &self.recorder119 }120 fn into_recorder(self) -> SubstrateRecorder<T> {121 self.recorder122 }123}124125impl<T: Config> CollectionHandle<T> {126 127 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {128 <CollectionById<T>>::get(id).map(|collection| Self {129 id,130 collection,131 recorder: SubstrateRecorder::new(gas_limit),132 })133 }134135 136 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {137 <CollectionById<T>>::get(id).map(|collection| Self {138 id,139 collection,140 recorder,141 })142 }143144 145 146 pub fn new(id: CollectionId) -> Option<Self> {147 Self::new_with_gas_limit(id, u64::MAX)148 }149150 151 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {152 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)153 }154155 156 pub fn consume_store_reads(157 &self,158 reads: u64,159 ) -> pallet_evm_coder_substrate::execution::Result<()> {160 self.recorder161 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(162 <T as frame_system::Config>::DbWeight::get()163 .read164 .saturating_mul(reads),165 166 0,167 )))168 }169170 171 pub fn consume_store_writes(172 &self,173 writes: u64,174 ) -> pallet_evm_coder_substrate::execution::Result<()> {175 self.recorder176 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(177 <T as frame_system::Config>::DbWeight::get()178 .write179 .saturating_mul(writes),180 181 0,182 )))183 }184185 186 pub fn consume_store_reads_and_writes(187 &self,188 reads: u64,189 writes: u64,190 ) -> pallet_evm_coder_substrate::execution::Result<()> {191 let weight = <T as frame_system::Config>::DbWeight::get();192 let reads = weight.read.saturating_mul(reads);193 let writes = weight.read.saturating_mul(writes);194 self.recorder195 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(196 reads.saturating_add(writes),197 198 0,199 )))200 }201202 203 pub fn save(&self) -> DispatchResult {204 <CollectionById<T>>::insert(self.id, &self.collection);205 Ok(())206 }207208 209 210 211 212 213 pub fn set_sponsor(214 &mut self,215 sender: &T::CrossAccountId,216 sponsor: T::AccountId,217 ) -> DispatchResult {218 self.check_is_internal()?;219 self.check_is_owner_or_admin(sender)?;220221 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());222223 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));224 <PalletEvm<T>>::deposit_log(225 erc::CollectionHelpersEvents::CollectionChanged {226 collection_id: eth::collection_id_to_address(self.id),227 }228 .to_log(T::ContractAddress::get()),229 );230231 self.save()232 }233234 235 236 237 238 239 240 241 242 243 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {244 self.check_is_internal()?;245246 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());247248 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));249 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));250 <PalletEvm<T>>::deposit_log(251 erc::CollectionHelpersEvents::CollectionChanged {252 collection_id: eth::collection_id_to_address(self.id),253 }254 .to_log(T::ContractAddress::get()),255 );256257 self.save()258 }259260 261 262 263 264 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {265 self.check_is_internal()?;266 ensure!(267 self.collection.sponsorship.pending_sponsor() == Some(sender),268 Error::<T>::ConfirmSponsorshipFail269 );270271 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());272273 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));274 <PalletEvm<T>>::deposit_log(275 erc::CollectionHelpersEvents::CollectionChanged {276 collection_id: eth::collection_id_to_address(self.id),277 }278 .to_log(T::ContractAddress::get()),279 );280281 self.save()282 }283284 285 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {286 self.check_is_internal()?;287 self.check_is_owner_or_admin(sender)?;288289 self.collection.sponsorship = SponsorshipState::Disabled;290291 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));292 <PalletEvm<T>>::deposit_log(293 erc::CollectionHelpersEvents::CollectionChanged {294 collection_id: eth::collection_id_to_address(self.id),295 }296 .to_log(T::ContractAddress::get()),297 );298 self.save()299 }300301 302 303 304 305 pub fn force_remove_sponsor(&mut self) -> DispatchResult {306 self.check_is_internal()?;307308 self.collection.sponsorship = SponsorshipState::Disabled;309310 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));311 <PalletEvm<T>>::deposit_log(312 erc::CollectionHelpersEvents::CollectionChanged {313 collection_id: eth::collection_id_to_address(self.id),314 }315 .to_log(T::ContractAddress::get()),316 );317 self.save()318 }319320 321 322 pub fn check_is_internal(&self) -> DispatchResult {323 if self.flags.external {324 return Err(<Error<T>>::CollectionIsExternal)?;325 }326327 Ok(())328 }329330 331 332 pub fn check_is_external(&self) -> DispatchResult {333 if !self.flags.external {334 return Err(<Error<T>>::CollectionIsInternal)?;335 }336337 Ok(())338 }339}340341impl<T: Config> Deref for CollectionHandle<T> {342 type Target = Collection<T::AccountId>;343344 fn deref(&self) -> &Self::Target {345 &self.collection346 }347}348349impl<T: Config> DerefMut for CollectionHandle<T> {350 fn deref_mut(&mut self) -> &mut Self::Target {351 &mut self.collection352 }353}354355impl<T: Config> CollectionHandle<T> {356 357 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {358 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);359 Ok(())360 }361362 363 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {364 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))365 }366367 368 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {369 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);370 Ok(())371 }372373 374 375 376 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {377 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)378 }379380 381 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {382 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)383 }384385 386 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {387 ensure!(388 <Allowlist<T>>::get((self.id, user)),389 <Error<T>>::AddressNotInAllowlist390 );391 Ok(())392 }393394 395 396 397 pub fn change_owner(398 &mut self,399 caller: T::CrossAccountId,400 new_owner: T::CrossAccountId,401 ) -> DispatchResult {402 self.check_is_internal()?;403 self.check_is_owner(&caller)?;404 self.collection.owner = new_owner.as_sub().clone();405406 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(407 self.id,408 new_owner.as_sub().clone(),409 ));410 <PalletEvm<T>>::deposit_log(411 erc::CollectionHelpersEvents::CollectionChanged {412 collection_id: eth::collection_id_to_address(self.id),413 }414 .to_log(T::ContractAddress::get()),415 );416417 self.save()418 }419}420421#[frame_support::pallet]422pub mod pallet {423 use core::marker::PhantomData;424425 use super::*;426 use dispatch::CollectionDispatch;427 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};428 use frame_support::traits::Currency;429 use up_data_structs::{TokenId, mapping::TokenAddressMapping};430 use scale_info::TypeInfo;431 use weights::WeightInfo;432433 #[pallet::config]434 pub trait Config:435 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo436 {437 438 type WeightInfo: WeightInfo;439440 441 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;442443 444 type Currency: Currency<Self::AccountId>;445446 447 #[pallet::constant]448 type CollectionCreationPrice: Get<449 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,450 >;451452 453 type CollectionDispatch: CollectionDispatch<Self>;454455 456 type TreasuryAccountId: Get<Self::AccountId>;457458 459 #[pallet::constant]460 type ContractAddress: Get<H160>;461462 463 type EvmTokenAddressMapping: TokenAddressMapping<H160>;464465 466 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;467 }468469 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);470471 #[pallet::pallet]472 #[pallet::storage_version(STORAGE_VERSION)]473 pub struct Pallet<T>(_);474475 #[pallet::extra_constants]476 impl<T: Config> Pallet<T> {477 478 pub fn collection_admins_limit() -> u32 {479 COLLECTION_ADMINS_LIMIT480 }481 }482483 #[pallet::genesis_config]484 pub struct GenesisConfig<T>(PhantomData<T>);485486 #[cfg(feature = "std")]487 impl<T: Config> Default for GenesisConfig<T> {488 fn default() -> Self {489 Self(Default::default())490 }491 }492493 #[pallet::genesis_build]494 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {495 fn build(&self) {496 StorageVersion::new(1).put::<Pallet<T>>();497 }498 }499500 impl<T: Config> Pallet<T> {501 502 pub fn deposit_event(event: Event<T>) {503 let event = <T as Config>::RuntimeEvent::from(event);504 let event = event.into();505 <frame_system::Pallet<T>>::deposit_event(event)506 }507 }508509 #[pallet::event]510 pub enum Event<T: Config> {511 512 CollectionCreated(513 514 CollectionId,515 516 u8,517 518 T::AccountId,519 ),520521 522 CollectionDestroyed(523 524 CollectionId,525 ),526527 528 ItemCreated(529 530 CollectionId,531 532 TokenId,533 534 T::CrossAccountId,535 536 u128,537 ),538539 540 ItemDestroyed(541 542 CollectionId,543 544 TokenId,545 546 T::CrossAccountId,547 548 u128,549 ),550551 552 Transfer(553 554 CollectionId,555 556 TokenId,557 558 T::CrossAccountId,559 560 T::CrossAccountId,561 562 u128,563 ),564565 566 Approved(567 568 CollectionId,569 570 TokenId,571 572 T::CrossAccountId,573 574 T::CrossAccountId,575 576 u128,577 ),578579 580 ApprovedForAll(581 582 CollectionId,583 584 T::CrossAccountId,585 586 T::CrossAccountId,587 588 bool,589 ),590591 592 CollectionPropertySet(593 594 CollectionId,595 596 PropertyKey,597 ),598599 600 CollectionPropertyDeleted(601 602 CollectionId,603 604 PropertyKey,605 ),606607 608 TokenPropertySet(609 610 CollectionId,611 612 TokenId,613 614 PropertyKey,615 ),616617 618 TokenPropertyDeleted(619 620 CollectionId,621 622 TokenId,623 624 PropertyKey,625 ),626627 628 PropertyPermissionSet(629 630 CollectionId,631 632 PropertyKey,633 ),634635 636 AllowListAddressAdded(637 638 CollectionId,639 640 T::CrossAccountId,641 ),642643 644 AllowListAddressRemoved(645 646 CollectionId,647 648 T::CrossAccountId,649 ),650651 652 CollectionAdminAdded(653 654 CollectionId,655 656 T::CrossAccountId,657 ),658659 660 CollectionAdminRemoved(661 662 CollectionId,663 664 T::CrossAccountId,665 ),666667 668 CollectionLimitSet(669 670 CollectionId,671 ),672673 674 CollectionOwnerChanged(675 676 CollectionId,677 678 T::AccountId,679 ),680681 682 CollectionPermissionSet(683 684 CollectionId,685 ),686687 688 CollectionSponsorSet(689 690 CollectionId,691 692 T::AccountId,693 ),694695 696 SponsorshipConfirmed(697 698 CollectionId,699 700 T::AccountId,701 ),702703 704 CollectionSponsorRemoved(705 706 CollectionId,707 ),708 }709710 #[pallet::error]711 pub enum Error<T> {712 713 CollectionNotFound,714 715 MustBeTokenOwner,716 717 NoPermission,718 719 CantDestroyNotEmptyCollection,720 721 PublicMintingNotAllowed,722 723 AddressNotInAllowlist,724725 726 CollectionNameLimitExceeded,727 728 CollectionDescriptionLimitExceeded,729 730 CollectionTokenPrefixLimitExceeded,731 732 TotalCollectionsLimitExceeded,733 734 CollectionAdminCountExceeded,735 736 CollectionLimitBoundsExceeded,737 738 OwnerPermissionsCantBeReverted,739 740 TransferNotAllowed,741 742 AccountTokenLimitExceeded,743 744 CollectionTokenLimitExceeded,745 746 MetadataFlagFrozen,747748 749 TokenNotFound,750 751 TokenValueTooLow,752 753 ApprovedValueTooLow,754 755 CantApproveMoreThanOwned,756 757 AddressIsNotEthMirror,758759 760 AddressIsZero,761762 763 UnsupportedOperation,764765 766 NotSufficientFounds,767768 769 UserIsNotAllowedToNest,770 771 SourceCollectionIsNotAllowedToNest,772773 774 CollectionFieldSizeExceeded,775776 777 NoSpaceForProperty,778779 780 PropertyLimitReached,781782 783 PropertyKeyIsTooLong,784785 786 InvalidCharacterInPropertyKey,787788 789 EmptyPropertyKey,790791 792 CollectionIsExternal,793794 795 CollectionIsInternal,796797 798 ConfirmSponsorshipFail,799800 801 UserIsNotCollectionAdmin,802 }803804 805 #[pallet::storage]806 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;807808 809 #[pallet::storage]810 pub type DestroyedCollectionCount<T> =811 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;812813 814 #[pallet::storage]815 pub type CollectionById<T> = StorageMap<816 Hasher = Blake2_128Concat,817 Key = CollectionId,818 Value = Collection<<T as frame_system::Config>::AccountId>,819 QueryKind = OptionQuery,820 >;821822 823 #[pallet::storage]824 #[pallet::getter(fn collection_properties)]825 pub type CollectionProperties<T> = StorageMap<826 Hasher = Blake2_128Concat,827 Key = CollectionId,828 Value = CollectionPropertiesT,829 QueryKind = ValueQuery,830 >;831832 833 #[pallet::storage]834 #[pallet::getter(fn property_permissions)]835 pub type CollectionPropertyPermissions<T> = StorageMap<836 Hasher = Blake2_128Concat,837 Key = CollectionId,838 Value = PropertiesPermissionMap,839 QueryKind = ValueQuery,840 >;841842 843 #[pallet::storage]844 pub type AdminAmount<T> = StorageMap<845 Hasher = Blake2_128Concat,846 Key = CollectionId,847 Value = u32,848 QueryKind = ValueQuery,849 >;850851 852 #[pallet::storage]853 pub type IsAdmin<T: Config> = StorageNMap<854 Key = (855 Key<Blake2_128Concat, CollectionId>,856 Key<Blake2_128Concat, T::CrossAccountId>,857 ),858 Value = bool,859 QueryKind = ValueQuery,860 >;861862 863 #[pallet::storage]864 pub type Allowlist<T: Config> = StorageNMap<865 Key = (866 Key<Blake2_128Concat, CollectionId>,867 Key<Blake2_128Concat, T::CrossAccountId>,868 ),869 Value = bool,870 QueryKind = ValueQuery,871 >;872873 874 #[pallet::storage]875 pub type DummyStorageValue<T: Config> = StorageValue<876 Value = (877 CollectionStats,878 CollectionId,879 TokenId,880 TokenChild,881 PhantomType<(882 TokenData<T::CrossAccountId>,883 RpcCollection<T::AccountId>,884 885 PovInfo,886 )>,887 ),888 QueryKind = OptionQuery,889 >;890}891892impl<T: Config> Pallet<T> {893 894 895 896 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {897 ensure!(898 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,899 <Error<T>>::AddressIsZero900 );901 Ok(())902 }903904 905 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {906 <IsAdmin<T>>::iter_prefix((collection,))907 .map(|(a, _)| a)908 .collect()909 }910911 912 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {913 <Allowlist<T>>::iter_prefix((collection,))914 .map(|(a, _)| a)915 .collect()916 }917918 919 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {920 <Allowlist<T>>::get((collection, user))921 }922923 924 pub fn collection_stats() -> CollectionStats {925 let created = <CreatedCollectionCount<T>>::get();926 let destroyed = <DestroyedCollectionCount<T>>::get();927 CollectionStats {928 created: created.0,929 destroyed: destroyed.0,930 alive: created.0 - destroyed.0,931 }932 }933934 935 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {936 let collection = <CollectionById<T>>::get(collection)?;937 let limits = collection.limits;938 let effective_limits = CollectionLimits {939 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),940 sponsored_data_size: Some(limits.sponsored_data_size()),941 sponsored_data_rate_limit: Some(942 limits943 .sponsored_data_rate_limit944 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),945 ),946 token_limit: Some(limits.token_limit()),947 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(948 match collection.mode {949 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,950 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,951 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,952 },953 )),954 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),955 owner_can_transfer: Some(limits.owner_can_transfer()),956 owner_can_destroy: Some(limits.owner_can_destroy()),957 transfers_enabled: Some(limits.transfers_enabled()),958 };959960 Some(effective_limits)961 }962963 964 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {965 let Collection {966 name,967 description,968 owner,969 mode,970 token_prefix,971 sponsorship,972 limits,973 permissions,974 flags,975 } = <CollectionById<T>>::get(collection)?;976977 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)978 .into_iter()979 .map(|(key, permission)| PropertyKeyPermission { key, permission })980 .collect();981982 let properties = <CollectionProperties<T>>::get(collection)983 .into_iter()984 .map(|(key, value)| Property { key, value })985 .collect();986987 let permissions = CollectionPermissions {988 access: Some(permissions.access()),989 mint_mode: Some(permissions.mint_mode()),990 nesting: Some(permissions.nesting().clone()),991 };992993 Some(RpcCollection {994 name: name.into_inner(),995 description: description.into_inner(),996 owner,997 mode,998 token_prefix: token_prefix.into_inner(),999 sponsorship,1000 limits,1001 permissions,1002 token_property_permissions,1003 properties,1004 read_only: flags.external,10051006 flags: RpcCollectionFlags {1007 foreign: flags.foreign,1008 erc721metadata: flags.erc721metadata,1009 },1010 })1011 }1012}10131014macro_rules! limit_default {1015 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1016 $(1017 if let Some($new) = $new.$field {1018 let $old = $old.$field($($arg)?);1019 let _ = $new;1020 let _ = $old;1021 $check1022 } else {1023 $new.$field = $old.$field1024 }1025 )*1026 }};1027}1028macro_rules! limit_default_clone {1029 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1030 $(1031 if let Some($new) = $new.$field.clone() {1032 let $old = $old.$field($($arg)?);1033 let _ = $new;1034 let _ = $old;1035 $check1036 } else {1037 $new.$field = $old.$field.clone()1038 }1039 )*1040 }};1041}10421043impl<T: Config> Pallet<T> {1044 1045 1046 1047 1048 1049 pub fn init_collection(1050 owner: T::CrossAccountId,1051 payer: T::CrossAccountId,1052 data: CreateCollectionData<T::AccountId>,1053 flags: CollectionFlags,1054 ) -> Result<CollectionId, DispatchError> {1055 {1056 ensure!(1057 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1058 Error::<T>::CollectionTokenPrefixLimitExceeded1059 );1060 }10611062 let created_count = <CreatedCollectionCount<T>>::get()1063 .01064 .checked_add(1)1065 .ok_or(ArithmeticError::Overflow)?;1066 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1067 let id = CollectionId(created_count);10681069 1070 ensure!(1071 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1072 <Error<T>>::TotalCollectionsLimitExceeded1073 );10741075 10761077 let collection = Collection {1078 owner: owner.as_sub().clone(),1079 name: data.name,1080 mode: data.mode.clone(),1081 description: data.description,1082 token_prefix: data.token_prefix,1083 sponsorship: data1084 .pending_sponsor1085 .map(SponsorshipState::Unconfirmed)1086 .unwrap_or_default(),1087 limits: data1088 .limits1089 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1090 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1091 permissions: data1092 .permissions1093 .map(|permissions| {1094 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1095 })1096 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1097 flags,1098 };10991100 let mut collection_properties = CollectionPropertiesT::new();1101 collection_properties1102 .try_set_from_iter(data.properties.into_iter())1103 .map_err(<Error<T>>::from)?;11041105 CollectionProperties::<T>::insert(id, collection_properties);11061107 let mut token_props_permissions = PropertiesPermissionMap::new();1108 token_props_permissions1109 .try_set_from_iter(data.token_property_permissions.into_iter())1110 .map_err(<Error<T>>::from)?;11111112 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11131114 1115 {1116 let mut imbalance =1117 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1118 imbalance.subsume(1119 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1120 &T::TreasuryAccountId::get(),1121 T::CollectionCreationPrice::get(),1122 ),1123 );1124 <T as Config>::Currency::settle(1125 payer.as_sub(),1126 imbalance,1127 WithdrawReasons::TRANSFER,1128 ExistenceRequirement::KeepAlive,1129 )1130 .map_err(|_| Error::<T>::NotSufficientFounds)?;1131 }11321133 <CreatedCollectionCount<T>>::put(created_count);1134 <Pallet<T>>::deposit_event(Event::CollectionCreated(1135 id,1136 data.mode.id(),1137 owner.as_sub().clone(),1138 ));1139 <PalletEvm<T>>::deposit_log(1140 erc::CollectionHelpersEvents::CollectionCreated {1141 owner: *owner.as_eth(),1142 collection_id: eth::collection_id_to_address(id),1143 }1144 .to_log(T::ContractAddress::get()),1145 );1146 <CollectionById<T>>::insert(id, collection);1147 Ok(id)1148 }11491150 1151 1152 1153 1154 pub fn destroy_collection(1155 collection: CollectionHandle<T>,1156 sender: &T::CrossAccountId,1157 ) -> DispatchResult {1158 ensure!(1159 collection.limits.owner_can_destroy(),1160 <Error<T>>::NoPermission,1161 );1162 collection.check_is_owner(sender)?;11631164 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1165 .01166 .checked_add(1)1167 .ok_or(ArithmeticError::Overflow)?;11681169 11701171 <DestroyedCollectionCount<T>>::put(destroyed_collections);1172 <CollectionById<T>>::remove(collection.id);1173 <AdminAmount<T>>::remove(collection.id);1174 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1175 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1176 <CollectionProperties<T>>::remove(collection.id);11771178 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11791180 <PalletEvm<T>>::deposit_log(1181 erc::CollectionHelpersEvents::CollectionDestroyed {1182 collection_id: eth::collection_id_to_address(collection.id),1183 }1184 .to_log(T::ContractAddress::get()),1185 );1186 Ok(())1187 }11881189 1190 1191 1192 1193 1194 1195 1196 1197 #[transactional]1198 fn modify_collection_properties(1199 collection: &CollectionHandle<T>,1200 sender: &T::CrossAccountId,1201 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1202 ) -> DispatchResult {1203 collection.check_is_owner_or_admin(sender)?;12041205 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12061207 for (key, value) in properties_updates {1208 match value {1209 Some(value) => {1210 stored_properties1211 .try_set(key.clone(), value)1212 .map_err(<Error<T>>::from)?;12131214 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1215 <PalletEvm<T>>::deposit_log(1216 erc::CollectionHelpersEvents::CollectionChanged {1217 collection_id: eth::collection_id_to_address(collection.id),1218 }1219 .to_log(T::ContractAddress::get()),1220 );1221 }1222 None => {1223 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12241225 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1226 <PalletEvm<T>>::deposit_log(1227 erc::CollectionHelpersEvents::CollectionChanged {1228 collection_id: eth::collection_id_to_address(collection.id),1229 }1230 .to_log(T::ContractAddress::get()),1231 );1232 }1233 }1234 }12351236 <CollectionProperties<T>>::set(collection.id, stored_properties);12371238 Ok(())1239 }12401241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 pub fn modify_token_properties(1259 collection: &CollectionHandle<T>,1260 sender: &T::CrossAccountId,1261 token_id: TokenId,1262 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1263 is_token_create: bool,1264 mut stored_properties: TokenProperties,1265 is_token_owner: impl Fn() -> Result<bool, DispatchError>,1266 set_token_properties: impl FnOnce(TokenProperties),1267 log: evm_coder::ethereum::Log,1268 ) -> DispatchResult {1269 let is_collection_admin = collection.is_owner_or_admin(sender);1270 let permissions = Self::property_permissions(collection.id);12711272 let mut token_owner_result = None;1273 let mut is_token_owner = || -> Result<bool, DispatchError> {1274 *token_owner_result.get_or_insert_with(&is_token_owner)1275 };12761277 for (key, value) in properties_updates {1278 let permission = permissions1279 .get(&key)1280 .cloned()1281 .unwrap_or_else(PropertyPermission::none);12821283 let is_property_exists = stored_properties.get(&key).is_some();12841285 match permission {1286 PropertyPermission { mutable: false, .. } if is_property_exists => {1287 return Err(<Error<T>>::NoPermission.into());1288 }12891290 PropertyPermission {1291 collection_admin,1292 token_owner,1293 ..1294 } => {1295 1296 let is_token_create =1297 is_token_create && (collection_admin || token_owner) && value.is_some();1298 if !(is_token_create1299 || (collection_admin && is_collection_admin)1300 || (token_owner && is_token_owner()?))1301 {1302 fail!(<Error<T>>::NoPermission);1303 }1304 }1305 }13061307 match value {1308 Some(value) => {1309 stored_properties1310 .try_set(key.clone(), value)1311 .map_err(<Error<T>>::from)?;13121313 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1314 }1315 None => {1316 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13171318 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1319 }1320 }13211322 <PalletEvm<T>>::deposit_log(log.clone());1323 }13241325 set_token_properties(stored_properties);13261327 Ok(())1328 }13291330 1331 1332 1333 1334 1335 1336 pub fn set_allowance_for_all(1337 collection: &CollectionHandle<T>,1338 owner: &T::CrossAccountId,1339 operator: &T::CrossAccountId,1340 approve: bool,1341 set_allowance: impl FnOnce(),1342 log: evm_coder::ethereum::Log,1343 ) -> DispatchResult {1344 if collection.permissions.access() == AccessMode::AllowList {1345 collection.check_allowlist(owner)?;1346 collection.check_allowlist(operator)?;1347 }13481349 Self::ensure_correct_receiver(operator)?;13501351 set_allowance();13521353 <PalletEvm<T>>::deposit_log(log);1354 Self::deposit_event(Event::ApprovedForAll(1355 collection.id,1356 owner.clone(),1357 operator.clone(),1358 approve,1359 ));1360 Ok(())1361 }13621363 1364 1365 1366 1367 1368 pub fn set_collection_property(1369 collection: &CollectionHandle<T>,1370 sender: &T::CrossAccountId,1371 property: Property,1372 ) -> DispatchResult {1373 Self::set_collection_properties(collection, sender, [property].into_iter())1374 }13751376 1377 1378 1379 1380 1381 1382 pub fn set_scoped_collection_property(1383 collection_id: CollectionId,1384 scope: PropertyScope,1385 property: Property,1386 ) -> DispatchResult {1387 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1388 properties.try_scoped_set(scope, property.key, property.value)1389 })1390 .map_err(<Error<T>>::from)?;13911392 Ok(())1393 }13941395 1396 1397 1398 1399 1400 1401 pub fn set_scoped_collection_properties(1402 collection_id: CollectionId,1403 scope: PropertyScope,1404 properties: impl Iterator<Item = Property>,1405 ) -> DispatchResult {1406 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1407 stored_properties.try_scoped_set_from_iter(scope, properties)1408 })1409 .map_err(<Error<T>>::from)?;14101411 Ok(())1412 }14131414 1415 1416 1417 1418 1419 pub fn set_collection_properties(1420 collection: &CollectionHandle<T>,1421 sender: &T::CrossAccountId,1422 properties: impl Iterator<Item = Property>,1423 ) -> DispatchResult {1424 Self::modify_collection_properties(1425 collection,1426 sender,1427 properties.map(|property| (property.key, Some(property.value))),1428 )1429 }14301431 1432 1433 1434 1435 1436 pub fn delete_collection_property(1437 collection: &CollectionHandle<T>,1438 sender: &T::CrossAccountId,1439 property_key: PropertyKey,1440 ) -> DispatchResult {1441 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1442 }14431444 1445 1446 1447 1448 1449 pub fn delete_collection_properties(1450 collection: &CollectionHandle<T>,1451 sender: &T::CrossAccountId,1452 property_keys: impl Iterator<Item = PropertyKey>,1453 ) -> DispatchResult {1454 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1455 }14561457 1458 1459 1460 1461 1462 1463 pub fn set_property_permission_unchecked(1464 collection: CollectionId,1465 property_permission: PropertyKeyPermission,1466 ) -> DispatchResult {1467 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1468 permissions.try_set(property_permission.key, property_permission.permission)1469 })1470 .map_err(<Error<T>>::from)?;1471 Ok(())1472 }14731474 1475 1476 1477 1478 1479 pub fn set_property_permission(1480 collection: &CollectionHandle<T>,1481 sender: &T::CrossAccountId,1482 property_permission: PropertyKeyPermission,1483 ) -> DispatchResult {1484 Self::set_scoped_property_permission(1485 collection,1486 sender,1487 PropertyScope::None,1488 property_permission,1489 )1490 }14911492 1493 1494 1495 1496 1497 1498 pub fn set_scoped_property_permission(1499 collection: &CollectionHandle<T>,1500 sender: &T::CrossAccountId,1501 scope: PropertyScope,1502 property_permission: PropertyKeyPermission,1503 ) -> DispatchResult {1504 collection.check_is_owner_or_admin(sender)?;15051506 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1507 let current_permission = all_permissions.get(&property_permission.key);1508 if matches![1509 current_permission,1510 Some(PropertyPermission { mutable: false, .. })1511 ] {1512 return Err(<Error<T>>::NoPermission.into());1513 }15141515 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1516 let property_permission = property_permission.clone();1517 permissions.try_scoped_set(1518 scope,1519 property_permission.key,1520 property_permission.permission,1521 )1522 })1523 .map_err(<Error<T>>::from)?;15241525 Self::deposit_event(Event::PropertyPermissionSet(1526 collection.id,1527 property_permission.key,1528 ));1529 <PalletEvm<T>>::deposit_log(1530 erc::CollectionHelpersEvents::CollectionChanged {1531 collection_id: eth::collection_id_to_address(collection.id),1532 }1533 .to_log(T::ContractAddress::get()),1534 );15351536 Ok(())1537 }15381539 1540 1541 1542 1543 1544 #[transactional]1545 pub fn set_token_property_permissions(1546 collection: &CollectionHandle<T>,1547 sender: &T::CrossAccountId,1548 property_permissions: Vec<PropertyKeyPermission>,1549 ) -> DispatchResult {1550 Self::set_scoped_token_property_permissions(1551 collection,1552 sender,1553 PropertyScope::None,1554 property_permissions,1555 )1556 }15571558 1559 1560 1561 1562 1563 1564 #[transactional]1565 pub fn set_scoped_token_property_permissions(1566 collection: &CollectionHandle<T>,1567 sender: &T::CrossAccountId,1568 scope: PropertyScope,1569 property_permissions: Vec<PropertyKeyPermission>,1570 ) -> DispatchResult {1571 for prop_pemission in property_permissions {1572 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1573 }15741575 Ok(())1576 }15771578 1579 pub fn get_collection_property(1580 collection_id: CollectionId,1581 key: &PropertyKey,1582 ) -> Option<PropertyValue> {1583 Self::collection_properties(collection_id).get(key).cloned()1584 }15851586 1587 pub fn bytes_keys_to_property_keys(1588 keys: Vec<Vec<u8>>,1589 ) -> Result<Vec<PropertyKey>, DispatchError> {1590 keys.into_iter()1591 .map(|key| -> Result<PropertyKey, DispatchError> {1592 key.try_into()1593 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1594 })1595 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1596 }15971598 1599 pub fn filter_collection_properties(1600 collection_id: CollectionId,1601 keys: Option<Vec<PropertyKey>>,1602 ) -> Result<Vec<Property>, DispatchError> {1603 let properties = Self::collection_properties(collection_id);16041605 let properties = keys1606 .map(|keys| {1607 keys.into_iter()1608 .filter_map(|key| {1609 properties.get(&key).map(|value| Property {1610 key,1611 value: value.clone(),1612 })1613 })1614 .collect()1615 })1616 .unwrap_or_else(|| {1617 properties1618 .into_iter()1619 .map(|(key, value)| Property { key, value })1620 .collect()1621 });16221623 Ok(properties)1624 }16251626 1627 pub fn filter_property_permissions(1628 collection_id: CollectionId,1629 keys: Option<Vec<PropertyKey>>,1630 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1631 let permissions = Self::property_permissions(collection_id);16321633 let key_permissions = keys1634 .map(|keys| {1635 keys.into_iter()1636 .filter_map(|key| {1637 permissions1638 .get(&key)1639 .map(|permission| PropertyKeyPermission {1640 key,1641 permission: permission.clone(),1642 })1643 })1644 .collect()1645 })1646 .unwrap_or_else(|| {1647 permissions1648 .into_iter()1649 .map(|(key, permission)| PropertyKeyPermission { key, permission })1650 .collect()1651 });16521653 Ok(key_permissions)1654 }16551656 1657 1658 1659 pub fn toggle_allowlist(1660 collection: &CollectionHandle<T>,1661 sender: &T::CrossAccountId,1662 user: &T::CrossAccountId,1663 allowed: bool,1664 ) -> DispatchResult {1665 collection.check_is_owner_or_admin(sender)?;16661667 16681669 if allowed {1670 <Allowlist<T>>::insert((collection.id, user), true);1671 Self::deposit_event(Event::<T>::AllowListAddressAdded(1672 collection.id,1673 user.clone(),1674 ));1675 } else {1676 <Allowlist<T>>::remove((collection.id, user));1677 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1678 collection.id,1679 user.clone(),1680 ));1681 }16821683 <PalletEvm<T>>::deposit_log(1684 erc::CollectionHelpersEvents::CollectionChanged {1685 collection_id: eth::collection_id_to_address(collection.id),1686 }1687 .to_log(T::ContractAddress::get()),1688 );16891690 Ok(())1691 }16921693 1694 1695 1696 pub fn toggle_admin(1697 collection: &CollectionHandle<T>,1698 sender: &T::CrossAccountId,1699 user: &T::CrossAccountId,1700 admin: bool,1701 ) -> DispatchResult {1702 collection.check_is_internal()?;1703 collection.check_is_owner(sender)?;17041705 let is_admin = <IsAdmin<T>>::get((collection.id, user));1706 if is_admin == admin {1707 if admin {1708 return Ok(());1709 } else {1710 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1711 }1712 }1713 let amount = <AdminAmount<T>>::get(collection.id);17141715 17161717 if admin {1718 let amount = amount1719 .checked_add(1)1720 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1721 ensure!(1722 amount <= Self::collection_admins_limit(),1723 <Error<T>>::CollectionAdminCountExceeded,1724 );17251726 <AdminAmount<T>>::insert(collection.id, amount);1727 <IsAdmin<T>>::insert((collection.id, user), true);17281729 Self::deposit_event(Event::<T>::CollectionAdminAdded(1730 collection.id,1731 user.clone(),1732 ));1733 } else {1734 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1735 <IsAdmin<T>>::remove((collection.id, user));17361737 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1738 collection.id,1739 user.clone(),1740 ));1741 }17421743 <PalletEvm<T>>::deposit_log(1744 erc::CollectionHelpersEvents::CollectionChanged {1745 collection_id: eth::collection_id_to_address(collection.id),1746 }1747 .to_log(T::ContractAddress::get()),1748 );17491750 Ok(())1751 }17521753 1754 pub fn update_limits(1755 user: &T::CrossAccountId,1756 collection: &mut CollectionHandle<T>,1757 new_limit: CollectionLimits,1758 ) -> DispatchResult {1759 collection.check_is_internal()?;1760 collection.check_is_owner_or_admin(user)?;17611762 collection.limits =1763 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17641765 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1766 <PalletEvm<T>>::deposit_log(1767 erc::CollectionHelpersEvents::CollectionChanged {1768 collection_id: eth::collection_id_to_address(collection.id),1769 }1770 .to_log(T::ContractAddress::get()),1771 );17721773 collection.save()1774 }17751776 1777 fn clamp_limits(1778 mode: CollectionMode,1779 old_limit: &CollectionLimits,1780 mut new_limit: CollectionLimits,1781 ) -> Result<CollectionLimits, DispatchError> {1782 let limits = old_limit;1783 limit_default!(old_limit, new_limit,1784 account_token_ownership_limit => ensure!(1785 new_limit <= MAX_TOKEN_OWNERSHIP,1786 <Error<T>>::CollectionLimitBoundsExceeded,1787 ),1788 sponsored_data_size => ensure!(1789 new_limit <= CUSTOM_DATA_LIMIT,1790 <Error<T>>::CollectionLimitBoundsExceeded,1791 ),17921793 sponsored_data_rate_limit => {},1794 token_limit => ensure!(1795 old_limit >= new_limit && new_limit > 0,1796 <Error<T>>::CollectionTokenLimitExceeded1797 ),17981799 sponsor_transfer_timeout(match mode {1800 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1801 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1802 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1803 }) => ensure!(1804 new_limit <= MAX_SPONSOR_TIMEOUT,1805 <Error<T>>::CollectionLimitBoundsExceeded,1806 ),1807 sponsor_approve_timeout => {},1808 owner_can_transfer => ensure!(1809 !limits.owner_can_transfer_instaled() ||1810 old_limit || !new_limit,1811 <Error<T>>::OwnerPermissionsCantBeReverted,1812 ),1813 owner_can_destroy => ensure!(1814 old_limit || !new_limit,1815 <Error<T>>::OwnerPermissionsCantBeReverted,1816 ),1817 transfers_enabled => {},1818 );1819 Ok(new_limit)1820 }18211822 1823 pub fn update_permissions(1824 user: &T::CrossAccountId,1825 collection: &mut CollectionHandle<T>,1826 new_permission: CollectionPermissions,1827 ) -> DispatchResult {1828 collection.check_is_internal()?;1829 collection.check_is_owner_or_admin(user)?;1830 collection.permissions = Self::clamp_permissions(1831 collection.mode.clone(),1832 &collection.permissions,1833 new_permission,1834 )?;18351836 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1837 <PalletEvm<T>>::deposit_log(1838 erc::CollectionHelpersEvents::CollectionChanged {1839 collection_id: eth::collection_id_to_address(collection.id),1840 }1841 .to_log(T::ContractAddress::get()),1842 );18431844 collection.save()1845 }18461847 1848 fn clamp_permissions(1849 _mode: CollectionMode,1850 old_permission: &CollectionPermissions,1851 mut new_permission: CollectionPermissions,1852 ) -> Result<CollectionPermissions, DispatchError> {1853 limit_default_clone!(old_permission, new_permission,1854 access => {},1855 mint_mode => {},1856 nesting => { },1857 );1858 Ok(new_permission)1859 }18601861 1862 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1863 CollectionProperties::<T>::mutate(collection_id, |properties| {1864 properties.recompute_consumed_space();1865 });18661867 Ok(())1868 }1869}187018711872#[macro_export]1873macro_rules! unsupported {1874 ($runtime:path) => {1875 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1876 };1877}187818791880pub trait CommonWeightInfo<CrossAccountId> {1881 1882 fn create_item(data: &CreateItemData) -> Weight {1883 Self::create_multiple_items(from_ref(data))1884 }18851886 1887 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18881889 1890 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18911892 1893 fn burn_item() -> Weight;18941895 1896 1897 1898 fn set_collection_properties(amount: u32) -> Weight;18991900 1901 1902 1903 fn delete_collection_properties(amount: u32) -> Weight;19041905 1906 1907 1908 fn set_token_properties(amount: u32) -> Weight;19091910 1911 1912 1913 fn delete_token_properties(amount: u32) -> Weight;19141915 1916 1917 1918 fn set_token_property_permissions(amount: u32) -> Weight;19191920 1921 fn transfer() -> Weight;19221923 1924 fn approve() -> Weight;19251926 1927 fn approve_from() -> Weight;19281929 1930 fn transfer_from() -> Weight;19311932 1933 fn burn_from() -> Weight;19341935 1936 1937 1938 1939 fn burn_recursively_self_raw() -> Weight;19401941 1942 1943 1944 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19451946 1947 1948 1949 1950 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1951 Self::burn_recursively_self_raw()1952 .saturating_mul(max_selfs.max(1) as u64)1953 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1954 }19551956 1957 fn token_owner() -> Weight;19581959 1960 fn set_allowance_for_all() -> Weight;19611962 1963 fn force_repair_item() -> Weight;1964}196519661967pub trait RefungibleExtensionsWeightInfo {1968 1969 fn repartition() -> Weight;1970}197119721973197419751976pub trait CommonCollectionOperations<T: Config> {1977 1978 1979 1980 1981 1982 1983 fn create_item(1984 &self,1985 sender: T::CrossAccountId,1986 to: T::CrossAccountId,1987 data: CreateItemData,1988 nesting_budget: &dyn Budget,1989 ) -> DispatchResultWithPostInfo;19901991 1992 1993 1994 1995 1996 1997 fn create_multiple_items(1998 &self,1999 sender: T::CrossAccountId,2000 to: T::CrossAccountId,2001 data: Vec<CreateItemData>,2002 nesting_budget: &dyn Budget,2003 ) -> DispatchResultWithPostInfo;20042005 2006 2007 2008 2009 2010 2011 fn create_multiple_items_ex(2012 &self,2013 sender: T::CrossAccountId,2014 data: CreateItemExData<T::CrossAccountId>,2015 nesting_budget: &dyn Budget,2016 ) -> DispatchResultWithPostInfo;20172018 2019 2020 2021 2022 2023 fn burn_item(2024 &self,2025 sender: T::CrossAccountId,2026 token: TokenId,2027 amount: u128,2028 ) -> DispatchResultWithPostInfo;20292030 2031 2032 2033 2034 2035 2036 fn burn_item_recursively(2037 &self,2038 sender: T::CrossAccountId,2039 token: TokenId,2040 self_budget: &dyn Budget,2041 breadth_budget: &dyn Budget,2042 ) -> DispatchResultWithPostInfo;20432044 2045 2046 2047 2048 fn set_collection_properties(2049 &self,2050 sender: T::CrossAccountId,2051 properties: Vec<Property>,2052 ) -> DispatchResultWithPostInfo;20532054 2055 2056 2057 2058 fn delete_collection_properties(2059 &self,2060 sender: &T::CrossAccountId,2061 property_keys: Vec<PropertyKey>,2062 ) -> DispatchResultWithPostInfo;20632064 2065 2066 2067 2068 2069 2070 2071 2072 2073 fn set_token_properties(2074 &self,2075 sender: T::CrossAccountId,2076 token_id: TokenId,2077 properties: Vec<Property>,2078 budget: &dyn Budget,2079 ) -> DispatchResultWithPostInfo;20802081 2082 2083 2084 2085 2086 2087 2088 2089 2090 fn delete_token_properties(2091 &self,2092 sender: T::CrossAccountId,2093 token_id: TokenId,2094 property_keys: Vec<PropertyKey>,2095 budget: &dyn Budget,2096 ) -> DispatchResultWithPostInfo;20972098 2099 2100 2101 2102 2103 2104 fn set_token_property_permissions(2105 &self,2106 sender: &T::CrossAccountId,2107 property_permissions: Vec<PropertyKeyPermission>,2108 ) -> DispatchResultWithPostInfo;21092110 2111 2112 2113 2114 2115 2116 2117 fn transfer(2118 &self,2119 sender: T::CrossAccountId,2120 to: T::CrossAccountId,2121 token: TokenId,2122 amount: u128,2123 budget: &dyn Budget,2124 ) -> DispatchResultWithPostInfo;21252126 2127 2128 2129 2130 2131 2132 fn approve(2133 &self,2134 sender: T::CrossAccountId,2135 spender: T::CrossAccountId,2136 token: TokenId,2137 amount: u128,2138 ) -> DispatchResultWithPostInfo;21392140 2141 2142 2143 2144 2145 2146 2147 fn approve_from(2148 &self,2149 sender: T::CrossAccountId,2150 from: T::CrossAccountId,2151 to: T::CrossAccountId,2152 token: TokenId,2153 amount: u128,2154 ) -> DispatchResultWithPostInfo;21552156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 fn transfer_from(2167 &self,2168 sender: T::CrossAccountId,2169 from: T::CrossAccountId,2170 to: T::CrossAccountId,2171 token: TokenId,2172 amount: u128,2173 budget: &dyn Budget,2174 ) -> DispatchResultWithPostInfo;21752176 2177 2178 2179 2180 2181 2182 2183 2184 2185 fn burn_from(2186 &self,2187 sender: T::CrossAccountId,2188 from: T::CrossAccountId,2189 token: TokenId,2190 amount: u128,2191 budget: &dyn Budget,2192 ) -> DispatchResultWithPostInfo;21932194 2195 2196 2197 2198 2199 2200 fn check_nesting(2201 &self,2202 sender: T::CrossAccountId,2203 from: (CollectionId, TokenId),2204 under: TokenId,2205 budget: &dyn Budget,2206 ) -> DispatchResult;22072208 2209 2210 2211 2212 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22132214 2215 2216 2217 2218 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22192220 2221 2222 2223 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22242225 2226 fn collection_tokens(&self) -> Vec<TokenId>;22272228 2229 2230 2231 fn token_exists(&self, token: TokenId) -> bool;22322233 2234 fn last_token_id(&self) -> TokenId;22352236 2237 2238 2239 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22402241 2242 2243 2244 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22452246 2247 2248 2249 2250 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22512252 2253 2254 2255 2256 2257 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22582259 2260 fn total_supply(&self) -> u32;22612262 2263 2264 2265 fn account_balance(&self, account: T::CrossAccountId) -> u32;22662267 2268 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22692270 2271 fn total_pieces(&self, token: TokenId) -> Option<u128>;22722273 2274 2275 2276 2277 2278 fn allowance(2279 &self,2280 sender: T::CrossAccountId,2281 spender: T::CrossAccountId,2282 token: TokenId,2283 ) -> u128;22842285 2286 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22872288 2289 2290 2291 2292 fn set_allowance_for_all(2293 &self,2294 owner: T::CrossAccountId,2295 operator: T::CrossAccountId,2296 approve: bool,2297 ) -> DispatchResultWithPostInfo;22982299 2300 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23012302 2303 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2304}230523062307pub trait RefungibleExtensions<T>2308where2309 T: Config,2310{2311 2312 2313 2314 2315 2316 2317 2318 fn repartition(2319 &self,2320 sender: &T::CrossAccountId,2321 token: TokenId,2322 amount: u128,2323 ) -> DispatchResultWithPostInfo;2324}23252326232723282329pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2330 let post_info = PostDispatchInfo {2331 actual_weight: Some(weight),2332 pays_fee: Pays::Yes,2333 };2334 match res {2335 Ok(()) => Ok(post_info),2336 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2337 }2338}23392340impl<T: Config> From<PropertiesError> for Error<T> {2341 fn from(error: PropertiesError) -> Self {2342 match error {2343 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2344 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2345 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2346 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2347 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2348 }2349 }2350}