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 foreign: flags.foreign,1092 erc721metadata: flags.erc721metadata,1093 },1094 })1095 }1096}10971098macro_rules! limit_default {1099 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1100 $(1101 if let Some($new) = $new.$field {1102 let $old = $old.$field($($arg)?);1103 let _ = $new;1104 let _ = $old;1105 $check1106 } else {1107 $new.$field = $old.$field1108 }1109 )*1110 }};1111}1112macro_rules! limit_default_clone {1113 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1114 $(1115 if let Some($new) = $new.$field.clone() {1116 let $old = $old.$field($($arg)?);1117 let _ = $new;1118 let _ = $old;1119 $check1120 } else {1121 $new.$field = $old.$field.clone()1122 }1123 )*1124 }};1125}11261127impl<T: Config> Pallet<T> {1128 1129 1130 1131 1132 1133 1134 pub fn init_collection(1135 owner: T::CrossAccountId,1136 payer: Option<T::CrossAccountId>,1137 is_special_collection: bool,1138 data: CreateCollectionData<T::CrossAccountId>,1139 ) -> Result<CollectionId, DispatchError> {1140 if !is_special_collection {1141 ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);1142 }11431144 1145 if let Some(payer) = payer {1146 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1147 imbalance.subsume(<T as Config>::Currency::deposit(1148 &T::TreasuryAccountId::get(),1149 T::CollectionCreationPrice::get(),1150 Precision::Exact,1151 )?);1152 let credit =1153 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1154 .map_err(|_| Error::<T>::NotSufficientFounds)?;11551156 debug_assert!(credit.peek().is_zero())1157 }11581159 {1160 ensure!(1161 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1162 Error::<T>::CollectionTokenPrefixLimitExceeded1163 );1164 }11651166 let created_count = <CreatedCollectionCount<T>>::get()1167 .01168 .checked_add(1)1169 .ok_or(ArithmeticError::Overflow)?;1170 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1171 let id = CollectionId(created_count);11721173 1174 ensure!(1175 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1176 <Error<T>>::TotalCollectionsLimitExceeded1177 );11781179 11801181 let collection = Collection {1182 owner: owner.as_sub().clone(),1183 name: data.name,1184 mode: data.mode.clone(),1185 description: data.description,1186 token_prefix: data.token_prefix,1187 sponsorship: data1188 .pending_sponsor1189 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1190 .unwrap_or_default(),1191 limits: data1192 .limits1193 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1194 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1195 permissions: data1196 .permissions1197 .map(|permissions| {1198 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1199 })1200 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1201 flags: data.flags,1202 };12031204 let mut collection_properties = CollectionPropertiesT::new();1205 collection_properties1206 .try_set_from_iter(data.properties.into_iter())1207 .map_err(<Error<T>>::from)?;12081209 CollectionProperties::<T>::insert(id, collection_properties);12101211 let mut token_props_permissions = PropertiesPermissionMap::new();1212 token_props_permissions1213 .try_set_from_iter(data.token_property_permissions.into_iter())1214 .map_err(<Error<T>>::from)?;12151216 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);12171218 let mut admin_amount = 0u32;1219 for admin in data.admin_list.iter() {1220 if !<IsAdmin<T>>::get((id, admin)) {1221 <IsAdmin<T>>::insert((id, admin), true);1222 admin_amount = admin_amount1223 .checked_add(1)1224 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1225 }1226 }1227 ensure!(1228 admin_amount <= Self::collection_admins_limit(),1229 <Error<T>>::CollectionAdminCountExceeded,1230 );1231 <AdminAmount<T>>::insert(id, admin_amount);12321233 <CreatedCollectionCount<T>>::put(created_count);1234 <Pallet<T>>::deposit_event(Event::CollectionCreated(1235 id,1236 data.mode.id(),1237 owner.as_sub().clone(),1238 ));1239 <PalletEvm<T>>::deposit_log(1240 erc::CollectionHelpersEvents::CollectionCreated {1241 owner: *owner.as_eth(),1242 collection_id: eth::collection_id_to_address(id),1243 }1244 .to_log(T::ContractAddress::get()),1245 );1246 <CollectionById<T>>::insert(id, collection);1247 Ok(id)1248 }12491250 1251 1252 1253 1254 pub fn destroy_collection(1255 collection: CollectionHandle<T>,1256 sender: &T::CrossAccountId,1257 ) -> DispatchResult {1258 ensure!(1259 collection.limits.owner_can_destroy(),1260 <Error<T>>::NoPermission,1261 );1262 collection.check_is_owner(sender)?;12631264 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1265 .01266 .checked_add(1)1267 .ok_or(ArithmeticError::Overflow)?;12681269 12701271 <DestroyedCollectionCount<T>>::put(destroyed_collections);1272 <CollectionById<T>>::remove(collection.id);1273 <AdminAmount<T>>::remove(collection.id);1274 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1275 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1276 <CollectionProperties<T>>::remove(collection.id);12771278 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12791280 <PalletEvm<T>>::deposit_log(1281 erc::CollectionHelpersEvents::CollectionDestroyed {1282 collection_id: eth::collection_id_to_address(collection.id),1283 }1284 .to_log(T::ContractAddress::get()),1285 );1286 Ok(())1287 }12881289 1290 1291 1292 1293 1294 1295 1296 1297 #[transactional]1298 fn modify_collection_properties(1299 collection: &CollectionHandle<T>,1300 sender: &T::CrossAccountId,1301 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1302 ) -> DispatchResult {1303 collection.check_is_owner_or_admin(sender)?;13041305 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);13061307 for (key, value) in properties_updates {1308 match value {1309 Some(value) => {1310 stored_properties1311 .try_set(key.clone(), value)1312 .map_err(<Error<T>>::from)?;13131314 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1315 <PalletEvm<T>>::deposit_log(1316 erc::CollectionHelpersEvents::CollectionChanged {1317 collection_id: eth::collection_id_to_address(collection.id),1318 }1319 .to_log(T::ContractAddress::get()),1320 );1321 }1322 None => {1323 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13241325 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1326 <PalletEvm<T>>::deposit_log(1327 erc::CollectionHelpersEvents::CollectionChanged {1328 collection_id: eth::collection_id_to_address(collection.id),1329 }1330 .to_log(T::ContractAddress::get()),1331 );1332 }1333 }1334 }13351336 <CollectionProperties<T>>::set(collection.id, stored_properties);13371338 Ok(())1339 }13401341 1342 1343 1344 1345 1346 1347 pub fn set_allowance_for_all(1348 collection: &CollectionHandle<T>,1349 owner: &T::CrossAccountId,1350 operator: &T::CrossAccountId,1351 approve: bool,1352 set_allowance: impl FnOnce(),1353 log: evm_coder::ethereum::Log,1354 ) -> DispatchResult {1355 if collection.permissions.access() == AccessMode::AllowList {1356 collection.check_allowlist(owner)?;1357 collection.check_allowlist(operator)?;1358 }13591360 Self::ensure_correct_receiver(operator)?;13611362 set_allowance();13631364 <PalletEvm<T>>::deposit_log(log);1365 Self::deposit_event(Event::ApprovedForAll(1366 collection.id,1367 owner.clone(),1368 operator.clone(),1369 approve,1370 ));1371 Ok(())1372 }13731374 1375 1376 1377 1378 1379 pub fn set_collection_property(1380 collection: &CollectionHandle<T>,1381 sender: &T::CrossAccountId,1382 property: Property,1383 ) -> DispatchResult {1384 Self::set_collection_properties(collection, sender, [property].into_iter())1385 }13861387 1388 1389 1390 1391 1392 1393 pub fn set_scoped_collection_property(1394 collection_id: CollectionId,1395 scope: PropertyScope,1396 property: Property,1397 ) -> DispatchResult {1398 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1399 properties.try_scoped_set(scope, property.key, property.value)1400 })1401 .map_err(<Error<T>>::from)?;14021403 Ok(())1404 }14051406 1407 1408 1409 1410 1411 1412 pub fn set_scoped_collection_properties(1413 collection_id: CollectionId,1414 scope: PropertyScope,1415 properties: impl Iterator<Item = Property>,1416 ) -> DispatchResult {1417 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1418 stored_properties.try_scoped_set_from_iter(scope, properties)1419 })1420 .map_err(<Error<T>>::from)?;14211422 Ok(())1423 }14241425 1426 1427 1428 1429 1430 pub fn set_collection_properties(1431 collection: &CollectionHandle<T>,1432 sender: &T::CrossAccountId,1433 properties: impl Iterator<Item = Property>,1434 ) -> DispatchResult {1435 Self::modify_collection_properties(1436 collection,1437 sender,1438 properties.map(|property| (property.key, Some(property.value))),1439 )1440 }14411442 1443 1444 1445 1446 1447 pub fn delete_collection_property(1448 collection: &CollectionHandle<T>,1449 sender: &T::CrossAccountId,1450 property_key: PropertyKey,1451 ) -> DispatchResult {1452 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1453 }14541455 1456 1457 1458 1459 1460 pub fn delete_collection_properties(1461 collection: &CollectionHandle<T>,1462 sender: &T::CrossAccountId,1463 property_keys: impl Iterator<Item = PropertyKey>,1464 ) -> DispatchResult {1465 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1466 }14671468 1469 1470 1471 1472 1473 1474 pub fn set_property_permission_unchecked(1475 collection: CollectionId,1476 property_permission: PropertyKeyPermission,1477 ) -> DispatchResult {1478 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1479 permissions.try_set(property_permission.key, property_permission.permission)1480 })1481 .map_err(<Error<T>>::from)?;1482 Ok(())1483 }14841485 1486 1487 1488 1489 1490 pub fn set_property_permission(1491 collection: &CollectionHandle<T>,1492 sender: &T::CrossAccountId,1493 property_permission: PropertyKeyPermission,1494 ) -> DispatchResult {1495 Self::set_scoped_property_permission(1496 collection,1497 sender,1498 PropertyScope::None,1499 property_permission,1500 )1501 }15021503 1504 1505 1506 1507 1508 1509 pub fn set_scoped_property_permission(1510 collection: &CollectionHandle<T>,1511 sender: &T::CrossAccountId,1512 scope: PropertyScope,1513 property_permission: PropertyKeyPermission,1514 ) -> DispatchResult {1515 collection.check_is_owner_or_admin(sender)?;15161517 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1518 let current_permission = all_permissions.get(&property_permission.key);1519 if matches![1520 current_permission,1521 Some(PropertyPermission { mutable: false, .. })1522 ] {1523 return Err(<Error<T>>::NoPermission.into());1524 }15251526 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1527 let property_permission = property_permission.clone();1528 permissions.try_scoped_set(1529 scope,1530 property_permission.key,1531 property_permission.permission,1532 )1533 })1534 .map_err(<Error<T>>::from)?;15351536 Self::deposit_event(Event::PropertyPermissionSet(1537 collection.id,1538 property_permission.key,1539 ));1540 <PalletEvm<T>>::deposit_log(1541 erc::CollectionHelpersEvents::CollectionChanged {1542 collection_id: eth::collection_id_to_address(collection.id),1543 }1544 .to_log(T::ContractAddress::get()),1545 );15461547 Ok(())1548 }15491550 1551 1552 1553 1554 1555 #[transactional]1556 pub fn set_token_property_permissions(1557 collection: &CollectionHandle<T>,1558 sender: &T::CrossAccountId,1559 property_permissions: Vec<PropertyKeyPermission>,1560 ) -> DispatchResult {1561 Self::set_scoped_token_property_permissions(1562 collection,1563 sender,1564 PropertyScope::None,1565 property_permissions,1566 )1567 }15681569 1570 1571 1572 1573 1574 1575 #[transactional]1576 pub fn set_scoped_token_property_permissions(1577 collection: &CollectionHandle<T>,1578 sender: &T::CrossAccountId,1579 scope: PropertyScope,1580 property_permissions: Vec<PropertyKeyPermission>,1581 ) -> DispatchResult {1582 for prop_pemission in property_permissions {1583 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1584 }15851586 Ok(())1587 }15881589 1590 pub fn get_collection_property(1591 collection_id: CollectionId,1592 key: &PropertyKey,1593 ) -> Option<PropertyValue> {1594 Self::collection_properties(collection_id).get(key).cloned()1595 }15961597 1598 pub fn bytes_keys_to_property_keys(1599 keys: Vec<Vec<u8>>,1600 ) -> Result<Vec<PropertyKey>, DispatchError> {1601 keys.into_iter()1602 .map(|key| -> Result<PropertyKey, DispatchError> {1603 key.try_into()1604 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1605 })1606 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1607 }16081609 1610 pub fn filter_collection_properties(1611 collection_id: CollectionId,1612 keys: Option<Vec<PropertyKey>>,1613 ) -> Result<Vec<Property>, DispatchError> {1614 let properties = Self::collection_properties(collection_id);16151616 let properties = keys1617 .map(|keys| {1618 keys.into_iter()1619 .filter_map(|key| {1620 properties.get(&key).map(|value| Property {1621 key,1622 value: value.clone(),1623 })1624 })1625 .collect()1626 })1627 .unwrap_or_else(|| {1628 properties1629 .into_iter()1630 .map(|(key, value)| Property { key, value })1631 .collect()1632 });16331634 Ok(properties)1635 }16361637 1638 pub fn filter_property_permissions(1639 collection_id: CollectionId,1640 keys: Option<Vec<PropertyKey>>,1641 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1642 let permissions = Self::property_permissions(collection_id);16431644 let key_permissions = keys1645 .map(|keys| {1646 keys.into_iter()1647 .filter_map(|key| {1648 permissions1649 .get(&key)1650 .map(|permission| PropertyKeyPermission {1651 key,1652 permission: permission.clone(),1653 })1654 })1655 .collect()1656 })1657 .unwrap_or_else(|| {1658 permissions1659 .into_iter()1660 .map(|(key, permission)| PropertyKeyPermission { key, permission })1661 .collect()1662 });16631664 Ok(key_permissions)1665 }16661667 1668 1669 1670 pub fn toggle_allowlist(1671 collection: &CollectionHandle<T>,1672 sender: &T::CrossAccountId,1673 user: &T::CrossAccountId,1674 allowed: bool,1675 ) -> DispatchResult {1676 collection.check_is_owner_or_admin(sender)?;16771678 16791680 if allowed {1681 <Allowlist<T>>::insert((collection.id, user), true);1682 Self::deposit_event(Event::<T>::AllowListAddressAdded(1683 collection.id,1684 user.clone(),1685 ));1686 } else {1687 <Allowlist<T>>::remove((collection.id, user));1688 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1689 collection.id,1690 user.clone(),1691 ));1692 }16931694 <PalletEvm<T>>::deposit_log(1695 erc::CollectionHelpersEvents::CollectionChanged {1696 collection_id: eth::collection_id_to_address(collection.id),1697 }1698 .to_log(T::ContractAddress::get()),1699 );17001701 Ok(())1702 }17031704 1705 1706 1707 pub fn toggle_admin(1708 collection: &CollectionHandle<T>,1709 sender: &T::CrossAccountId,1710 user: &T::CrossAccountId,1711 admin: bool,1712 ) -> DispatchResult {1713 collection.check_is_internal()?;1714 collection.check_is_owner(sender)?;17151716 let is_admin = <IsAdmin<T>>::get((collection.id, user));1717 if is_admin == admin {1718 if admin {1719 return Ok(());1720 } else {1721 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1722 }1723 }1724 let amount = <AdminAmount<T>>::get(collection.id);17251726 17271728 if admin {1729 let amount = amount1730 .checked_add(1)1731 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1732 ensure!(1733 amount <= Self::collection_admins_limit(),1734 <Error<T>>::CollectionAdminCountExceeded,1735 );17361737 <AdminAmount<T>>::insert(collection.id, amount);1738 <IsAdmin<T>>::insert((collection.id, user), true);17391740 Self::deposit_event(Event::<T>::CollectionAdminAdded(1741 collection.id,1742 user.clone(),1743 ));1744 } else {1745 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1746 <IsAdmin<T>>::remove((collection.id, user));17471748 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1749 collection.id,1750 user.clone(),1751 ));1752 }17531754 <PalletEvm<T>>::deposit_log(1755 erc::CollectionHelpersEvents::CollectionChanged {1756 collection_id: eth::collection_id_to_address(collection.id),1757 }1758 .to_log(T::ContractAddress::get()),1759 );17601761 Ok(())1762 }17631764 1765 pub fn update_limits(1766 user: &T::CrossAccountId,1767 collection: &mut CollectionHandle<T>,1768 new_limit: CollectionLimits,1769 ) -> DispatchResult {1770 collection.check_is_internal()?;1771 collection.check_is_owner_or_admin(user)?;17721773 collection.limits =1774 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17751776 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1777 <PalletEvm<T>>::deposit_log(1778 erc::CollectionHelpersEvents::CollectionChanged {1779 collection_id: eth::collection_id_to_address(collection.id),1780 }1781 .to_log(T::ContractAddress::get()),1782 );17831784 collection.save()1785 }17861787 1788 fn clamp_limits(1789 mode: CollectionMode,1790 old_limit: &CollectionLimits,1791 mut new_limit: CollectionLimits,1792 ) -> Result<CollectionLimits, DispatchError> {1793 let limits = old_limit;1794 limit_default!(old_limit, new_limit,1795 account_token_ownership_limit => ensure!(1796 new_limit <= MAX_TOKEN_OWNERSHIP,1797 <Error<T>>::CollectionLimitBoundsExceeded,1798 ),1799 sponsored_data_size => ensure!(1800 new_limit <= CUSTOM_DATA_LIMIT,1801 <Error<T>>::CollectionLimitBoundsExceeded,1802 ),18031804 sponsored_data_rate_limit => {},1805 token_limit => ensure!(1806 old_limit >= new_limit && new_limit > 0,1807 <Error<T>>::CollectionTokenLimitExceeded1808 ),18091810 sponsor_transfer_timeout(match mode {1811 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1812 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1813 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1814 }) => ensure!(1815 new_limit <= MAX_SPONSOR_TIMEOUT,1816 <Error<T>>::CollectionLimitBoundsExceeded,1817 ),1818 sponsor_approve_timeout => {},1819 owner_can_transfer => ensure!(1820 !limits.owner_can_transfer_instaled() ||1821 old_limit || !new_limit,1822 <Error<T>>::OwnerPermissionsCantBeReverted,1823 ),1824 owner_can_destroy => ensure!(1825 old_limit || !new_limit,1826 <Error<T>>::OwnerPermissionsCantBeReverted,1827 ),1828 transfers_enabled => {},1829 );1830 Ok(new_limit)1831 }18321833 1834 pub fn update_permissions(1835 user: &T::CrossAccountId,1836 collection: &mut CollectionHandle<T>,1837 new_permission: CollectionPermissions,1838 ) -> DispatchResult {1839 collection.check_is_internal()?;1840 collection.check_is_owner_or_admin(user)?;1841 collection.permissions = Self::clamp_permissions(1842 collection.mode.clone(),1843 &collection.permissions,1844 new_permission,1845 )?;18461847 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1848 <PalletEvm<T>>::deposit_log(1849 erc::CollectionHelpersEvents::CollectionChanged {1850 collection_id: eth::collection_id_to_address(collection.id),1851 }1852 .to_log(T::ContractAddress::get()),1853 );18541855 collection.save()1856 }18571858 1859 fn clamp_permissions(1860 _mode: CollectionMode,1861 old_permission: &CollectionPermissions,1862 mut new_permission: CollectionPermissions,1863 ) -> Result<CollectionPermissions, DispatchError> {1864 limit_default_clone!(old_permission, new_permission,1865 access => {},1866 mint_mode => {},1867 nesting => { },1868 );1869 Ok(new_permission)1870 }18711872 1873 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1874 CollectionProperties::<T>::mutate(collection_id, |properties| {1875 properties.recompute_consumed_space();1876 });18771878 Ok(())1879 }1880}188118821883#[macro_export]1884macro_rules! unsupported {1885 ($runtime:path) => {1886 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1887 };1888}188918901891pub trait CommonWeightInfo<CrossAccountId> {1892 1893 fn create_item(data: &CreateItemData) -> Weight {1894 Self::create_multiple_items(from_ref(data))1895 }18961897 1898 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18991900 1901 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19021903 1904 fn burn_item() -> Weight;19051906 1907 1908 1909 fn set_collection_properties(amount: u32) -> Weight;19101911 1912 1913 1914 fn delete_collection_properties(amount: u32) -> Weight {1915 Self::set_collection_properties(amount)1916 }19171918 1919 1920 1921 fn set_token_properties(amount: u32) -> Weight;19221923 1924 1925 1926 fn delete_token_properties(amount: u32) -> Weight {1927 Self::set_token_properties(amount)1928 }19291930 1931 1932 1933 fn set_token_property_permissions(amount: u32) -> Weight;19341935 1936 fn transfer() -> Weight;19371938 1939 fn approve() -> Weight;19401941 1942 fn approve_from() -> Weight;19431944 1945 fn transfer_from() -> Weight;19461947 1948 fn burn_from() -> Weight;19491950 1951 fn set_allowance_for_all() -> Weight;19521953 1954 fn force_repair_item() -> Weight;1955}195619571958pub trait RefungibleExtensionsWeightInfo {1959 1960 fn repartition() -> Weight;1961}196219631964196519661967pub trait CommonCollectionOperations<T: Config> {1968 1969 1970 1971 1972 1973 1974 fn create_item(1975 &self,1976 sender: T::CrossAccountId,1977 to: T::CrossAccountId,1978 data: CreateItemData,1979 nesting_budget: &dyn Budget,1980 ) -> DispatchResultWithPostInfo;19811982 1983 1984 1985 1986 1987 1988 fn create_multiple_items(1989 &self,1990 sender: T::CrossAccountId,1991 to: T::CrossAccountId,1992 data: Vec<CreateItemData>,1993 nesting_budget: &dyn Budget,1994 ) -> DispatchResultWithPostInfo;19951996 1997 1998 1999 2000 2001 2002 fn create_multiple_items_ex(2003 &self,2004 sender: T::CrossAccountId,2005 data: CreateItemExData<T::CrossAccountId>,2006 nesting_budget: &dyn Budget,2007 ) -> DispatchResultWithPostInfo;20082009 2010 2011 2012 2013 2014 fn burn_item(2015 &self,2016 sender: T::CrossAccountId,2017 token: TokenId,2018 amount: u128,2019 ) -> DispatchResultWithPostInfo;20202021 2022 2023 2024 2025 fn set_collection_properties(2026 &self,2027 sender: T::CrossAccountId,2028 properties: Vec<Property>,2029 ) -> DispatchResultWithPostInfo;20302031 2032 2033 2034 2035 fn delete_collection_properties(2036 &self,2037 sender: &T::CrossAccountId,2038 property_keys: Vec<PropertyKey>,2039 ) -> DispatchResultWithPostInfo;20402041 2042 2043 2044 2045 2046 2047 2048 2049 2050 fn set_token_properties(2051 &self,2052 sender: T::CrossAccountId,2053 token_id: TokenId,2054 properties: Vec<Property>,2055 budget: &dyn Budget,2056 ) -> DispatchResultWithPostInfo;20572058 2059 2060 2061 2062 2063 2064 2065 2066 2067 fn delete_token_properties(2068 &self,2069 sender: T::CrossAccountId,2070 token_id: TokenId,2071 property_keys: Vec<PropertyKey>,2072 budget: &dyn Budget,2073 ) -> DispatchResultWithPostInfo;20742075 2076 2077 2078 fn get_token_properties_raw(&self, token_id: TokenId) -> Option<TokenProperties>;20792080 2081 2082 2083 2084 fn set_token_properties_raw(&self, token_id: TokenId, map: TokenProperties);20852086 2087 2088 2089 2090 2091 2092 fn set_token_property_permissions(2093 &self,2094 sender: &T::CrossAccountId,2095 property_permissions: Vec<PropertyKeyPermission>,2096 ) -> DispatchResultWithPostInfo;20972098 2099 2100 2101 2102 2103 2104 2105 fn transfer(2106 &self,2107 sender: T::CrossAccountId,2108 to: T::CrossAccountId,2109 token: TokenId,2110 amount: u128,2111 budget: &dyn Budget,2112 ) -> DispatchResultWithPostInfo;21132114 2115 2116 2117 2118 2119 2120 fn approve(2121 &self,2122 sender: T::CrossAccountId,2123 spender: T::CrossAccountId,2124 token: TokenId,2125 amount: u128,2126 ) -> DispatchResultWithPostInfo;21272128 2129 2130 2131 2132 2133 2134 2135 fn approve_from(2136 &self,2137 sender: T::CrossAccountId,2138 from: T::CrossAccountId,2139 to: T::CrossAccountId,2140 token: TokenId,2141 amount: u128,2142 ) -> DispatchResultWithPostInfo;21432144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 fn transfer_from(2155 &self,2156 sender: T::CrossAccountId,2157 from: T::CrossAccountId,2158 to: T::CrossAccountId,2159 token: TokenId,2160 amount: u128,2161 budget: &dyn Budget,2162 ) -> DispatchResultWithPostInfo;21632164 2165 2166 2167 2168 2169 2170 2171 2172 2173 fn burn_from(2174 &self,2175 sender: T::CrossAccountId,2176 from: T::CrossAccountId,2177 token: TokenId,2178 amount: u128,2179 budget: &dyn Budget,2180 ) -> DispatchResultWithPostInfo;21812182 2183 2184 2185 2186 2187 2188 fn check_nesting(2189 &self,2190 sender: &T::CrossAccountId,2191 from: (CollectionId, TokenId),2192 under: TokenId,2193 budget: &dyn Budget,2194 ) -> DispatchResult;21952196 2197 2198 2199 2200 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22012202 2203 2204 2205 2206 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22072208 2209 2210 2211 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22122213 2214 fn collection_tokens(&self) -> Vec<TokenId>;22152216 2217 2218 2219 fn token_exists(&self, token: TokenId) -> bool;22202221 2222 fn last_token_id(&self) -> TokenId;22232224 2225 2226 2227 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22282229 2230 2231 2232 2233 2234 fn check_token_indirect_owner(2235 &self,2236 token: TokenId,2237 maybe_owner: &T::CrossAccountId,2238 nesting_budget: &dyn Budget,2239 ) -> Result<bool, DispatchError>;22402241 2242 2243 2244 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22452246 2247 2248 2249 2250 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22512252 2253 2254 2255 2256 2257 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22582259 2260 fn total_supply(&self) -> u32;22612262 2263 2264 2265 fn account_balance(&self, account: T::CrossAccountId) -> u32;22662267 2268 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22692270 2271 fn total_pieces(&self, token: TokenId) -> Option<u128>;22722273 2274 2275 2276 2277 2278 fn allowance(2279 &self,2280 sender: T::CrossAccountId,2281 spender: T::CrossAccountId,2282 token: TokenId,2283 ) -> u128;22842285 2286 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {2287 None2288 }22892290 2291 fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {2292 None2293 }22942295 2296 2297 2298 2299 fn set_allowance_for_all(2300 &self,2301 owner: T::CrossAccountId,2302 operator: T::CrossAccountId,2303 approve: bool,2304 ) -> DispatchResultWithPostInfo;23052306 2307 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23082309 2310 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2311}231223132314pub trait RefungibleExtensions<T>2315where2316 T: Config,2317{2318 2319 2320 2321 2322 2323 2324 2325 fn repartition(2326 &self,2327 sender: &T::CrossAccountId,2328 token: TokenId,2329 amount: u128,2330 ) -> DispatchResultWithPostInfo;2331}233223332334pub trait XcmExtensions<T>2335where2336 T: Config,2337{2338 2339 fn token_has_children(&self, _token: TokenId) -> bool {2340 false2341 }23422343 2344 2345 2346 #[transactional]2347 fn create_item(2348 &self,2349 depositor: &T::CrossAccountId,2350 to: T::CrossAccountId,2351 data: CreateItemData,2352 nesting_budget: &dyn Budget,2353 ) -> Result<TokenId, DispatchError> {2354 if T::CrossTokenAddressMapping::is_token_address(&to) {2355 return unsupported!(T);2356 }23572358 self.create_item_internal(depositor, to, data, nesting_budget)2359 }23602361 2362 fn create_item_internal(2363 &self,2364 depositor: &T::CrossAccountId,2365 to: T::CrossAccountId,2366 data: CreateItemData,2367 nesting_budget: &dyn Budget,2368 ) -> Result<TokenId, DispatchError>;23692370 2371 2372 2373 #[transactional]2374 fn transfer_item(2375 &self,2376 depositor: &T::CrossAccountId,2377 from: &T::CrossAccountId,2378 to: &T::CrossAccountId,2379 token: TokenId,2380 amount: u128,2381 nesting_budget: &dyn Budget,2382 ) -> DispatchResult {2383 if T::CrossTokenAddressMapping::is_token_address(to) {2384 return unsupported!(T);2385 }23862387 if self.token_has_children(token) {2388 return unsupported!(T);2389 }23902391 self.transfer_item_internal(depositor, from, to, token, amount, nesting_budget)2392 }23932394 2395 fn transfer_item_internal(2396 &self,2397 depositor: &T::CrossAccountId,2398 from: &T::CrossAccountId,2399 to: &T::CrossAccountId,2400 token: TokenId,2401 amount: u128,2402 nesting_budget: &dyn Budget,2403 ) -> DispatchResult;24042405 2406 #[transactional]2407 fn burn_item(&self, from: T::CrossAccountId, token: TokenId, amount: u128) -> DispatchResult {2408 self.burn_item_internal(from, token, amount)2409 }24102411 2412 fn burn_item_internal(2413 &self,2414 from: T::CrossAccountId,2415 token: TokenId,2416 amount: u128,2417 ) -> DispatchResult;2418}24192420242124222423pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2424 let post_info = PostDispatchInfo {2425 actual_weight: Some(weight),2426 pays_fee: Pays::Yes,2427 };2428 match res {2429 Ok(()) => Ok(post_info),2430 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2431 }2432}24332434impl<T: Config> From<PropertiesError> for Error<T> {2435 fn from(error: PropertiesError) -> Self {2436 match error {2437 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2438 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2439 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2440 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2441 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2442 }2443 }2444}24452446244724482449245024512452pub struct PropertyWriter<'a, WriterVariant, T, Handle> {2453 collection: &'a Handle,2454 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2455 _phantom: PhantomData<(T, WriterVariant)>,2456}24572458impl<'a, T, Handle, WriterVariant> PropertyWriter<'a, WriterVariant, T, Handle>2459where2460 T: Config,2461 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2462{2463 fn internal_write_token_properties(2464 &mut self,2465 token_id: TokenId,2466 mut token_lazy_info: PropertyWriterLazyTokenInfo,2467 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2468 log: evm_coder::ethereum::Log,2469 ) -> DispatchResult {2470 for (key, value) in properties_updates {2471 let permission = self2472 .collection_lazy_info2473 .property_permissions2474 .value()2475 .get(&key)2476 .cloned()2477 .unwrap_or_else(PropertyPermission::none);24782479 match permission {2480 PropertyPermission { mutable: false, .. }2481 if token_lazy_info2482 .stored_properties2483 .value()2484 .get(&key)2485 .is_some() =>2486 {2487 return Err(<Error<T>>::NoPermission.into());2488 }24892490 PropertyPermission {2491 collection_admin,2492 token_owner,2493 ..2494 } => check_token_permissions::<T>(2495 collection_admin,2496 token_owner,2497 &mut self.collection_lazy_info.is_collection_admin,2498 &mut token_lazy_info.is_token_owner,2499 &mut token_lazy_info.is_token_exist,2500 )?,2501 }25022503 match value {2504 Some(value) => {2505 token_lazy_info2506 .stored_properties2507 .value_mut()2508 .try_set(key.clone(), value)2509 .map_err(<Error<T>>::from)?;25102511 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2512 self.collection.id,2513 token_id,2514 key,2515 ));2516 }2517 None => {2518 token_lazy_info2519 .stored_properties2520 .value_mut()2521 .remove(&key)2522 .map_err(<Error<T>>::from)?;25232524 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2525 self.collection.id,2526 token_id,2527 key,2528 ));2529 }2530 }2531 }25322533 let properties_changed = token_lazy_info.stored_properties.has_value();2534 if properties_changed {2535 <PalletEvm<T>>::deposit_log(log);25362537 self.collection2538 .set_token_properties_raw(token_id, token_lazy_info.stored_properties.into_inner());2539 }25402541 Ok(())2542 }2543}25442545254625472548pub struct PropertyWriterLazyCollectionInfo<'a> {2549 is_collection_admin: LazyValue<'a, bool>,2550 property_permissions: LazyValue<'a, PropertiesPermissionMap>,2551}2552255325542555pub struct PropertyWriterLazyTokenInfo<'a> {2556 is_token_exist: LazyValue<'a, bool>,2557 is_token_owner: LazyValue<'a, Result<bool, DispatchError>>,2558 stored_properties: LazyValue<'a, TokenProperties>,2559}25602561impl<'a> PropertyWriterLazyTokenInfo<'a> {2562 2563 pub fn new(2564 check_token_exist: impl FnOnce() -> bool + 'a,2565 check_token_owner: impl FnOnce() -> Result<bool, DispatchError> + 'a,2566 get_token_properties: impl FnOnce() -> TokenProperties + 'a,2567 ) -> Self {2568 Self {2569 is_token_exist: LazyValue::new(check_token_exist),2570 is_token_owner: LazyValue::new(check_token_owner),2571 stored_properties: LazyValue::new(get_token_properties),2572 }2573 }2574}2575257625772578pub struct NewTokenPropertyWriter<T>(PhantomData<T>);2579impl<T: Config> NewTokenPropertyWriter<T> {2580 2581 pub fn new<'a, Handle>(2582 collection: &'a Handle,2583 sender: &'a T::CrossAccountId,2584 ) -> PropertyWriter<'a, Self, T, Handle>2585 where2586 T: Config,2587 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2588 {2589 PropertyWriter {2590 collection,2591 collection_lazy_info: PropertyWriterLazyCollectionInfo {2592 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2593 property_permissions: LazyValue::new(|| {2594 <Pallet<T>>::property_permissions(collection.id)2595 }),2596 },2597 _phantom: PhantomData,2598 }2599 }2600}26012602impl<'a, T, Handle> PropertyWriter<'a, NewTokenPropertyWriter<T>, T, Handle>2603where2604 T: Config,2605 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2606{2607 2608 pub fn write_token_properties(2609 &mut self,2610 mint_target_is_sender: bool,2611 token_id: TokenId,2612 properties_updates: impl Iterator<Item = Property>,2613 log: evm_coder::ethereum::Log,2614 ) -> DispatchResult {2615 let check_token_exist = || {2616 debug_assert!(self.collection.token_exists(token_id));2617 true2618 };26192620 let check_token_owner = || Ok(mint_target_is_sender);26212622 let get_token_properties = || {2623 debug_assert!(self.collection.get_token_properties_raw(token_id).is_none());2624 TokenProperties::new()2625 };26262627 self.internal_write_token_properties(2628 token_id,2629 PropertyWriterLazyTokenInfo::new(2630 check_token_exist,2631 check_token_owner,2632 get_token_properties,2633 ),2634 properties_updates.map(|p| (p.key, Some(p.value))),2635 log,2636 )2637 }2638}2639264026412642pub struct ExistingTokenPropertyWriter<T>(PhantomData<T>);2643impl<T: Config> ExistingTokenPropertyWriter<T> {2644 2645 pub fn new<'a, Handle>(2646 collection: &'a Handle,2647 sender: &'a T::CrossAccountId,2648 ) -> PropertyWriter<'a, Self, T, Handle>2649 where2650 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2651 {2652 PropertyWriter {2653 collection,2654 collection_lazy_info: PropertyWriterLazyCollectionInfo {2655 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2656 property_permissions: LazyValue::new(|| {2657 <Pallet<T>>::property_permissions(collection.id)2658 }),2659 },2660 _phantom: PhantomData,2661 }2662 }2663}26642665impl<'a, T, Handle> PropertyWriter<'a, ExistingTokenPropertyWriter<T>, T, Handle>2666where2667 T: Config,2668 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2669{2670 2671 pub fn write_token_properties(2672 &mut self,2673 sender: &T::CrossAccountId,2674 token_id: TokenId,2675 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2676 nesting_budget: &dyn Budget,2677 log: evm_coder::ethereum::Log,2678 ) -> DispatchResult {2679 let check_token_exist = || self.collection.token_exists(token_id);2680 let check_token_owner = || {2681 self.collection2682 .check_token_indirect_owner(token_id, sender, nesting_budget)2683 };2684 let get_token_properties = || {2685 self.collection2686 .get_token_properties_raw(token_id)2687 .unwrap_or_default()2688 };26892690 self.internal_write_token_properties(2691 token_id,2692 PropertyWriterLazyTokenInfo::new(2693 check_token_exist,2694 check_token_owner,2695 get_token_properties,2696 ),2697 properties_updates,2698 log,2699 )2700 }2701}2702270327042705#[cfg(feature = "runtime-benchmarks")]2706pub struct BenchmarkPropertyWriter<T>(PhantomData<T>);27072708#[cfg(feature = "runtime-benchmarks")]2709impl<T: Config> BenchmarkPropertyWriter<T> {2710 2711 pub fn new<'a, Handle>(2712 collection: &'a Handle,2713 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2714 ) -> PropertyWriter<'a, Self, T, Handle>2715 where2716 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2717 {2718 PropertyWriter {2719 collection,2720 collection_lazy_info,2721 _phantom: PhantomData,2722 }2723 }27242725 2726 pub fn load_collection_info<Handle>(2727 collection_handle: &Handle,2728 sender: &T::CrossAccountId,2729 ) -> PropertyWriterLazyCollectionInfo<'static>2730 where2731 Handle: Deref<Target = CollectionHandle<T>>,2732 {2733 let is_collection_admin = collection_handle.is_owner_or_admin(sender);2734 let property_permissions = <Pallet<T>>::property_permissions(collection_handle.id);27352736 PropertyWriterLazyCollectionInfo {2737 is_collection_admin: LazyValue::new(move || is_collection_admin),2738 property_permissions: LazyValue::new(move || property_permissions),2739 }2740 }27412742 2743 pub fn load_token_properties<Handle>(2744 collection: &Handle,2745 token_id: TokenId,2746 ) -> PropertyWriterLazyTokenInfo2747 where2748 Handle: CommonCollectionOperations<T>,2749 {2750 let stored_properties = collection2751 .get_token_properties_raw(token_id)2752 .unwrap_or_default();27532754 PropertyWriterLazyTokenInfo {2755 is_token_exist: LazyValue::new(|| true),2756 is_token_owner: LazyValue::new(|| Ok(true)),2757 stored_properties: LazyValue::new(move || stored_properties),2758 }2759 }2760}27612762#[cfg(feature = "runtime-benchmarks")]2763impl<'a, T, Handle> PropertyWriter<'a, BenchmarkPropertyWriter<T>, T, Handle>2764where2765 T: Config,2766 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2767{2768 2769 pub fn write_token_properties(2770 &mut self,2771 token_id: TokenId,2772 properties_updates: impl Iterator<Item = Property>,2773 log: evm_coder::ethereum::Log,2774 ) -> DispatchResult {2775 let check_token_exist = || true;2776 let check_token_owner = || Ok(true);2777 let get_token_properties = TokenProperties::new;27782779 self.internal_write_token_properties(2780 token_id,2781 PropertyWriterLazyTokenInfo::new(2782 check_token_exist,2783 check_token_owner,2784 get_token_properties,2785 ),2786 properties_updates.map(|p| (p.key, Some(p.value))),2787 log,2788 )2789 }2790}279127922793279427952796pub fn write_token_properties_total_weight<T: Config, I: Fn(u32) -> Weight>(2797 properties_nums: impl Iterator<Item = u32>,2798 per_token_weight: I,2799) -> Weight {2800 let mut weight = properties_nums2801 .filter_map(|properties_num| {2802 if properties_num > 0 {2803 Some(per_token_weight(properties_num))2804 } else {2805 None2806 }2807 })2808 .fold(Weight::zero(), |a, b| a.saturating_add(b));28092810 if !weight.is_zero() {2811 2812 2813 28142815 weight = weight.saturating_add(<SelfWeightOf<T>>::property_writer_load_collection_info());2816 }28172818 weight2819}28202821#[cfg(any(feature = "tests", test))]2822#[allow(missing_docs)]2823pub mod tests {2824 use crate::{Config, DispatchError, DispatchResult, LazyValue};28252826 const fn to_bool(u: u8) -> bool {2827 u != 02828 }28292830 #[derive(Debug)]2831 pub struct TestCase {2832 pub collection_admin: bool,2833 pub is_collection_admin: bool,2834 pub token_owner: bool,2835 pub is_token_owner: bool,2836 pub no_permission: bool,2837 }28382839 impl TestCase {2840 const fn new(2841 collection_admin: u8,2842 is_collection_admin: u8,2843 token_owner: u8,2844 is_token_owner: u8,2845 no_permission: u8,2846 ) -> Self {2847 Self {2848 collection_admin: to_bool(collection_admin),2849 is_collection_admin: to_bool(is_collection_admin),2850 token_owner: to_bool(token_owner),2851 is_token_owner: to_bool(is_token_owner),2852 no_permission: to_bool(no_permission),2853 }2854 }2855 }28562857 #[rustfmt::skip]2858 pub const TABLE: [TestCase; 16] = [2859 2860 2861 2862 2863 2864 TestCase::new(0, 0, 0, 0, 1),2865 TestCase::new(0, 0, 0, 1, 1),2866 TestCase::new(0, 0, 1, 0, 1),2867 TestCase::new(0, 0, 1, 1, 0),2868 TestCase::new(0, 1, 0, 0, 1),2869 TestCase::new(0, 1, 0, 1, 1),2870 TestCase::new(0, 1, 1, 0, 1),2871 TestCase::new(0, 1, 1, 1, 0),2872 TestCase::new(1, 0, 0, 0, 1),2873 TestCase::new(1, 0, 0, 1, 1),2874 TestCase::new(1, 0, 1, 0, 1),2875 TestCase::new(1, 0, 1, 1, 0),2876 TestCase::new(1, 1, 0, 0, 0),2877 TestCase::new(1, 1, 0, 1, 0),2878 TestCase::new(1, 1, 1, 0, 0),2879 TestCase::new(1, 1, 1, 1, 0),2880 ];28812882 pub fn check_token_permissions<T: Config>(2883 collection_admin_permitted: bool,2884 token_owner_permitted: bool,2885 is_collection_admin: &mut LazyValue<bool>,2886 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>>,2887 check_token_existence: &mut LazyValue<bool>,2888 ) -> DispatchResult {2889 crate::check_token_permissions::<T>(2890 collection_admin_permitted,2891 token_owner_permitted,2892 is_collection_admin,2893 check_token_ownership,2894 check_token_existence,2895 )2896 }2897}