12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::{57 marker::PhantomData,58 ops::{Deref, DerefMut},59 slice::from_ref,60};6162use evm_coder::ToLog;63use frame_support::{64 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Pays, PostDispatchInfo},65 ensure, fail,66 traits::{67 fungible::{Balanced, Debt, Inspect},68 tokens::{Imbalance, Precision, Preservation},69 Get,70 },71 transactional,72};73pub use pallet::*;74use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};75use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};76use sp_core::H160;77use sp_runtime::{traits::Zero, ArithmeticError, DispatchError, DispatchResult};78use sp_std::vec::Vec;79use sp_weights::Weight;80use up_data_structs::{81 budget::Budget, AccessMode, Collection, CollectionId, CollectionLimits, CollectionMode,82 CollectionPermissions, CollectionProperties as CollectionPropertiesT, CollectionStats,83 CreateCollectionData, CreateItemData, CreateItemExData, PhantomType, PropertiesError,84 PropertiesPermissionMap, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,85 PropertyScope, PropertyValue, RpcCollection, RpcCollectionFlags, SponsoringRateLimit,86 SponsorshipState, TokenChild, TokenData, TokenId, TokenOwnerError, TokenProperties,87 TrySetProperty, COLLECTION_ADMINS_LIMIT, COLLECTION_NUMBER_LIMIT, CUSTOM_DATA_LIMIT,88 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP,89 MAX_TOKEN_PREFIX_LENGTH, NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,90};91use up_pov_estimate_rpc::PovInfo;9293#[cfg(feature = "runtime-benchmarks")]94pub mod benchmarking;95pub mod dispatch;96pub mod erc;97pub mod eth;98pub mod helpers;99#[allow(missing_docs)]100pub mod weights;101102use weights::WeightInfo;103104105pub type SelfWeightOf<T> = <T as Config>::WeightInfo;106107108109110111112113#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]114pub struct CollectionHandle<T: Config> {115 116 pub id: CollectionId,117 collection: Collection<T::AccountId>,118 119 pub recorder: SubstrateRecorder<T>,120}121122impl<T: Config> WithRecorder<T> for CollectionHandle<T> {123 fn recorder(&self) -> &SubstrateRecorder<T> {124 &self.recorder125 }126 fn into_recorder(self) -> SubstrateRecorder<T> {127 self.recorder128 }129}130131impl<T: Config> CollectionHandle<T> {132 133 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {134 Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))135 }136137 138 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {139 <CollectionById<T>>::get(id).map(|collection| Self {140 id,141 collection,142 recorder,143 })144 }145146 147 148 pub fn new(id: CollectionId) -> Option<Self> {149 Self::new_with_gas_limit(id, u64::MAX)150 }151152 153 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {154 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)155 }156157 158 pub fn consume_store_reads(159 &self,160 reads: u64,161 ) -> pallet_evm_coder_substrate::execution::Result<()> {162 self.recorder().consume_store_reads(reads)163 }164165 166 pub fn consume_store_writes(167 &self,168 writes: u64,169 ) -> pallet_evm_coder_substrate::execution::Result<()> {170 self.recorder().consume_store_writes(writes)171 }172173 174 pub fn consume_store_reads_and_writes(175 &self,176 reads: u64,177 writes: u64,178 ) -> pallet_evm_coder_substrate::execution::Result<()> {179 self.recorder()180 .consume_store_reads_and_writes(reads, writes)181 }182183 184 pub fn save(&self) -> DispatchResult {185 <CollectionById<T>>::insert(self.id, &self.collection);186 Ok(())187 }188189 190 191 192 193 194 pub fn set_sponsor(195 &mut self,196 sender: &T::CrossAccountId,197 sponsor: T::AccountId,198 ) -> DispatchResult {199 self.check_is_internal()?;200 self.check_is_owner_or_admin(sender)?;201202 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());203204 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));205 <PalletEvm<T>>::deposit_log(206 erc::CollectionHelpersEvents::CollectionChanged {207 collection_id: eth::collection_id_to_address(self.id),208 }209 .to_log(T::ContractAddress::get()),210 );211212 self.save()213 }214215 216 217 218 219 220 221 222 223 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {224 self.check_is_internal()?;225226 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());227228 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));229 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));230 <PalletEvm<T>>::deposit_log(231 erc::CollectionHelpersEvents::CollectionChanged {232 collection_id: eth::collection_id_to_address(self.id),233 }234 .to_log(T::ContractAddress::get()),235 );236237 self.save()238 }239240 241 242 243 244 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {245 self.check_is_internal()?;246 ensure!(247 self.collection.sponsorship.pending_sponsor() == Some(sender),248 Error::<T>::ConfirmSponsorshipFail249 );250251 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());252253 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));254 <PalletEvm<T>>::deposit_log(255 erc::CollectionHelpersEvents::CollectionChanged {256 collection_id: eth::collection_id_to_address(self.id),257 }258 .to_log(T::ContractAddress::get()),259 );260261 self.save()262 }263264 265 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {266 self.check_is_internal()?;267 self.check_is_owner_or_admin(sender)?;268269 self.collection.sponsorship = SponsorshipState::Disabled;270271 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));272 <PalletEvm<T>>::deposit_log(273 erc::CollectionHelpersEvents::CollectionChanged {274 collection_id: eth::collection_id_to_address(self.id),275 }276 .to_log(T::ContractAddress::get()),277 );278 self.save()279 }280281 282 283 284 285 pub fn force_remove_sponsor(&mut self) -> DispatchResult {286 self.check_is_internal()?;287288 self.collection.sponsorship = SponsorshipState::Disabled;289290 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));291 <PalletEvm<T>>::deposit_log(292 erc::CollectionHelpersEvents::CollectionChanged {293 collection_id: eth::collection_id_to_address(self.id),294 }295 .to_log(T::ContractAddress::get()),296 );297 self.save()298 }299300 301 302 pub fn check_is_internal(&self) -> DispatchResult {303 if self.flags.external {304 return Err(<Error<T>>::CollectionIsExternal)?;305 }306307 Ok(())308 }309310 311 312 pub fn check_is_external(&self) -> DispatchResult {313 if !self.flags.external {314 return Err(<Error<T>>::CollectionIsInternal)?;315 }316317 Ok(())318 }319}320321impl<T: Config> Deref for CollectionHandle<T> {322 type Target = Collection<T::AccountId>;323324 fn deref(&self) -> &Self::Target {325 &self.collection326 }327}328329impl<T: Config> DerefMut for CollectionHandle<T> {330 fn deref_mut(&mut self) -> &mut Self::Target {331 &mut self.collection332 }333}334335impl<T: Config> CollectionHandle<T> {336 337 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {338 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);339 Ok(())340 }341342 343 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {344 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))345 }346347 348 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {349 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);350 Ok(())351 }352353 354 355 356 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {357 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)358 }359360 361 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {362 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)363 }364365 366 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {367 ensure!(368 <Allowlist<T>>::get((self.id, user)),369 <Error<T>>::AddressNotInAllowlist370 );371 Ok(())372 }373374 375 376 377 pub fn change_owner(378 &mut self,379 caller: T::CrossAccountId,380 new_owner: T::CrossAccountId,381 ) -> DispatchResult {382 self.check_is_internal()?;383 self.check_is_owner(&caller)?;384 self.collection.owner = new_owner.as_sub().clone();385386 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(387 self.id,388 new_owner.as_sub().clone(),389 ));390 <PalletEvm<T>>::deposit_log(391 erc::CollectionHelpersEvents::CollectionChanged {392 collection_id: eth::collection_id_to_address(self.id),393 }394 .to_log(T::ContractAddress::get()),395 );396397 self.save()398 }399}400401#[frame_support::pallet]402pub mod pallet {403404 use dispatch::CollectionDispatch;405 use frame_support::{406 pallet_prelude::*, storage::Key, traits::StorageVersion, Blake2_128Concat,407 };408 use scale_info::TypeInfo;409 use up_data_structs::{mapping::TokenAddressMapping, TokenId};410 use weights::WeightInfo;411412 use super::*;413414 #[pallet::config]415 pub trait Config:416 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo417 {418 419 type WeightInfo: WeightInfo;420421 422 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;423424 425 type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;426427 428 #[pallet::constant]429 type CollectionCreationPrice: Get<430 <<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,431 >;432433 434 type CollectionDispatch: CollectionDispatch<Self>;435436 437 type TreasuryAccountId: Get<Self::AccountId>;438439 440 #[pallet::constant]441 type ContractAddress: Get<H160>;442443 444 type EvmTokenAddressMapping: TokenAddressMapping<H160>;445446 447 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;448 }449450 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);451 452 pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);453454 #[pallet::pallet]455 #[pallet::storage_version(STORAGE_VERSION)]456 pub struct Pallet<T>(_);457458 #[pallet::extra_constants]459 impl<T: Config> Pallet<T> {460 461 pub fn collection_admins_limit() -> u32 {462 COLLECTION_ADMINS_LIMIT463 }464 }465466 #[pallet::genesis_config]467 pub struct GenesisConfig<T>(PhantomData<T>);468469 impl<T: Config> Default for GenesisConfig<T> {470 fn default() -> Self {471 Self(Default::default())472 }473 }474475 #[pallet::genesis_build]476 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {477 fn build(&self) {478 StorageVersion::new(1).put::<Pallet<T>>();479 }480 }481482 impl<T: Config> Pallet<T> {483 484 pub fn deposit_event(event: Event<T>) {485 let event = <T as Config>::RuntimeEvent::from(event);486 let event = event.into();487 <frame_system::Pallet<T>>::deposit_event(event)488 }489 }490491 #[pallet::event]492 pub enum Event<T: Config> {493 494 CollectionCreated(495 496 CollectionId,497 498 u8,499 500 T::AccountId,501 ),502503 504 CollectionDestroyed(505 506 CollectionId,507 ),508509 510 ItemCreated(511 512 CollectionId,513 514 TokenId,515 516 T::CrossAccountId,517 518 u128,519 ),520521 522 ItemDestroyed(523 524 CollectionId,525 526 TokenId,527 528 T::CrossAccountId,529 530 u128,531 ),532533 534 Transfer(535 536 CollectionId,537 538 TokenId,539 540 T::CrossAccountId,541 542 T::CrossAccountId,543 544 u128,545 ),546547 548 Approved(549 550 CollectionId,551 552 TokenId,553 554 T::CrossAccountId,555 556 T::CrossAccountId,557 558 u128,559 ),560561 562 ApprovedForAll(563 564 CollectionId,565 566 T::CrossAccountId,567 568 T::CrossAccountId,569 570 bool,571 ),572573 574 CollectionPropertySet(575 576 CollectionId,577 578 PropertyKey,579 ),580581 582 CollectionPropertyDeleted(583 584 CollectionId,585 586 PropertyKey,587 ),588589 590 TokenPropertySet(591 592 CollectionId,593 594 TokenId,595 596 PropertyKey,597 ),598599 600 TokenPropertyDeleted(601 602 CollectionId,603 604 TokenId,605 606 PropertyKey,607 ),608609 610 PropertyPermissionSet(611 612 CollectionId,613 614 PropertyKey,615 ),616617 618 AllowListAddressAdded(619 620 CollectionId,621 622 T::CrossAccountId,623 ),624625 626 AllowListAddressRemoved(627 628 CollectionId,629 630 T::CrossAccountId,631 ),632633 634 CollectionAdminAdded(635 636 CollectionId,637 638 T::CrossAccountId,639 ),640641 642 CollectionAdminRemoved(643 644 CollectionId,645 646 T::CrossAccountId,647 ),648649 650 CollectionLimitSet(651 652 CollectionId,653 ),654655 656 CollectionOwnerChanged(657 658 CollectionId,659 660 T::AccountId,661 ),662663 664 CollectionPermissionSet(665 666 CollectionId,667 ),668669 670 CollectionSponsorSet(671 672 CollectionId,673 674 T::AccountId,675 ),676677 678 SponsorshipConfirmed(679 680 CollectionId,681 682 T::AccountId,683 ),684685 686 CollectionSponsorRemoved(687 688 CollectionId,689 ),690 }691692 #[pallet::error]693 pub enum Error<T> {694 695 CollectionNotFound,696 697 MustBeTokenOwner,698 699 NoPermission,700 701 CantDestroyNotEmptyCollection,702 703 PublicMintingNotAllowed,704 705 AddressNotInAllowlist,706707 708 CollectionNameLimitExceeded,709 710 CollectionDescriptionLimitExceeded,711 712 CollectionTokenPrefixLimitExceeded,713 714 TotalCollectionsLimitExceeded,715 716 CollectionAdminCountExceeded,717 718 CollectionLimitBoundsExceeded,719 720 OwnerPermissionsCantBeReverted,721 722 TransferNotAllowed,723 724 AccountTokenLimitExceeded,725 726 CollectionTokenLimitExceeded,727 728 MetadataFlagFrozen,729730 731 TokenNotFound,732 733 TokenValueTooLow,734 735 ApprovedValueTooLow,736 737 CantApproveMoreThanOwned,738 739 AddressIsNotEthMirror,740741 742 AddressIsZero,743744 745 UnsupportedOperation,746747 748 NotSufficientFounds,749750 751 UserIsNotAllowedToNest,752 753 SourceCollectionIsNotAllowedToNest,754755 756 CollectionFieldSizeExceeded,757758 759 NoSpaceForProperty,760761 762 PropertyLimitReached,763764 765 PropertyKeyIsTooLong,766767 768 InvalidCharacterInPropertyKey,769770 771 EmptyPropertyKey,772773 774 CollectionIsExternal,775776 777 CollectionIsInternal,778779 780 ConfirmSponsorshipFail,781782 783 UserIsNotCollectionAdmin,784 }785786 787 #[pallet::storage]788 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;789790 791 #[pallet::storage]792 pub type DestroyedCollectionCount<T> =793 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;794795 796 #[pallet::storage]797 pub type CollectionById<T> = StorageMap<798 Hasher = Blake2_128Concat,799 Key = CollectionId,800 Value = Collection<<T as frame_system::Config>::AccountId>,801 QueryKind = OptionQuery,802 >;803804 805 #[pallet::storage]806 #[pallet::getter(fn collection_properties)]807 pub type CollectionProperties<T> = StorageMap<808 Hasher = Blake2_128Concat,809 Key = CollectionId,810 Value = CollectionPropertiesT,811 QueryKind = ValueQuery,812 >;813814 815 #[pallet::storage]816 #[pallet::getter(fn property_permissions)]817 pub type CollectionPropertyPermissions<T> = StorageMap<818 Hasher = Blake2_128Concat,819 Key = CollectionId,820 Value = PropertiesPermissionMap,821 QueryKind = ValueQuery,822 >;823824 825 #[pallet::storage]826 pub type AdminAmount<T> = StorageMap<827 Hasher = Blake2_128Concat,828 Key = CollectionId,829 Value = u32,830 QueryKind = ValueQuery,831 >;832833 834 #[pallet::storage]835 pub type IsAdmin<T: Config> = StorageNMap<836 Key = (837 Key<Blake2_128Concat, CollectionId>,838 Key<Blake2_128Concat, T::CrossAccountId>,839 ),840 Value = bool,841 QueryKind = ValueQuery,842 >;843844 845 #[pallet::storage]846 pub type Allowlist<T: Config> = StorageNMap<847 Key = (848 Key<Blake2_128Concat, CollectionId>,849 Key<Blake2_128Concat, T::CrossAccountId>,850 ),851 Value = bool,852 QueryKind = ValueQuery,853 >;854855 856 #[pallet::storage]857 pub type DummyStorageValue<T: Config> = StorageValue<858 Value = (859 CollectionStats,860 CollectionId,861 TokenId,862 TokenChild,863 PhantomType<(864 TokenData<T::CrossAccountId>,865 RpcCollection<T::AccountId>,866 867 PovInfo,868 )>,869 ),870 QueryKind = OptionQuery,871 >;872}873874875pub struct LazyValue<T, F> {876 value: Option<T>,877 f: Option<F>,878}879880impl<T, F: FnOnce() -> T> LazyValue<T, F> {881 882 pub fn new(f: F) -> Self {883 Self {884 value: None,885 f: Some(f),886 }887 }888889 890 pub fn value(&mut self) -> &T {891 self.force_value();892 self.value.as_ref().unwrap()893 }894895 896 pub fn value_mut(&mut self) -> &mut T {897 self.force_value();898 self.value.as_mut().unwrap()899 }900901 fn into_inner(mut self) -> T {902 self.force_value();903 self.value.unwrap()904 }905906 907 pub fn has_value(&self) -> bool {908 self.value.is_some()909 }910911 fn force_value(&mut self) {912 if self.value.is_none() {913 self.value = Some(self.f.take().unwrap()())914 }915 }916}917918fn check_token_permissions<T, FCA, FTO, FTE>(919 collection_admin_permitted: bool,920 token_owner_permitted: bool,921 is_collection_admin: &mut LazyValue<bool, FCA>,922 is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,923 is_token_exist: &mut LazyValue<bool, FTE>,924) -> DispatchResult925where926 T: Config,927 FCA: FnOnce() -> bool,928 FTO: FnOnce() -> Result<bool, DispatchError>,929 FTE: FnOnce() -> bool,930{931 if !(collection_admin_permitted && *is_collection_admin.value()932 || token_owner_permitted && (*is_token_owner.value())?)933 {934 fail!(<Error<T>>::NoPermission);935 }936937 let token_exist_due_to_owner_check_success =938 is_token_owner.has_value() && (*is_token_owner.value())?;939940 941 942 if !token_exist_due_to_owner_check_success {943 944 945 if !is_token_exist.value() {946 fail!(<Error<T>>::TokenNotFound);947 }948 }949950 Ok(())951}952953impl<T: Config> Pallet<T> {954 955 956 957 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {958 ensure!(959 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,960 <Error<T>>::AddressIsZero961 );962 Ok(())963 }964965 966 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {967 <IsAdmin<T>>::iter_prefix((collection,))968 .map(|(a, _)| a)969 .collect()970 }971972 973 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {974 <Allowlist<T>>::iter_prefix((collection,))975 .map(|(a, _)| a)976 .collect()977 }978979 980 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {981 <Allowlist<T>>::get((collection, user))982 }983984 985 pub fn collection_stats() -> CollectionStats {986 let created = <CreatedCollectionCount<T>>::get();987 let destroyed = <DestroyedCollectionCount<T>>::get();988 CollectionStats {989 created: created.0,990 destroyed: destroyed.0,991 alive: created.0 - destroyed.0,992 }993 }994995 996 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {997 let collection = <CollectionById<T>>::get(collection)?;998 let limits = collection.limits;999 let effective_limits = CollectionLimits {1000 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),1001 sponsored_data_size: Some(limits.sponsored_data_size()),1002 sponsored_data_rate_limit: Some(1003 limits1004 .sponsored_data_rate_limit1005 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),1006 ),1007 token_limit: Some(limits.token_limit()),1008 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(1009 match collection.mode {1010 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1011 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1012 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1013 },1014 )),1015 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),1016 owner_can_transfer: Some(limits.owner_can_transfer()),1017 owner_can_destroy: Some(limits.owner_can_destroy()),1018 transfers_enabled: Some(limits.transfers_enabled()),1019 };10201021 Some(effective_limits)1022 }10231024 1025 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1026 let Collection {1027 name,1028 description,1029 owner,1030 mode,1031 token_prefix,1032 sponsorship,1033 limits,1034 permissions,1035 flags,1036 } = <CollectionById<T>>::get(collection)?;10371038 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1039 .into_iter()1040 .map(|(key, permission)| PropertyKeyPermission { key, permission })1041 .collect();10421043 let properties = <CollectionProperties<T>>::get(collection)1044 .into_iter()1045 .map(|(key, value)| Property { key, value })1046 .collect();10471048 let permissions = CollectionPermissions {1049 access: Some(permissions.access()),1050 mint_mode: Some(permissions.mint_mode()),1051 nesting: Some(permissions.nesting().clone()),1052 };10531054 Some(RpcCollection {1055 name: name.into_inner(),1056 description: description.into_inner(),1057 owner,1058 mode,1059 token_prefix: token_prefix.into_inner(),1060 sponsorship,1061 limits,1062 permissions,1063 token_property_permissions,1064 properties,1065 read_only: flags.external,10661067 flags: RpcCollectionFlags {1068 foreign: flags.foreign,1069 erc721metadata: flags.erc721metadata,1070 },1071 })1072 }1073}10741075macro_rules! limit_default {1076 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1077 $(1078 if let Some($new) = $new.$field {1079 let $old = $old.$field($($arg)?);1080 let _ = $new;1081 let _ = $old;1082 $check1083 } else {1084 $new.$field = $old.$field1085 }1086 )*1087 }};1088}1089macro_rules! limit_default_clone {1090 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1091 $(1092 if let Some($new) = $new.$field.clone() {1093 let $old = $old.$field($($arg)?);1094 let _ = $new;1095 let _ = $old;1096 $check1097 } else {1098 $new.$field = $old.$field.clone()1099 }1100 )*1101 }};1102}11031104impl<T: Config> Pallet<T> {1105 1106 1107 1108 1109 1110 pub fn init_collection(1111 owner: T::CrossAccountId,1112 payer: T::CrossAccountId,1113 data: CreateCollectionData<T::CrossAccountId>,1114 ) -> Result<CollectionId, DispatchError> {1115 ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);1116 Self::init_collection_internal(owner, payer, data)1117 }11181119 1120 pub fn init_foreign_collection(1121 owner: T::CrossAccountId,1122 payer: T::CrossAccountId,1123 mut data: CreateCollectionData<T::CrossAccountId>,1124 ) -> Result<CollectionId, DispatchError> {1125 data.flags.foreign = true;1126 let id = Self::init_collection_internal(owner, payer, data)?;1127 Ok(id)1128 }11291130 fn init_collection_internal(1131 owner: T::CrossAccountId,1132 payer: T::CrossAccountId,1133 data: CreateCollectionData<T::CrossAccountId>,1134 ) -> Result<CollectionId, DispatchError> {1135 {1136 ensure!(1137 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1138 Error::<T>::CollectionTokenPrefixLimitExceeded1139 );1140 }11411142 let created_count = <CreatedCollectionCount<T>>::get()1143 .01144 .checked_add(1)1145 .ok_or(ArithmeticError::Overflow)?;1146 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1147 let id = CollectionId(created_count);11481149 1150 ensure!(1151 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1152 <Error<T>>::TotalCollectionsLimitExceeded1153 );11541155 11561157 let collection = Collection {1158 owner: owner.as_sub().clone(),1159 name: data.name,1160 mode: data.mode.clone(),1161 description: data.description,1162 token_prefix: data.token_prefix,1163 sponsorship: data1164 .pending_sponsor1165 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1166 .unwrap_or_default(),1167 limits: data1168 .limits1169 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1170 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1171 permissions: data1172 .permissions1173 .map(|permissions| {1174 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1175 })1176 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1177 flags: data.flags,1178 };11791180 let mut collection_properties = CollectionPropertiesT::new();1181 collection_properties1182 .try_set_from_iter(data.properties.into_iter())1183 .map_err(<Error<T>>::from)?;11841185 CollectionProperties::<T>::insert(id, collection_properties);11861187 let mut token_props_permissions = PropertiesPermissionMap::new();1188 token_props_permissions1189 .try_set_from_iter(data.token_property_permissions.into_iter())1190 .map_err(<Error<T>>::from)?;11911192 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11931194 let mut admin_amount = 0u32;1195 for admin in data.admin_list.iter() {1196 if !<IsAdmin<T>>::get((id, admin)) {1197 <IsAdmin<T>>::insert((id, admin), true);1198 admin_amount = admin_amount1199 .checked_add(1)1200 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1201 }1202 }1203 ensure!(1204 admin_amount <= Self::collection_admins_limit(),1205 <Error<T>>::CollectionAdminCountExceeded,1206 );1207 <AdminAmount<T>>::insert(id, admin_amount);12081209 1210 {1211 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1212 imbalance.subsume(<T as Config>::Currency::deposit(1213 &T::TreasuryAccountId::get(),1214 T::CollectionCreationPrice::get(),1215 Precision::Exact,1216 )?);1217 let credit =1218 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1219 .map_err(|_| Error::<T>::NotSufficientFounds)?;12201221 debug_assert!(credit.peek().is_zero())1222 }12231224 <CreatedCollectionCount<T>>::put(created_count);1225 <Pallet<T>>::deposit_event(Event::CollectionCreated(1226 id,1227 data.mode.id(),1228 owner.as_sub().clone(),1229 ));1230 <PalletEvm<T>>::deposit_log(1231 erc::CollectionHelpersEvents::CollectionCreated {1232 owner: *owner.as_eth(),1233 collection_id: eth::collection_id_to_address(id),1234 }1235 .to_log(T::ContractAddress::get()),1236 );1237 <CollectionById<T>>::insert(id, collection);1238 Ok(id)1239 }12401241 1242 1243 1244 1245 pub fn destroy_collection(1246 collection: CollectionHandle<T>,1247 sender: &T::CrossAccountId,1248 ) -> DispatchResult {1249 ensure!(1250 collection.limits.owner_can_destroy(),1251 <Error<T>>::NoPermission,1252 );1253 collection.check_is_owner(sender)?;12541255 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1256 .01257 .checked_add(1)1258 .ok_or(ArithmeticError::Overflow)?;12591260 12611262 <DestroyedCollectionCount<T>>::put(destroyed_collections);1263 <CollectionById<T>>::remove(collection.id);1264 <AdminAmount<T>>::remove(collection.id);1265 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1266 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1267 <CollectionProperties<T>>::remove(collection.id);12681269 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12701271 <PalletEvm<T>>::deposit_log(1272 erc::CollectionHelpersEvents::CollectionDestroyed {1273 collection_id: eth::collection_id_to_address(collection.id),1274 }1275 .to_log(T::ContractAddress::get()),1276 );1277 Ok(())1278 }12791280 1281 1282 1283 1284 1285 1286 1287 1288 #[transactional]1289 fn modify_collection_properties(1290 collection: &CollectionHandle<T>,1291 sender: &T::CrossAccountId,1292 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1293 ) -> DispatchResult {1294 collection.check_is_owner_or_admin(sender)?;12951296 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12971298 for (key, value) in properties_updates {1299 match value {1300 Some(value) => {1301 stored_properties1302 .try_set(key.clone(), value)1303 .map_err(<Error<T>>::from)?;13041305 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1306 <PalletEvm<T>>::deposit_log(1307 erc::CollectionHelpersEvents::CollectionChanged {1308 collection_id: eth::collection_id_to_address(collection.id),1309 }1310 .to_log(T::ContractAddress::get()),1311 );1312 }1313 None => {1314 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13151316 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1317 <PalletEvm<T>>::deposit_log(1318 erc::CollectionHelpersEvents::CollectionChanged {1319 collection_id: eth::collection_id_to_address(collection.id),1320 }1321 .to_log(T::ContractAddress::get()),1322 );1323 }1324 }1325 }13261327 <CollectionProperties<T>>::set(collection.id, stored_properties);13281329 Ok(())1330 }13311332 1333 1334 1335 1336 1337 1338 pub fn set_allowance_for_all(1339 collection: &CollectionHandle<T>,1340 owner: &T::CrossAccountId,1341 operator: &T::CrossAccountId,1342 approve: bool,1343 set_allowance: impl FnOnce(),1344 log: evm_coder::ethereum::Log,1345 ) -> DispatchResult {1346 if collection.permissions.access() == AccessMode::AllowList {1347 collection.check_allowlist(owner)?;1348 collection.check_allowlist(operator)?;1349 }13501351 Self::ensure_correct_receiver(operator)?;13521353 set_allowance();13541355 <PalletEvm<T>>::deposit_log(log);1356 Self::deposit_event(Event::ApprovedForAll(1357 collection.id,1358 owner.clone(),1359 operator.clone(),1360 approve,1361 ));1362 Ok(())1363 }13641365 1366 1367 1368 1369 1370 pub fn set_collection_property(1371 collection: &CollectionHandle<T>,1372 sender: &T::CrossAccountId,1373 property: Property,1374 ) -> DispatchResult {1375 Self::set_collection_properties(collection, sender, [property].into_iter())1376 }13771378 1379 1380 1381 1382 1383 1384 pub fn set_scoped_collection_property(1385 collection_id: CollectionId,1386 scope: PropertyScope,1387 property: Property,1388 ) -> DispatchResult {1389 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1390 properties.try_scoped_set(scope, property.key, property.value)1391 })1392 .map_err(<Error<T>>::from)?;13931394 Ok(())1395 }13961397 1398 1399 1400 1401 1402 1403 pub fn set_scoped_collection_properties(1404 collection_id: CollectionId,1405 scope: PropertyScope,1406 properties: impl Iterator<Item = Property>,1407 ) -> DispatchResult {1408 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1409 stored_properties.try_scoped_set_from_iter(scope, properties)1410 })1411 .map_err(<Error<T>>::from)?;14121413 Ok(())1414 }14151416 1417 1418 1419 1420 1421 pub fn set_collection_properties(1422 collection: &CollectionHandle<T>,1423 sender: &T::CrossAccountId,1424 properties: impl Iterator<Item = Property>,1425 ) -> DispatchResult {1426 Self::modify_collection_properties(1427 collection,1428 sender,1429 properties.map(|property| (property.key, Some(property.value))),1430 )1431 }14321433 1434 1435 1436 1437 1438 pub fn delete_collection_property(1439 collection: &CollectionHandle<T>,1440 sender: &T::CrossAccountId,1441 property_key: PropertyKey,1442 ) -> DispatchResult {1443 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1444 }14451446 1447 1448 1449 1450 1451 pub fn delete_collection_properties(1452 collection: &CollectionHandle<T>,1453 sender: &T::CrossAccountId,1454 property_keys: impl Iterator<Item = PropertyKey>,1455 ) -> DispatchResult {1456 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1457 }14581459 1460 1461 1462 1463 1464 1465 pub fn set_property_permission_unchecked(1466 collection: CollectionId,1467 property_permission: PropertyKeyPermission,1468 ) -> DispatchResult {1469 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1470 permissions.try_set(property_permission.key, property_permission.permission)1471 })1472 .map_err(<Error<T>>::from)?;1473 Ok(())1474 }14751476 1477 1478 1479 1480 1481 pub fn set_property_permission(1482 collection: &CollectionHandle<T>,1483 sender: &T::CrossAccountId,1484 property_permission: PropertyKeyPermission,1485 ) -> DispatchResult {1486 Self::set_scoped_property_permission(1487 collection,1488 sender,1489 PropertyScope::None,1490 property_permission,1491 )1492 }14931494 1495 1496 1497 1498 1499 1500 pub fn set_scoped_property_permission(1501 collection: &CollectionHandle<T>,1502 sender: &T::CrossAccountId,1503 scope: PropertyScope,1504 property_permission: PropertyKeyPermission,1505 ) -> DispatchResult {1506 collection.check_is_owner_or_admin(sender)?;15071508 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1509 let current_permission = all_permissions.get(&property_permission.key);1510 if matches![1511 current_permission,1512 Some(PropertyPermission { mutable: false, .. })1513 ] {1514 return Err(<Error<T>>::NoPermission.into());1515 }15161517 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1518 let property_permission = property_permission.clone();1519 permissions.try_scoped_set(1520 scope,1521 property_permission.key,1522 property_permission.permission,1523 )1524 })1525 .map_err(<Error<T>>::from)?;15261527 Self::deposit_event(Event::PropertyPermissionSet(1528 collection.id,1529 property_permission.key,1530 ));1531 <PalletEvm<T>>::deposit_log(1532 erc::CollectionHelpersEvents::CollectionChanged {1533 collection_id: eth::collection_id_to_address(collection.id),1534 }1535 .to_log(T::ContractAddress::get()),1536 );15371538 Ok(())1539 }15401541 1542 1543 1544 1545 1546 #[transactional]1547 pub fn set_token_property_permissions(1548 collection: &CollectionHandle<T>,1549 sender: &T::CrossAccountId,1550 property_permissions: Vec<PropertyKeyPermission>,1551 ) -> DispatchResult {1552 Self::set_scoped_token_property_permissions(1553 collection,1554 sender,1555 PropertyScope::None,1556 property_permissions,1557 )1558 }15591560 1561 1562 1563 1564 1565 1566 #[transactional]1567 pub fn set_scoped_token_property_permissions(1568 collection: &CollectionHandle<T>,1569 sender: &T::CrossAccountId,1570 scope: PropertyScope,1571 property_permissions: Vec<PropertyKeyPermission>,1572 ) -> DispatchResult {1573 for prop_pemission in property_permissions {1574 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1575 }15761577 Ok(())1578 }15791580 1581 pub fn get_collection_property(1582 collection_id: CollectionId,1583 key: &PropertyKey,1584 ) -> Option<PropertyValue> {1585 Self::collection_properties(collection_id).get(key).cloned()1586 }15871588 1589 pub fn bytes_keys_to_property_keys(1590 keys: Vec<Vec<u8>>,1591 ) -> Result<Vec<PropertyKey>, DispatchError> {1592 keys.into_iter()1593 .map(|key| -> Result<PropertyKey, DispatchError> {1594 key.try_into()1595 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1596 })1597 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1598 }15991600 1601 pub fn filter_collection_properties(1602 collection_id: CollectionId,1603 keys: Option<Vec<PropertyKey>>,1604 ) -> Result<Vec<Property>, DispatchError> {1605 let properties = Self::collection_properties(collection_id);16061607 let properties = keys1608 .map(|keys| {1609 keys.into_iter()1610 .filter_map(|key| {1611 properties.get(&key).map(|value| Property {1612 key,1613 value: value.clone(),1614 })1615 })1616 .collect()1617 })1618 .unwrap_or_else(|| {1619 properties1620 .into_iter()1621 .map(|(key, value)| Property { key, value })1622 .collect()1623 });16241625 Ok(properties)1626 }16271628 1629 pub fn filter_property_permissions(1630 collection_id: CollectionId,1631 keys: Option<Vec<PropertyKey>>,1632 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1633 let permissions = Self::property_permissions(collection_id);16341635 let key_permissions = keys1636 .map(|keys| {1637 keys.into_iter()1638 .filter_map(|key| {1639 permissions1640 .get(&key)1641 .map(|permission| PropertyKeyPermission {1642 key,1643 permission: permission.clone(),1644 })1645 })1646 .collect()1647 })1648 .unwrap_or_else(|| {1649 permissions1650 .into_iter()1651 .map(|(key, permission)| PropertyKeyPermission { key, permission })1652 .collect()1653 });16541655 Ok(key_permissions)1656 }16571658 1659 1660 1661 pub fn toggle_allowlist(1662 collection: &CollectionHandle<T>,1663 sender: &T::CrossAccountId,1664 user: &T::CrossAccountId,1665 allowed: bool,1666 ) -> DispatchResult {1667 collection.check_is_owner_or_admin(sender)?;16681669 16701671 if allowed {1672 <Allowlist<T>>::insert((collection.id, user), true);1673 Self::deposit_event(Event::<T>::AllowListAddressAdded(1674 collection.id,1675 user.clone(),1676 ));1677 } else {1678 <Allowlist<T>>::remove((collection.id, user));1679 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1680 collection.id,1681 user.clone(),1682 ));1683 }16841685 <PalletEvm<T>>::deposit_log(1686 erc::CollectionHelpersEvents::CollectionChanged {1687 collection_id: eth::collection_id_to_address(collection.id),1688 }1689 .to_log(T::ContractAddress::get()),1690 );16911692 Ok(())1693 }16941695 1696 1697 1698 pub fn toggle_admin(1699 collection: &CollectionHandle<T>,1700 sender: &T::CrossAccountId,1701 user: &T::CrossAccountId,1702 admin: bool,1703 ) -> DispatchResult {1704 collection.check_is_internal()?;1705 collection.check_is_owner(sender)?;17061707 let is_admin = <IsAdmin<T>>::get((collection.id, user));1708 if is_admin == admin {1709 if admin {1710 return Ok(());1711 } else {1712 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1713 }1714 }1715 let amount = <AdminAmount<T>>::get(collection.id);17161717 17181719 if admin {1720 let amount = amount1721 .checked_add(1)1722 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1723 ensure!(1724 amount <= Self::collection_admins_limit(),1725 <Error<T>>::CollectionAdminCountExceeded,1726 );17271728 <AdminAmount<T>>::insert(collection.id, amount);1729 <IsAdmin<T>>::insert((collection.id, user), true);17301731 Self::deposit_event(Event::<T>::CollectionAdminAdded(1732 collection.id,1733 user.clone(),1734 ));1735 } else {1736 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1737 <IsAdmin<T>>::remove((collection.id, user));17381739 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1740 collection.id,1741 user.clone(),1742 ));1743 }17441745 <PalletEvm<T>>::deposit_log(1746 erc::CollectionHelpersEvents::CollectionChanged {1747 collection_id: eth::collection_id_to_address(collection.id),1748 }1749 .to_log(T::ContractAddress::get()),1750 );17511752 Ok(())1753 }17541755 1756 pub fn update_limits(1757 user: &T::CrossAccountId,1758 collection: &mut CollectionHandle<T>,1759 new_limit: CollectionLimits,1760 ) -> DispatchResult {1761 collection.check_is_internal()?;1762 collection.check_is_owner_or_admin(user)?;17631764 collection.limits =1765 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17661767 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1768 <PalletEvm<T>>::deposit_log(1769 erc::CollectionHelpersEvents::CollectionChanged {1770 collection_id: eth::collection_id_to_address(collection.id),1771 }1772 .to_log(T::ContractAddress::get()),1773 );17741775 collection.save()1776 }17771778 1779 fn clamp_limits(1780 mode: CollectionMode,1781 old_limit: &CollectionLimits,1782 mut new_limit: CollectionLimits,1783 ) -> Result<CollectionLimits, DispatchError> {1784 let limits = old_limit;1785 limit_default!(old_limit, new_limit,1786 account_token_ownership_limit => ensure!(1787 new_limit <= MAX_TOKEN_OWNERSHIP,1788 <Error<T>>::CollectionLimitBoundsExceeded,1789 ),1790 sponsored_data_size => ensure!(1791 new_limit <= CUSTOM_DATA_LIMIT,1792 <Error<T>>::CollectionLimitBoundsExceeded,1793 ),17941795 sponsored_data_rate_limit => {},1796 token_limit => ensure!(1797 old_limit >= new_limit && new_limit > 0,1798 <Error<T>>::CollectionTokenLimitExceeded1799 ),18001801 sponsor_transfer_timeout(match mode {1802 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1803 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1804 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1805 }) => ensure!(1806 new_limit <= MAX_SPONSOR_TIMEOUT,1807 <Error<T>>::CollectionLimitBoundsExceeded,1808 ),1809 sponsor_approve_timeout => {},1810 owner_can_transfer => ensure!(1811 !limits.owner_can_transfer_instaled() ||1812 old_limit || !new_limit,1813 <Error<T>>::OwnerPermissionsCantBeReverted,1814 ),1815 owner_can_destroy => ensure!(1816 old_limit || !new_limit,1817 <Error<T>>::OwnerPermissionsCantBeReverted,1818 ),1819 transfers_enabled => {},1820 );1821 Ok(new_limit)1822 }18231824 1825 pub fn update_permissions(1826 user: &T::CrossAccountId,1827 collection: &mut CollectionHandle<T>,1828 new_permission: CollectionPermissions,1829 ) -> DispatchResult {1830 collection.check_is_internal()?;1831 collection.check_is_owner_or_admin(user)?;1832 collection.permissions = Self::clamp_permissions(1833 collection.mode.clone(),1834 &collection.permissions,1835 new_permission,1836 )?;18371838 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1839 <PalletEvm<T>>::deposit_log(1840 erc::CollectionHelpersEvents::CollectionChanged {1841 collection_id: eth::collection_id_to_address(collection.id),1842 }1843 .to_log(T::ContractAddress::get()),1844 );18451846 collection.save()1847 }18481849 1850 fn clamp_permissions(1851 _mode: CollectionMode,1852 old_permission: &CollectionPermissions,1853 mut new_permission: CollectionPermissions,1854 ) -> Result<CollectionPermissions, DispatchError> {1855 limit_default_clone!(old_permission, new_permission,1856 access => {},1857 mint_mode => {},1858 nesting => { },1859 );1860 Ok(new_permission)1861 }18621863 1864 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1865 CollectionProperties::<T>::mutate(collection_id, |properties| {1866 properties.recompute_consumed_space();1867 });18681869 Ok(())1870 }1871}187218731874#[macro_export]1875macro_rules! unsupported {1876 ($runtime:path) => {1877 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1878 };1879}188018811882pub trait CommonWeightInfo<CrossAccountId> {1883 1884 fn create_item(data: &CreateItemData) -> Weight {1885 Self::create_multiple_items(from_ref(data))1886 }18871888 1889 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18901891 1892 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18931894 1895 fn burn_item() -> Weight;18961897 1898 1899 1900 fn set_collection_properties(amount: u32) -> Weight;19011902 1903 1904 1905 fn delete_collection_properties(amount: u32) -> Weight {1906 Self::set_collection_properties(amount)1907 }19081909 1910 1911 1912 fn set_token_properties(amount: u32) -> Weight;19131914 1915 1916 1917 fn delete_token_properties(amount: u32) -> Weight {1918 Self::set_token_properties(amount)1919 }19201921 1922 1923 1924 fn set_token_property_permissions(amount: u32) -> Weight;19251926 1927 fn transfer() -> Weight;19281929 1930 fn approve() -> Weight;19311932 1933 fn approve_from() -> Weight;19341935 1936 fn transfer_from() -> Weight;19371938 1939 fn burn_from() -> Weight;19401941 1942 fn set_allowance_for_all() -> Weight;19431944 1945 fn force_repair_item() -> Weight;1946}194719481949pub trait RefungibleExtensionsWeightInfo {1950 1951 fn repartition() -> Weight;1952}195319541955195619571958pub trait CommonCollectionOperations<T: Config> {1959 1960 1961 1962 1963 1964 1965 fn create_item(1966 &self,1967 sender: T::CrossAccountId,1968 to: T::CrossAccountId,1969 data: CreateItemData,1970 nesting_budget: &dyn Budget,1971 ) -> DispatchResultWithPostInfo;19721973 1974 1975 1976 1977 1978 1979 fn create_multiple_items(1980 &self,1981 sender: T::CrossAccountId,1982 to: T::CrossAccountId,1983 data: Vec<CreateItemData>,1984 nesting_budget: &dyn Budget,1985 ) -> DispatchResultWithPostInfo;19861987 1988 1989 1990 1991 1992 1993 fn create_multiple_items_ex(1994 &self,1995 sender: T::CrossAccountId,1996 data: CreateItemExData<T::CrossAccountId>,1997 nesting_budget: &dyn Budget,1998 ) -> DispatchResultWithPostInfo;19992000 2001 2002 2003 2004 2005 fn burn_item(2006 &self,2007 sender: T::CrossAccountId,2008 token: TokenId,2009 amount: u128,2010 ) -> DispatchResultWithPostInfo;20112012 2013 2014 2015 2016 fn set_collection_properties(2017 &self,2018 sender: T::CrossAccountId,2019 properties: Vec<Property>,2020 ) -> DispatchResultWithPostInfo;20212022 2023 2024 2025 2026 fn delete_collection_properties(2027 &self,2028 sender: &T::CrossAccountId,2029 property_keys: Vec<PropertyKey>,2030 ) -> DispatchResultWithPostInfo;20312032 2033 2034 2035 2036 2037 2038 2039 2040 2041 fn set_token_properties(2042 &self,2043 sender: T::CrossAccountId,2044 token_id: TokenId,2045 properties: Vec<Property>,2046 budget: &dyn Budget,2047 ) -> DispatchResultWithPostInfo;20482049 2050 2051 2052 2053 2054 2055 2056 2057 2058 fn delete_token_properties(2059 &self,2060 sender: T::CrossAccountId,2061 token_id: TokenId,2062 property_keys: Vec<PropertyKey>,2063 budget: &dyn Budget,2064 ) -> DispatchResultWithPostInfo;20652066 2067 2068 2069 fn get_token_properties_raw(&self, token_id: TokenId) -> Option<TokenProperties>;20702071 2072 2073 2074 2075 fn set_token_properties_raw(&self, token_id: TokenId, map: TokenProperties);20762077 2078 2079 2080 2081 2082 2083 fn set_token_property_permissions(2084 &self,2085 sender: &T::CrossAccountId,2086 property_permissions: Vec<PropertyKeyPermission>,2087 ) -> DispatchResultWithPostInfo;20882089 2090 2091 2092 2093 2094 2095 2096 fn transfer(2097 &self,2098 sender: T::CrossAccountId,2099 to: T::CrossAccountId,2100 token: TokenId,2101 amount: u128,2102 budget: &dyn Budget,2103 ) -> DispatchResultWithPostInfo;21042105 2106 2107 2108 2109 2110 2111 fn approve(2112 &self,2113 sender: T::CrossAccountId,2114 spender: T::CrossAccountId,2115 token: TokenId,2116 amount: u128,2117 ) -> DispatchResultWithPostInfo;21182119 2120 2121 2122 2123 2124 2125 2126 fn approve_from(2127 &self,2128 sender: T::CrossAccountId,2129 from: T::CrossAccountId,2130 to: T::CrossAccountId,2131 token: TokenId,2132 amount: u128,2133 ) -> DispatchResultWithPostInfo;21342135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 fn transfer_from(2146 &self,2147 sender: T::CrossAccountId,2148 from: T::CrossAccountId,2149 to: T::CrossAccountId,2150 token: TokenId,2151 amount: u128,2152 budget: &dyn Budget,2153 ) -> DispatchResultWithPostInfo;21542155 2156 2157 2158 2159 2160 2161 2162 2163 2164 fn burn_from(2165 &self,2166 sender: T::CrossAccountId,2167 from: T::CrossAccountId,2168 token: TokenId,2169 amount: u128,2170 budget: &dyn Budget,2171 ) -> DispatchResultWithPostInfo;21722173 2174 2175 2176 2177 2178 2179 fn check_nesting(2180 &self,2181 sender: T::CrossAccountId,2182 from: (CollectionId, TokenId),2183 under: TokenId,2184 budget: &dyn Budget,2185 ) -> DispatchResult;21862187 2188 2189 2190 2191 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21922193 2194 2195 2196 2197 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21982199 2200 2201 2202 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22032204 2205 fn collection_tokens(&self) -> Vec<TokenId>;22062207 2208 2209 2210 fn token_exists(&self, token: TokenId) -> bool;22112212 2213 fn last_token_id(&self) -> TokenId;22142215 2216 2217 2218 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22192220 2221 2222 2223 2224 2225 fn check_token_indirect_owner(2226 &self,2227 token: TokenId,2228 maybe_owner: &T::CrossAccountId,2229 nesting_budget: &dyn Budget,2230 ) -> Result<bool, DispatchError>;22312232 2233 2234 2235 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22362237 2238 2239 2240 2241 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22422243 2244 2245 2246 2247 2248 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22492250 2251 fn total_supply(&self) -> u32;22522253 2254 2255 2256 fn account_balance(&self, account: T::CrossAccountId) -> u32;22572258 2259 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22602261 2262 fn total_pieces(&self, token: TokenId) -> Option<u128>;22632264 2265 2266 2267 2268 2269 fn allowance(2270 &self,2271 sender: T::CrossAccountId,2272 spender: T::CrossAccountId,2273 token: TokenId,2274 ) -> u128;22752276 2277 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22782279 2280 2281 2282 2283 fn set_allowance_for_all(2284 &self,2285 owner: T::CrossAccountId,2286 operator: T::CrossAccountId,2287 approve: bool,2288 ) -> DispatchResultWithPostInfo;22892290 2291 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;22922293 2294 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2295}229622972298pub trait RefungibleExtensions<T>2299where2300 T: Config,2301{2302 2303 2304 2305 2306 2307 2308 2309 fn repartition(2310 &self,2311 sender: &T::CrossAccountId,2312 token: TokenId,2313 amount: u128,2314 ) -> DispatchResultWithPostInfo;2315}23162317231823192320pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2321 let post_info = PostDispatchInfo {2322 actual_weight: Some(weight),2323 pays_fee: Pays::Yes,2324 };2325 match res {2326 Ok(()) => Ok(post_info),2327 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2328 }2329}23302331impl<T: Config> From<PropertiesError> for Error<T> {2332 fn from(error: PropertiesError) -> Self {2333 match error {2334 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2335 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2336 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2337 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2338 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2339 }2340 }2341}23422343234423452346234723482349pub struct PropertyWriter<'a, WriterVariant, T, Handle, FIsAdmin, FPropertyPermissions> {2350 collection: &'a Handle,2351 collection_lazy_info: PropertyWriterLazyCollectionInfo<FIsAdmin, FPropertyPermissions>,2352 _phantom: PhantomData<(T, WriterVariant)>,2353}23542355impl<'a, T, Handle, WriterVariant, FIsAdmin, FPropertyPermissions>2356 PropertyWriter<'a, WriterVariant, T, Handle, FIsAdmin, FPropertyPermissions>2357where2358 T: Config,2359 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2360 FIsAdmin: FnOnce() -> bool,2361 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2362{2363 fn internal_write_token_properties<FCheckTokenExist, FCheckTokenOwner, FGetProperties>(2364 &mut self,2365 token_id: TokenId,2366 mut token_lazy_info: PropertyWriterLazyTokenInfo<2367 FCheckTokenExist,2368 FCheckTokenOwner,2369 FGetProperties,2370 >,2371 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2372 log: evm_coder::ethereum::Log,2373 ) -> DispatchResult2374 where2375 FCheckTokenExist: FnOnce() -> bool,2376 FCheckTokenOwner: FnOnce() -> Result<bool, DispatchError>,2377 FGetProperties: FnOnce() -> TokenProperties,2378 {2379 for (key, value) in properties_updates {2380 let permission = self2381 .collection_lazy_info2382 .property_permissions2383 .value()2384 .get(&key)2385 .cloned()2386 .unwrap_or_else(PropertyPermission::none);23872388 match permission {2389 PropertyPermission { mutable: false, .. }2390 if token_lazy_info2391 .stored_properties2392 .value()2393 .get(&key)2394 .is_some() =>2395 {2396 return Err(<Error<T>>::NoPermission.into());2397 }23982399 PropertyPermission {2400 collection_admin,2401 token_owner,2402 ..2403 } => check_token_permissions::<T, _, _, _>(2404 collection_admin,2405 token_owner,2406 &mut self.collection_lazy_info.is_collection_admin,2407 &mut token_lazy_info.is_token_owner,2408 &mut token_lazy_info.is_token_exist,2409 )?,2410 }24112412 match value {2413 Some(value) => {2414 token_lazy_info2415 .stored_properties2416 .value_mut()2417 .try_set(key.clone(), value)2418 .map_err(<Error<T>>::from)?;24192420 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2421 self.collection.id,2422 token_id,2423 key,2424 ));2425 }2426 None => {2427 token_lazy_info2428 .stored_properties2429 .value_mut()2430 .remove(&key)2431 .map_err(<Error<T>>::from)?;24322433 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2434 self.collection.id,2435 token_id,2436 key,2437 ));2438 }2439 }2440 }24412442 let properties_changed = token_lazy_info.stored_properties.has_value();2443 if properties_changed {2444 <PalletEvm<T>>::deposit_log(log);24452446 self.collection2447 .set_token_properties_raw(token_id, token_lazy_info.stored_properties.into_inner());2448 }24492450 Ok(())2451 }2452}24532454245524562457pub struct PropertyWriterLazyCollectionInfo<FIsAdmin, FPropertyPermissions> {2458 is_collection_admin: LazyValue<bool, FIsAdmin>,2459 property_permissions: LazyValue<PropertiesPermissionMap, FPropertyPermissions>,2460}2461246224632464pub struct PropertyWriterLazyTokenInfo<FCheckTokenExist, FCheckTokenOwner, FGetProperties> {2465 is_token_exist: LazyValue<bool, FCheckTokenExist>,2466 is_token_owner: LazyValue<Result<bool, DispatchError>, FCheckTokenOwner>,2467 stored_properties: LazyValue<TokenProperties, FGetProperties>,2468}24692470impl<FCheckTokenExist, FCheckTokenOwner, FGetProperties>2471 PropertyWriterLazyTokenInfo<FCheckTokenExist, FCheckTokenOwner, FGetProperties>2472where2473 FCheckTokenExist: FnOnce() -> bool,2474 FCheckTokenOwner: FnOnce() -> Result<bool, DispatchError>,2475 FGetProperties: FnOnce() -> TokenProperties,2476{2477 2478 pub fn new(2479 check_token_exist: FCheckTokenExist,2480 check_token_owner: FCheckTokenOwner,2481 get_token_properties: FGetProperties,2482 ) -> Self {2483 Self {2484 is_token_exist: LazyValue::new(check_token_exist),2485 is_token_owner: LazyValue::new(check_token_owner),2486 stored_properties: LazyValue::new(get_token_properties),2487 }2488 }2489}2490249124922493pub struct NewTokenPropertyWriter<T>(PhantomData<T>);2494impl<T: Config> NewTokenPropertyWriter<T> {2495 2496 pub fn new<'a, Handle>(2497 collection: &'a Handle,2498 sender: &'a T::CrossAccountId,2499 ) -> PropertyWriter<2500 'a,2501 Self,2502 T,2503 Handle,2504 impl FnOnce() -> bool + 'a,2505 impl FnOnce() -> PropertiesPermissionMap + 'a,2506 >2507 where2508 T: Config,2509 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2510 {2511 PropertyWriter {2512 collection,2513 collection_lazy_info: PropertyWriterLazyCollectionInfo {2514 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2515 property_permissions: LazyValue::new(|| {2516 <Pallet<T>>::property_permissions(collection.id)2517 }),2518 },2519 _phantom: PhantomData,2520 }2521 }2522}25232524impl<'a, T, Handle, FIsAdmin, FPropertyPermissions>2525 PropertyWriter<'a, NewTokenPropertyWriter<T>, T, Handle, FIsAdmin, FPropertyPermissions>2526where2527 T: Config,2528 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2529 FIsAdmin: FnOnce() -> bool,2530 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2531{2532 2533 pub fn write_token_properties(2534 &mut self,2535 mint_target_is_sender: bool,2536 token_id: TokenId,2537 properties_updates: impl Iterator<Item = Property>,2538 log: evm_coder::ethereum::Log,2539 ) -> DispatchResult {2540 let check_token_exist = || {2541 debug_assert!(self.collection.token_exists(token_id));2542 true2543 };25442545 let check_token_owner = || Ok(mint_target_is_sender);25462547 let get_token_properties = || {2548 debug_assert!(self.collection.get_token_properties_raw(token_id).is_none());2549 TokenProperties::new()2550 };25512552 self.internal_write_token_properties(2553 token_id,2554 PropertyWriterLazyTokenInfo::new(2555 check_token_exist,2556 check_token_owner,2557 get_token_properties,2558 ),2559 properties_updates.map(|p| (p.key, Some(p.value))),2560 log,2561 )2562 }2563}2564256525662567pub struct ExistingTokenPropertyWriter<T>(PhantomData<T>);2568impl<T: Config> ExistingTokenPropertyWriter<T> {2569 2570 pub fn new<'a, Handle>(2571 collection: &'a Handle,2572 sender: &'a T::CrossAccountId,2573 ) -> PropertyWriter<2574 'a,2575 Self,2576 T,2577 Handle,2578 impl FnOnce() -> bool + 'a,2579 impl FnOnce() -> PropertiesPermissionMap + 'a,2580 >2581 where2582 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2583 {2584 PropertyWriter {2585 collection,2586 collection_lazy_info: PropertyWriterLazyCollectionInfo {2587 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2588 property_permissions: LazyValue::new(|| {2589 <Pallet<T>>::property_permissions(collection.id)2590 }),2591 },2592 _phantom: PhantomData,2593 }2594 }2595}25962597impl<'a, T, Handle, FIsAdmin, FPropertyPermissions>2598 PropertyWriter<'a, ExistingTokenPropertyWriter<T>, T, Handle, FIsAdmin, FPropertyPermissions>2599where2600 T: Config,2601 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2602 FIsAdmin: FnOnce() -> bool,2603 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2604{2605 2606 pub fn write_token_properties(2607 &mut self,2608 sender: &T::CrossAccountId,2609 token_id: TokenId,2610 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2611 nesting_budget: &dyn Budget,2612 log: evm_coder::ethereum::Log,2613 ) -> DispatchResult {2614 let check_token_exist = || self.collection.token_exists(token_id);2615 let check_token_owner = || {2616 self.collection2617 .check_token_indirect_owner(token_id, sender, nesting_budget)2618 };2619 let get_token_properties = || {2620 self.collection2621 .get_token_properties_raw(token_id)2622 .unwrap_or_default()2623 };26242625 self.internal_write_token_properties(2626 token_id,2627 PropertyWriterLazyTokenInfo::new(2628 check_token_exist,2629 check_token_owner,2630 get_token_properties,2631 ),2632 properties_updates,2633 log,2634 )2635 }2636}2637263826392640#[cfg(feature = "runtime-benchmarks")]2641pub struct BenchmarkPropertyWriter<T>(PhantomData<T>);26422643#[cfg(feature = "runtime-benchmarks")]2644impl<T: Config> BenchmarkPropertyWriter<T> {2645 2646 pub fn new<'a, Handle, FIsAdmin, FPropertyPermissions>(2647 collection: &Handle,2648 collection_lazy_info: PropertyWriterLazyCollectionInfo<FIsAdmin, FPropertyPermissions>,2649 ) -> PropertyWriter<Self, T, Handle, FIsAdmin, FPropertyPermissions>2650 where2651 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2652 FIsAdmin: FnOnce() -> bool,2653 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2654 {2655 PropertyWriter {2656 collection,2657 collection_lazy_info,2658 _phantom: PhantomData,2659 }2660 }26612662 2663 pub fn load_collection_info<Handle>(2664 collection_handle: &Handle,2665 sender: &T::CrossAccountId,2666 ) -> PropertyWriterLazyCollectionInfo<2667 impl FnOnce() -> bool,2668 impl FnOnce() -> PropertiesPermissionMap,2669 >2670 where2671 Handle: Deref<Target = CollectionHandle<T>>,2672 {2673 let is_collection_admin = collection_handle.is_owner_or_admin(sender);2674 let property_permissions = <Pallet<T>>::property_permissions(collection_handle.id);26752676 PropertyWriterLazyCollectionInfo {2677 is_collection_admin: LazyValue::new(move || is_collection_admin),2678 property_permissions: LazyValue::new(move || property_permissions),2679 }2680 }26812682 2683 pub fn load_token_properties<Handle>(2684 collection: &Handle,2685 token_id: TokenId,2686 ) -> PropertyWriterLazyTokenInfo<2687 impl FnOnce() -> bool,2688 impl FnOnce() -> Result<bool, DispatchError>,2689 impl FnOnce() -> TokenProperties,2690 >2691 where2692 Handle: CommonCollectionOperations<T>,2693 {2694 let stored_properties = collection2695 .get_token_properties_raw(token_id)2696 .unwrap_or_default();26972698 PropertyWriterLazyTokenInfo {2699 is_token_exist: LazyValue::new(|| true),2700 is_token_owner: LazyValue::new(|| Ok(true)),2701 stored_properties: LazyValue::new(move || stored_properties),2702 }2703 }2704}27052706#[cfg(feature = "runtime-benchmarks")]2707impl<'a, T, Handle, FIsAdmin, FPropertyPermissions>2708 PropertyWriter<'a, BenchmarkPropertyWriter<T>, T, Handle, FIsAdmin, FPropertyPermissions>2709where2710 T: Config,2711 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2712 FIsAdmin: FnOnce() -> bool,2713 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2714{2715 2716 pub fn write_token_properties(2717 &mut self,2718 token_id: TokenId,2719 properties_updates: impl Iterator<Item = Property>,2720 log: evm_coder::ethereum::Log,2721 ) -> DispatchResult {2722 let check_token_exist = || true;2723 let check_token_owner = || Ok(true);2724 let get_token_properties = || TokenProperties::new();27252726 self.internal_write_token_properties(2727 token_id,2728 PropertyWriterLazyTokenInfo::new(2729 check_token_exist,2730 check_token_owner,2731 get_token_properties,2732 ),2733 properties_updates.map(|p| (p.key, Some(p.value))),2734 log,2735 )2736 }2737}273827392740274127422743pub fn write_token_properties_total_weight<T: Config, I: Fn(u32) -> Weight>(2744 properties_nums: impl Iterator<Item = u32>,2745 per_token_weight: I,2746) -> Weight {2747 let mut weight = properties_nums2748 .filter_map(|properties_num| {2749 if properties_num > 0 {2750 Some(per_token_weight(properties_num))2751 } else {2752 None2753 }2754 })2755 .fold(Weight::zero(), |a, b| a.saturating_add(b));27562757 if !weight.is_zero() {2758 2759 2760 27612762 weight = weight.saturating_add(<SelfWeightOf<T>>::property_writer_load_collection_info());2763 }27642765 weight2766}27672768#[cfg(any(feature = "tests", test))]2769#[allow(missing_docs)]2770pub mod tests {2771 use crate::{Config, DispatchError, DispatchResult, LazyValue};27722773 const fn to_bool(u: u8) -> bool {2774 u != 02775 }27762777 #[derive(Debug)]2778 pub struct TestCase {2779 pub collection_admin: bool,2780 pub is_collection_admin: bool,2781 pub token_owner: bool,2782 pub is_token_owner: bool,2783 pub no_permission: bool,2784 }27852786 impl TestCase {2787 const fn new(2788 collection_admin: u8,2789 is_collection_admin: u8,2790 token_owner: u8,2791 is_token_owner: u8,2792 no_permission: u8,2793 ) -> Self {2794 Self {2795 collection_admin: to_bool(collection_admin),2796 is_collection_admin: to_bool(is_collection_admin),2797 token_owner: to_bool(token_owner),2798 is_token_owner: to_bool(is_token_owner),2799 no_permission: to_bool(no_permission),2800 }2801 }2802 }28032804 #[rustfmt::skip]2805 pub const TABLE: [TestCase; 16] = [2806 2807 2808 2809 2810 2811 TestCase::new(0, 0, 0, 0, 1),2812 TestCase::new(0, 0, 0, 1, 1),2813 TestCase::new(0, 0, 1, 0, 1),2814 TestCase::new(0, 0, 1, 1, 0),2815 TestCase::new(0, 1, 0, 0, 1),2816 TestCase::new(0, 1, 0, 1, 1),2817 TestCase::new(0, 1, 1, 0, 1),2818 TestCase::new(0, 1, 1, 1, 0),2819 TestCase::new(1, 0, 0, 0, 1),2820 TestCase::new(1, 0, 0, 1, 1),2821 TestCase::new(1, 0, 1, 0, 1),2822 TestCase::new(1, 0, 1, 1, 0),2823 TestCase::new(1, 1, 0, 0, 0),2824 TestCase::new(1, 1, 0, 1, 0),2825 TestCase::new(1, 1, 1, 0, 0),2826 TestCase::new(1, 1, 1, 1, 0),2827 ];28282829 pub fn check_token_permissions<T, FCA, FTO, FTE>(2830 collection_admin_permitted: bool,2831 token_owner_permitted: bool,2832 is_collection_admin: &mut LazyValue<bool, FCA>,2833 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>, FTO>,2834 check_token_existence: &mut LazyValue<bool, FTE>,2835 ) -> DispatchResult2836 where2837 T: Config,2838 FCA: FnOnce() -> bool,2839 FTO: FnOnce() -> Result<bool, DispatchError>,2840 FTE: FnOnce() -> bool,2841 {2842 crate::check_token_permissions::<T, FCA, FTO, FTE>(2843 collection_admin_permitted,2844 token_owner_permitted,2845 is_collection_admin,2846 check_token_ownership,2847 check_token_existence,2848 )2849 }2850}