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(&self, reads: u64) -> evm_coder::execution::Result<()> {196 self.recorder197 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(198 <T as frame_system::Config>::DbWeight::get()199 .read200 .saturating_mul(reads),201 )))202 }203204 205 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {206 self.recorder207 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(208 <T as frame_system::Config>::DbWeight::get()209 .write210 .saturating_mul(writes),211 )))212 }213214 215 pub fn consume_store_reads_and_writes(216 &self,217 reads: u64,218 writes: u64,219 ) -> evm_coder::execution::Result<()> {220 let weight = <T as frame_system::Config>::DbWeight::get();221 let reads = weight.read.saturating_mul(reads);222 let writes = weight.read.saturating_mul(writes);223 self.recorder224 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(225 reads.saturating_add(writes),226 )))227 }228229 230 pub fn save(&self) -> DispatchResult {231 <CollectionById<T>>::insert(self.id, &self.collection);232 Ok(())233 }234235 236 237 238 239 240 pub fn set_sponsor(241 &mut self,242 sender: &T::CrossAccountId,243 sponsor: T::AccountId,244 ) -> DispatchResult {245 self.check_is_internal()?;246 self.check_is_owner_or_admin(sender)?;247248 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());249250 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));251 <PalletEvm<T>>::deposit_log(252 erc::CollectionHelpersEvents::CollectionChanged {253 collection_id: eth::collection_id_to_address(self.id),254 }255 .to_log(T::ContractAddress::get()),256 );257258 self.save()259 }260261 262 263 264 265 266 267 268 269 270 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {271 self.check_is_internal()?;272273 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());274275 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));276 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));277 <PalletEvm<T>>::deposit_log(278 erc::CollectionHelpersEvents::CollectionChanged {279 collection_id: eth::collection_id_to_address(self.id),280 }281 .to_log(T::ContractAddress::get()),282 );283284 self.save()285 }286287 288 289 290 291 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {292 self.check_is_internal()?;293 ensure!(294 self.collection.sponsorship.pending_sponsor() == Some(sender),295 Error::<T>::ConfirmSponsorshipFail296 );297298 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());299300 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));301 <PalletEvm<T>>::deposit_log(302 erc::CollectionHelpersEvents::CollectionChanged {303 collection_id: eth::collection_id_to_address(self.id),304 }305 .to_log(T::ContractAddress::get()),306 );307308 self.save()309 }310311 312 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {313 self.check_is_internal()?;314 self.check_is_owner_or_admin(sender)?;315316 self.collection.sponsorship = SponsorshipState::Disabled;317318 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));319 <PalletEvm<T>>::deposit_log(320 erc::CollectionHelpersEvents::CollectionChanged {321 collection_id: eth::collection_id_to_address(self.id),322 }323 .to_log(T::ContractAddress::get()),324 );325 self.save()326 }327328 329 330 331 332 pub fn force_remove_sponsor(&mut self) -> DispatchResult {333 self.check_is_internal()?;334335 self.collection.sponsorship = SponsorshipState::Disabled;336337 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));338 <PalletEvm<T>>::deposit_log(339 erc::CollectionHelpersEvents::CollectionChanged {340 collection_id: eth::collection_id_to_address(self.id),341 }342 .to_log(T::ContractAddress::get()),343 );344 self.save()345 }346347 348 349 pub fn check_is_internal(&self) -> DispatchResult {350 if self.flags.external {351 return Err(<Error<T>>::CollectionIsExternal)?;352 }353354 Ok(())355 }356357 358 359 pub fn check_is_external(&self) -> DispatchResult {360 if !self.flags.external {361 return Err(<Error<T>>::CollectionIsInternal)?;362 }363364 Ok(())365 }366}367368impl<T: Config> Deref for CollectionHandle<T> {369 type Target = Collection<T::AccountId>;370371 fn deref(&self) -> &Self::Target {372 &self.collection373 }374}375376impl<T: Config> DerefMut for CollectionHandle<T> {377 fn deref_mut(&mut self) -> &mut Self::Target {378 &mut self.collection379 }380}381382impl<T: Config> CollectionHandle<T> {383 384 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {385 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);386 Ok(())387 }388389 390 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {391 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))392 }393394 395 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {396 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);397 Ok(())398 }399400 401 402 403 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {404 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)405 }406407 408 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {409 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)410 }411412 413 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {414 ensure!(415 <Allowlist<T>>::get((self.id, user)),416 <Error<T>>::AddressNotInAllowlist417 );418 Ok(())419 }420421 422 423 424 pub fn change_owner(425 &mut self,426 caller: T::CrossAccountId,427 new_owner: T::CrossAccountId,428 ) -> DispatchResult {429 self.check_is_internal()?;430 self.check_is_owner(&caller)?;431 self.collection.owner = new_owner.as_sub().clone();432433 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(434 self.id,435 new_owner.as_sub().clone(),436 ));437 <PalletEvm<T>>::deposit_log(438 erc::CollectionHelpersEvents::CollectionChanged {439 collection_id: eth::collection_id_to_address(self.id),440 }441 .to_log(T::ContractAddress::get()),442 );443444 self.save()445 }446}447448#[frame_support::pallet]449pub mod pallet {450 use super::*;451 use dispatch::CollectionDispatch;452 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};453 use frame_system::pallet_prelude::*;454 use frame_support::traits::Currency;455 use up_data_structs::{TokenId, mapping::TokenAddressMapping};456 use scale_info::TypeInfo;457 use weights::WeightInfo;458459 #[pallet::config]460 pub trait Config:461 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo462 {463 464 type WeightInfo: WeightInfo;465466 467 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;468469 470 type Currency: Currency<Self::AccountId>;471472 473 #[pallet::constant]474 type CollectionCreationPrice: Get<475 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,476 >;477478 479 type CollectionDispatch: CollectionDispatch<Self>;480481 482 type TreasuryAccountId: Get<Self::AccountId>;483484 485 #[pallet::constant]486 type ContractAddress: Get<H160>;487488 489 type EvmTokenAddressMapping: TokenAddressMapping<H160>;490491 492 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;493 }494495 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);496497 #[pallet::pallet]498 #[pallet::storage_version(STORAGE_VERSION)]499 #[pallet::generate_store(pub(super) trait Store)]500 pub struct Pallet<T>(_);501502 #[pallet::extra_constants]503 impl<T: Config> Pallet<T> {504 505 pub fn collection_admins_limit() -> u32 {506 COLLECTION_ADMINS_LIMIT507 }508 }509510 impl<T: Config> Pallet<T> {511 512 pub fn deposit_event(event: Event<T>) {513 let event = <T as Config>::RuntimeEvent::from(event);514 let event = event.into();515 <frame_system::Pallet<T>>::deposit_event(event)516 }517 }518519 #[pallet::event]520 pub enum Event<T: Config> {521 522 CollectionCreated(523 524 CollectionId,525 526 u8,527 528 T::AccountId,529 ),530531 532 CollectionDestroyed(533 534 CollectionId,535 ),536537 538 ItemCreated(539 540 CollectionId,541 542 TokenId,543 544 T::CrossAccountId,545 546 u128,547 ),548549 550 ItemDestroyed(551 552 CollectionId,553 554 TokenId,555 556 T::CrossAccountId,557 558 u128,559 ),560561 562 Transfer(563 564 CollectionId,565 566 TokenId,567 568 T::CrossAccountId,569 570 T::CrossAccountId,571 572 u128,573 ),574575 576 Approved(577 578 CollectionId,579 580 TokenId,581 582 T::CrossAccountId,583 584 T::CrossAccountId,585 586 u128,587 ),588589 590 ApprovedForAll(591 592 CollectionId,593 594 T::CrossAccountId,595 596 T::CrossAccountId,597 598 bool,599 ),600601 602 CollectionPropertySet(603 604 CollectionId,605 606 PropertyKey,607 ),608609 610 CollectionPropertyDeleted(611 612 CollectionId,613 614 PropertyKey,615 ),616617 618 TokenPropertySet(619 620 CollectionId,621 622 TokenId,623 624 PropertyKey,625 ),626627 628 TokenPropertyDeleted(629 630 CollectionId,631 632 TokenId,633 634 PropertyKey,635 ),636637 638 PropertyPermissionSet(639 640 CollectionId,641 642 PropertyKey,643 ),644645 646 AllowListAddressAdded(647 648 CollectionId,649 650 T::CrossAccountId,651 ),652653 654 AllowListAddressRemoved(655 656 CollectionId,657 658 T::CrossAccountId,659 ),660661 662 CollectionAdminAdded(663 664 CollectionId,665 666 T::CrossAccountId,667 ),668669 670 CollectionAdminRemoved(671 672 CollectionId,673 674 T::CrossAccountId,675 ),676677 678 CollectionLimitSet(679 680 CollectionId,681 ),682683 684 CollectionOwnerChanged(685 686 CollectionId,687 688 T::AccountId,689 ),690691 692 CollectionPermissionSet(693 694 CollectionId,695 ),696697 698 CollectionSponsorSet(699 700 CollectionId,701 702 T::AccountId,703 ),704705 706 SponsorshipConfirmed(707 708 CollectionId,709 710 T::AccountId,711 ),712713 714 CollectionSponsorRemoved(715 716 CollectionId,717 ),718 }719720 #[pallet::error]721 pub enum Error<T> {722 723 CollectionNotFound,724 725 MustBeTokenOwner,726 727 NoPermission,728 729 CantDestroyNotEmptyCollection,730 731 PublicMintingNotAllowed,732 733 AddressNotInAllowlist,734735 736 CollectionNameLimitExceeded,737 738 CollectionDescriptionLimitExceeded,739 740 CollectionTokenPrefixLimitExceeded,741 742 TotalCollectionsLimitExceeded,743 744 CollectionAdminCountExceeded,745 746 CollectionLimitBoundsExceeded,747 748 OwnerPermissionsCantBeReverted,749 750 TransferNotAllowed,751 752 AccountTokenLimitExceeded,753 754 CollectionTokenLimitExceeded,755 756 MetadataFlagFrozen,757758 759 TokenNotFound,760 761 TokenValueTooLow,762 763 ApprovedValueTooLow,764 765 CantApproveMoreThanOwned,766 767 AddressIsNotEthMirror,768769 770 AddressIsZero,771772 773 UnsupportedOperation,774775 776 NotSufficientFounds,777778 779 UserIsNotAllowedToNest,780 781 SourceCollectionIsNotAllowedToNest,782783 784 CollectionFieldSizeExceeded,785786 787 NoSpaceForProperty,788789 790 PropertyLimitReached,791792 793 PropertyKeyIsTooLong,794795 796 InvalidCharacterInPropertyKey,797798 799 EmptyPropertyKey,800801 802 CollectionIsExternal,803804 805 CollectionIsInternal,806807 808 ConfirmSponsorshipFail,809810 811 UserIsNotCollectionAdmin,812 }813814 815 #[pallet::storage]816 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;817818 819 #[pallet::storage]820 pub type DestroyedCollectionCount<T> =821 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;822823 824 #[pallet::storage]825 pub type CollectionById<T> = StorageMap<826 Hasher = Blake2_128Concat,827 Key = CollectionId,828 Value = Collection<<T as frame_system::Config>::AccountId>,829 QueryKind = OptionQuery,830 >;831832 833 #[pallet::storage]834 #[pallet::getter(fn collection_properties)]835 pub type CollectionProperties<T> = StorageMap<836 Hasher = Blake2_128Concat,837 Key = CollectionId,838 Value = Properties,839 QueryKind = ValueQuery,840 OnEmpty = up_data_structs::CollectionProperties,841 >;842843 844 #[pallet::storage]845 #[pallet::getter(fn property_permissions)]846 pub type CollectionPropertyPermissions<T> = StorageMap<847 Hasher = Blake2_128Concat,848 Key = CollectionId,849 Value = PropertiesPermissionMap,850 QueryKind = ValueQuery,851 >;852853 854 #[pallet::storage]855 pub type AdminAmount<T> = StorageMap<856 Hasher = Blake2_128Concat,857 Key = CollectionId,858 Value = u32,859 QueryKind = ValueQuery,860 >;861862 863 #[pallet::storage]864 pub type IsAdmin<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 Allowlist<T: Config> = StorageNMap<876 Key = (877 Key<Blake2_128Concat, CollectionId>,878 Key<Blake2_128Concat, T::CrossAccountId>,879 ),880 Value = bool,881 QueryKind = ValueQuery,882 >;883884 885 #[pallet::storage]886 pub type DummyStorageValue<T: Config> = StorageValue<887 Value = (888 CollectionStats,889 CollectionId,890 TokenId,891 TokenChild,892 PhantomType<(893 TokenData<T::CrossAccountId>,894 RpcCollection<T::AccountId>,895 896 RmrkCollectionInfo<T::AccountId>,897 RmrkInstanceInfo<T::AccountId>,898 RmrkResourceInfo,899 RmrkPropertyInfo,900 RmrkBaseInfo<T::AccountId>,901 RmrkPartType,902 RmrkBoundedTheme,903 RmrkNftChild,904 905 PovInfo,906 )>,907 ),908 QueryKind = OptionQuery,909 >;910911 #[pallet::hooks]912 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {913 fn on_runtime_upgrade() -> Weight {914 StorageVersion::new(1).put::<Pallet<T>>();915916 Weight::zero()917 }918 }919}920921impl<T: Config> Pallet<T> {922 923 924 925 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {926 ensure!(927 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,928 <Error<T>>::AddressIsZero929 );930 Ok(())931 }932933 934 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {935 <IsAdmin<T>>::iter_prefix((collection,))936 .map(|(a, _)| a)937 .collect()938 }939940 941 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {942 <Allowlist<T>>::iter_prefix((collection,))943 .map(|(a, _)| a)944 .collect()945 }946947 948 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {949 <Allowlist<T>>::get((collection, user))950 }951952 953 pub fn collection_stats() -> CollectionStats {954 let created = <CreatedCollectionCount<T>>::get();955 let destroyed = <DestroyedCollectionCount<T>>::get();956 CollectionStats {957 created: created.0,958 destroyed: destroyed.0,959 alive: created.0 - destroyed.0,960 }961 }962963 964 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {965 let collection = <CollectionById<T>>::get(collection)?;966 let limits = collection.limits;967 let effective_limits = CollectionLimits {968 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),969 sponsored_data_size: Some(limits.sponsored_data_size()),970 sponsored_data_rate_limit: Some(971 limits972 .sponsored_data_rate_limit973 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),974 ),975 token_limit: Some(limits.token_limit()),976 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(977 match collection.mode {978 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,979 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,980 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,981 },982 )),983 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),984 owner_can_transfer: Some(limits.owner_can_transfer()),985 owner_can_destroy: Some(limits.owner_can_destroy()),986 transfers_enabled: Some(limits.transfers_enabled()),987 };988989 Some(effective_limits)990 }991992 993 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {994 let Collection {995 name,996 description,997 owner,998 mode,999 token_prefix,1000 sponsorship,1001 limits,1002 permissions,1003 flags,1004 } = <CollectionById<T>>::get(collection)?;10051006 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1007 .into_iter()1008 .map(|(key, permission)| PropertyKeyPermission { key, permission })1009 .collect();10101011 let properties = <CollectionProperties<T>>::get(collection)1012 .into_iter()1013 .map(|(key, value)| Property { key, value })1014 .collect();10151016 let permissions = CollectionPermissions {1017 access: Some(permissions.access()),1018 mint_mode: Some(permissions.mint_mode()),1019 nesting: Some(permissions.nesting().clone()),1020 };10211022 Some(RpcCollection {1023 name: name.into_inner(),1024 description: description.into_inner(),1025 owner,1026 mode,1027 token_prefix: token_prefix.into_inner(),1028 sponsorship,1029 limits,1030 permissions,1031 token_property_permissions,1032 properties,1033 read_only: flags.external,10341035 flags: RpcCollectionFlags {1036 foreign: flags.foreign,1037 erc721metadata: flags.erc721metadata,1038 },1039 })1040 }1041}10421043macro_rules! limit_default {1044 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1045 $(1046 if let Some($new) = $new.$field {1047 let $old = $old.$field($($arg)?);1048 let _ = $new;1049 let _ = $old;1050 $check1051 } else {1052 $new.$field = $old.$field1053 }1054 )*1055 }};1056}1057macro_rules! limit_default_clone {1058 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1059 $(1060 if let Some($new) = $new.$field.clone() {1061 let $old = $old.$field($($arg)?);1062 let _ = $new;1063 let _ = $old;1064 $check1065 } else {1066 $new.$field = $old.$field.clone()1067 }1068 )*1069 }};1070}10711072impl<T: Config> Pallet<T> {1073 1074 1075 1076 1077 1078 pub fn init_collection(1079 owner: T::CrossAccountId,1080 payer: T::CrossAccountId,1081 data: CreateCollectionData<T::AccountId>,1082 flags: CollectionFlags,1083 ) -> Result<CollectionId, DispatchError> {1084 {1085 ensure!(1086 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1087 Error::<T>::CollectionTokenPrefixLimitExceeded1088 );1089 }10901091 let created_count = <CreatedCollectionCount<T>>::get()1092 .01093 .checked_add(1)1094 .ok_or(ArithmeticError::Overflow)?;1095 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1096 let id = CollectionId(created_count);10971098 1099 ensure!(1100 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1101 <Error<T>>::TotalCollectionsLimitExceeded1102 );11031104 11051106 let collection = Collection {1107 owner: owner.as_sub().clone(),1108 name: data.name,1109 mode: data.mode.clone(),1110 description: data.description,1111 token_prefix: data.token_prefix,1112 sponsorship: data1113 .pending_sponsor1114 .map(SponsorshipState::Unconfirmed)1115 .unwrap_or_default(),1116 limits: data1117 .limits1118 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1119 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1120 permissions: data1121 .permissions1122 .map(|permissions| {1123 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1124 })1125 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1126 flags,1127 };11281129 let mut collection_properties = up_data_structs::CollectionProperties::get();1130 collection_properties1131 .try_set_from_iter(data.properties.into_iter())1132 .map_err(<Error<T>>::from)?;11331134 CollectionProperties::<T>::insert(id, collection_properties);11351136 let mut token_props_permissions = PropertiesPermissionMap::new();1137 token_props_permissions1138 .try_set_from_iter(data.token_property_permissions.into_iter())1139 .map_err(<Error<T>>::from)?;11401141 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11421143 1144 {1145 let mut imbalance =1146 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1147 imbalance.subsume(1148 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1149 &T::TreasuryAccountId::get(),1150 T::CollectionCreationPrice::get(),1151 ),1152 );1153 <T as Config>::Currency::settle(1154 payer.as_sub(),1155 imbalance,1156 WithdrawReasons::TRANSFER,1157 ExistenceRequirement::KeepAlive,1158 )1159 .map_err(|_| Error::<T>::NotSufficientFounds)?;1160 }11611162 <CreatedCollectionCount<T>>::put(created_count);1163 <Pallet<T>>::deposit_event(Event::CollectionCreated(1164 id,1165 data.mode.id(),1166 owner.as_sub().clone(),1167 ));1168 <PalletEvm<T>>::deposit_log(1169 erc::CollectionHelpersEvents::CollectionCreated {1170 owner: *owner.as_eth(),1171 collection_id: eth::collection_id_to_address(id),1172 }1173 .to_log(T::ContractAddress::get()),1174 );1175 <CollectionById<T>>::insert(id, collection);1176 Ok(id)1177 }11781179 1180 1181 1182 1183 pub fn destroy_collection(1184 collection: CollectionHandle<T>,1185 sender: &T::CrossAccountId,1186 ) -> DispatchResult {1187 ensure!(1188 collection.limits.owner_can_destroy(),1189 <Error<T>>::NoPermission,1190 );1191 collection.check_is_owner(sender)?;11921193 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1194 .01195 .checked_add(1)1196 .ok_or(ArithmeticError::Overflow)?;11971198 11991200 <DestroyedCollectionCount<T>>::put(destroyed_collections);1201 <CollectionById<T>>::remove(collection.id);1202 <AdminAmount<T>>::remove(collection.id);1203 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1204 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1205 <CollectionProperties<T>>::remove(collection.id);12061207 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12081209 <PalletEvm<T>>::deposit_log(1210 erc::CollectionHelpersEvents::CollectionDestroyed {1211 collection_id: eth::collection_id_to_address(collection.id),1212 }1213 .to_log(T::ContractAddress::get()),1214 );1215 Ok(())1216 }12171218 1219 1220 1221 1222 1223 1224 1225 1226 #[transactional]1227 fn modify_collection_properties(1228 collection: &CollectionHandle<T>,1229 sender: &T::CrossAccountId,1230 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1231 ) -> DispatchResult {1232 collection.check_is_owner_or_admin(sender)?;12331234 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12351236 for (key, value) in properties_updates {1237 match value {1238 Some(value) => {1239 stored_properties1240 .try_set(key.clone(), value)1241 .map_err(<Error<T>>::from)?;12421243 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1244 <PalletEvm<T>>::deposit_log(1245 erc::CollectionHelpersEvents::CollectionChanged {1246 collection_id: eth::collection_id_to_address(collection.id),1247 }1248 .to_log(T::ContractAddress::get()),1249 );1250 }1251 None => {1252 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12531254 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1255 <PalletEvm<T>>::deposit_log(1256 erc::CollectionHelpersEvents::CollectionChanged {1257 collection_id: eth::collection_id_to_address(collection.id),1258 }1259 .to_log(T::ContractAddress::get()),1260 );1261 }1262 }1263 }12641265 <CollectionProperties<T>>::set(collection.id, stored_properties);12661267 Ok(())1268 }12691270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 pub fn modify_token_properties(1288 collection: &CollectionHandle<T>,1289 sender: &T::CrossAccountId,1290 token_id: TokenId,1291 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1292 is_token_create: bool,1293 mut stored_properties: Properties,1294 is_token_owner: impl Fn() -> Result<bool, DispatchError>,1295 set_token_properties: impl FnOnce(Properties),1296 ) -> DispatchResult {1297 let is_collection_admin = collection.is_owner_or_admin(sender);1298 let permissions = Self::property_permissions(collection.id);12991300 let mut token_owner_result = None;1301 let mut is_token_owner = || -> Result<bool, DispatchError> {1302 *token_owner_result.get_or_insert_with(&is_token_owner)1303 };13041305 for (key, value) in properties_updates {1306 let permission = permissions1307 .get(&key)1308 .cloned()1309 .unwrap_or_else(PropertyPermission::none);13101311 let is_property_exists = stored_properties.get(&key).is_some();13121313 match permission {1314 PropertyPermission { mutable: false, .. } if is_property_exists => {1315 return Err(<Error<T>>::NoPermission.into());1316 }13171318 PropertyPermission {1319 collection_admin,1320 token_owner,1321 ..1322 } => {1323 1324 let is_token_create =1325 is_token_create && (collection_admin || token_owner) && value.is_some();1326 if !(is_token_create1327 || (collection_admin && is_collection_admin)1328 || (token_owner && is_token_owner()?))1329 {1330 fail!(<Error<T>>::NoPermission);1331 }1332 }1333 }13341335 match value {1336 Some(value) => {1337 stored_properties1338 .try_set(key.clone(), value)1339 .map_err(<Error<T>>::from)?;13401341 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1342 }1343 None => {1344 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13451346 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1347 }1348 }13491350 <PalletEvm<T>>::deposit_log(1351 CollectionHelpersEvents::TokenChanged {1352 collection_id: eth::collection_id_to_address(collection.id),1353 token_id: token_id.into(),1354 }1355 .to_log(T::ContractAddress::get()),1356 );1357 }13581359 set_token_properties(stored_properties);13601361 Ok(())1362 }13631364 1365 1366 1367 1368 1369 1370 pub fn set_allowance_for_all(1371 collection: &CollectionHandle<T>,1372 owner: &T::CrossAccountId,1373 operator: &T::CrossAccountId,1374 approve: bool,1375 set_allowance: impl FnOnce(),1376 log: evm_coder::ethereum::Log,1377 ) -> DispatchResult {1378 if collection.permissions.access() == AccessMode::AllowList {1379 collection.check_allowlist(owner)?;1380 collection.check_allowlist(operator)?;1381 }13821383 Self::ensure_correct_receiver(operator)?;13841385 set_allowance();13861387 <PalletEvm<T>>::deposit_log(log);1388 Self::deposit_event(Event::ApprovedForAll(1389 collection.id,1390 owner.clone(),1391 operator.clone(),1392 approve,1393 ));1394 Ok(())1395 }13961397 1398 1399 1400 1401 1402 pub fn set_collection_property(1403 collection: &CollectionHandle<T>,1404 sender: &T::CrossAccountId,1405 property: Property,1406 ) -> DispatchResult {1407 Self::set_collection_properties(collection, sender, [property].into_iter())1408 }14091410 1411 1412 1413 1414 1415 1416 pub fn set_scoped_collection_property(1417 collection_id: CollectionId,1418 scope: PropertyScope,1419 property: Property,1420 ) -> DispatchResult {1421 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1422 properties.try_scoped_set(scope, property.key, property.value)1423 })1424 .map_err(<Error<T>>::from)?;14251426 Ok(())1427 }14281429 1430 1431 1432 1433 1434 1435 pub fn set_scoped_collection_properties(1436 collection_id: CollectionId,1437 scope: PropertyScope,1438 properties: impl Iterator<Item = Property>,1439 ) -> DispatchResult {1440 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1441 stored_properties.try_scoped_set_from_iter(scope, properties)1442 })1443 .map_err(<Error<T>>::from)?;14441445 Ok(())1446 }14471448 1449 1450 1451 1452 1453 pub fn set_collection_properties(1454 collection: &CollectionHandle<T>,1455 sender: &T::CrossAccountId,1456 properties: impl Iterator<Item = Property>,1457 ) -> DispatchResult {1458 Self::modify_collection_properties(1459 collection,1460 sender,1461 properties.map(|property| (property.key, Some(property.value))),1462 )1463 }14641465 1466 1467 1468 1469 1470 pub fn delete_collection_property(1471 collection: &CollectionHandle<T>,1472 sender: &T::CrossAccountId,1473 property_key: PropertyKey,1474 ) -> DispatchResult {1475 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1476 }14771478 1479 1480 1481 1482 1483 pub fn delete_collection_properties(1484 collection: &CollectionHandle<T>,1485 sender: &T::CrossAccountId,1486 property_keys: impl Iterator<Item = PropertyKey>,1487 ) -> DispatchResult {1488 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1489 }14901491 1492 1493 1494 1495 1496 1497 pub fn set_property_permission_unchecked(1498 collection: CollectionId,1499 property_permission: PropertyKeyPermission,1500 ) -> DispatchResult {1501 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1502 permissions.try_set(property_permission.key, property_permission.permission)1503 })1504 .map_err(<Error<T>>::from)?;1505 Ok(())1506 }15071508 1509 1510 1511 1512 1513 pub fn set_property_permission(1514 collection: &CollectionHandle<T>,1515 sender: &T::CrossAccountId,1516 property_permission: PropertyKeyPermission,1517 ) -> DispatchResult {1518 Self::set_scoped_property_permission(1519 collection,1520 sender,1521 PropertyScope::None,1522 property_permission,1523 )1524 }15251526 1527 1528 1529 1530 1531 1532 pub fn set_scoped_property_permission(1533 collection: &CollectionHandle<T>,1534 sender: &T::CrossAccountId,1535 scope: PropertyScope,1536 property_permission: PropertyKeyPermission,1537 ) -> DispatchResult {1538 collection.check_is_owner_or_admin(sender)?;15391540 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1541 let current_permission = all_permissions.get(&property_permission.key);1542 if matches![1543 current_permission,1544 Some(PropertyPermission { mutable: false, .. })1545 ] {1546 return Err(<Error<T>>::NoPermission.into());1547 }15481549 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1550 let property_permission = property_permission.clone();1551 permissions.try_scoped_set(1552 scope,1553 property_permission.key,1554 property_permission.permission,1555 )1556 })1557 .map_err(<Error<T>>::from)?;15581559 Self::deposit_event(Event::PropertyPermissionSet(1560 collection.id,1561 property_permission.key,1562 ));1563 <PalletEvm<T>>::deposit_log(1564 erc::CollectionHelpersEvents::CollectionChanged {1565 collection_id: eth::collection_id_to_address(collection.id),1566 }1567 .to_log(T::ContractAddress::get()),1568 );15691570 Ok(())1571 }15721573 1574 1575 1576 1577 1578 #[transactional]1579 pub fn set_token_property_permissions(1580 collection: &CollectionHandle<T>,1581 sender: &T::CrossAccountId,1582 property_permissions: Vec<PropertyKeyPermission>,1583 ) -> DispatchResult {1584 Self::set_scoped_token_property_permissions(1585 collection,1586 sender,1587 PropertyScope::None,1588 property_permissions,1589 )1590 }15911592 1593 1594 1595 1596 1597 1598 #[transactional]1599 pub fn set_scoped_token_property_permissions(1600 collection: &CollectionHandle<T>,1601 sender: &T::CrossAccountId,1602 scope: PropertyScope,1603 property_permissions: Vec<PropertyKeyPermission>,1604 ) -> DispatchResult {1605 for prop_pemission in property_permissions {1606 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1607 }16081609 Ok(())1610 }16111612 1613 pub fn get_collection_property(1614 collection_id: CollectionId,1615 key: &PropertyKey,1616 ) -> Option<PropertyValue> {1617 Self::collection_properties(collection_id).get(key).cloned()1618 }16191620 1621 pub fn bytes_keys_to_property_keys(1622 keys: Vec<Vec<u8>>,1623 ) -> Result<Vec<PropertyKey>, DispatchError> {1624 keys.into_iter()1625 .map(|key| -> Result<PropertyKey, DispatchError> {1626 key.try_into()1627 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1628 })1629 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1630 }16311632 1633 pub fn filter_collection_properties(1634 collection_id: CollectionId,1635 keys: Option<Vec<PropertyKey>>,1636 ) -> Result<Vec<Property>, DispatchError> {1637 let properties = Self::collection_properties(collection_id);16381639 let properties = keys1640 .map(|keys| {1641 keys.into_iter()1642 .filter_map(|key| {1643 properties.get(&key).map(|value| Property {1644 key,1645 value: value.clone(),1646 })1647 })1648 .collect()1649 })1650 .unwrap_or_else(|| {1651 properties1652 .into_iter()1653 .map(|(key, value)| Property { key, value })1654 .collect()1655 });16561657 Ok(properties)1658 }16591660 1661 pub fn filter_property_permissions(1662 collection_id: CollectionId,1663 keys: Option<Vec<PropertyKey>>,1664 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1665 let permissions = Self::property_permissions(collection_id);16661667 let key_permissions = keys1668 .map(|keys| {1669 keys.into_iter()1670 .filter_map(|key| {1671 permissions1672 .get(&key)1673 .map(|permission| PropertyKeyPermission {1674 key,1675 permission: permission.clone(),1676 })1677 })1678 .collect()1679 })1680 .unwrap_or_else(|| {1681 permissions1682 .into_iter()1683 .map(|(key, permission)| PropertyKeyPermission { key, permission })1684 .collect()1685 });16861687 Ok(key_permissions)1688 }16891690 1691 1692 1693 pub fn toggle_allowlist(1694 collection: &CollectionHandle<T>,1695 sender: &T::CrossAccountId,1696 user: &T::CrossAccountId,1697 allowed: bool,1698 ) -> DispatchResult {1699 collection.check_is_owner_or_admin(sender)?;17001701 17021703 if allowed {1704 <Allowlist<T>>::insert((collection.id, user), true);1705 Self::deposit_event(Event::<T>::AllowListAddressAdded(1706 collection.id,1707 user.clone(),1708 ));1709 } else {1710 <Allowlist<T>>::remove((collection.id, user));1711 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1712 collection.id,1713 user.clone(),1714 ));1715 }17161717 <PalletEvm<T>>::deposit_log(1718 erc::CollectionHelpersEvents::CollectionChanged {1719 collection_id: eth::collection_id_to_address(collection.id),1720 }1721 .to_log(T::ContractAddress::get()),1722 );17231724 Ok(())1725 }17261727 1728 1729 1730 pub fn toggle_admin(1731 collection: &CollectionHandle<T>,1732 sender: &T::CrossAccountId,1733 user: &T::CrossAccountId,1734 admin: bool,1735 ) -> DispatchResult {1736 collection.check_is_internal()?;1737 collection.check_is_owner(sender)?;17381739 let is_admin = <IsAdmin<T>>::get((collection.id, user));1740 if is_admin == admin {1741 if admin {1742 return Ok(());1743 } else {1744 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1745 }1746 }1747 let amount = <AdminAmount<T>>::get(collection.id);17481749 17501751 if admin {1752 let amount = amount1753 .checked_add(1)1754 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1755 ensure!(1756 amount <= Self::collection_admins_limit(),1757 <Error<T>>::CollectionAdminCountExceeded,1758 );17591760 <AdminAmount<T>>::insert(collection.id, amount);1761 <IsAdmin<T>>::insert((collection.id, user), true);17621763 Self::deposit_event(Event::<T>::CollectionAdminAdded(1764 collection.id,1765 user.clone(),1766 ));1767 } else {1768 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1769 <IsAdmin<T>>::remove((collection.id, user));17701771 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1772 collection.id,1773 user.clone(),1774 ));1775 }17761777 <PalletEvm<T>>::deposit_log(1778 erc::CollectionHelpersEvents::CollectionChanged {1779 collection_id: eth::collection_id_to_address(collection.id),1780 }1781 .to_log(T::ContractAddress::get()),1782 );17831784 Ok(())1785 }17861787 1788 pub fn update_limits(1789 user: &T::CrossAccountId,1790 collection: &mut CollectionHandle<T>,1791 new_limit: CollectionLimits,1792 ) -> DispatchResult {1793 collection.check_is_internal()?;1794 collection.check_is_owner_or_admin(user)?;17951796 collection.limits =1797 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17981799 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1800 <PalletEvm<T>>::deposit_log(1801 erc::CollectionHelpersEvents::CollectionChanged {1802 collection_id: eth::collection_id_to_address(collection.id),1803 }1804 .to_log(T::ContractAddress::get()),1805 );18061807 collection.save()1808 }18091810 1811 fn clamp_limits(1812 mode: CollectionMode,1813 old_limit: &CollectionLimits,1814 mut new_limit: CollectionLimits,1815 ) -> Result<CollectionLimits, DispatchError> {1816 let limits = old_limit;1817 limit_default!(old_limit, new_limit,1818 account_token_ownership_limit => ensure!(1819 new_limit <= MAX_TOKEN_OWNERSHIP,1820 <Error<T>>::CollectionLimitBoundsExceeded,1821 ),1822 sponsored_data_size => ensure!(1823 new_limit <= CUSTOM_DATA_LIMIT,1824 <Error<T>>::CollectionLimitBoundsExceeded,1825 ),18261827 sponsored_data_rate_limit => {},1828 token_limit => ensure!(1829 old_limit >= new_limit && new_limit > 0,1830 <Error<T>>::CollectionTokenLimitExceeded1831 ),18321833 sponsor_transfer_timeout(match mode {1834 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1835 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1836 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1837 }) => ensure!(1838 new_limit <= MAX_SPONSOR_TIMEOUT,1839 <Error<T>>::CollectionLimitBoundsExceeded,1840 ),1841 sponsor_approve_timeout => {},1842 owner_can_transfer => ensure!(1843 !limits.owner_can_transfer_instaled() ||1844 old_limit || !new_limit,1845 <Error<T>>::OwnerPermissionsCantBeReverted,1846 ),1847 owner_can_destroy => ensure!(1848 old_limit || !new_limit,1849 <Error<T>>::OwnerPermissionsCantBeReverted,1850 ),1851 transfers_enabled => {},1852 );1853 Ok(new_limit)1854 }18551856 1857 pub fn update_permissions(1858 user: &T::CrossAccountId,1859 collection: &mut CollectionHandle<T>,1860 new_permission: CollectionPermissions,1861 ) -> DispatchResult {1862 collection.check_is_internal()?;1863 collection.check_is_owner_or_admin(user)?;1864 collection.permissions = Self::clamp_permissions(1865 collection.mode.clone(),1866 &collection.permissions,1867 new_permission,1868 )?;18691870 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1871 <PalletEvm<T>>::deposit_log(1872 erc::CollectionHelpersEvents::CollectionChanged {1873 collection_id: eth::collection_id_to_address(collection.id),1874 }1875 .to_log(T::ContractAddress::get()),1876 );18771878 collection.save()1879 }18801881 1882 fn clamp_permissions(1883 _mode: CollectionMode,1884 old_permission: &CollectionPermissions,1885 mut new_permission: CollectionPermissions,1886 ) -> Result<CollectionPermissions, DispatchError> {1887 limit_default_clone!(old_permission, new_permission,1888 access => {},1889 mint_mode => {},1890 nesting => { },1891 );1892 Ok(new_permission)1893 }18941895 1896 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1897 CollectionProperties::<T>::mutate(collection_id, |properties| {1898 properties.recompute_consumed_space();1899 });19001901 Ok(())1902 }1903}190419051906#[macro_export]1907macro_rules! unsupported {1908 ($runtime:path) => {1909 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1910 };1911}191219131914pub trait CommonWeightInfo<CrossAccountId> {1915 1916 fn create_item(data: &CreateItemData) -> Weight {1917 Self::create_multiple_items(from_ref(data))1918 }19191920 1921 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19221923 1924 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19251926 1927 fn burn_item() -> Weight;19281929 1930 1931 1932 fn set_collection_properties(amount: u32) -> Weight;19331934 1935 1936 1937 fn delete_collection_properties(amount: u32) -> Weight;19381939 1940 1941 1942 fn set_token_properties(amount: u32) -> Weight;19431944 1945 1946 1947 fn delete_token_properties(amount: u32) -> Weight;19481949 1950 1951 1952 fn set_token_property_permissions(amount: u32) -> Weight;19531954 1955 fn transfer() -> Weight;19561957 1958 fn approve() -> Weight;19591960 1961 fn approve_from() -> Weight;19621963 1964 fn transfer_from() -> Weight;19651966 1967 fn burn_from() -> Weight;19681969 1970 1971 1972 1973 fn burn_recursively_self_raw() -> Weight;19741975 1976 1977 1978 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19791980 1981 1982 1983 1984 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1985 Self::burn_recursively_self_raw()1986 .saturating_mul(max_selfs.max(1) as u64)1987 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1988 }19891990 1991 fn token_owner() -> Weight;19921993 1994 fn set_allowance_for_all() -> Weight;19951996 1997 fn force_repair_item() -> Weight;1998}199920002001pub trait RefungibleExtensionsWeightInfo {2002 2003 fn repartition() -> Weight;2004}200520062007200820092010pub trait CommonCollectionOperations<T: Config> {2011 2012 2013 2014 2015 2016 2017 fn create_item(2018 &self,2019 sender: T::CrossAccountId,2020 to: T::CrossAccountId,2021 data: CreateItemData,2022 nesting_budget: &dyn Budget,2023 ) -> DispatchResultWithPostInfo;20242025 2026 2027 2028 2029 2030 2031 fn create_multiple_items(2032 &self,2033 sender: T::CrossAccountId,2034 to: T::CrossAccountId,2035 data: Vec<CreateItemData>,2036 nesting_budget: &dyn Budget,2037 ) -> DispatchResultWithPostInfo;20382039 2040 2041 2042 2043 2044 2045 fn create_multiple_items_ex(2046 &self,2047 sender: T::CrossAccountId,2048 data: CreateItemExData<T::CrossAccountId>,2049 nesting_budget: &dyn Budget,2050 ) -> DispatchResultWithPostInfo;20512052 2053 2054 2055 2056 2057 fn burn_item(2058 &self,2059 sender: T::CrossAccountId,2060 token: TokenId,2061 amount: u128,2062 ) -> DispatchResultWithPostInfo;20632064 2065 2066 2067 2068 2069 2070 fn burn_item_recursively(2071 &self,2072 sender: T::CrossAccountId,2073 token: TokenId,2074 self_budget: &dyn Budget,2075 breadth_budget: &dyn Budget,2076 ) -> DispatchResultWithPostInfo;20772078 2079 2080 2081 2082 fn set_collection_properties(2083 &self,2084 sender: T::CrossAccountId,2085 properties: Vec<Property>,2086 ) -> DispatchResultWithPostInfo;20872088 2089 2090 2091 2092 fn delete_collection_properties(2093 &self,2094 sender: &T::CrossAccountId,2095 property_keys: Vec<PropertyKey>,2096 ) -> DispatchResultWithPostInfo;20972098 2099 2100 2101 2102 2103 2104 2105 2106 2107 fn set_token_properties(2108 &self,2109 sender: T::CrossAccountId,2110 token_id: TokenId,2111 properties: Vec<Property>,2112 budget: &dyn Budget,2113 ) -> DispatchResultWithPostInfo;21142115 2116 2117 2118 2119 2120 2121 2122 2123 2124 fn delete_token_properties(2125 &self,2126 sender: T::CrossAccountId,2127 token_id: TokenId,2128 property_keys: Vec<PropertyKey>,2129 budget: &dyn Budget,2130 ) -> DispatchResultWithPostInfo;21312132 2133 2134 2135 2136 2137 2138 fn set_token_property_permissions(2139 &self,2140 sender: &T::CrossAccountId,2141 property_permissions: Vec<PropertyKeyPermission>,2142 ) -> DispatchResultWithPostInfo;21432144 2145 2146 2147 2148 2149 2150 2151 fn transfer(2152 &self,2153 sender: T::CrossAccountId,2154 to: T::CrossAccountId,2155 token: TokenId,2156 amount: u128,2157 budget: &dyn Budget,2158 ) -> DispatchResultWithPostInfo;21592160 2161 2162 2163 2164 2165 2166 fn approve(2167 &self,2168 sender: T::CrossAccountId,2169 spender: T::CrossAccountId,2170 token: TokenId,2171 amount: u128,2172 ) -> DispatchResultWithPostInfo;21732174 2175 2176 2177 2178 2179 2180 2181 fn approve_from(2182 &self,2183 sender: T::CrossAccountId,2184 from: T::CrossAccountId,2185 to: T::CrossAccountId,2186 token: TokenId,2187 amount: u128,2188 ) -> DispatchResultWithPostInfo;21892190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 fn transfer_from(2201 &self,2202 sender: T::CrossAccountId,2203 from: T::CrossAccountId,2204 to: T::CrossAccountId,2205 token: TokenId,2206 amount: u128,2207 budget: &dyn Budget,2208 ) -> DispatchResultWithPostInfo;22092210 2211 2212 2213 2214 2215 2216 2217 2218 2219 fn burn_from(2220 &self,2221 sender: T::CrossAccountId,2222 from: T::CrossAccountId,2223 token: TokenId,2224 amount: u128,2225 budget: &dyn Budget,2226 ) -> DispatchResultWithPostInfo;22272228 2229 2230 2231 2232 2233 2234 fn check_nesting(2235 &self,2236 sender: T::CrossAccountId,2237 from: (CollectionId, TokenId),2238 under: TokenId,2239 budget: &dyn Budget,2240 ) -> DispatchResult;22412242 2243 2244 2245 2246 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22472248 2249 2250 2251 2252 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22532254 2255 2256 2257 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22582259 2260 fn collection_tokens(&self) -> Vec<TokenId>;22612262 2263 2264 2265 fn token_exists(&self, token: TokenId) -> bool;22662267 2268 fn last_token_id(&self) -> TokenId;22692270 2271 2272 2273 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22742275 2276 2277 2278 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22792280 2281 2282 2283 2284 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22852286 2287 2288 2289 2290 2291 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22922293 2294 fn total_supply(&self) -> u32;22952296 2297 2298 2299 fn account_balance(&self, account: T::CrossAccountId) -> u32;23002301 2302 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;23032304 2305 fn total_pieces(&self, token: TokenId) -> Option<u128>;23062307 2308 2309 2310 2311 2312 fn allowance(2313 &self,2314 sender: T::CrossAccountId,2315 spender: T::CrossAccountId,2316 token: TokenId,2317 ) -> u128;23182319 2320 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;23212322 2323 2324 2325 2326 fn set_allowance_for_all(2327 &self,2328 owner: T::CrossAccountId,2329 operator: T::CrossAccountId,2330 approve: bool,2331 ) -> DispatchResultWithPostInfo;23322333 2334 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23352336 2337 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2338}233923402341pub trait RefungibleExtensions<T>2342where2343 T: Config,2344{2345 2346 2347 2348 2349 2350 2351 2352 fn repartition(2353 &self,2354 sender: &T::CrossAccountId,2355 token: TokenId,2356 amount: u128,2357 ) -> DispatchResultWithPostInfo;2358}23592360236123622363pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2364 let post_info = PostDispatchInfo {2365 actual_weight: Some(weight),2366 pays_fee: Pays::Yes,2367 };2368 match res {2369 Ok(()) => Ok(post_info),2370 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2371 }2372}23732374impl<T: Config> From<PropertiesError> for Error<T> {2375 fn from(error: PropertiesError) -> Self {2376 match error {2377 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2378 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2379 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2380 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2381 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2382 }2383 }2384}