12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use alloc::boxed::Box;57use core::{58 marker::PhantomData,59 ops::{Deref, DerefMut},60 slice::from_ref,61 unreachable,62};6364use evm_coder::ToLog;65use frame_support::{66 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Pays, PostDispatchInfo},67 ensure, fail,68 traits::{69 fungible::{Balanced, Debt, Inspect},70 tokens::{Imbalance, Precision, Preservation},71 Get,72 },73 transactional,74};75pub use pallet::*;76use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};77use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};78use sp_core::H160;79use sp_runtime::{traits::Zero, ArithmeticError, DispatchError, DispatchResult};80use sp_std::vec::Vec;81use sp_weights::Weight;82use up_data_structs::{83 budget::Budget, mapping::TokenAddressMapping, AccessMode, Collection, CollectionId,84 CollectionLimits, CollectionMode, CollectionPermissions,85 CollectionProperties as CollectionPropertiesT, CollectionStats, CreateCollectionData,86 CreateItemData, CreateItemExData, PhantomType, PropertiesError, PropertiesPermissionMap,87 Property, PropertyKey, PropertyKeyPermission, PropertyPermission, PropertyScope, PropertyValue,88 RpcCollection, RpcCollectionFlags, SponsoringRateLimit, SponsorshipState, TokenChild,89 TokenData, TokenId, TokenOwnerError, TokenProperties, TrySetProperty, COLLECTION_ADMINS_LIMIT,90 COLLECTION_NUMBER_LIMIT, CUSTOM_DATA_LIMIT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,91 MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP, MAX_TOKEN_PREFIX_LENGTH,92 NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,93};94use up_pov_estimate_rpc::PovInfo;9596#[cfg(feature = "runtime-benchmarks")]97pub mod benchmarking;98pub mod dispatch;99pub mod erc;100pub mod eth;101pub mod helpers;102#[allow(missing_docs)]103pub mod weights;104105use weights::WeightInfo;106107108pub type SelfWeightOf<T> = <T as Config>::WeightInfo;109110111112113114115116#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]117pub struct CollectionHandle<T: Config> {118 119 pub id: CollectionId,120 collection: Collection<T::AccountId>,121 122 pub recorder: SubstrateRecorder<T>,123}124125impl<T: Config> WithRecorder<T> for CollectionHandle<T> {126 fn recorder(&self) -> &SubstrateRecorder<T> {127 &self.recorder128 }129 fn into_recorder(self) -> SubstrateRecorder<T> {130 self.recorder131 }132}133134impl<T: Config> CollectionHandle<T> {135 136 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {137 Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))138 }139140 141 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {142 <CollectionById<T>>::get(id).map(|collection| Self {143 id,144 collection,145 recorder,146 })147 }148149 150 151 pub fn new(id: CollectionId) -> Option<Self> {152 Self::new_with_gas_limit(id, u64::MAX)153 }154155 156 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {157 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)158 }159160 161 pub fn consume_store_reads(162 &self,163 reads: u64,164 ) -> pallet_evm_coder_substrate::execution::Result<()> {165 self.recorder().consume_store_reads(reads)166 }167168 169 pub fn consume_store_writes(170 &self,171 writes: u64,172 ) -> pallet_evm_coder_substrate::execution::Result<()> {173 self.recorder().consume_store_writes(writes)174 }175176 177 pub fn consume_store_reads_and_writes(178 &self,179 reads: u64,180 writes: u64,181 ) -> pallet_evm_coder_substrate::execution::Result<()> {182 self.recorder()183 .consume_store_reads_and_writes(reads, writes)184 }185186 187 pub fn save(&self) -> DispatchResult {188 <CollectionById<T>>::insert(self.id, &self.collection);189 Ok(())190 }191192 193 194 195 196 197 pub fn set_sponsor(198 &mut self,199 sender: &T::CrossAccountId,200 sponsor: T::AccountId,201 ) -> DispatchResult {202 self.check_is_internal()?;203 self.check_is_owner_or_admin(sender)?;204205 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());206207 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));208 <PalletEvm<T>>::deposit_log(209 erc::CollectionHelpersEvents::CollectionChanged {210 collection_id: eth::collection_id_to_address(self.id),211 }212 .to_log(T::ContractAddress::get()),213 );214215 self.save()216 }217218 219 220 221 222 223 224 225 226 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {227 self.check_is_internal()?;228229 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());230231 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));232 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));233 <PalletEvm<T>>::deposit_log(234 erc::CollectionHelpersEvents::CollectionChanged {235 collection_id: eth::collection_id_to_address(self.id),236 }237 .to_log(T::ContractAddress::get()),238 );239240 self.save()241 }242243 244 245 246 247 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {248 self.check_is_internal()?;249 ensure!(250 self.collection.sponsorship.pending_sponsor() == Some(sender),251 Error::<T>::ConfirmSponsorshipFail252 );253254 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());255256 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));257 <PalletEvm<T>>::deposit_log(258 erc::CollectionHelpersEvents::CollectionChanged {259 collection_id: eth::collection_id_to_address(self.id),260 }261 .to_log(T::ContractAddress::get()),262 );263264 self.save()265 }266267 268 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {269 self.check_is_internal()?;270 self.check_is_owner_or_admin(sender)?;271272 self.collection.sponsorship = SponsorshipState::Disabled;273274 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));275 <PalletEvm<T>>::deposit_log(276 erc::CollectionHelpersEvents::CollectionChanged {277 collection_id: eth::collection_id_to_address(self.id),278 }279 .to_log(T::ContractAddress::get()),280 );281 self.save()282 }283284 285 286 287 288 pub fn force_remove_sponsor(&mut self) -> DispatchResult {289 self.check_is_internal()?;290291 self.collection.sponsorship = SponsorshipState::Disabled;292293 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));294 <PalletEvm<T>>::deposit_log(295 erc::CollectionHelpersEvents::CollectionChanged {296 collection_id: eth::collection_id_to_address(self.id),297 }298 .to_log(T::ContractAddress::get()),299 );300 self.save()301 }302303 304 305 pub fn check_is_internal(&self) -> DispatchResult {306 if self.flags.external {307 return Err(<Error<T>>::CollectionIsExternal)?;308 }309310 Ok(())311 }312313 314 315 pub fn check_is_external(&self) -> DispatchResult {316 if !self.flags.external {317 return Err(<Error<T>>::CollectionIsInternal)?;318 }319320 Ok(())321 }322}323324impl<T: Config> Deref for CollectionHandle<T> {325 type Target = Collection<T::AccountId>;326327 fn deref(&self) -> &Self::Target {328 &self.collection329 }330}331332impl<T: Config> DerefMut for CollectionHandle<T> {333 fn deref_mut(&mut self) -> &mut Self::Target {334 &mut self.collection335 }336}337338impl<T: Config> CollectionHandle<T> {339 340 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {341 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);342 Ok(())343 }344345 346 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {347 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))348 }349350 351 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {352 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);353 Ok(())354 }355356 357 358 359 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {360 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)361 }362363 364 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {365 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)366 }367368 369 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {370 ensure!(371 <Allowlist<T>>::get((self.id, user)),372 <Error<T>>::AddressNotInAllowlist373 );374 Ok(())375 }376377 378 379 380 pub fn change_owner(381 &mut self,382 caller: T::CrossAccountId,383 new_owner: T::CrossAccountId,384 ) -> DispatchResult {385 self.check_is_internal()?;386 self.check_is_owner(&caller)?;387 self.collection.owner = new_owner.as_sub().clone();388389 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(390 self.id,391 new_owner.as_sub().clone(),392 ));393 <PalletEvm<T>>::deposit_log(394 erc::CollectionHelpersEvents::CollectionChanged {395 collection_id: eth::collection_id_to_address(self.id),396 }397 .to_log(T::ContractAddress::get()),398 );399400 self.save()401 }402}403404#[frame_support::pallet]405pub mod pallet {406407 use dispatch::CollectionDispatch;408 use frame_support::{409 pallet_prelude::*, storage::Key, traits::StorageVersion, Blake2_128Concat,410 };411 use scale_info::TypeInfo;412 use up_data_structs::{mapping::TokenAddressMapping, TokenId};413 use weights::WeightInfo;414415 use super::*;416417 #[pallet::config]418 pub trait Config:419 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo420 {421 422 type WeightInfo: WeightInfo;423424 425 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;426427 428 type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;429430 431 #[pallet::constant]432 type CollectionCreationPrice: Get<433 <<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,434 >;435436 437 type CollectionDispatch: CollectionDispatch<Self>;438439 440 type TreasuryAccountId: Get<Self::AccountId>;441442 443 #[pallet::constant]444 type ContractAddress: Get<H160>;445446 447 type EvmTokenAddressMapping: TokenAddressMapping<H160>;448449 450 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;451 }452453 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);454 455 pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);456457 #[pallet::pallet]458 #[pallet::storage_version(STORAGE_VERSION)]459 pub struct Pallet<T>(_);460461 #[pallet::extra_constants]462 impl<T: Config> Pallet<T> {463 464 pub fn collection_admins_limit() -> u32 {465 COLLECTION_ADMINS_LIMIT466 }467 }468469 #[pallet::genesis_config]470 pub struct GenesisConfig<T>(PhantomData<T>);471472 impl<T: Config> Default for GenesisConfig<T> {473 fn default() -> Self {474 Self(Default::default())475 }476 }477478 #[pallet::genesis_build]479 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {480 fn build(&self) {481 StorageVersion::new(1).put::<Pallet<T>>();482 }483 }484485 impl<T: Config> Pallet<T> {486 487 pub fn deposit_event(event: Event<T>) {488 let event = <T as Config>::RuntimeEvent::from(event);489 let event = event.into();490 <frame_system::Pallet<T>>::deposit_event(event)491 }492 }493494 #[pallet::event]495 pub enum Event<T: Config> {496 497 CollectionCreated(498 499 CollectionId,500 501 u8,502 503 T::AccountId,504 ),505506 507 CollectionDestroyed(508 509 CollectionId,510 ),511512 513 ItemCreated(514 515 CollectionId,516 517 TokenId,518 519 T::CrossAccountId,520 521 u128,522 ),523524 525 ItemDestroyed(526 527 CollectionId,528 529 TokenId,530 531 T::CrossAccountId,532 533 u128,534 ),535536 537 Transfer(538 539 CollectionId,540 541 TokenId,542 543 T::CrossAccountId,544 545 T::CrossAccountId,546 547 u128,548 ),549550 551 Approved(552 553 CollectionId,554 555 TokenId,556 557 T::CrossAccountId,558 559 T::CrossAccountId,560 561 u128,562 ),563564 565 ApprovedForAll(566 567 CollectionId,568 569 T::CrossAccountId,570 571 T::CrossAccountId,572 573 bool,574 ),575576 577 CollectionPropertySet(578 579 CollectionId,580 581 PropertyKey,582 ),583584 585 CollectionPropertyDeleted(586 587 CollectionId,588 589 PropertyKey,590 ),591592 593 TokenPropertySet(594 595 CollectionId,596 597 TokenId,598 599 PropertyKey,600 ),601602 603 TokenPropertyDeleted(604 605 CollectionId,606 607 TokenId,608 609 PropertyKey,610 ),611612 613 PropertyPermissionSet(614 615 CollectionId,616 617 PropertyKey,618 ),619620 621 AllowListAddressAdded(622 623 CollectionId,624 625 T::CrossAccountId,626 ),627628 629 AllowListAddressRemoved(630 631 CollectionId,632 633 T::CrossAccountId,634 ),635636 637 CollectionAdminAdded(638 639 CollectionId,640 641 T::CrossAccountId,642 ),643644 645 CollectionAdminRemoved(646 647 CollectionId,648 649 T::CrossAccountId,650 ),651652 653 CollectionLimitSet(654 655 CollectionId,656 ),657658 659 CollectionOwnerChanged(660 661 CollectionId,662 663 T::AccountId,664 ),665666 667 CollectionPermissionSet(668 669 CollectionId,670 ),671672 673 CollectionSponsorSet(674 675 CollectionId,676 677 T::AccountId,678 ),679680 681 SponsorshipConfirmed(682 683 CollectionId,684 685 T::AccountId,686 ),687688 689 CollectionSponsorRemoved(690 691 CollectionId,692 ),693 }694695 #[pallet::error]696 pub enum Error<T> {697 698 CollectionNotFound,699 700 MustBeTokenOwner,701 702 NoPermission,703 704 CantDestroyNotEmptyCollection,705 706 PublicMintingNotAllowed,707 708 AddressNotInAllowlist,709710 711 CollectionNameLimitExceeded,712 713 CollectionDescriptionLimitExceeded,714 715 CollectionTokenPrefixLimitExceeded,716 717 TotalCollectionsLimitExceeded,718 719 CollectionAdminCountExceeded,720 721 CollectionLimitBoundsExceeded,722 723 OwnerPermissionsCantBeReverted,724 725 TransferNotAllowed,726 727 AccountTokenLimitExceeded,728 729 CollectionTokenLimitExceeded,730 731 MetadataFlagFrozen,732733 734 TokenNotFound,735 736 TokenValueTooLow,737 738 ApprovedValueTooLow,739 740 CantApproveMoreThanOwned,741 742 AddressIsNotEthMirror,743744 745 AddressIsZero,746747 748 UnsupportedOperation,749750 751 NotSufficientFounds,752753 754 UserIsNotAllowedToNest,755 756 SourceCollectionIsNotAllowedToNest,757758 759 CollectionFieldSizeExceeded,760761 762 NoSpaceForProperty,763764 765 PropertyLimitReached,766767 768 PropertyKeyIsTooLong,769770 771 InvalidCharacterInPropertyKey,772773 774 EmptyPropertyKey,775776 777 CollectionIsExternal,778779 780 CollectionIsInternal,781782 783 ConfirmSponsorshipFail,784785 786 UserIsNotCollectionAdmin,787788 789 FungibleItemsHaveNoId,790791 792 NotFungibleDataUsedToMintFungibleCollectionToken,793 }794795 796 #[pallet::storage]797 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;798799 800 #[pallet::storage]801 pub type DestroyedCollectionCount<T> =802 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;803804 805 #[pallet::storage]806 pub type CollectionById<T> = StorageMap<807 Hasher = Blake2_128Concat,808 Key = CollectionId,809 Value = Collection<<T as frame_system::Config>::AccountId>,810 QueryKind = OptionQuery,811 >;812813 814 #[pallet::storage]815 #[pallet::getter(fn collection_properties)]816 pub type CollectionProperties<T> = StorageMap<817 Hasher = Blake2_128Concat,818 Key = CollectionId,819 Value = CollectionPropertiesT,820 QueryKind = ValueQuery,821 >;822823 824 #[pallet::storage]825 #[pallet::getter(fn property_permissions)]826 pub type CollectionPropertyPermissions<T> = StorageMap<827 Hasher = Blake2_128Concat,828 Key = CollectionId,829 Value = PropertiesPermissionMap,830 QueryKind = ValueQuery,831 >;832833 834 #[pallet::storage]835 pub type AdminAmount<T> = StorageMap<836 Hasher = Blake2_128Concat,837 Key = CollectionId,838 Value = u32,839 QueryKind = ValueQuery,840 >;841842 843 #[pallet::storage]844 pub type IsAdmin<T: Config> = StorageNMap<845 Key = (846 Key<Blake2_128Concat, CollectionId>,847 Key<Blake2_128Concat, T::CrossAccountId>,848 ),849 Value = bool,850 QueryKind = ValueQuery,851 >;852853 854 #[pallet::storage]855 pub type Allowlist<T: Config> = StorageNMap<856 Key = (857 Key<Blake2_128Concat, CollectionId>,858 Key<Blake2_128Concat, T::CrossAccountId>,859 ),860 Value = bool,861 QueryKind = ValueQuery,862 >;863864 865 #[pallet::storage]866 pub type DummyStorageValue<T: Config> = StorageValue<867 Value = (868 CollectionStats,869 CollectionId,870 TokenId,871 TokenChild,872 PhantomType<(873 TokenData<T::CrossAccountId>,874 RpcCollection<T::AccountId>,875 876 PovInfo,877 )>,878 ),879 QueryKind = OptionQuery,880 >;881}882883enum LazyValueState<'a, T> {884 Pending(Box<dyn FnOnce() -> T + 'a>),885 InProgress,886 Computed(T),887}888889890pub struct LazyValue<'a, T> {891 state: LazyValueState<'a, T>,892}893894impl<'a, T> LazyValue<'a, T> {895 896 pub fn new(f: impl FnOnce() -> T + 'a) -> Self {897 Self {898 state: LazyValueState::Pending(Box::new(f)),899 }900 }901902 903 pub fn value(&mut self) -> &T {904 self.force_value();905 self.value_mut()906 }907908 909 pub fn value_mut(&mut self) -> &mut T {910 self.force_value();911912 if let LazyValueState::Computed(value) = &mut self.state {913 value914 } else {915 unreachable!()916 }917 }918919 fn into_inner(mut self) -> T {920 self.force_value();921 if let LazyValueState::Computed(value) = self.state {922 value923 } else {924 unreachable!()925 }926 }927928 929 pub fn has_value(&self) -> bool {930 matches!(self.state, LazyValueState::Computed(_))931 }932933 fn force_value(&mut self) {934 use LazyValueState::*;935936 if self.has_value() {937 return;938 }939940 match sp_std::mem::replace(&mut self.state, InProgress) {941 Pending(f) => self.state = Computed(f()),942 _ => panic!("recursion isn't supported"),943 }944 }945}946947fn check_token_permissions<T: Config>(948 collection_admin_permitted: bool,949 token_owner_permitted: bool,950 is_collection_admin: &mut LazyValue<bool>,951 is_token_owner: &mut LazyValue<Result<bool, DispatchError>>,952 is_token_exist: &mut LazyValue<bool>,953) -> DispatchResult {954 if !(collection_admin_permitted && *is_collection_admin.value()955 || token_owner_permitted && (*is_token_owner.value())?)956 {957 fail!(<Error<T>>::NoPermission);958 }959960 let token_exist_due_to_owner_check_success =961 is_token_owner.has_value() && (*is_token_owner.value())?;962963 964 965 if !token_exist_due_to_owner_check_success {966 967 968 if !is_token_exist.value() {969 fail!(<Error<T>>::TokenNotFound);970 }971 }972973 Ok(())974}975976impl<T: Config> Pallet<T> {977 978 979 980 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {981 ensure!(982 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,983 <Error<T>>::AddressIsZero984 );985 Ok(())986 }987988 989 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {990 <IsAdmin<T>>::iter_prefix((collection,))991 .map(|(a, _)| a)992 .collect()993 }994995 996 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {997 <Allowlist<T>>::iter_prefix((collection,))998 .map(|(a, _)| a)999 .collect()1000 }10011002 1003 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {1004 <Allowlist<T>>::get((collection, user))1005 }10061007 1008 pub fn collection_stats() -> CollectionStats {1009 let created = <CreatedCollectionCount<T>>::get();1010 let destroyed = <DestroyedCollectionCount<T>>::get();1011 CollectionStats {1012 created: created.0,1013 destroyed: destroyed.0,1014 alive: created.0 - destroyed.0,1015 }1016 }10171018 1019 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {1020 let collection = <CollectionById<T>>::get(collection)?;1021 let limits = collection.limits;1022 let effective_limits = CollectionLimits {1023 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),1024 sponsored_data_size: Some(limits.sponsored_data_size()),1025 sponsored_data_rate_limit: Some(1026 limits1027 .sponsored_data_rate_limit1028 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),1029 ),1030 token_limit: Some(limits.token_limit()),1031 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(1032 match collection.mode {1033 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1034 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1035 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1036 },1037 )),1038 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),1039 owner_can_transfer: Some(limits.owner_can_transfer()),1040 owner_can_destroy: Some(limits.owner_can_destroy()),1041 transfers_enabled: Some(limits.transfers_enabled()),1042 };10431044 Some(effective_limits)1045 }10461047 1048 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1049 let Collection {1050 name,1051 description,1052 owner,1053 mode,1054 token_prefix,1055 sponsorship,1056 limits,1057 permissions,1058 flags,1059 } = <CollectionById<T>>::get(collection)?;10601061 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1062 .into_iter()1063 .map(|(key, permission)| PropertyKeyPermission { key, permission })1064 .collect();10651066 let properties = <CollectionProperties<T>>::get(collection)1067 .into_iter()1068 .map(|(key, value)| Property { key, value })1069 .collect();10701071 let permissions = CollectionPermissions {1072 access: Some(permissions.access()),1073 mint_mode: Some(permissions.mint_mode()),1074 nesting: Some(permissions.nesting().clone()),1075 };10761077 Some(RpcCollection {1078 name: name.into_inner(),1079 description: description.into_inner(),1080 owner,1081 mode,1082 token_prefix: token_prefix.into_inner(),1083 sponsorship,1084 limits,1085 permissions,1086 token_property_permissions,1087 properties,1088 read_only: flags.external,10891090 flags: RpcCollectionFlags {1091 erc721metadata: flags.erc721metadata,1092 },1093 })1094 }1095}10961097macro_rules! limit_default {1098 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1099 $(1100 if let Some($new) = $new.$field {1101 let $old = $old.$field($($arg)?);1102 let _ = $new;1103 let _ = $old;1104 $check1105 } else {1106 $new.$field = $old.$field1107 }1108 )*1109 }};1110}1111macro_rules! limit_default_clone {1112 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1113 $(1114 if let Some($new) = $new.$field.clone() {1115 let $old = $old.$field($($arg)?);1116 let _ = $new;1117 let _ = $old;1118 $check1119 } else {1120 $new.$field = $old.$field.clone()1121 }1122 )*1123 }};1124}11251126impl<T: Config> Pallet<T> {1127 1128 1129 1130 1131 1132 pub fn init_collection(1133 owner: T::CrossAccountId,1134 payer: Option<T::CrossAccountId>,1135 data: CreateCollectionData<T::CrossAccountId>,1136 ) -> Result<CollectionId, DispatchError> {1137 ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);11381139 1140 if let Some(payer) = payer {1141 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1142 imbalance.subsume(<T as Config>::Currency::deposit(1143 &T::TreasuryAccountId::get(),1144 T::CollectionCreationPrice::get(),1145 Precision::Exact,1146 )?);1147 let credit =1148 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1149 .map_err(|_| Error::<T>::NotSufficientFounds)?;11501151 debug_assert!(credit.peek().is_zero())1152 }11531154 Self::init_collection_internal(owner, data)1155 }11561157 fn init_collection_internal(1158 owner: T::CrossAccountId,1159 data: CreateCollectionData<T::CrossAccountId>,1160 ) -> Result<CollectionId, DispatchError> {1161 {1162 ensure!(1163 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1164 Error::<T>::CollectionTokenPrefixLimitExceeded1165 );1166 }11671168 let created_count = <CreatedCollectionCount<T>>::get()1169 .01170 .checked_add(1)1171 .ok_or(ArithmeticError::Overflow)?;1172 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1173 let id = CollectionId(created_count);11741175 1176 ensure!(1177 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1178 <Error<T>>::TotalCollectionsLimitExceeded1179 );11801181 11821183 let collection = Collection {1184 owner: owner.as_sub().clone(),1185 name: data.name,1186 mode: data.mode.clone(),1187 description: data.description,1188 token_prefix: data.token_prefix,1189 sponsorship: data1190 .pending_sponsor1191 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1192 .unwrap_or_default(),1193 limits: data1194 .limits1195 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1196 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1197 permissions: data1198 .permissions1199 .map(|permissions| {1200 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1201 })1202 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1203 flags: data.flags,1204 };12051206 let mut collection_properties = CollectionPropertiesT::new();1207 collection_properties1208 .try_set_from_iter(data.properties.into_iter())1209 .map_err(<Error<T>>::from)?;12101211 CollectionProperties::<T>::insert(id, collection_properties);12121213 let mut token_props_permissions = PropertiesPermissionMap::new();1214 token_props_permissions1215 .try_set_from_iter(data.token_property_permissions.into_iter())1216 .map_err(<Error<T>>::from)?;12171218 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);12191220 let mut admin_amount = 0u32;1221 for admin in data.admin_list.iter() {1222 if !<IsAdmin<T>>::get((id, admin)) {1223 <IsAdmin<T>>::insert((id, admin), true);1224 admin_amount = admin_amount1225 .checked_add(1)1226 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1227 }1228 }1229 ensure!(1230 admin_amount <= Self::collection_admins_limit(),1231 <Error<T>>::CollectionAdminCountExceeded,1232 );1233 <AdminAmount<T>>::insert(id, admin_amount);12341235 <CreatedCollectionCount<T>>::put(created_count);1236 <Pallet<T>>::deposit_event(Event::CollectionCreated(1237 id,1238 data.mode.id(),1239 owner.as_sub().clone(),1240 ));1241 <PalletEvm<T>>::deposit_log(1242 erc::CollectionHelpersEvents::CollectionCreated {1243 owner: *owner.as_eth(),1244 collection_id: eth::collection_id_to_address(id),1245 }1246 .to_log(T::ContractAddress::get()),1247 );1248 <CollectionById<T>>::insert(id, collection);1249 Ok(id)1250 }12511252 1253 1254 1255 1256 pub fn destroy_collection(1257 collection: CollectionHandle<T>,1258 sender: &T::CrossAccountId,1259 ) -> DispatchResult {1260 ensure!(1261 collection.limits.owner_can_destroy(),1262 <Error<T>>::NoPermission,1263 );1264 collection.check_is_owner(sender)?;12651266 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1267 .01268 .checked_add(1)1269 .ok_or(ArithmeticError::Overflow)?;12701271 12721273 <DestroyedCollectionCount<T>>::put(destroyed_collections);1274 <CollectionById<T>>::remove(collection.id);1275 <AdminAmount<T>>::remove(collection.id);1276 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1277 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1278 <CollectionProperties<T>>::remove(collection.id);12791280 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12811282 <PalletEvm<T>>::deposit_log(1283 erc::CollectionHelpersEvents::CollectionDestroyed {1284 collection_id: eth::collection_id_to_address(collection.id),1285 }1286 .to_log(T::ContractAddress::get()),1287 );1288 Ok(())1289 }12901291 1292 1293 1294 1295 1296 1297 1298 1299 #[transactional]1300 fn modify_collection_properties(1301 collection: &CollectionHandle<T>,1302 sender: &T::CrossAccountId,1303 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1304 ) -> DispatchResult {1305 collection.check_is_owner_or_admin(sender)?;13061307 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);13081309 for (key, value) in properties_updates {1310 match value {1311 Some(value) => {1312 stored_properties1313 .try_set(key.clone(), value)1314 .map_err(<Error<T>>::from)?;13151316 Self::deposit_event(Event::CollectionPropertySet(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 None => {1325 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13261327 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1328 <PalletEvm<T>>::deposit_log(1329 erc::CollectionHelpersEvents::CollectionChanged {1330 collection_id: eth::collection_id_to_address(collection.id),1331 }1332 .to_log(T::ContractAddress::get()),1333 );1334 }1335 }1336 }13371338 <CollectionProperties<T>>::set(collection.id, stored_properties);13391340 Ok(())1341 }13421343 1344 1345 1346 1347 1348 1349 pub fn set_allowance_for_all(1350 collection: &CollectionHandle<T>,1351 owner: &T::CrossAccountId,1352 operator: &T::CrossAccountId,1353 approve: bool,1354 set_allowance: impl FnOnce(),1355 log: evm_coder::ethereum::Log,1356 ) -> DispatchResult {1357 if collection.permissions.access() == AccessMode::AllowList {1358 collection.check_allowlist(owner)?;1359 collection.check_allowlist(operator)?;1360 }13611362 Self::ensure_correct_receiver(operator)?;13631364 set_allowance();13651366 <PalletEvm<T>>::deposit_log(log);1367 Self::deposit_event(Event::ApprovedForAll(1368 collection.id,1369 owner.clone(),1370 operator.clone(),1371 approve,1372 ));1373 Ok(())1374 }13751376 1377 1378 1379 1380 1381 pub fn set_collection_property(1382 collection: &CollectionHandle<T>,1383 sender: &T::CrossAccountId,1384 property: Property,1385 ) -> DispatchResult {1386 Self::set_collection_properties(collection, sender, [property].into_iter())1387 }13881389 1390 1391 1392 1393 1394 1395 pub fn set_scoped_collection_property(1396 collection_id: CollectionId,1397 scope: PropertyScope,1398 property: Property,1399 ) -> DispatchResult {1400 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1401 properties.try_scoped_set(scope, property.key, property.value)1402 })1403 .map_err(<Error<T>>::from)?;14041405 Ok(())1406 }14071408 1409 1410 1411 1412 1413 1414 pub fn set_scoped_collection_properties(1415 collection_id: CollectionId,1416 scope: PropertyScope,1417 properties: impl Iterator<Item = Property>,1418 ) -> DispatchResult {1419 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1420 stored_properties.try_scoped_set_from_iter(scope, properties)1421 })1422 .map_err(<Error<T>>::from)?;14231424 Ok(())1425 }14261427 1428 1429 1430 1431 1432 pub fn set_collection_properties(1433 collection: &CollectionHandle<T>,1434 sender: &T::CrossAccountId,1435 properties: impl Iterator<Item = Property>,1436 ) -> DispatchResult {1437 Self::modify_collection_properties(1438 collection,1439 sender,1440 properties.map(|property| (property.key, Some(property.value))),1441 )1442 }14431444 1445 1446 1447 1448 1449 pub fn delete_collection_property(1450 collection: &CollectionHandle<T>,1451 sender: &T::CrossAccountId,1452 property_key: PropertyKey,1453 ) -> DispatchResult {1454 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1455 }14561457 1458 1459 1460 1461 1462 pub fn delete_collection_properties(1463 collection: &CollectionHandle<T>,1464 sender: &T::CrossAccountId,1465 property_keys: impl Iterator<Item = PropertyKey>,1466 ) -> DispatchResult {1467 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1468 }14691470 1471 1472 1473 1474 1475 1476 pub fn set_property_permission_unchecked(1477 collection: CollectionId,1478 property_permission: PropertyKeyPermission,1479 ) -> DispatchResult {1480 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1481 permissions.try_set(property_permission.key, property_permission.permission)1482 })1483 .map_err(<Error<T>>::from)?;1484 Ok(())1485 }14861487 1488 1489 1490 1491 1492 pub fn set_property_permission(1493 collection: &CollectionHandle<T>,1494 sender: &T::CrossAccountId,1495 property_permission: PropertyKeyPermission,1496 ) -> DispatchResult {1497 Self::set_scoped_property_permission(1498 collection,1499 sender,1500 PropertyScope::None,1501 property_permission,1502 )1503 }15041505 1506 1507 1508 1509 1510 1511 pub fn set_scoped_property_permission(1512 collection: &CollectionHandle<T>,1513 sender: &T::CrossAccountId,1514 scope: PropertyScope,1515 property_permission: PropertyKeyPermission,1516 ) -> DispatchResult {1517 collection.check_is_owner_or_admin(sender)?;15181519 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1520 let current_permission = all_permissions.get(&property_permission.key);1521 if matches![1522 current_permission,1523 Some(PropertyPermission { mutable: false, .. })1524 ] {1525 return Err(<Error<T>>::NoPermission.into());1526 }15271528 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1529 let property_permission = property_permission.clone();1530 permissions.try_scoped_set(1531 scope,1532 property_permission.key,1533 property_permission.permission,1534 )1535 })1536 .map_err(<Error<T>>::from)?;15371538 Self::deposit_event(Event::PropertyPermissionSet(1539 collection.id,1540 property_permission.key,1541 ));1542 <PalletEvm<T>>::deposit_log(1543 erc::CollectionHelpersEvents::CollectionChanged {1544 collection_id: eth::collection_id_to_address(collection.id),1545 }1546 .to_log(T::ContractAddress::get()),1547 );15481549 Ok(())1550 }15511552 1553 1554 1555 1556 1557 #[transactional]1558 pub fn set_token_property_permissions(1559 collection: &CollectionHandle<T>,1560 sender: &T::CrossAccountId,1561 property_permissions: Vec<PropertyKeyPermission>,1562 ) -> DispatchResult {1563 Self::set_scoped_token_property_permissions(1564 collection,1565 sender,1566 PropertyScope::None,1567 property_permissions,1568 )1569 }15701571 1572 1573 1574 1575 1576 1577 #[transactional]1578 pub fn set_scoped_token_property_permissions(1579 collection: &CollectionHandle<T>,1580 sender: &T::CrossAccountId,1581 scope: PropertyScope,1582 property_permissions: Vec<PropertyKeyPermission>,1583 ) -> DispatchResult {1584 for prop_pemission in property_permissions {1585 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1586 }15871588 Ok(())1589 }15901591 1592 pub fn get_collection_property(1593 collection_id: CollectionId,1594 key: &PropertyKey,1595 ) -> Option<PropertyValue> {1596 Self::collection_properties(collection_id).get(key).cloned()1597 }15981599 1600 pub fn bytes_keys_to_property_keys(1601 keys: Vec<Vec<u8>>,1602 ) -> Result<Vec<PropertyKey>, DispatchError> {1603 keys.into_iter()1604 .map(|key| -> Result<PropertyKey, DispatchError> {1605 key.try_into()1606 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1607 })1608 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1609 }16101611 1612 pub fn filter_collection_properties(1613 collection_id: CollectionId,1614 keys: Option<Vec<PropertyKey>>,1615 ) -> Result<Vec<Property>, DispatchError> {1616 let properties = Self::collection_properties(collection_id);16171618 let properties = keys1619 .map(|keys| {1620 keys.into_iter()1621 .filter_map(|key| {1622 properties.get(&key).map(|value| Property {1623 key,1624 value: value.clone(),1625 })1626 })1627 .collect()1628 })1629 .unwrap_or_else(|| {1630 properties1631 .into_iter()1632 .map(|(key, value)| Property { key, value })1633 .collect()1634 });16351636 Ok(properties)1637 }16381639 1640 pub fn filter_property_permissions(1641 collection_id: CollectionId,1642 keys: Option<Vec<PropertyKey>>,1643 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1644 let permissions = Self::property_permissions(collection_id);16451646 let key_permissions = keys1647 .map(|keys| {1648 keys.into_iter()1649 .filter_map(|key| {1650 permissions1651 .get(&key)1652 .map(|permission| PropertyKeyPermission {1653 key,1654 permission: permission.clone(),1655 })1656 })1657 .collect()1658 })1659 .unwrap_or_else(|| {1660 permissions1661 .into_iter()1662 .map(|(key, permission)| PropertyKeyPermission { key, permission })1663 .collect()1664 });16651666 Ok(key_permissions)1667 }16681669 1670 1671 1672 pub fn toggle_allowlist(1673 collection: &CollectionHandle<T>,1674 sender: &T::CrossAccountId,1675 user: &T::CrossAccountId,1676 allowed: bool,1677 ) -> DispatchResult {1678 collection.check_is_owner_or_admin(sender)?;16791680 16811682 if allowed {1683 <Allowlist<T>>::insert((collection.id, user), true);1684 Self::deposit_event(Event::<T>::AllowListAddressAdded(1685 collection.id,1686 user.clone(),1687 ));1688 } else {1689 <Allowlist<T>>::remove((collection.id, user));1690 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1691 collection.id,1692 user.clone(),1693 ));1694 }16951696 <PalletEvm<T>>::deposit_log(1697 erc::CollectionHelpersEvents::CollectionChanged {1698 collection_id: eth::collection_id_to_address(collection.id),1699 }1700 .to_log(T::ContractAddress::get()),1701 );17021703 Ok(())1704 }17051706 1707 1708 1709 pub fn toggle_admin(1710 collection: &CollectionHandle<T>,1711 sender: &T::CrossAccountId,1712 user: &T::CrossAccountId,1713 admin: bool,1714 ) -> DispatchResult {1715 collection.check_is_internal()?;1716 collection.check_is_owner(sender)?;17171718 let is_admin = <IsAdmin<T>>::get((collection.id, user));1719 if is_admin == admin {1720 if admin {1721 return Ok(());1722 } else {1723 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1724 }1725 }1726 let amount = <AdminAmount<T>>::get(collection.id);17271728 17291730 if admin {1731 let amount = amount1732 .checked_add(1)1733 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1734 ensure!(1735 amount <= Self::collection_admins_limit(),1736 <Error<T>>::CollectionAdminCountExceeded,1737 );17381739 <AdminAmount<T>>::insert(collection.id, amount);1740 <IsAdmin<T>>::insert((collection.id, user), true);17411742 Self::deposit_event(Event::<T>::CollectionAdminAdded(1743 collection.id,1744 user.clone(),1745 ));1746 } else {1747 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1748 <IsAdmin<T>>::remove((collection.id, user));17491750 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1751 collection.id,1752 user.clone(),1753 ));1754 }17551756 <PalletEvm<T>>::deposit_log(1757 erc::CollectionHelpersEvents::CollectionChanged {1758 collection_id: eth::collection_id_to_address(collection.id),1759 }1760 .to_log(T::ContractAddress::get()),1761 );17621763 Ok(())1764 }17651766 1767 pub fn update_limits(1768 user: &T::CrossAccountId,1769 collection: &mut CollectionHandle<T>,1770 new_limit: CollectionLimits,1771 ) -> DispatchResult {1772 collection.check_is_internal()?;1773 collection.check_is_owner_or_admin(user)?;17741775 collection.limits =1776 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17771778 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1779 <PalletEvm<T>>::deposit_log(1780 erc::CollectionHelpersEvents::CollectionChanged {1781 collection_id: eth::collection_id_to_address(collection.id),1782 }1783 .to_log(T::ContractAddress::get()),1784 );17851786 collection.save()1787 }17881789 1790 fn clamp_limits(1791 mode: CollectionMode,1792 old_limit: &CollectionLimits,1793 mut new_limit: CollectionLimits,1794 ) -> Result<CollectionLimits, DispatchError> {1795 let limits = old_limit;1796 limit_default!(old_limit, new_limit,1797 account_token_ownership_limit => ensure!(1798 new_limit <= MAX_TOKEN_OWNERSHIP,1799 <Error<T>>::CollectionLimitBoundsExceeded,1800 ),1801 sponsored_data_size => ensure!(1802 new_limit <= CUSTOM_DATA_LIMIT,1803 <Error<T>>::CollectionLimitBoundsExceeded,1804 ),18051806 sponsored_data_rate_limit => {},1807 token_limit => ensure!(1808 old_limit >= new_limit && new_limit > 0,1809 <Error<T>>::CollectionTokenLimitExceeded1810 ),18111812 sponsor_transfer_timeout(match mode {1813 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1814 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1815 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1816 }) => ensure!(1817 new_limit <= MAX_SPONSOR_TIMEOUT,1818 <Error<T>>::CollectionLimitBoundsExceeded,1819 ),1820 sponsor_approve_timeout => {},1821 owner_can_transfer => ensure!(1822 !limits.owner_can_transfer_instaled() ||1823 old_limit || !new_limit,1824 <Error<T>>::OwnerPermissionsCantBeReverted,1825 ),1826 owner_can_destroy => ensure!(1827 old_limit || !new_limit,1828 <Error<T>>::OwnerPermissionsCantBeReverted,1829 ),1830 transfers_enabled => {},1831 );1832 Ok(new_limit)1833 }18341835 1836 pub fn update_permissions(1837 user: &T::CrossAccountId,1838 collection: &mut CollectionHandle<T>,1839 new_permission: CollectionPermissions,1840 ) -> DispatchResult {1841 collection.check_is_internal()?;1842 collection.check_is_owner_or_admin(user)?;1843 collection.permissions = Self::clamp_permissions(1844 collection.mode.clone(),1845 &collection.permissions,1846 new_permission,1847 )?;18481849 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1850 <PalletEvm<T>>::deposit_log(1851 erc::CollectionHelpersEvents::CollectionChanged {1852 collection_id: eth::collection_id_to_address(collection.id),1853 }1854 .to_log(T::ContractAddress::get()),1855 );18561857 collection.save()1858 }18591860 1861 fn clamp_permissions(1862 _mode: CollectionMode,1863 old_permission: &CollectionPermissions,1864 mut new_permission: CollectionPermissions,1865 ) -> Result<CollectionPermissions, DispatchError> {1866 limit_default_clone!(old_permission, new_permission,1867 access => {},1868 mint_mode => {},1869 nesting => { },1870 );1871 Ok(new_permission)1872 }18731874 1875 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1876 CollectionProperties::<T>::mutate(collection_id, |properties| {1877 properties.recompute_consumed_space();1878 });18791880 Ok(())1881 }1882}188318841885#[macro_export]1886macro_rules! unsupported {1887 ($runtime:path) => {1888 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1889 };1890}189118921893pub trait CommonWeightInfo<CrossAccountId> {1894 1895 fn create_item(data: &CreateItemData) -> Weight {1896 Self::create_multiple_items(from_ref(data))1897 }18981899 1900 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19011902 1903 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19041905 1906 fn burn_item() -> Weight;19071908 1909 1910 1911 fn set_collection_properties(amount: u32) -> Weight;19121913 1914 1915 1916 fn delete_collection_properties(amount: u32) -> Weight {1917 Self::set_collection_properties(amount)1918 }19191920 1921 1922 1923 fn set_token_properties(amount: u32) -> Weight;19241925 1926 1927 1928 fn delete_token_properties(amount: u32) -> Weight {1929 Self::set_token_properties(amount)1930 }19311932 1933 1934 1935 fn set_token_property_permissions(amount: u32) -> Weight;19361937 1938 fn transfer() -> Weight;19391940 1941 fn approve() -> Weight;19421943 1944 fn approve_from() -> Weight;19451946 1947 fn transfer_from() -> Weight;19481949 1950 fn burn_from() -> Weight;19511952 1953 fn set_allowance_for_all() -> Weight;19541955 1956 fn force_repair_item() -> Weight;1957}195819591960pub trait RefungibleExtensionsWeightInfo {1961 1962 fn repartition() -> Weight;1963}196419651966196719681969pub trait CommonCollectionOperations<T: Config> {1970 1971 1972 1973 1974 1975 1976 fn create_item(1977 &self,1978 sender: T::CrossAccountId,1979 to: T::CrossAccountId,1980 data: CreateItemData,1981 nesting_budget: &dyn Budget,1982 ) -> DispatchResultWithPostInfo;19831984 1985 1986 1987 1988 1989 1990 fn create_multiple_items(1991 &self,1992 sender: T::CrossAccountId,1993 to: T::CrossAccountId,1994 data: Vec<CreateItemData>,1995 nesting_budget: &dyn Budget,1996 ) -> DispatchResultWithPostInfo;19971998 1999 2000 2001 2002 2003 2004 fn create_multiple_items_ex(2005 &self,2006 sender: T::CrossAccountId,2007 data: CreateItemExData<T::CrossAccountId>,2008 nesting_budget: &dyn Budget,2009 ) -> DispatchResultWithPostInfo;20102011 2012 2013 2014 2015 2016 fn burn_item(2017 &self,2018 sender: T::CrossAccountId,2019 token: TokenId,2020 amount: u128,2021 ) -> DispatchResultWithPostInfo;20222023 2024 2025 2026 2027 fn set_collection_properties(2028 &self,2029 sender: T::CrossAccountId,2030 properties: Vec<Property>,2031 ) -> DispatchResultWithPostInfo;20322033 2034 2035 2036 2037 fn delete_collection_properties(2038 &self,2039 sender: &T::CrossAccountId,2040 property_keys: Vec<PropertyKey>,2041 ) -> DispatchResultWithPostInfo;20422043 2044 2045 2046 2047 2048 2049 2050 2051 2052 fn set_token_properties(2053 &self,2054 sender: T::CrossAccountId,2055 token_id: TokenId,2056 properties: Vec<Property>,2057 budget: &dyn Budget,2058 ) -> DispatchResultWithPostInfo;20592060 2061 2062 2063 2064 2065 2066 2067 2068 2069 fn delete_token_properties(2070 &self,2071 sender: T::CrossAccountId,2072 token_id: TokenId,2073 property_keys: Vec<PropertyKey>,2074 budget: &dyn Budget,2075 ) -> DispatchResultWithPostInfo;20762077 2078 2079 2080 fn get_token_properties_raw(&self, token_id: TokenId) -> Option<TokenProperties>;20812082 2083 2084 2085 2086 fn set_token_properties_raw(&self, token_id: TokenId, map: TokenProperties);20872088 2089 2090 2091 2092 2093 2094 fn set_token_property_permissions(2095 &self,2096 sender: &T::CrossAccountId,2097 property_permissions: Vec<PropertyKeyPermission>,2098 ) -> DispatchResultWithPostInfo;20992100 2101 2102 2103 2104 2105 2106 2107 fn transfer(2108 &self,2109 sender: T::CrossAccountId,2110 to: T::CrossAccountId,2111 token: TokenId,2112 amount: u128,2113 budget: &dyn Budget,2114 ) -> DispatchResultWithPostInfo;21152116 2117 2118 2119 2120 2121 2122 fn approve(2123 &self,2124 sender: T::CrossAccountId,2125 spender: T::CrossAccountId,2126 token: TokenId,2127 amount: u128,2128 ) -> DispatchResultWithPostInfo;21292130 2131 2132 2133 2134 2135 2136 2137 fn approve_from(2138 &self,2139 sender: T::CrossAccountId,2140 from: T::CrossAccountId,2141 to: T::CrossAccountId,2142 token: TokenId,2143 amount: u128,2144 ) -> DispatchResultWithPostInfo;21452146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 fn transfer_from(2157 &self,2158 sender: T::CrossAccountId,2159 from: T::CrossAccountId,2160 to: T::CrossAccountId,2161 token: TokenId,2162 amount: u128,2163 budget: &dyn Budget,2164 ) -> DispatchResultWithPostInfo;21652166 2167 2168 2169 2170 2171 2172 2173 2174 2175 fn burn_from(2176 &self,2177 sender: T::CrossAccountId,2178 from: T::CrossAccountId,2179 token: TokenId,2180 amount: u128,2181 budget: &dyn Budget,2182 ) -> DispatchResultWithPostInfo;21832184 2185 2186 2187 2188 2189 2190 fn check_nesting(2191 &self,2192 sender: &T::CrossAccountId,2193 from: (CollectionId, TokenId),2194 under: TokenId,2195 budget: &dyn Budget,2196 ) -> DispatchResult;21972198 2199 2200 2201 2202 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22032204 2205 2206 2207 2208 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22092210 2211 2212 2213 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22142215 2216 fn collection_tokens(&self) -> Vec<TokenId>;22172218 2219 2220 2221 fn token_exists(&self, token: TokenId) -> bool;22222223 2224 fn last_token_id(&self) -> TokenId;22252226 2227 2228 2229 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22302231 2232 2233 2234 2235 2236 fn check_token_indirect_owner(2237 &self,2238 token: TokenId,2239 maybe_owner: &T::CrossAccountId,2240 nesting_budget: &dyn Budget,2241 ) -> Result<bool, DispatchError>;22422243 2244 2245 2246 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22472248 2249 2250 2251 2252 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22532254 2255 2256 2257 2258 2259 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22602261 2262 fn total_supply(&self) -> u32;22632264 2265 2266 2267 fn account_balance(&self, account: T::CrossAccountId) -> u32;22682269 2270 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22712272 2273 fn total_pieces(&self, token: TokenId) -> Option<u128>;22742275 2276 2277 2278 2279 2280 fn allowance(2281 &self,2282 sender: T::CrossAccountId,2283 spender: T::CrossAccountId,2284 token: TokenId,2285 ) -> u128;22862287 2288 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {2289 None2290 }22912292 2293 fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {2294 None2295 }22962297 2298 2299 2300 2301 fn set_allowance_for_all(2302 &self,2303 owner: T::CrossAccountId,2304 operator: T::CrossAccountId,2305 approve: bool,2306 ) -> DispatchResultWithPostInfo;23072308 2309 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23102311 2312 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2313}231423152316pub trait RefungibleExtensions<T>2317where2318 T: Config,2319{2320 2321 2322 2323 2324 2325 2326 2327 fn repartition(2328 &self,2329 sender: &T::CrossAccountId,2330 token: TokenId,2331 amount: u128,2332 ) -> DispatchResultWithPostInfo;2333}233423352336pub trait XcmExtensions<T>2337where2338 T: Config,2339{2340 2341 fn token_has_children(&self, _token: TokenId) -> bool {2342 false2343 }23442345 2346 2347 2348 #[transactional]2349 fn create_item(2350 &self,2351 depositor: &T::CrossAccountId,2352 to: T::CrossAccountId,2353 data: CreateItemData,2354 nesting_budget: &dyn Budget,2355 ) -> Result<TokenId, DispatchError> {2356 if T::CrossTokenAddressMapping::is_token_address(&to) {2357 return unsupported!(T);2358 }23592360 self.create_item_internal(depositor, to, data, nesting_budget)2361 }23622363 2364 fn create_item_internal(2365 &self,2366 depositor: &T::CrossAccountId,2367 to: T::CrossAccountId,2368 data: CreateItemData,2369 nesting_budget: &dyn Budget,2370 ) -> Result<TokenId, DispatchError>;23712372 2373 2374 2375 #[transactional]2376 fn transfer_item(2377 &self,2378 depositor: &T::CrossAccountId,2379 from: &T::CrossAccountId,2380 to: &T::CrossAccountId,2381 token: TokenId,2382 amount: u128,2383 nesting_budget: &dyn Budget,2384 ) -> DispatchResult {2385 if T::CrossTokenAddressMapping::is_token_address(&to) {2386 return unsupported!(T);2387 }23882389 self.transfer_item_internal(depositor, from, to, token, amount, nesting_budget)2390 }23912392 2393 fn transfer_item_internal(2394 &self,2395 depositor: &T::CrossAccountId,2396 from: &T::CrossAccountId,2397 to: &T::CrossAccountId,2398 token: TokenId,2399 amount: u128,2400 nesting_budget: &dyn Budget,2401 ) -> DispatchResult;24022403 2404 #[transactional]2405 fn burn_item(&self, from: T::CrossAccountId, token: TokenId, amount: u128) -> DispatchResult {2406 self.burn_item_internal(from, token, amount)2407 }24082409 2410 fn burn_item_internal(2411 &self,2412 from: T::CrossAccountId,2413 token: TokenId,2414 amount: u128,2415 ) -> DispatchResult;2416}24172418241924202421pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2422 let post_info = PostDispatchInfo {2423 actual_weight: Some(weight),2424 pays_fee: Pays::Yes,2425 };2426 match res {2427 Ok(()) => Ok(post_info),2428 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2429 }2430}24312432impl<T: Config> From<PropertiesError> for Error<T> {2433 fn from(error: PropertiesError) -> Self {2434 match error {2435 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2436 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2437 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2438 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2439 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2440 }2441 }2442}24432444244524462447244824492450pub struct PropertyWriter<'a, WriterVariant, T, Handle> {2451 collection: &'a Handle,2452 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2453 _phantom: PhantomData<(T, WriterVariant)>,2454}24552456impl<'a, T, Handle, WriterVariant> PropertyWriter<'a, WriterVariant, T, Handle>2457where2458 T: Config,2459 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2460{2461 fn internal_write_token_properties(2462 &mut self,2463 token_id: TokenId,2464 mut token_lazy_info: PropertyWriterLazyTokenInfo,2465 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2466 log: evm_coder::ethereum::Log,2467 ) -> DispatchResult {2468 for (key, value) in properties_updates {2469 let permission = self2470 .collection_lazy_info2471 .property_permissions2472 .value()2473 .get(&key)2474 .cloned()2475 .unwrap_or_else(PropertyPermission::none);24762477 match permission {2478 PropertyPermission { mutable: false, .. }2479 if token_lazy_info2480 .stored_properties2481 .value()2482 .get(&key)2483 .is_some() =>2484 {2485 return Err(<Error<T>>::NoPermission.into());2486 }24872488 PropertyPermission {2489 collection_admin,2490 token_owner,2491 ..2492 } => check_token_permissions::<T>(2493 collection_admin,2494 token_owner,2495 &mut self.collection_lazy_info.is_collection_admin,2496 &mut token_lazy_info.is_token_owner,2497 &mut token_lazy_info.is_token_exist,2498 )?,2499 }25002501 match value {2502 Some(value) => {2503 token_lazy_info2504 .stored_properties2505 .value_mut()2506 .try_set(key.clone(), value)2507 .map_err(<Error<T>>::from)?;25082509 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2510 self.collection.id,2511 token_id,2512 key,2513 ));2514 }2515 None => {2516 token_lazy_info2517 .stored_properties2518 .value_mut()2519 .remove(&key)2520 .map_err(<Error<T>>::from)?;25212522 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2523 self.collection.id,2524 token_id,2525 key,2526 ));2527 }2528 }2529 }25302531 let properties_changed = token_lazy_info.stored_properties.has_value();2532 if properties_changed {2533 <PalletEvm<T>>::deposit_log(log);25342535 self.collection2536 .set_token_properties_raw(token_id, token_lazy_info.stored_properties.into_inner());2537 }25382539 Ok(())2540 }2541}25422543254425452546pub struct PropertyWriterLazyCollectionInfo<'a> {2547 is_collection_admin: LazyValue<'a, bool>,2548 property_permissions: LazyValue<'a, PropertiesPermissionMap>,2549}2550255125522553pub struct PropertyWriterLazyTokenInfo<'a> {2554 is_token_exist: LazyValue<'a, bool>,2555 is_token_owner: LazyValue<'a, Result<bool, DispatchError>>,2556 stored_properties: LazyValue<'a, TokenProperties>,2557}25582559impl<'a> PropertyWriterLazyTokenInfo<'a> {2560 2561 pub fn new(2562 check_token_exist: impl FnOnce() -> bool + 'a,2563 check_token_owner: impl FnOnce() -> Result<bool, DispatchError> + 'a,2564 get_token_properties: impl FnOnce() -> TokenProperties + 'a,2565 ) -> Self {2566 Self {2567 is_token_exist: LazyValue::new(check_token_exist),2568 is_token_owner: LazyValue::new(check_token_owner),2569 stored_properties: LazyValue::new(get_token_properties),2570 }2571 }2572}2573257425752576pub struct NewTokenPropertyWriter<T>(PhantomData<T>);2577impl<T: Config> NewTokenPropertyWriter<T> {2578 2579 pub fn new<'a, Handle>(2580 collection: &'a Handle,2581 sender: &'a T::CrossAccountId,2582 ) -> PropertyWriter<'a, Self, T, Handle>2583 where2584 T: Config,2585 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2586 {2587 PropertyWriter {2588 collection,2589 collection_lazy_info: PropertyWriterLazyCollectionInfo {2590 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2591 property_permissions: LazyValue::new(|| {2592 <Pallet<T>>::property_permissions(collection.id)2593 }),2594 },2595 _phantom: PhantomData,2596 }2597 }2598}25992600impl<'a, T, Handle> PropertyWriter<'a, NewTokenPropertyWriter<T>, T, Handle>2601where2602 T: Config,2603 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2604{2605 2606 pub fn write_token_properties(2607 &mut self,2608 mint_target_is_sender: bool,2609 token_id: TokenId,2610 properties_updates: impl Iterator<Item = Property>,2611 log: evm_coder::ethereum::Log,2612 ) -> DispatchResult {2613 let check_token_exist = || {2614 debug_assert!(self.collection.token_exists(token_id));2615 true2616 };26172618 let check_token_owner = || Ok(mint_target_is_sender);26192620 let get_token_properties = || {2621 debug_assert!(self.collection.get_token_properties_raw(token_id).is_none());2622 TokenProperties::new()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.map(|p| (p.key, Some(p.value))),2633 log,2634 )2635 }2636}2637263826392640pub struct ExistingTokenPropertyWriter<T>(PhantomData<T>);2641impl<T: Config> ExistingTokenPropertyWriter<T> {2642 2643 pub fn new<'a, Handle>(2644 collection: &'a Handle,2645 sender: &'a T::CrossAccountId,2646 ) -> PropertyWriter<'a, Self, T, Handle>2647 where2648 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2649 {2650 PropertyWriter {2651 collection,2652 collection_lazy_info: PropertyWriterLazyCollectionInfo {2653 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2654 property_permissions: LazyValue::new(|| {2655 <Pallet<T>>::property_permissions(collection.id)2656 }),2657 },2658 _phantom: PhantomData,2659 }2660 }2661}26622663impl<'a, T, Handle> PropertyWriter<'a, ExistingTokenPropertyWriter<T>, T, Handle>2664where2665 T: Config,2666 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2667{2668 2669 pub fn write_token_properties(2670 &mut self,2671 sender: &T::CrossAccountId,2672 token_id: TokenId,2673 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2674 nesting_budget: &dyn Budget,2675 log: evm_coder::ethereum::Log,2676 ) -> DispatchResult {2677 let check_token_exist = || self.collection.token_exists(token_id);2678 let check_token_owner = || {2679 self.collection2680 .check_token_indirect_owner(token_id, sender, nesting_budget)2681 };2682 let get_token_properties = || {2683 self.collection2684 .get_token_properties_raw(token_id)2685 .unwrap_or_default()2686 };26872688 self.internal_write_token_properties(2689 token_id,2690 PropertyWriterLazyTokenInfo::new(2691 check_token_exist,2692 check_token_owner,2693 get_token_properties,2694 ),2695 properties_updates,2696 log,2697 )2698 }2699}2700270127022703#[cfg(feature = "runtime-benchmarks")]2704pub struct BenchmarkPropertyWriter<T>(PhantomData<T>);27052706#[cfg(feature = "runtime-benchmarks")]2707impl<T: Config> BenchmarkPropertyWriter<T> {2708 2709 pub fn new<'a, Handle>(2710 collection: &'a Handle,2711 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2712 ) -> PropertyWriter<'a, Self, T, Handle>2713 where2714 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2715 {2716 PropertyWriter {2717 collection,2718 collection_lazy_info,2719 _phantom: PhantomData,2720 }2721 }27222723 2724 pub fn load_collection_info<Handle>(2725 collection_handle: &Handle,2726 sender: &T::CrossAccountId,2727 ) -> PropertyWriterLazyCollectionInfo<'static>2728 where2729 Handle: Deref<Target = CollectionHandle<T>>,2730 {2731 let is_collection_admin = collection_handle.is_owner_or_admin(sender);2732 let property_permissions = <Pallet<T>>::property_permissions(collection_handle.id);27332734 PropertyWriterLazyCollectionInfo {2735 is_collection_admin: LazyValue::new(move || is_collection_admin),2736 property_permissions: LazyValue::new(move || property_permissions),2737 }2738 }27392740 2741 pub fn load_token_properties<Handle>(2742 collection: &Handle,2743 token_id: TokenId,2744 ) -> PropertyWriterLazyTokenInfo2745 where2746 Handle: CommonCollectionOperations<T>,2747 {2748 let stored_properties = collection2749 .get_token_properties_raw(token_id)2750 .unwrap_or_default();27512752 PropertyWriterLazyTokenInfo {2753 is_token_exist: LazyValue::new(|| true),2754 is_token_owner: LazyValue::new(|| Ok(true)),2755 stored_properties: LazyValue::new(move || stored_properties),2756 }2757 }2758}27592760#[cfg(feature = "runtime-benchmarks")]2761impl<'a, T, Handle> PropertyWriter<'a, BenchmarkPropertyWriter<T>, T, Handle>2762where2763 T: Config,2764 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2765{2766 2767 pub fn write_token_properties(2768 &mut self,2769 token_id: TokenId,2770 properties_updates: impl Iterator<Item = Property>,2771 log: evm_coder::ethereum::Log,2772 ) -> DispatchResult {2773 let check_token_exist = || true;2774 let check_token_owner = || Ok(true);2775 let get_token_properties = TokenProperties::new;27762777 self.internal_write_token_properties(2778 token_id,2779 PropertyWriterLazyTokenInfo::new(2780 check_token_exist,2781 check_token_owner,2782 get_token_properties,2783 ),2784 properties_updates.map(|p| (p.key, Some(p.value))),2785 log,2786 )2787 }2788}278927902791279227932794pub fn write_token_properties_total_weight<T: Config, I: Fn(u32) -> Weight>(2795 properties_nums: impl Iterator<Item = u32>,2796 per_token_weight: I,2797) -> Weight {2798 let mut weight = properties_nums2799 .filter_map(|properties_num| {2800 if properties_num > 0 {2801 Some(per_token_weight(properties_num))2802 } else {2803 None2804 }2805 })2806 .fold(Weight::zero(), |a, b| a.saturating_add(b));28072808 if !weight.is_zero() {2809 2810 2811 28122813 weight = weight.saturating_add(<SelfWeightOf<T>>::property_writer_load_collection_info());2814 }28152816 weight2817}28182819#[cfg(any(feature = "tests", test))]2820#[allow(missing_docs)]2821pub mod tests {2822 use crate::{Config, DispatchError, DispatchResult, LazyValue};28232824 const fn to_bool(u: u8) -> bool {2825 u != 02826 }28272828 #[derive(Debug)]2829 pub struct TestCase {2830 pub collection_admin: bool,2831 pub is_collection_admin: bool,2832 pub token_owner: bool,2833 pub is_token_owner: bool,2834 pub no_permission: bool,2835 }28362837 impl TestCase {2838 const fn new(2839 collection_admin: u8,2840 is_collection_admin: u8,2841 token_owner: u8,2842 is_token_owner: u8,2843 no_permission: u8,2844 ) -> Self {2845 Self {2846 collection_admin: to_bool(collection_admin),2847 is_collection_admin: to_bool(is_collection_admin),2848 token_owner: to_bool(token_owner),2849 is_token_owner: to_bool(is_token_owner),2850 no_permission: to_bool(no_permission),2851 }2852 }2853 }28542855 #[rustfmt::skip]2856 pub const TABLE: [TestCase; 16] = [2857 2858 2859 2860 2861 2862 TestCase::new(0, 0, 0, 0, 1),2863 TestCase::new(0, 0, 0, 1, 1),2864 TestCase::new(0, 0, 1, 0, 1),2865 TestCase::new(0, 0, 1, 1, 0),2866 TestCase::new(0, 1, 0, 0, 1),2867 TestCase::new(0, 1, 0, 1, 1),2868 TestCase::new(0, 1, 1, 0, 1),2869 TestCase::new(0, 1, 1, 1, 0),2870 TestCase::new(1, 0, 0, 0, 1),2871 TestCase::new(1, 0, 0, 1, 1),2872 TestCase::new(1, 0, 1, 0, 1),2873 TestCase::new(1, 0, 1, 1, 0),2874 TestCase::new(1, 1, 0, 0, 0),2875 TestCase::new(1, 1, 0, 1, 0),2876 TestCase::new(1, 1, 1, 0, 0),2877 TestCase::new(1, 1, 1, 1, 0),2878 ];28792880 pub fn check_token_permissions<T: Config>(2881 collection_admin_permitted: bool,2882 token_owner_permitted: bool,2883 is_collection_admin: &mut LazyValue<bool>,2884 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>>,2885 check_token_existence: &mut LazyValue<bool>,2886 ) -> DispatchResult {2887 crate::check_token_permissions::<T>(2888 collection_admin_permitted,2889 token_owner_permitted,2890 is_collection_admin,2891 check_token_ownership,2892 check_token_existence,2893 )2894 }2895}