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}946947948pub enum CollectionIssuer<CrossAccountId> {949 950 User(CrossAccountId),951952 953 Internals,954}955956fn check_token_permissions<T: Config>(957 collection_admin_permitted: bool,958 token_owner_permitted: bool,959 is_collection_admin: &mut LazyValue<bool>,960 is_token_owner: &mut LazyValue<Result<bool, DispatchError>>,961 is_token_exist: &mut LazyValue<bool>,962) -> DispatchResult {963 if !(collection_admin_permitted && *is_collection_admin.value()964 || token_owner_permitted && (*is_token_owner.value())?)965 {966 fail!(<Error<T>>::NoPermission);967 }968969 let token_exist_due_to_owner_check_success =970 is_token_owner.has_value() && (*is_token_owner.value())?;971972 973 974 if !token_exist_due_to_owner_check_success {975 976 977 if !is_token_exist.value() {978 fail!(<Error<T>>::TokenNotFound);979 }980 }981982 Ok(())983}984985impl<T: Config> Pallet<T> {986 987 988 989 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {990 ensure!(991 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,992 <Error<T>>::AddressIsZero993 );994 Ok(())995 }996997 998 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {999 <IsAdmin<T>>::iter_prefix((collection,))1000 .map(|(a, _)| a)1001 .collect()1002 }10031004 1005 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {1006 <Allowlist<T>>::iter_prefix((collection,))1007 .map(|(a, _)| a)1008 .collect()1009 }10101011 1012 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {1013 <Allowlist<T>>::get((collection, user))1014 }10151016 1017 pub fn collection_stats() -> CollectionStats {1018 let created = <CreatedCollectionCount<T>>::get();1019 let destroyed = <DestroyedCollectionCount<T>>::get();1020 CollectionStats {1021 created: created.0,1022 destroyed: destroyed.0,1023 alive: created.0 - destroyed.0,1024 }1025 }10261027 1028 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {1029 let collection = <CollectionById<T>>::get(collection)?;1030 let limits = collection.limits;1031 let effective_limits = CollectionLimits {1032 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),1033 sponsored_data_size: Some(limits.sponsored_data_size()),1034 sponsored_data_rate_limit: Some(1035 limits1036 .sponsored_data_rate_limit1037 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),1038 ),1039 token_limit: Some(limits.token_limit()),1040 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(1041 match collection.mode {1042 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1043 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1044 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1045 },1046 )),1047 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),1048 owner_can_transfer: Some(limits.owner_can_transfer()),1049 owner_can_destroy: Some(limits.owner_can_destroy()),1050 transfers_enabled: Some(limits.transfers_enabled()),1051 };10521053 Some(effective_limits)1054 }10551056 1057 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1058 let Collection {1059 name,1060 description,1061 owner,1062 mode,1063 token_prefix,1064 sponsorship,1065 limits,1066 permissions,1067 flags,1068 } = <CollectionById<T>>::get(collection)?;10691070 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1071 .into_iter()1072 .map(|(key, permission)| PropertyKeyPermission { key, permission })1073 .collect();10741075 let properties = <CollectionProperties<T>>::get(collection)1076 .into_iter()1077 .map(|(key, value)| Property { key, value })1078 .collect();10791080 let permissions = CollectionPermissions {1081 access: Some(permissions.access()),1082 mint_mode: Some(permissions.mint_mode()),1083 nesting: Some(permissions.nesting().clone()),1084 };10851086 Some(RpcCollection {1087 name: name.into_inner(),1088 description: description.into_inner(),1089 owner,1090 mode,1091 token_prefix: token_prefix.into_inner(),1092 sponsorship,1093 limits,1094 permissions,1095 token_property_permissions,1096 properties,1097 read_only: flags.external,10981099 flags: RpcCollectionFlags {1100 foreign: flags.foreign,1101 erc721metadata: flags.erc721metadata,1102 },1103 })1104 }1105}11061107macro_rules! limit_default {1108 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1109 $(1110 if let Some($new) = $new.$field {1111 let $old = $old.$field($($arg)?);1112 let _ = $new;1113 let _ = $old;1114 $check1115 } else {1116 $new.$field = $old.$field1117 }1118 )*1119 }};1120}1121macro_rules! limit_default_clone {1122 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1123 $(1124 if let Some($new) = $new.$field.clone() {1125 let $old = $old.$field($($arg)?);1126 let _ = $new;1127 let _ = $old;1128 $check1129 } else {1130 $new.$field = $old.$field.clone()1131 }1132 )*1133 }};1134}11351136impl<T: Config> Pallet<T> {1137 1138 1139 1140 1141 1142 pub fn init_collection(1143 owner: T::CrossAccountId,1144 issuer: CollectionIssuer<T::CrossAccountId>,1145 data: CreateCollectionData<T::CrossAccountId>,1146 ) -> Result<CollectionId, DispatchError> {1147 match issuer {1148 CollectionIssuer::User(payer) => {1149 ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);11501151 1152 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1153 imbalance.subsume(<T as Config>::Currency::deposit(1154 &T::TreasuryAccountId::get(),1155 T::CollectionCreationPrice::get(),1156 Precision::Exact,1157 )?);1158 let credit = <T as Config>::Currency::settle(1159 payer.as_sub(),1160 imbalance,1161 Preservation::Preserve,1162 )1163 .map_err(|_| Error::<T>::NotSufficientFounds)?;11641165 debug_assert!(credit.peek().is_zero());1166 }1167 CollectionIssuer::Internals => {}1168 }11691170 {1171 ensure!(1172 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1173 Error::<T>::CollectionTokenPrefixLimitExceeded1174 );1175 }11761177 let created_count = <CreatedCollectionCount<T>>::get()1178 .01179 .checked_add(1)1180 .ok_or(ArithmeticError::Overflow)?;1181 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1182 let id = CollectionId(created_count);11831184 1185 ensure!(1186 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1187 <Error<T>>::TotalCollectionsLimitExceeded1188 );11891190 11911192 let collection = Collection {1193 owner: owner.as_sub().clone(),1194 name: data.name,1195 mode: data.mode.clone(),1196 description: data.description,1197 token_prefix: data.token_prefix,1198 sponsorship: data1199 .pending_sponsor1200 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1201 .unwrap_or_default(),1202 limits: data1203 .limits1204 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1205 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1206 permissions: data1207 .permissions1208 .map(|permissions| {1209 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1210 })1211 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1212 flags: data.flags,1213 };12141215 let mut collection_properties = CollectionPropertiesT::new();1216 collection_properties1217 .try_set_from_iter(data.properties.into_iter())1218 .map_err(<Error<T>>::from)?;12191220 CollectionProperties::<T>::insert(id, collection_properties);12211222 let mut token_props_permissions = PropertiesPermissionMap::new();1223 token_props_permissions1224 .try_set_from_iter(data.token_property_permissions.into_iter())1225 .map_err(<Error<T>>::from)?;12261227 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);12281229 let mut admin_amount = 0u32;1230 for admin in data.admin_list.iter() {1231 if !<IsAdmin<T>>::get((id, admin)) {1232 <IsAdmin<T>>::insert((id, admin), true);1233 admin_amount = admin_amount1234 .checked_add(1)1235 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1236 }1237 }1238 ensure!(1239 admin_amount <= Self::collection_admins_limit(),1240 <Error<T>>::CollectionAdminCountExceeded,1241 );1242 <AdminAmount<T>>::insert(id, admin_amount);12431244 <CreatedCollectionCount<T>>::put(created_count);1245 <Pallet<T>>::deposit_event(Event::CollectionCreated(1246 id,1247 data.mode.id(),1248 owner.as_sub().clone(),1249 ));1250 <PalletEvm<T>>::deposit_log(1251 erc::CollectionHelpersEvents::CollectionCreated {1252 owner: *owner.as_eth(),1253 collection_id: eth::collection_id_to_address(id),1254 }1255 .to_log(T::ContractAddress::get()),1256 );1257 <CollectionById<T>>::insert(id, collection);1258 Ok(id)1259 }12601261 1262 1263 1264 1265 pub fn destroy_collection(1266 collection: CollectionHandle<T>,1267 sender: &T::CrossAccountId,1268 ) -> DispatchResult {1269 ensure!(1270 collection.limits.owner_can_destroy(),1271 <Error<T>>::NoPermission,1272 );1273 collection.check_is_owner(sender)?;12741275 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1276 .01277 .checked_add(1)1278 .ok_or(ArithmeticError::Overflow)?;12791280 12811282 <DestroyedCollectionCount<T>>::put(destroyed_collections);1283 <CollectionById<T>>::remove(collection.id);1284 <AdminAmount<T>>::remove(collection.id);1285 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1286 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1287 <CollectionProperties<T>>::remove(collection.id);12881289 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12901291 <PalletEvm<T>>::deposit_log(1292 erc::CollectionHelpersEvents::CollectionDestroyed {1293 collection_id: eth::collection_id_to_address(collection.id),1294 }1295 .to_log(T::ContractAddress::get()),1296 );1297 Ok(())1298 }12991300 1301 1302 1303 1304 1305 1306 1307 1308 #[transactional]1309 fn modify_collection_properties(1310 collection: &CollectionHandle<T>,1311 sender: &T::CrossAccountId,1312 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1313 ) -> DispatchResult {1314 collection.check_is_owner_or_admin(sender)?;13151316 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);13171318 for (key, value) in properties_updates {1319 match value {1320 Some(value) => {1321 stored_properties1322 .try_set(key.clone(), value)1323 .map_err(<Error<T>>::from)?;13241325 Self::deposit_event(Event::CollectionPropertySet(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 None => {1334 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13351336 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1337 <PalletEvm<T>>::deposit_log(1338 erc::CollectionHelpersEvents::CollectionChanged {1339 collection_id: eth::collection_id_to_address(collection.id),1340 }1341 .to_log(T::ContractAddress::get()),1342 );1343 }1344 }1345 }13461347 <CollectionProperties<T>>::set(collection.id, stored_properties);13481349 Ok(())1350 }13511352 1353 1354 1355 1356 1357 1358 pub fn set_allowance_for_all(1359 collection: &CollectionHandle<T>,1360 owner: &T::CrossAccountId,1361 operator: &T::CrossAccountId,1362 approve: bool,1363 set_allowance: impl FnOnce(),1364 log: evm_coder::ethereum::Log,1365 ) -> DispatchResult {1366 if collection.permissions.access() == AccessMode::AllowList {1367 collection.check_allowlist(owner)?;1368 collection.check_allowlist(operator)?;1369 }13701371 Self::ensure_correct_receiver(operator)?;13721373 set_allowance();13741375 <PalletEvm<T>>::deposit_log(log);1376 Self::deposit_event(Event::ApprovedForAll(1377 collection.id,1378 owner.clone(),1379 operator.clone(),1380 approve,1381 ));1382 Ok(())1383 }13841385 1386 1387 1388 1389 1390 pub fn set_collection_property(1391 collection: &CollectionHandle<T>,1392 sender: &T::CrossAccountId,1393 property: Property,1394 ) -> DispatchResult {1395 Self::set_collection_properties(collection, sender, [property].into_iter())1396 }13971398 1399 1400 1401 1402 1403 1404 pub fn set_scoped_collection_property(1405 collection_id: CollectionId,1406 scope: PropertyScope,1407 property: Property,1408 ) -> DispatchResult {1409 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1410 properties.try_scoped_set(scope, property.key, property.value)1411 })1412 .map_err(<Error<T>>::from)?;14131414 Ok(())1415 }14161417 1418 1419 1420 1421 1422 1423 pub fn set_scoped_collection_properties(1424 collection_id: CollectionId,1425 scope: PropertyScope,1426 properties: impl Iterator<Item = Property>,1427 ) -> DispatchResult {1428 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1429 stored_properties.try_scoped_set_from_iter(scope, properties)1430 })1431 .map_err(<Error<T>>::from)?;14321433 Ok(())1434 }14351436 1437 1438 1439 1440 1441 pub fn set_collection_properties(1442 collection: &CollectionHandle<T>,1443 sender: &T::CrossAccountId,1444 properties: impl Iterator<Item = Property>,1445 ) -> DispatchResult {1446 Self::modify_collection_properties(1447 collection,1448 sender,1449 properties.map(|property| (property.key, Some(property.value))),1450 )1451 }14521453 1454 1455 1456 1457 1458 pub fn delete_collection_property(1459 collection: &CollectionHandle<T>,1460 sender: &T::CrossAccountId,1461 property_key: PropertyKey,1462 ) -> DispatchResult {1463 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1464 }14651466 1467 1468 1469 1470 1471 pub fn delete_collection_properties(1472 collection: &CollectionHandle<T>,1473 sender: &T::CrossAccountId,1474 property_keys: impl Iterator<Item = PropertyKey>,1475 ) -> DispatchResult {1476 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1477 }14781479 1480 1481 1482 1483 1484 1485 pub fn set_property_permission_unchecked(1486 collection: CollectionId,1487 property_permission: PropertyKeyPermission,1488 ) -> DispatchResult {1489 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1490 permissions.try_set(property_permission.key, property_permission.permission)1491 })1492 .map_err(<Error<T>>::from)?;1493 Ok(())1494 }14951496 1497 1498 1499 1500 1501 pub fn set_property_permission(1502 collection: &CollectionHandle<T>,1503 sender: &T::CrossAccountId,1504 property_permission: PropertyKeyPermission,1505 ) -> DispatchResult {1506 Self::set_scoped_property_permission(1507 collection,1508 sender,1509 PropertyScope::None,1510 property_permission,1511 )1512 }15131514 1515 1516 1517 1518 1519 1520 pub fn set_scoped_property_permission(1521 collection: &CollectionHandle<T>,1522 sender: &T::CrossAccountId,1523 scope: PropertyScope,1524 property_permission: PropertyKeyPermission,1525 ) -> DispatchResult {1526 collection.check_is_owner_or_admin(sender)?;15271528 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1529 let current_permission = all_permissions.get(&property_permission.key);1530 if matches![1531 current_permission,1532 Some(PropertyPermission { mutable: false, .. })1533 ] {1534 return Err(<Error<T>>::NoPermission.into());1535 }15361537 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1538 let property_permission = property_permission.clone();1539 permissions.try_scoped_set(1540 scope,1541 property_permission.key,1542 property_permission.permission,1543 )1544 })1545 .map_err(<Error<T>>::from)?;15461547 Self::deposit_event(Event::PropertyPermissionSet(1548 collection.id,1549 property_permission.key,1550 ));1551 <PalletEvm<T>>::deposit_log(1552 erc::CollectionHelpersEvents::CollectionChanged {1553 collection_id: eth::collection_id_to_address(collection.id),1554 }1555 .to_log(T::ContractAddress::get()),1556 );15571558 Ok(())1559 }15601561 1562 1563 1564 1565 1566 #[transactional]1567 pub fn set_token_property_permissions(1568 collection: &CollectionHandle<T>,1569 sender: &T::CrossAccountId,1570 property_permissions: Vec<PropertyKeyPermission>,1571 ) -> DispatchResult {1572 Self::set_scoped_token_property_permissions(1573 collection,1574 sender,1575 PropertyScope::None,1576 property_permissions,1577 )1578 }15791580 1581 1582 1583 1584 1585 1586 #[transactional]1587 pub fn set_scoped_token_property_permissions(1588 collection: &CollectionHandle<T>,1589 sender: &T::CrossAccountId,1590 scope: PropertyScope,1591 property_permissions: Vec<PropertyKeyPermission>,1592 ) -> DispatchResult {1593 for prop_pemission in property_permissions {1594 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1595 }15961597 Ok(())1598 }15991600 1601 pub fn get_collection_property(1602 collection_id: CollectionId,1603 key: &PropertyKey,1604 ) -> Option<PropertyValue> {1605 Self::collection_properties(collection_id).get(key).cloned()1606 }16071608 1609 pub fn bytes_keys_to_property_keys(1610 keys: Vec<Vec<u8>>,1611 ) -> Result<Vec<PropertyKey>, DispatchError> {1612 keys.into_iter()1613 .map(|key| -> Result<PropertyKey, DispatchError> {1614 key.try_into()1615 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1616 })1617 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1618 }16191620 1621 pub fn filter_collection_properties(1622 collection_id: CollectionId,1623 keys: Option<Vec<PropertyKey>>,1624 ) -> Result<Vec<Property>, DispatchError> {1625 let properties = Self::collection_properties(collection_id);16261627 let properties = keys1628 .map(|keys| {1629 keys.into_iter()1630 .filter_map(|key| {1631 properties.get(&key).map(|value| Property {1632 key,1633 value: value.clone(),1634 })1635 })1636 .collect()1637 })1638 .unwrap_or_else(|| {1639 properties1640 .into_iter()1641 .map(|(key, value)| Property { key, value })1642 .collect()1643 });16441645 Ok(properties)1646 }16471648 1649 pub fn filter_property_permissions(1650 collection_id: CollectionId,1651 keys: Option<Vec<PropertyKey>>,1652 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1653 let permissions = Self::property_permissions(collection_id);16541655 let key_permissions = keys1656 .map(|keys| {1657 keys.into_iter()1658 .filter_map(|key| {1659 permissions1660 .get(&key)1661 .map(|permission| PropertyKeyPermission {1662 key,1663 permission: permission.clone(),1664 })1665 })1666 .collect()1667 })1668 .unwrap_or_else(|| {1669 permissions1670 .into_iter()1671 .map(|(key, permission)| PropertyKeyPermission { key, permission })1672 .collect()1673 });16741675 Ok(key_permissions)1676 }16771678 1679 1680 1681 pub fn toggle_allowlist(1682 collection: &CollectionHandle<T>,1683 sender: &T::CrossAccountId,1684 user: &T::CrossAccountId,1685 allowed: bool,1686 ) -> DispatchResult {1687 collection.check_is_owner_or_admin(sender)?;16881689 16901691 if allowed {1692 <Allowlist<T>>::insert((collection.id, user), true);1693 Self::deposit_event(Event::<T>::AllowListAddressAdded(1694 collection.id,1695 user.clone(),1696 ));1697 } else {1698 <Allowlist<T>>::remove((collection.id, user));1699 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1700 collection.id,1701 user.clone(),1702 ));1703 }17041705 <PalletEvm<T>>::deposit_log(1706 erc::CollectionHelpersEvents::CollectionChanged {1707 collection_id: eth::collection_id_to_address(collection.id),1708 }1709 .to_log(T::ContractAddress::get()),1710 );17111712 Ok(())1713 }17141715 1716 1717 1718 pub fn toggle_admin(1719 collection: &CollectionHandle<T>,1720 sender: &T::CrossAccountId,1721 user: &T::CrossAccountId,1722 admin: bool,1723 ) -> DispatchResult {1724 collection.check_is_internal()?;1725 collection.check_is_owner(sender)?;17261727 let is_admin = <IsAdmin<T>>::get((collection.id, user));1728 if is_admin == admin {1729 if admin {1730 return Ok(());1731 } else {1732 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1733 }1734 }1735 let amount = <AdminAmount<T>>::get(collection.id);17361737 17381739 if admin {1740 let amount = amount1741 .checked_add(1)1742 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1743 ensure!(1744 amount <= Self::collection_admins_limit(),1745 <Error<T>>::CollectionAdminCountExceeded,1746 );17471748 <AdminAmount<T>>::insert(collection.id, amount);1749 <IsAdmin<T>>::insert((collection.id, user), true);17501751 Self::deposit_event(Event::<T>::CollectionAdminAdded(1752 collection.id,1753 user.clone(),1754 ));1755 } else {1756 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1757 <IsAdmin<T>>::remove((collection.id, user));17581759 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1760 collection.id,1761 user.clone(),1762 ));1763 }17641765 <PalletEvm<T>>::deposit_log(1766 erc::CollectionHelpersEvents::CollectionChanged {1767 collection_id: eth::collection_id_to_address(collection.id),1768 }1769 .to_log(T::ContractAddress::get()),1770 );17711772 Ok(())1773 }17741775 1776 pub fn update_limits(1777 user: &T::CrossAccountId,1778 collection: &mut CollectionHandle<T>,1779 new_limit: CollectionLimits,1780 ) -> DispatchResult {1781 collection.check_is_internal()?;1782 collection.check_is_owner_or_admin(user)?;17831784 collection.limits =1785 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17861787 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1788 <PalletEvm<T>>::deposit_log(1789 erc::CollectionHelpersEvents::CollectionChanged {1790 collection_id: eth::collection_id_to_address(collection.id),1791 }1792 .to_log(T::ContractAddress::get()),1793 );17941795 collection.save()1796 }17971798 1799 fn clamp_limits(1800 mode: CollectionMode,1801 old_limit: &CollectionLimits,1802 mut new_limit: CollectionLimits,1803 ) -> Result<CollectionLimits, DispatchError> {1804 let limits = old_limit;1805 limit_default!(old_limit, new_limit,1806 account_token_ownership_limit => ensure!(1807 new_limit <= MAX_TOKEN_OWNERSHIP,1808 <Error<T>>::CollectionLimitBoundsExceeded,1809 ),1810 sponsored_data_size => ensure!(1811 new_limit <= CUSTOM_DATA_LIMIT,1812 <Error<T>>::CollectionLimitBoundsExceeded,1813 ),18141815 sponsored_data_rate_limit => {},1816 token_limit => ensure!(1817 old_limit >= new_limit && new_limit > 0,1818 <Error<T>>::CollectionTokenLimitExceeded1819 ),18201821 sponsor_transfer_timeout(match mode {1822 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1823 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1824 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1825 }) => ensure!(1826 new_limit <= MAX_SPONSOR_TIMEOUT,1827 <Error<T>>::CollectionLimitBoundsExceeded,1828 ),1829 sponsor_approve_timeout => {},1830 owner_can_transfer => ensure!(1831 !limits.owner_can_transfer_instaled() ||1832 old_limit || !new_limit,1833 <Error<T>>::OwnerPermissionsCantBeReverted,1834 ),1835 owner_can_destroy => ensure!(1836 old_limit || !new_limit,1837 <Error<T>>::OwnerPermissionsCantBeReverted,1838 ),1839 transfers_enabled => {},1840 );1841 Ok(new_limit)1842 }18431844 1845 pub fn update_permissions(1846 user: &T::CrossAccountId,1847 collection: &mut CollectionHandle<T>,1848 new_permission: CollectionPermissions,1849 ) -> DispatchResult {1850 collection.check_is_internal()?;1851 collection.check_is_owner_or_admin(user)?;1852 collection.permissions = Self::clamp_permissions(1853 collection.mode.clone(),1854 &collection.permissions,1855 new_permission,1856 )?;18571858 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1859 <PalletEvm<T>>::deposit_log(1860 erc::CollectionHelpersEvents::CollectionChanged {1861 collection_id: eth::collection_id_to_address(collection.id),1862 }1863 .to_log(T::ContractAddress::get()),1864 );18651866 collection.save()1867 }18681869 1870 fn clamp_permissions(1871 _mode: CollectionMode,1872 old_permission: &CollectionPermissions,1873 mut new_permission: CollectionPermissions,1874 ) -> Result<CollectionPermissions, DispatchError> {1875 limit_default_clone!(old_permission, new_permission,1876 access => {},1877 mint_mode => {},1878 nesting => { },1879 );1880 Ok(new_permission)1881 }18821883 1884 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1885 CollectionProperties::<T>::mutate(collection_id, |properties| {1886 properties.recompute_consumed_space();1887 });18881889 Ok(())1890 }1891}189218931894#[macro_export]1895macro_rules! unsupported {1896 ($runtime:path) => {1897 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1898 };1899}190019011902pub trait CommonWeightInfo<CrossAccountId> {1903 1904 fn create_item(data: &CreateItemData) -> Weight {1905 Self::create_multiple_items(from_ref(data))1906 }19071908 1909 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19101911 1912 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19131914 1915 fn burn_item() -> Weight;19161917 1918 1919 1920 fn set_collection_properties(amount: u32) -> Weight;19211922 1923 1924 1925 fn delete_collection_properties(amount: u32) -> Weight {1926 Self::set_collection_properties(amount)1927 }19281929 1930 1931 1932 fn set_token_properties(amount: u32) -> Weight;19331934 1935 1936 1937 fn delete_token_properties(amount: u32) -> Weight {1938 Self::set_token_properties(amount)1939 }19401941 1942 1943 1944 fn set_token_property_permissions(amount: u32) -> Weight;19451946 1947 fn transfer() -> Weight;19481949 1950 fn approve() -> Weight;19511952 1953 fn approve_from() -> Weight;19541955 1956 fn transfer_from() -> Weight;19571958 1959 fn burn_from() -> Weight;19601961 1962 fn set_allowance_for_all() -> Weight;19631964 1965 fn force_repair_item() -> Weight;1966}196719681969pub trait RefungibleExtensionsWeightInfo {1970 1971 fn repartition() -> Weight;1972}197319741975197619771978pub trait CommonCollectionOperations<T: Config> {1979 1980 1981 1982 1983 1984 1985 fn create_item(1986 &self,1987 sender: T::CrossAccountId,1988 to: T::CrossAccountId,1989 data: CreateItemData,1990 nesting_budget: &dyn Budget,1991 ) -> DispatchResultWithPostInfo;19921993 1994 1995 1996 1997 1998 1999 fn create_multiple_items(2000 &self,2001 sender: T::CrossAccountId,2002 to: T::CrossAccountId,2003 data: Vec<CreateItemData>,2004 nesting_budget: &dyn Budget,2005 ) -> DispatchResultWithPostInfo;20062007 2008 2009 2010 2011 2012 2013 fn create_multiple_items_ex(2014 &self,2015 sender: T::CrossAccountId,2016 data: CreateItemExData<T::CrossAccountId>,2017 nesting_budget: &dyn Budget,2018 ) -> DispatchResultWithPostInfo;20192020 2021 2022 2023 2024 2025 fn burn_item(2026 &self,2027 sender: T::CrossAccountId,2028 token: TokenId,2029 amount: u128,2030 ) -> DispatchResultWithPostInfo;20312032 2033 2034 2035 2036 fn set_collection_properties(2037 &self,2038 sender: T::CrossAccountId,2039 properties: Vec<Property>,2040 ) -> DispatchResultWithPostInfo;20412042 2043 2044 2045 2046 fn delete_collection_properties(2047 &self,2048 sender: &T::CrossAccountId,2049 property_keys: Vec<PropertyKey>,2050 ) -> DispatchResultWithPostInfo;20512052 2053 2054 2055 2056 2057 2058 2059 2060 2061 fn set_token_properties(2062 &self,2063 sender: T::CrossAccountId,2064 token_id: TokenId,2065 properties: Vec<Property>,2066 budget: &dyn Budget,2067 ) -> DispatchResultWithPostInfo;20682069 2070 2071 2072 2073 2074 2075 2076 2077 2078 fn delete_token_properties(2079 &self,2080 sender: T::CrossAccountId,2081 token_id: TokenId,2082 property_keys: Vec<PropertyKey>,2083 budget: &dyn Budget,2084 ) -> DispatchResultWithPostInfo;20852086 2087 2088 2089 fn get_token_properties_raw(&self, token_id: TokenId) -> Option<TokenProperties>;20902091 2092 2093 2094 2095 fn set_token_properties_raw(&self, token_id: TokenId, map: TokenProperties);20962097 2098 2099 2100 2101 2102 2103 fn set_token_property_permissions(2104 &self,2105 sender: &T::CrossAccountId,2106 property_permissions: Vec<PropertyKeyPermission>,2107 ) -> DispatchResultWithPostInfo;21082109 2110 2111 2112 2113 2114 2115 2116 fn transfer(2117 &self,2118 sender: T::CrossAccountId,2119 to: T::CrossAccountId,2120 token: TokenId,2121 amount: u128,2122 budget: &dyn Budget,2123 ) -> DispatchResultWithPostInfo;21242125 2126 2127 2128 2129 2130 2131 fn approve(2132 &self,2133 sender: T::CrossAccountId,2134 spender: T::CrossAccountId,2135 token: TokenId,2136 amount: u128,2137 ) -> DispatchResultWithPostInfo;21382139 2140 2141 2142 2143 2144 2145 2146 fn approve_from(2147 &self,2148 sender: T::CrossAccountId,2149 from: T::CrossAccountId,2150 to: T::CrossAccountId,2151 token: TokenId,2152 amount: u128,2153 ) -> DispatchResultWithPostInfo;21542155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 fn transfer_from(2166 &self,2167 sender: T::CrossAccountId,2168 from: T::CrossAccountId,2169 to: T::CrossAccountId,2170 token: TokenId,2171 amount: u128,2172 budget: &dyn Budget,2173 ) -> DispatchResultWithPostInfo;21742175 2176 2177 2178 2179 2180 2181 2182 2183 2184 fn burn_from(2185 &self,2186 sender: T::CrossAccountId,2187 from: T::CrossAccountId,2188 token: TokenId,2189 amount: u128,2190 budget: &dyn Budget,2191 ) -> DispatchResultWithPostInfo;21922193 2194 2195 2196 2197 2198 2199 fn check_nesting(2200 &self,2201 sender: &T::CrossAccountId,2202 from: (CollectionId, TokenId),2203 under: TokenId,2204 budget: &dyn Budget,2205 ) -> DispatchResult;22062207 2208 2209 2210 2211 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22122213 2214 2215 2216 2217 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22182219 2220 2221 2222 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22232224 2225 fn collection_tokens(&self) -> Vec<TokenId>;22262227 2228 2229 2230 fn token_exists(&self, token: TokenId) -> bool;22312232 2233 fn last_token_id(&self) -> TokenId;22342235 2236 2237 2238 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22392240 2241 2242 2243 2244 2245 fn check_token_indirect_owner(2246 &self,2247 token: TokenId,2248 maybe_owner: &T::CrossAccountId,2249 nesting_budget: &dyn Budget,2250 ) -> Result<bool, DispatchError>;22512252 2253 2254 2255 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22562257 2258 2259 2260 2261 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22622263 2264 2265 2266 2267 2268 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22692270 2271 fn total_supply(&self) -> u32;22722273 2274 2275 2276 fn account_balance(&self, account: T::CrossAccountId) -> u32;22772278 2279 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22802281 2282 fn total_pieces(&self, token: TokenId) -> Option<u128>;22832284 2285 2286 2287 2288 2289 fn allowance(2290 &self,2291 sender: T::CrossAccountId,2292 spender: T::CrossAccountId,2293 token: TokenId,2294 ) -> u128;22952296 2297 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {2298 None2299 }23002301 2302 fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {2303 None2304 }23052306 2307 2308 2309 2310 fn set_allowance_for_all(2311 &self,2312 owner: T::CrossAccountId,2313 operator: T::CrossAccountId,2314 approve: bool,2315 ) -> DispatchResultWithPostInfo;23162317 2318 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23192320 2321 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2322}232323242325pub trait RefungibleExtensions<T>2326where2327 T: Config,2328{2329 2330 2331 2332 2333 2334 2335 2336 fn repartition(2337 &self,2338 sender: &T::CrossAccountId,2339 token: TokenId,2340 amount: u128,2341 ) -> DispatchResultWithPostInfo;2342}234323442345pub trait XcmExtensions<T>2346where2347 T: Config,2348{2349 2350 fn token_has_children(&self, _token: TokenId) -> bool {2351 false2352 }23532354 2355 2356 2357 #[transactional]2358 fn create_item(2359 &self,2360 depositor: &T::CrossAccountId,2361 to: T::CrossAccountId,2362 data: CreateItemData,2363 nesting_budget: &dyn Budget,2364 ) -> Result<TokenId, DispatchError> {2365 if T::CrossTokenAddressMapping::is_token_address(&to) {2366 return unsupported!(T);2367 }23682369 self.create_item_internal(depositor, to, data, nesting_budget)2370 }23712372 2373 fn create_item_internal(2374 &self,2375 depositor: &T::CrossAccountId,2376 to: T::CrossAccountId,2377 data: CreateItemData,2378 nesting_budget: &dyn Budget,2379 ) -> Result<TokenId, DispatchError>;23802381 2382 2383 2384 #[transactional]2385 fn transfer_item(2386 &self,2387 depositor: &T::CrossAccountId,2388 from: &T::CrossAccountId,2389 to: &T::CrossAccountId,2390 token: TokenId,2391 amount: u128,2392 nesting_budget: &dyn Budget,2393 ) -> DispatchResult {2394 if T::CrossTokenAddressMapping::is_token_address(to) {2395 return unsupported!(T);2396 }23972398 if self.token_has_children(token) {2399 return unsupported!(T);2400 }24012402 self.transfer_item_internal(depositor, from, to, token, amount, nesting_budget)2403 }24042405 2406 fn transfer_item_internal(2407 &self,2408 depositor: &T::CrossAccountId,2409 from: &T::CrossAccountId,2410 to: &T::CrossAccountId,2411 token: TokenId,2412 amount: u128,2413 nesting_budget: &dyn Budget,2414 ) -> DispatchResult;24152416 2417 #[transactional]2418 fn burn_item(&self, from: T::CrossAccountId, token: TokenId, amount: u128) -> DispatchResult {2419 self.burn_item_internal(from, token, amount)2420 }24212422 2423 fn burn_item_internal(2424 &self,2425 from: T::CrossAccountId,2426 token: TokenId,2427 amount: u128,2428 ) -> DispatchResult;2429}24302431243224332434pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2435 let post_info = PostDispatchInfo {2436 actual_weight: Some(weight),2437 pays_fee: Pays::Yes,2438 };2439 match res {2440 Ok(()) => Ok(post_info),2441 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2442 }2443}24442445impl<T: Config> From<PropertiesError> for Error<T> {2446 fn from(error: PropertiesError) -> Self {2447 match error {2448 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2449 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2450 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2451 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2452 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2453 }2454 }2455}24562457245824592460246124622463pub struct PropertyWriter<'a, WriterVariant, T, Handle> {2464 collection: &'a Handle,2465 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2466 _phantom: PhantomData<(T, WriterVariant)>,2467}24682469impl<'a, T, Handle, WriterVariant> PropertyWriter<'a, WriterVariant, T, Handle>2470where2471 T: Config,2472 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2473{2474 fn internal_write_token_properties(2475 &mut self,2476 token_id: TokenId,2477 mut token_lazy_info: PropertyWriterLazyTokenInfo,2478 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2479 log: evm_coder::ethereum::Log,2480 ) -> DispatchResult {2481 for (key, value) in properties_updates {2482 let permission = self2483 .collection_lazy_info2484 .property_permissions2485 .value()2486 .get(&key)2487 .cloned()2488 .unwrap_or_else(PropertyPermission::none);24892490 match permission {2491 PropertyPermission { mutable: false, .. }2492 if token_lazy_info2493 .stored_properties2494 .value()2495 .get(&key)2496 .is_some() =>2497 {2498 return Err(<Error<T>>::NoPermission.into());2499 }25002501 PropertyPermission {2502 collection_admin,2503 token_owner,2504 ..2505 } => check_token_permissions::<T>(2506 collection_admin,2507 token_owner,2508 &mut self.collection_lazy_info.is_collection_admin,2509 &mut token_lazy_info.is_token_owner,2510 &mut token_lazy_info.is_token_exist,2511 )?,2512 }25132514 match value {2515 Some(value) => {2516 token_lazy_info2517 .stored_properties2518 .value_mut()2519 .try_set(key.clone(), value)2520 .map_err(<Error<T>>::from)?;25212522 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2523 self.collection.id,2524 token_id,2525 key,2526 ));2527 }2528 None => {2529 token_lazy_info2530 .stored_properties2531 .value_mut()2532 .remove(&key)2533 .map_err(<Error<T>>::from)?;25342535 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2536 self.collection.id,2537 token_id,2538 key,2539 ));2540 }2541 }2542 }25432544 let properties_changed = token_lazy_info.stored_properties.has_value();2545 if properties_changed {2546 <PalletEvm<T>>::deposit_log(log);25472548 self.collection2549 .set_token_properties_raw(token_id, token_lazy_info.stored_properties.into_inner());2550 }25512552 Ok(())2553 }2554}25552556255725582559pub struct PropertyWriterLazyCollectionInfo<'a> {2560 is_collection_admin: LazyValue<'a, bool>,2561 property_permissions: LazyValue<'a, PropertiesPermissionMap>,2562}2563256425652566pub struct PropertyWriterLazyTokenInfo<'a> {2567 is_token_exist: LazyValue<'a, bool>,2568 is_token_owner: LazyValue<'a, Result<bool, DispatchError>>,2569 stored_properties: LazyValue<'a, TokenProperties>,2570}25712572impl<'a> PropertyWriterLazyTokenInfo<'a> {2573 2574 pub fn new(2575 check_token_exist: impl FnOnce() -> bool + 'a,2576 check_token_owner: impl FnOnce() -> Result<bool, DispatchError> + 'a,2577 get_token_properties: impl FnOnce() -> TokenProperties + 'a,2578 ) -> Self {2579 Self {2580 is_token_exist: LazyValue::new(check_token_exist),2581 is_token_owner: LazyValue::new(check_token_owner),2582 stored_properties: LazyValue::new(get_token_properties),2583 }2584 }2585}2586258725882589pub struct NewTokenPropertyWriter<T>(PhantomData<T>);2590impl<T: Config> NewTokenPropertyWriter<T> {2591 2592 pub fn new<'a, Handle>(2593 collection: &'a Handle,2594 sender: &'a T::CrossAccountId,2595 ) -> PropertyWriter<'a, Self, T, Handle>2596 where2597 T: Config,2598 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2599 {2600 PropertyWriter {2601 collection,2602 collection_lazy_info: PropertyWriterLazyCollectionInfo {2603 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2604 property_permissions: LazyValue::new(|| {2605 <Pallet<T>>::property_permissions(collection.id)2606 }),2607 },2608 _phantom: PhantomData,2609 }2610 }2611}26122613impl<'a, T, Handle> PropertyWriter<'a, NewTokenPropertyWriter<T>, T, Handle>2614where2615 T: Config,2616 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2617{2618 2619 pub fn write_token_properties(2620 &mut self,2621 mint_target_is_sender: bool,2622 token_id: TokenId,2623 properties_updates: impl Iterator<Item = Property>,2624 log: evm_coder::ethereum::Log,2625 ) -> DispatchResult {2626 let check_token_exist = || {2627 debug_assert!(self.collection.token_exists(token_id));2628 true2629 };26302631 let check_token_owner = || Ok(mint_target_is_sender);26322633 let get_token_properties = || {2634 debug_assert!(self.collection.get_token_properties_raw(token_id).is_none());2635 TokenProperties::new()2636 };26372638 self.internal_write_token_properties(2639 token_id,2640 PropertyWriterLazyTokenInfo::new(2641 check_token_exist,2642 check_token_owner,2643 get_token_properties,2644 ),2645 properties_updates.map(|p| (p.key, Some(p.value))),2646 log,2647 )2648 }2649}2650265126522653pub struct ExistingTokenPropertyWriter<T>(PhantomData<T>);2654impl<T: Config> ExistingTokenPropertyWriter<T> {2655 2656 pub fn new<'a, Handle>(2657 collection: &'a Handle,2658 sender: &'a T::CrossAccountId,2659 ) -> PropertyWriter<'a, Self, T, Handle>2660 where2661 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2662 {2663 PropertyWriter {2664 collection,2665 collection_lazy_info: PropertyWriterLazyCollectionInfo {2666 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2667 property_permissions: LazyValue::new(|| {2668 <Pallet<T>>::property_permissions(collection.id)2669 }),2670 },2671 _phantom: PhantomData,2672 }2673 }2674}26752676impl<'a, T, Handle> PropertyWriter<'a, ExistingTokenPropertyWriter<T>, T, Handle>2677where2678 T: Config,2679 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2680{2681 2682 pub fn write_token_properties(2683 &mut self,2684 sender: &T::CrossAccountId,2685 token_id: TokenId,2686 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2687 nesting_budget: &dyn Budget,2688 log: evm_coder::ethereum::Log,2689 ) -> DispatchResult {2690 let check_token_exist = || self.collection.token_exists(token_id);2691 let check_token_owner = || {2692 self.collection2693 .check_token_indirect_owner(token_id, sender, nesting_budget)2694 };2695 let get_token_properties = || {2696 self.collection2697 .get_token_properties_raw(token_id)2698 .unwrap_or_default()2699 };27002701 self.internal_write_token_properties(2702 token_id,2703 PropertyWriterLazyTokenInfo::new(2704 check_token_exist,2705 check_token_owner,2706 get_token_properties,2707 ),2708 properties_updates,2709 log,2710 )2711 }2712}2713271427152716#[cfg(feature = "runtime-benchmarks")]2717pub struct BenchmarkPropertyWriter<T>(PhantomData<T>);27182719#[cfg(feature = "runtime-benchmarks")]2720impl<T: Config> BenchmarkPropertyWriter<T> {2721 2722 pub fn new<'a, Handle>(2723 collection: &'a Handle,2724 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2725 ) -> PropertyWriter<'a, Self, T, Handle>2726 where2727 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2728 {2729 PropertyWriter {2730 collection,2731 collection_lazy_info,2732 _phantom: PhantomData,2733 }2734 }27352736 2737 pub fn load_collection_info<Handle>(2738 collection_handle: &Handle,2739 sender: &T::CrossAccountId,2740 ) -> PropertyWriterLazyCollectionInfo<'static>2741 where2742 Handle: Deref<Target = CollectionHandle<T>>,2743 {2744 let is_collection_admin = collection_handle.is_owner_or_admin(sender);2745 let property_permissions = <Pallet<T>>::property_permissions(collection_handle.id);27462747 PropertyWriterLazyCollectionInfo {2748 is_collection_admin: LazyValue::new(move || is_collection_admin),2749 property_permissions: LazyValue::new(move || property_permissions),2750 }2751 }27522753 2754 pub fn load_token_properties<Handle>(2755 collection: &Handle,2756 token_id: TokenId,2757 ) -> PropertyWriterLazyTokenInfo2758 where2759 Handle: CommonCollectionOperations<T>,2760 {2761 let stored_properties = collection2762 .get_token_properties_raw(token_id)2763 .unwrap_or_default();27642765 PropertyWriterLazyTokenInfo {2766 is_token_exist: LazyValue::new(|| true),2767 is_token_owner: LazyValue::new(|| Ok(true)),2768 stored_properties: LazyValue::new(move || stored_properties),2769 }2770 }2771}27722773#[cfg(feature = "runtime-benchmarks")]2774impl<'a, T, Handle> PropertyWriter<'a, BenchmarkPropertyWriter<T>, T, Handle>2775where2776 T: Config,2777 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2778{2779 2780 pub fn write_token_properties(2781 &mut self,2782 token_id: TokenId,2783 properties_updates: impl Iterator<Item = Property>,2784 log: evm_coder::ethereum::Log,2785 ) -> DispatchResult {2786 let check_token_exist = || true;2787 let check_token_owner = || Ok(true);2788 let get_token_properties = TokenProperties::new;27892790 self.internal_write_token_properties(2791 token_id,2792 PropertyWriterLazyTokenInfo::new(2793 check_token_exist,2794 check_token_owner,2795 get_token_properties,2796 ),2797 properties_updates.map(|p| (p.key, Some(p.value))),2798 log,2799 )2800 }2801}280228032804280528062807pub fn write_token_properties_total_weight<T: Config, I: Fn(u32) -> Weight>(2808 properties_nums: impl Iterator<Item = u32>,2809 per_token_weight: I,2810) -> Weight {2811 let mut weight = properties_nums2812 .filter_map(|properties_num| {2813 if properties_num > 0 {2814 Some(per_token_weight(properties_num))2815 } else {2816 None2817 }2818 })2819 .fold(Weight::zero(), |a, b| a.saturating_add(b));28202821 if !weight.is_zero() {2822 2823 2824 28252826 weight = weight.saturating_add(<SelfWeightOf<T>>::property_writer_load_collection_info());2827 }28282829 weight2830}28312832#[cfg(any(feature = "tests", test))]2833#[allow(missing_docs)]2834pub mod tests {2835 use crate::{Config, DispatchError, DispatchResult, LazyValue};28362837 const fn to_bool(u: u8) -> bool {2838 u != 02839 }28402841 #[derive(Debug)]2842 pub struct TestCase {2843 pub collection_admin: bool,2844 pub is_collection_admin: bool,2845 pub token_owner: bool,2846 pub is_token_owner: bool,2847 pub no_permission: bool,2848 }28492850 impl TestCase {2851 const fn new(2852 collection_admin: u8,2853 is_collection_admin: u8,2854 token_owner: u8,2855 is_token_owner: u8,2856 no_permission: u8,2857 ) -> Self {2858 Self {2859 collection_admin: to_bool(collection_admin),2860 is_collection_admin: to_bool(is_collection_admin),2861 token_owner: to_bool(token_owner),2862 is_token_owner: to_bool(is_token_owner),2863 no_permission: to_bool(no_permission),2864 }2865 }2866 }28672868 #[rustfmt::skip]2869 pub const TABLE: [TestCase; 16] = [2870 2871 2872 2873 2874 2875 TestCase::new(0, 0, 0, 0, 1),2876 TestCase::new(0, 0, 0, 1, 1),2877 TestCase::new(0, 0, 1, 0, 1),2878 TestCase::new(0, 0, 1, 1, 0),2879 TestCase::new(0, 1, 0, 0, 1),2880 TestCase::new(0, 1, 0, 1, 1),2881 TestCase::new(0, 1, 1, 0, 1),2882 TestCase::new(0, 1, 1, 1, 0),2883 TestCase::new(1, 0, 0, 0, 1),2884 TestCase::new(1, 0, 0, 1, 1),2885 TestCase::new(1, 0, 1, 0, 1),2886 TestCase::new(1, 0, 1, 1, 0),2887 TestCase::new(1, 1, 0, 0, 0),2888 TestCase::new(1, 1, 0, 1, 0),2889 TestCase::new(1, 1, 1, 0, 0),2890 TestCase::new(1, 1, 1, 1, 0),2891 ];28922893 pub fn check_token_permissions<T: Config>(2894 collection_admin_permitted: bool,2895 token_owner_permitted: bool,2896 is_collection_admin: &mut LazyValue<bool>,2897 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>>,2898 check_token_existence: &mut LazyValue<bool>,2899 ) -> DispatchResult {2900 crate::check_token_permissions::<T>(2901 collection_admin_permitted,2902 token_owner_permitted,2903 is_collection_admin,2904 check_token_ownership,2905 check_token_existence,2906 )2907 }2908}