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 Self::init_collection_internal(owner, data)1160 }11611162 fn init_collection_internal(1163 owner: T::CrossAccountId,1164 data: CreateCollectionData<T::CrossAccountId>,1165 ) -> Result<CollectionId, DispatchError> {1166 {1167 ensure!(1168 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1169 Error::<T>::CollectionTokenPrefixLimitExceeded1170 );1171 }11721173 let created_count = <CreatedCollectionCount<T>>::get()1174 .01175 .checked_add(1)1176 .ok_or(ArithmeticError::Overflow)?;1177 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1178 let id = CollectionId(created_count);11791180 1181 ensure!(1182 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1183 <Error<T>>::TotalCollectionsLimitExceeded1184 );11851186 11871188 let collection = Collection {1189 owner: owner.as_sub().clone(),1190 name: data.name,1191 mode: data.mode.clone(),1192 description: data.description,1193 token_prefix: data.token_prefix,1194 sponsorship: data1195 .pending_sponsor1196 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1197 .unwrap_or_default(),1198 limits: data1199 .limits1200 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1201 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1202 permissions: data1203 .permissions1204 .map(|permissions| {1205 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1206 })1207 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1208 flags: data.flags,1209 };12101211 let mut collection_properties = CollectionPropertiesT::new();1212 collection_properties1213 .try_set_from_iter(data.properties.into_iter())1214 .map_err(<Error<T>>::from)?;12151216 CollectionProperties::<T>::insert(id, collection_properties);12171218 let mut token_props_permissions = PropertiesPermissionMap::new();1219 token_props_permissions1220 .try_set_from_iter(data.token_property_permissions.into_iter())1221 .map_err(<Error<T>>::from)?;12221223 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);12241225 let mut admin_amount = 0u32;1226 for admin in data.admin_list.iter() {1227 if !<IsAdmin<T>>::get((id, admin)) {1228 <IsAdmin<T>>::insert((id, admin), true);1229 admin_amount = admin_amount1230 .checked_add(1)1231 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1232 }1233 }1234 ensure!(1235 admin_amount <= Self::collection_admins_limit(),1236 <Error<T>>::CollectionAdminCountExceeded,1237 );1238 <AdminAmount<T>>::insert(id, admin_amount);12391240 <CreatedCollectionCount<T>>::put(created_count);1241 <Pallet<T>>::deposit_event(Event::CollectionCreated(1242 id,1243 data.mode.id(),1244 owner.as_sub().clone(),1245 ));1246 <PalletEvm<T>>::deposit_log(1247 erc::CollectionHelpersEvents::CollectionCreated {1248 owner: *owner.as_eth(),1249 collection_id: eth::collection_id_to_address(id),1250 }1251 .to_log(T::ContractAddress::get()),1252 );1253 <CollectionById<T>>::insert(id, collection);1254 Ok(id)1255 }12561257 1258 1259 1260 1261 pub fn destroy_collection(1262 collection: CollectionHandle<T>,1263 sender: &T::CrossAccountId,1264 ) -> DispatchResult {1265 ensure!(1266 collection.limits.owner_can_destroy(),1267 <Error<T>>::NoPermission,1268 );1269 collection.check_is_owner(sender)?;12701271 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1272 .01273 .checked_add(1)1274 .ok_or(ArithmeticError::Overflow)?;12751276 12771278 <DestroyedCollectionCount<T>>::put(destroyed_collections);1279 <CollectionById<T>>::remove(collection.id);1280 <AdminAmount<T>>::remove(collection.id);1281 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1282 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1283 <CollectionProperties<T>>::remove(collection.id);12841285 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12861287 <PalletEvm<T>>::deposit_log(1288 erc::CollectionHelpersEvents::CollectionDestroyed {1289 collection_id: eth::collection_id_to_address(collection.id),1290 }1291 .to_log(T::ContractAddress::get()),1292 );1293 Ok(())1294 }12951296 1297 1298 1299 1300 1301 1302 1303 1304 #[transactional]1305 fn modify_collection_properties(1306 collection: &CollectionHandle<T>,1307 sender: &T::CrossAccountId,1308 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1309 ) -> DispatchResult {1310 collection.check_is_owner_or_admin(sender)?;13111312 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);13131314 for (key, value) in properties_updates {1315 match value {1316 Some(value) => {1317 stored_properties1318 .try_set(key.clone(), value)1319 .map_err(<Error<T>>::from)?;13201321 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1322 <PalletEvm<T>>::deposit_log(1323 erc::CollectionHelpersEvents::CollectionChanged {1324 collection_id: eth::collection_id_to_address(collection.id),1325 }1326 .to_log(T::ContractAddress::get()),1327 );1328 }1329 None => {1330 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13311332 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1333 <PalletEvm<T>>::deposit_log(1334 erc::CollectionHelpersEvents::CollectionChanged {1335 collection_id: eth::collection_id_to_address(collection.id),1336 }1337 .to_log(T::ContractAddress::get()),1338 );1339 }1340 }1341 }13421343 <CollectionProperties<T>>::set(collection.id, stored_properties);13441345 Ok(())1346 }13471348 1349 1350 1351 1352 1353 1354 pub fn set_allowance_for_all(1355 collection: &CollectionHandle<T>,1356 owner: &T::CrossAccountId,1357 operator: &T::CrossAccountId,1358 approve: bool,1359 set_allowance: impl FnOnce(),1360 log: evm_coder::ethereum::Log,1361 ) -> DispatchResult {1362 if collection.permissions.access() == AccessMode::AllowList {1363 collection.check_allowlist(owner)?;1364 collection.check_allowlist(operator)?;1365 }13661367 Self::ensure_correct_receiver(operator)?;13681369 set_allowance();13701371 <PalletEvm<T>>::deposit_log(log);1372 Self::deposit_event(Event::ApprovedForAll(1373 collection.id,1374 owner.clone(),1375 operator.clone(),1376 approve,1377 ));1378 Ok(())1379 }13801381 1382 1383 1384 1385 1386 pub fn set_collection_property(1387 collection: &CollectionHandle<T>,1388 sender: &T::CrossAccountId,1389 property: Property,1390 ) -> DispatchResult {1391 Self::set_collection_properties(collection, sender, [property].into_iter())1392 }13931394 1395 1396 1397 1398 1399 1400 pub fn set_scoped_collection_property(1401 collection_id: CollectionId,1402 scope: PropertyScope,1403 property: Property,1404 ) -> DispatchResult {1405 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1406 properties.try_scoped_set(scope, property.key, property.value)1407 })1408 .map_err(<Error<T>>::from)?;14091410 Ok(())1411 }14121413 1414 1415 1416 1417 1418 1419 pub fn set_scoped_collection_properties(1420 collection_id: CollectionId,1421 scope: PropertyScope,1422 properties: impl Iterator<Item = Property>,1423 ) -> DispatchResult {1424 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1425 stored_properties.try_scoped_set_from_iter(scope, properties)1426 })1427 .map_err(<Error<T>>::from)?;14281429 Ok(())1430 }14311432 1433 1434 1435 1436 1437 pub fn set_collection_properties(1438 collection: &CollectionHandle<T>,1439 sender: &T::CrossAccountId,1440 properties: impl Iterator<Item = Property>,1441 ) -> DispatchResult {1442 Self::modify_collection_properties(1443 collection,1444 sender,1445 properties.map(|property| (property.key, Some(property.value))),1446 )1447 }14481449 1450 1451 1452 1453 1454 pub fn delete_collection_property(1455 collection: &CollectionHandle<T>,1456 sender: &T::CrossAccountId,1457 property_key: PropertyKey,1458 ) -> DispatchResult {1459 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1460 }14611462 1463 1464 1465 1466 1467 pub fn delete_collection_properties(1468 collection: &CollectionHandle<T>,1469 sender: &T::CrossAccountId,1470 property_keys: impl Iterator<Item = PropertyKey>,1471 ) -> DispatchResult {1472 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1473 }14741475 1476 1477 1478 1479 1480 1481 pub fn set_property_permission_unchecked(1482 collection: CollectionId,1483 property_permission: PropertyKeyPermission,1484 ) -> DispatchResult {1485 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1486 permissions.try_set(property_permission.key, property_permission.permission)1487 })1488 .map_err(<Error<T>>::from)?;1489 Ok(())1490 }14911492 1493 1494 1495 1496 1497 pub fn set_property_permission(1498 collection: &CollectionHandle<T>,1499 sender: &T::CrossAccountId,1500 property_permission: PropertyKeyPermission,1501 ) -> DispatchResult {1502 Self::set_scoped_property_permission(1503 collection,1504 sender,1505 PropertyScope::None,1506 property_permission,1507 )1508 }15091510 1511 1512 1513 1514 1515 1516 pub fn set_scoped_property_permission(1517 collection: &CollectionHandle<T>,1518 sender: &T::CrossAccountId,1519 scope: PropertyScope,1520 property_permission: PropertyKeyPermission,1521 ) -> DispatchResult {1522 collection.check_is_owner_or_admin(sender)?;15231524 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1525 let current_permission = all_permissions.get(&property_permission.key);1526 if matches![1527 current_permission,1528 Some(PropertyPermission { mutable: false, .. })1529 ] {1530 return Err(<Error<T>>::NoPermission.into());1531 }15321533 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1534 let property_permission = property_permission.clone();1535 permissions.try_scoped_set(1536 scope,1537 property_permission.key,1538 property_permission.permission,1539 )1540 })1541 .map_err(<Error<T>>::from)?;15421543 Self::deposit_event(Event::PropertyPermissionSet(1544 collection.id,1545 property_permission.key,1546 ));1547 <PalletEvm<T>>::deposit_log(1548 erc::CollectionHelpersEvents::CollectionChanged {1549 collection_id: eth::collection_id_to_address(collection.id),1550 }1551 .to_log(T::ContractAddress::get()),1552 );15531554 Ok(())1555 }15561557 1558 1559 1560 1561 1562 #[transactional]1563 pub fn set_token_property_permissions(1564 collection: &CollectionHandle<T>,1565 sender: &T::CrossAccountId,1566 property_permissions: Vec<PropertyKeyPermission>,1567 ) -> DispatchResult {1568 Self::set_scoped_token_property_permissions(1569 collection,1570 sender,1571 PropertyScope::None,1572 property_permissions,1573 )1574 }15751576 1577 1578 1579 1580 1581 1582 #[transactional]1583 pub fn set_scoped_token_property_permissions(1584 collection: &CollectionHandle<T>,1585 sender: &T::CrossAccountId,1586 scope: PropertyScope,1587 property_permissions: Vec<PropertyKeyPermission>,1588 ) -> DispatchResult {1589 for prop_pemission in property_permissions {1590 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1591 }15921593 Ok(())1594 }15951596 1597 pub fn get_collection_property(1598 collection_id: CollectionId,1599 key: &PropertyKey,1600 ) -> Option<PropertyValue> {1601 Self::collection_properties(collection_id).get(key).cloned()1602 }16031604 1605 pub fn bytes_keys_to_property_keys(1606 keys: Vec<Vec<u8>>,1607 ) -> Result<Vec<PropertyKey>, DispatchError> {1608 keys.into_iter()1609 .map(|key| -> Result<PropertyKey, DispatchError> {1610 key.try_into()1611 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1612 })1613 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1614 }16151616 1617 pub fn filter_collection_properties(1618 collection_id: CollectionId,1619 keys: Option<Vec<PropertyKey>>,1620 ) -> Result<Vec<Property>, DispatchError> {1621 let properties = Self::collection_properties(collection_id);16221623 let properties = keys1624 .map(|keys| {1625 keys.into_iter()1626 .filter_map(|key| {1627 properties.get(&key).map(|value| Property {1628 key,1629 value: value.clone(),1630 })1631 })1632 .collect()1633 })1634 .unwrap_or_else(|| {1635 properties1636 .into_iter()1637 .map(|(key, value)| Property { key, value })1638 .collect()1639 });16401641 Ok(properties)1642 }16431644 1645 pub fn filter_property_permissions(1646 collection_id: CollectionId,1647 keys: Option<Vec<PropertyKey>>,1648 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1649 let permissions = Self::property_permissions(collection_id);16501651 let key_permissions = keys1652 .map(|keys| {1653 keys.into_iter()1654 .filter_map(|key| {1655 permissions1656 .get(&key)1657 .map(|permission| PropertyKeyPermission {1658 key,1659 permission: permission.clone(),1660 })1661 })1662 .collect()1663 })1664 .unwrap_or_else(|| {1665 permissions1666 .into_iter()1667 .map(|(key, permission)| PropertyKeyPermission { key, permission })1668 .collect()1669 });16701671 Ok(key_permissions)1672 }16731674 1675 1676 1677 pub fn toggle_allowlist(1678 collection: &CollectionHandle<T>,1679 sender: &T::CrossAccountId,1680 user: &T::CrossAccountId,1681 allowed: bool,1682 ) -> DispatchResult {1683 collection.check_is_owner_or_admin(sender)?;16841685 16861687 if allowed {1688 <Allowlist<T>>::insert((collection.id, user), true);1689 Self::deposit_event(Event::<T>::AllowListAddressAdded(1690 collection.id,1691 user.clone(),1692 ));1693 } else {1694 <Allowlist<T>>::remove((collection.id, user));1695 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1696 collection.id,1697 user.clone(),1698 ));1699 }17001701 <PalletEvm<T>>::deposit_log(1702 erc::CollectionHelpersEvents::CollectionChanged {1703 collection_id: eth::collection_id_to_address(collection.id),1704 }1705 .to_log(T::ContractAddress::get()),1706 );17071708 Ok(())1709 }17101711 1712 1713 1714 pub fn toggle_admin(1715 collection: &CollectionHandle<T>,1716 sender: &T::CrossAccountId,1717 user: &T::CrossAccountId,1718 admin: bool,1719 ) -> DispatchResult {1720 collection.check_is_internal()?;1721 collection.check_is_owner(sender)?;17221723 let is_admin = <IsAdmin<T>>::get((collection.id, user));1724 if is_admin == admin {1725 if admin {1726 return Ok(());1727 } else {1728 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1729 }1730 }1731 let amount = <AdminAmount<T>>::get(collection.id);17321733 17341735 if admin {1736 let amount = amount1737 .checked_add(1)1738 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1739 ensure!(1740 amount <= Self::collection_admins_limit(),1741 <Error<T>>::CollectionAdminCountExceeded,1742 );17431744 <AdminAmount<T>>::insert(collection.id, amount);1745 <IsAdmin<T>>::insert((collection.id, user), true);17461747 Self::deposit_event(Event::<T>::CollectionAdminAdded(1748 collection.id,1749 user.clone(),1750 ));1751 } else {1752 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1753 <IsAdmin<T>>::remove((collection.id, user));17541755 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1756 collection.id,1757 user.clone(),1758 ));1759 }17601761 <PalletEvm<T>>::deposit_log(1762 erc::CollectionHelpersEvents::CollectionChanged {1763 collection_id: eth::collection_id_to_address(collection.id),1764 }1765 .to_log(T::ContractAddress::get()),1766 );17671768 Ok(())1769 }17701771 1772 pub fn update_limits(1773 user: &T::CrossAccountId,1774 collection: &mut CollectionHandle<T>,1775 new_limit: CollectionLimits,1776 ) -> DispatchResult {1777 collection.check_is_internal()?;1778 collection.check_is_owner_or_admin(user)?;17791780 collection.limits =1781 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17821783 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1784 <PalletEvm<T>>::deposit_log(1785 erc::CollectionHelpersEvents::CollectionChanged {1786 collection_id: eth::collection_id_to_address(collection.id),1787 }1788 .to_log(T::ContractAddress::get()),1789 );17901791 collection.save()1792 }17931794 1795 fn clamp_limits(1796 mode: CollectionMode,1797 old_limit: &CollectionLimits,1798 mut new_limit: CollectionLimits,1799 ) -> Result<CollectionLimits, DispatchError> {1800 let limits = old_limit;1801 limit_default!(old_limit, new_limit,1802 account_token_ownership_limit => ensure!(1803 new_limit <= MAX_TOKEN_OWNERSHIP,1804 <Error<T>>::CollectionLimitBoundsExceeded,1805 ),1806 sponsored_data_size => ensure!(1807 new_limit <= CUSTOM_DATA_LIMIT,1808 <Error<T>>::CollectionLimitBoundsExceeded,1809 ),18101811 sponsored_data_rate_limit => {},1812 token_limit => ensure!(1813 old_limit >= new_limit && new_limit > 0,1814 <Error<T>>::CollectionTokenLimitExceeded1815 ),18161817 sponsor_transfer_timeout(match mode {1818 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1819 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1820 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1821 }) => ensure!(1822 new_limit <= MAX_SPONSOR_TIMEOUT,1823 <Error<T>>::CollectionLimitBoundsExceeded,1824 ),1825 sponsor_approve_timeout => {},1826 owner_can_transfer => ensure!(1827 !limits.owner_can_transfer_instaled() ||1828 old_limit || !new_limit,1829 <Error<T>>::OwnerPermissionsCantBeReverted,1830 ),1831 owner_can_destroy => ensure!(1832 old_limit || !new_limit,1833 <Error<T>>::OwnerPermissionsCantBeReverted,1834 ),1835 transfers_enabled => {},1836 );1837 Ok(new_limit)1838 }18391840 1841 pub fn update_permissions(1842 user: &T::CrossAccountId,1843 collection: &mut CollectionHandle<T>,1844 new_permission: CollectionPermissions,1845 ) -> DispatchResult {1846 collection.check_is_internal()?;1847 collection.check_is_owner_or_admin(user)?;1848 collection.permissions = Self::clamp_permissions(1849 collection.mode.clone(),1850 &collection.permissions,1851 new_permission,1852 )?;18531854 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1855 <PalletEvm<T>>::deposit_log(1856 erc::CollectionHelpersEvents::CollectionChanged {1857 collection_id: eth::collection_id_to_address(collection.id),1858 }1859 .to_log(T::ContractAddress::get()),1860 );18611862 collection.save()1863 }18641865 1866 fn clamp_permissions(1867 _mode: CollectionMode,1868 old_permission: &CollectionPermissions,1869 mut new_permission: CollectionPermissions,1870 ) -> Result<CollectionPermissions, DispatchError> {1871 limit_default_clone!(old_permission, new_permission,1872 access => {},1873 mint_mode => {},1874 nesting => { },1875 );1876 Ok(new_permission)1877 }18781879 1880 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1881 CollectionProperties::<T>::mutate(collection_id, |properties| {1882 properties.recompute_consumed_space();1883 });18841885 Ok(())1886 }1887}188818891890#[macro_export]1891macro_rules! unsupported {1892 ($runtime:path) => {1893 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1894 };1895}189618971898pub trait CommonWeightInfo<CrossAccountId> {1899 1900 fn create_item(data: &CreateItemData) -> Weight {1901 Self::create_multiple_items(from_ref(data))1902 }19031904 1905 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19061907 1908 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19091910 1911 fn burn_item() -> Weight;19121913 1914 1915 1916 fn set_collection_properties(amount: u32) -> Weight;19171918 1919 1920 1921 fn delete_collection_properties(amount: u32) -> Weight {1922 Self::set_collection_properties(amount)1923 }19241925 1926 1927 1928 fn set_token_properties(amount: u32) -> Weight;19291930 1931 1932 1933 fn delete_token_properties(amount: u32) -> Weight {1934 Self::set_token_properties(amount)1935 }19361937 1938 1939 1940 fn set_token_property_permissions(amount: u32) -> Weight;19411942 1943 fn transfer() -> Weight;19441945 1946 fn approve() -> Weight;19471948 1949 fn approve_from() -> Weight;19501951 1952 fn transfer_from() -> Weight;19531954 1955 fn burn_from() -> Weight;19561957 1958 fn set_allowance_for_all() -> Weight;19591960 1961 fn force_repair_item() -> Weight;1962}196319641965pub trait RefungibleExtensionsWeightInfo {1966 1967 fn repartition() -> Weight;1968}196919701971197219731974pub trait CommonCollectionOperations<T: Config> {1975 1976 1977 1978 1979 1980 1981 fn create_item(1982 &self,1983 sender: T::CrossAccountId,1984 to: T::CrossAccountId,1985 data: CreateItemData,1986 nesting_budget: &dyn Budget,1987 ) -> DispatchResultWithPostInfo;19881989 1990 1991 1992 1993 1994 1995 fn create_multiple_items(1996 &self,1997 sender: T::CrossAccountId,1998 to: T::CrossAccountId,1999 data: Vec<CreateItemData>,2000 nesting_budget: &dyn Budget,2001 ) -> DispatchResultWithPostInfo;20022003 2004 2005 2006 2007 2008 2009 fn create_multiple_items_ex(2010 &self,2011 sender: T::CrossAccountId,2012 data: CreateItemExData<T::CrossAccountId>,2013 nesting_budget: &dyn Budget,2014 ) -> DispatchResultWithPostInfo;20152016 2017 2018 2019 2020 2021 fn burn_item(2022 &self,2023 sender: T::CrossAccountId,2024 token: TokenId,2025 amount: u128,2026 ) -> DispatchResultWithPostInfo;20272028 2029 2030 2031 2032 fn set_collection_properties(2033 &self,2034 sender: T::CrossAccountId,2035 properties: Vec<Property>,2036 ) -> DispatchResultWithPostInfo;20372038 2039 2040 2041 2042 fn delete_collection_properties(2043 &self,2044 sender: &T::CrossAccountId,2045 property_keys: Vec<PropertyKey>,2046 ) -> DispatchResultWithPostInfo;20472048 2049 2050 2051 2052 2053 2054 2055 2056 2057 fn set_token_properties(2058 &self,2059 sender: T::CrossAccountId,2060 token_id: TokenId,2061 properties: Vec<Property>,2062 budget: &dyn Budget,2063 ) -> DispatchResultWithPostInfo;20642065 2066 2067 2068 2069 2070 2071 2072 2073 2074 fn delete_token_properties(2075 &self,2076 sender: T::CrossAccountId,2077 token_id: TokenId,2078 property_keys: Vec<PropertyKey>,2079 budget: &dyn Budget,2080 ) -> DispatchResultWithPostInfo;20812082 2083 2084 2085 fn get_token_properties_raw(&self, token_id: TokenId) -> Option<TokenProperties>;20862087 2088 2089 2090 2091 fn set_token_properties_raw(&self, token_id: TokenId, map: TokenProperties);20922093 2094 2095 2096 2097 2098 2099 fn set_token_property_permissions(2100 &self,2101 sender: &T::CrossAccountId,2102 property_permissions: Vec<PropertyKeyPermission>,2103 ) -> DispatchResultWithPostInfo;21042105 2106 2107 2108 2109 2110 2111 2112 fn transfer(2113 &self,2114 sender: T::CrossAccountId,2115 to: T::CrossAccountId,2116 token: TokenId,2117 amount: u128,2118 budget: &dyn Budget,2119 ) -> DispatchResultWithPostInfo;21202121 2122 2123 2124 2125 2126 2127 fn approve(2128 &self,2129 sender: T::CrossAccountId,2130 spender: T::CrossAccountId,2131 token: TokenId,2132 amount: u128,2133 ) -> DispatchResultWithPostInfo;21342135 2136 2137 2138 2139 2140 2141 2142 fn approve_from(2143 &self,2144 sender: T::CrossAccountId,2145 from: T::CrossAccountId,2146 to: T::CrossAccountId,2147 token: TokenId,2148 amount: u128,2149 ) -> DispatchResultWithPostInfo;21502151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 fn transfer_from(2162 &self,2163 sender: T::CrossAccountId,2164 from: T::CrossAccountId,2165 to: T::CrossAccountId,2166 token: TokenId,2167 amount: u128,2168 budget: &dyn Budget,2169 ) -> DispatchResultWithPostInfo;21702171 2172 2173 2174 2175 2176 2177 2178 2179 2180 fn burn_from(2181 &self,2182 sender: T::CrossAccountId,2183 from: T::CrossAccountId,2184 token: TokenId,2185 amount: u128,2186 budget: &dyn Budget,2187 ) -> DispatchResultWithPostInfo;21882189 2190 2191 2192 2193 2194 2195 fn check_nesting(2196 &self,2197 sender: &T::CrossAccountId,2198 from: (CollectionId, TokenId),2199 under: TokenId,2200 budget: &dyn Budget,2201 ) -> DispatchResult;22022203 2204 2205 2206 2207 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22082209 2210 2211 2212 2213 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22142215 2216 2217 2218 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22192220 2221 fn collection_tokens(&self) -> Vec<TokenId>;22222223 2224 2225 2226 fn token_exists(&self, token: TokenId) -> bool;22272228 2229 fn last_token_id(&self) -> TokenId;22302231 2232 2233 2234 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22352236 2237 2238 2239 2240 2241 fn check_token_indirect_owner(2242 &self,2243 token: TokenId,2244 maybe_owner: &T::CrossAccountId,2245 nesting_budget: &dyn Budget,2246 ) -> Result<bool, DispatchError>;22472248 2249 2250 2251 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22522253 2254 2255 2256 2257 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22582259 2260 2261 2262 2263 2264 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22652266 2267 fn total_supply(&self) -> u32;22682269 2270 2271 2272 fn account_balance(&self, account: T::CrossAccountId) -> u32;22732274 2275 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22762277 2278 fn total_pieces(&self, token: TokenId) -> Option<u128>;22792280 2281 2282 2283 2284 2285 fn allowance(2286 &self,2287 sender: T::CrossAccountId,2288 spender: T::CrossAccountId,2289 token: TokenId,2290 ) -> u128;22912292 2293 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {2294 None2295 }22962297 2298 fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {2299 None2300 }23012302 2303 2304 2305 2306 fn set_allowance_for_all(2307 &self,2308 owner: T::CrossAccountId,2309 operator: T::CrossAccountId,2310 approve: bool,2311 ) -> DispatchResultWithPostInfo;23122313 2314 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23152316 2317 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2318}231923202321pub trait RefungibleExtensions<T>2322where2323 T: Config,2324{2325 2326 2327 2328 2329 2330 2331 2332 fn repartition(2333 &self,2334 sender: &T::CrossAccountId,2335 token: TokenId,2336 amount: u128,2337 ) -> DispatchResultWithPostInfo;2338}233923402341pub trait XcmExtensions<T>2342where2343 T: Config,2344{2345 2346 fn token_has_children(&self, _token: TokenId) -> bool {2347 false2348 }23492350 2351 2352 2353 #[transactional]2354 fn create_item(2355 &self,2356 depositor: &T::CrossAccountId,2357 to: T::CrossAccountId,2358 data: CreateItemData,2359 nesting_budget: &dyn Budget,2360 ) -> Result<TokenId, DispatchError> {2361 if T::CrossTokenAddressMapping::is_token_address(&to) {2362 return unsupported!(T);2363 }23642365 self.create_item_internal(depositor, to, data, nesting_budget)2366 }23672368 2369 fn create_item_internal(2370 &self,2371 depositor: &T::CrossAccountId,2372 to: T::CrossAccountId,2373 data: CreateItemData,2374 nesting_budget: &dyn Budget,2375 ) -> Result<TokenId, DispatchError>;23762377 2378 2379 2380 #[transactional]2381 fn transfer_item(2382 &self,2383 depositor: &T::CrossAccountId,2384 from: &T::CrossAccountId,2385 to: &T::CrossAccountId,2386 token: TokenId,2387 amount: u128,2388 nesting_budget: &dyn Budget,2389 ) -> DispatchResult {2390 if T::CrossTokenAddressMapping::is_token_address(to) {2391 return unsupported!(T);2392 }23932394 self.transfer_item_internal(depositor, from, to, token, amount, nesting_budget)2395 }23962397 2398 fn transfer_item_internal(2399 &self,2400 depositor: &T::CrossAccountId,2401 from: &T::CrossAccountId,2402 to: &T::CrossAccountId,2403 token: TokenId,2404 amount: u128,2405 nesting_budget: &dyn Budget,2406 ) -> DispatchResult;24072408 2409 #[transactional]2410 fn burn_item(&self, from: T::CrossAccountId, token: TokenId, amount: u128) -> DispatchResult {2411 self.burn_item_internal(from, token, amount)2412 }24132414 2415 fn burn_item_internal(2416 &self,2417 from: T::CrossAccountId,2418 token: TokenId,2419 amount: u128,2420 ) -> DispatchResult;2421}24222423242424252426pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2427 let post_info = PostDispatchInfo {2428 actual_weight: Some(weight),2429 pays_fee: Pays::Yes,2430 };2431 match res {2432 Ok(()) => Ok(post_info),2433 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2434 }2435}24362437impl<T: Config> From<PropertiesError> for Error<T> {2438 fn from(error: PropertiesError) -> Self {2439 match error {2440 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2441 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2442 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2443 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2444 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2445 }2446 }2447}24482449245024512452245324542455pub struct PropertyWriter<'a, WriterVariant, T, Handle> {2456 collection: &'a Handle,2457 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2458 _phantom: PhantomData<(T, WriterVariant)>,2459}24602461impl<'a, T, Handle, WriterVariant> PropertyWriter<'a, WriterVariant, T, Handle>2462where2463 T: Config,2464 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2465{2466 fn internal_write_token_properties(2467 &mut self,2468 token_id: TokenId,2469 mut token_lazy_info: PropertyWriterLazyTokenInfo,2470 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2471 log: evm_coder::ethereum::Log,2472 ) -> DispatchResult {2473 for (key, value) in properties_updates {2474 let permission = self2475 .collection_lazy_info2476 .property_permissions2477 .value()2478 .get(&key)2479 .cloned()2480 .unwrap_or_else(PropertyPermission::none);24812482 match permission {2483 PropertyPermission { mutable: false, .. }2484 if token_lazy_info2485 .stored_properties2486 .value()2487 .get(&key)2488 .is_some() =>2489 {2490 return Err(<Error<T>>::NoPermission.into());2491 }24922493 PropertyPermission {2494 collection_admin,2495 token_owner,2496 ..2497 } => check_token_permissions::<T>(2498 collection_admin,2499 token_owner,2500 &mut self.collection_lazy_info.is_collection_admin,2501 &mut token_lazy_info.is_token_owner,2502 &mut token_lazy_info.is_token_exist,2503 )?,2504 }25052506 match value {2507 Some(value) => {2508 token_lazy_info2509 .stored_properties2510 .value_mut()2511 .try_set(key.clone(), value)2512 .map_err(<Error<T>>::from)?;25132514 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2515 self.collection.id,2516 token_id,2517 key,2518 ));2519 }2520 None => {2521 token_lazy_info2522 .stored_properties2523 .value_mut()2524 .remove(&key)2525 .map_err(<Error<T>>::from)?;25262527 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2528 self.collection.id,2529 token_id,2530 key,2531 ));2532 }2533 }2534 }25352536 let properties_changed = token_lazy_info.stored_properties.has_value();2537 if properties_changed {2538 <PalletEvm<T>>::deposit_log(log);25392540 self.collection2541 .set_token_properties_raw(token_id, token_lazy_info.stored_properties.into_inner());2542 }25432544 Ok(())2545 }2546}25472548254925502551pub struct PropertyWriterLazyCollectionInfo<'a> {2552 is_collection_admin: LazyValue<'a, bool>,2553 property_permissions: LazyValue<'a, PropertiesPermissionMap>,2554}2555255625572558pub struct PropertyWriterLazyTokenInfo<'a> {2559 is_token_exist: LazyValue<'a, bool>,2560 is_token_owner: LazyValue<'a, Result<bool, DispatchError>>,2561 stored_properties: LazyValue<'a, TokenProperties>,2562}25632564impl<'a> PropertyWriterLazyTokenInfo<'a> {2565 2566 pub fn new(2567 check_token_exist: impl FnOnce() -> bool + 'a,2568 check_token_owner: impl FnOnce() -> Result<bool, DispatchError> + 'a,2569 get_token_properties: impl FnOnce() -> TokenProperties + 'a,2570 ) -> Self {2571 Self {2572 is_token_exist: LazyValue::new(check_token_exist),2573 is_token_owner: LazyValue::new(check_token_owner),2574 stored_properties: LazyValue::new(get_token_properties),2575 }2576 }2577}2578257925802581pub struct NewTokenPropertyWriter<T>(PhantomData<T>);2582impl<T: Config> NewTokenPropertyWriter<T> {2583 2584 pub fn new<'a, Handle>(2585 collection: &'a Handle,2586 sender: &'a T::CrossAccountId,2587 ) -> PropertyWriter<'a, Self, T, Handle>2588 where2589 T: Config,2590 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2591 {2592 PropertyWriter {2593 collection,2594 collection_lazy_info: PropertyWriterLazyCollectionInfo {2595 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2596 property_permissions: LazyValue::new(|| {2597 <Pallet<T>>::property_permissions(collection.id)2598 }),2599 },2600 _phantom: PhantomData,2601 }2602 }2603}26042605impl<'a, T, Handle> PropertyWriter<'a, NewTokenPropertyWriter<T>, T, Handle>2606where2607 T: Config,2608 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2609{2610 2611 pub fn write_token_properties(2612 &mut self,2613 mint_target_is_sender: bool,2614 token_id: TokenId,2615 properties_updates: impl Iterator<Item = Property>,2616 log: evm_coder::ethereum::Log,2617 ) -> DispatchResult {2618 let check_token_exist = || {2619 debug_assert!(self.collection.token_exists(token_id));2620 true2621 };26222623 let check_token_owner = || Ok(mint_target_is_sender);26242625 let get_token_properties = || {2626 debug_assert!(self.collection.get_token_properties_raw(token_id).is_none());2627 TokenProperties::new()2628 };26292630 self.internal_write_token_properties(2631 token_id,2632 PropertyWriterLazyTokenInfo::new(2633 check_token_exist,2634 check_token_owner,2635 get_token_properties,2636 ),2637 properties_updates.map(|p| (p.key, Some(p.value))),2638 log,2639 )2640 }2641}2642264326442645pub struct ExistingTokenPropertyWriter<T>(PhantomData<T>);2646impl<T: Config> ExistingTokenPropertyWriter<T> {2647 2648 pub fn new<'a, Handle>(2649 collection: &'a Handle,2650 sender: &'a T::CrossAccountId,2651 ) -> PropertyWriter<'a, Self, T, Handle>2652 where2653 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2654 {2655 PropertyWriter {2656 collection,2657 collection_lazy_info: PropertyWriterLazyCollectionInfo {2658 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2659 property_permissions: LazyValue::new(|| {2660 <Pallet<T>>::property_permissions(collection.id)2661 }),2662 },2663 _phantom: PhantomData,2664 }2665 }2666}26672668impl<'a, T, Handle> PropertyWriter<'a, ExistingTokenPropertyWriter<T>, T, Handle>2669where2670 T: Config,2671 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2672{2673 2674 pub fn write_token_properties(2675 &mut self,2676 sender: &T::CrossAccountId,2677 token_id: TokenId,2678 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2679 nesting_budget: &dyn Budget,2680 log: evm_coder::ethereum::Log,2681 ) -> DispatchResult {2682 let check_token_exist = || self.collection.token_exists(token_id);2683 let check_token_owner = || {2684 self.collection2685 .check_token_indirect_owner(token_id, sender, nesting_budget)2686 };2687 let get_token_properties = || {2688 self.collection2689 .get_token_properties_raw(token_id)2690 .unwrap_or_default()2691 };26922693 self.internal_write_token_properties(2694 token_id,2695 PropertyWriterLazyTokenInfo::new(2696 check_token_exist,2697 check_token_owner,2698 get_token_properties,2699 ),2700 properties_updates,2701 log,2702 )2703 }2704}2705270627072708#[cfg(feature = "runtime-benchmarks")]2709pub struct BenchmarkPropertyWriter<T>(PhantomData<T>);27102711#[cfg(feature = "runtime-benchmarks")]2712impl<T: Config> BenchmarkPropertyWriter<T> {2713 2714 pub fn new<'a, Handle>(2715 collection: &'a Handle,2716 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2717 ) -> PropertyWriter<'a, Self, T, Handle>2718 where2719 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2720 {2721 PropertyWriter {2722 collection,2723 collection_lazy_info,2724 _phantom: PhantomData,2725 }2726 }27272728 2729 pub fn load_collection_info<Handle>(2730 collection_handle: &Handle,2731 sender: &T::CrossAccountId,2732 ) -> PropertyWriterLazyCollectionInfo<'static>2733 where2734 Handle: Deref<Target = CollectionHandle<T>>,2735 {2736 let is_collection_admin = collection_handle.is_owner_or_admin(sender);2737 let property_permissions = <Pallet<T>>::property_permissions(collection_handle.id);27382739 PropertyWriterLazyCollectionInfo {2740 is_collection_admin: LazyValue::new(move || is_collection_admin),2741 property_permissions: LazyValue::new(move || property_permissions),2742 }2743 }27442745 2746 pub fn load_token_properties<Handle>(2747 collection: &Handle,2748 token_id: TokenId,2749 ) -> PropertyWriterLazyTokenInfo2750 where2751 Handle: CommonCollectionOperations<T>,2752 {2753 let stored_properties = collection2754 .get_token_properties_raw(token_id)2755 .unwrap_or_default();27562757 PropertyWriterLazyTokenInfo {2758 is_token_exist: LazyValue::new(|| true),2759 is_token_owner: LazyValue::new(|| Ok(true)),2760 stored_properties: LazyValue::new(move || stored_properties),2761 }2762 }2763}27642765#[cfg(feature = "runtime-benchmarks")]2766impl<'a, T, Handle> PropertyWriter<'a, BenchmarkPropertyWriter<T>, T, Handle>2767where2768 T: Config,2769 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2770{2771 2772 pub fn write_token_properties(2773 &mut self,2774 token_id: TokenId,2775 properties_updates: impl Iterator<Item = Property>,2776 log: evm_coder::ethereum::Log,2777 ) -> DispatchResult {2778 let check_token_exist = || true;2779 let check_token_owner = || Ok(true);2780 let get_token_properties = TokenProperties::new;27812782 self.internal_write_token_properties(2783 token_id,2784 PropertyWriterLazyTokenInfo::new(2785 check_token_exist,2786 check_token_owner,2787 get_token_properties,2788 ),2789 properties_updates.map(|p| (p.key, Some(p.value))),2790 log,2791 )2792 }2793}279427952796279727982799pub fn write_token_properties_total_weight<T: Config, I: Fn(u32) -> Weight>(2800 properties_nums: impl Iterator<Item = u32>,2801 per_token_weight: I,2802) -> Weight {2803 let mut weight = properties_nums2804 .filter_map(|properties_num| {2805 if properties_num > 0 {2806 Some(per_token_weight(properties_num))2807 } else {2808 None2809 }2810 })2811 .fold(Weight::zero(), |a, b| a.saturating_add(b));28122813 if !weight.is_zero() {2814 2815 2816 28172818 weight = weight.saturating_add(<SelfWeightOf<T>>::property_writer_load_collection_info());2819 }28202821 weight2822}28232824#[cfg(any(feature = "tests", test))]2825#[allow(missing_docs)]2826pub mod tests {2827 use crate::{Config, DispatchError, DispatchResult, LazyValue};28282829 const fn to_bool(u: u8) -> bool {2830 u != 02831 }28322833 #[derive(Debug)]2834 pub struct TestCase {2835 pub collection_admin: bool,2836 pub is_collection_admin: bool,2837 pub token_owner: bool,2838 pub is_token_owner: bool,2839 pub no_permission: bool,2840 }28412842 impl TestCase {2843 const fn new(2844 collection_admin: u8,2845 is_collection_admin: u8,2846 token_owner: u8,2847 is_token_owner: u8,2848 no_permission: u8,2849 ) -> Self {2850 Self {2851 collection_admin: to_bool(collection_admin),2852 is_collection_admin: to_bool(is_collection_admin),2853 token_owner: to_bool(token_owner),2854 is_token_owner: to_bool(is_token_owner),2855 no_permission: to_bool(no_permission),2856 }2857 }2858 }28592860 #[rustfmt::skip]2861 pub const TABLE: [TestCase; 16] = [2862 2863 2864 2865 2866 2867 TestCase::new(0, 0, 0, 0, 1),2868 TestCase::new(0, 0, 0, 1, 1),2869 TestCase::new(0, 0, 1, 0, 1),2870 TestCase::new(0, 0, 1, 1, 0),2871 TestCase::new(0, 1, 0, 0, 1),2872 TestCase::new(0, 1, 0, 1, 1),2873 TestCase::new(0, 1, 1, 0, 1),2874 TestCase::new(0, 1, 1, 1, 0),2875 TestCase::new(1, 0, 0, 0, 1),2876 TestCase::new(1, 0, 0, 1, 1),2877 TestCase::new(1, 0, 1, 0, 1),2878 TestCase::new(1, 0, 1, 1, 0),2879 TestCase::new(1, 1, 0, 0, 0),2880 TestCase::new(1, 1, 0, 1, 0),2881 TestCase::new(1, 1, 1, 0, 0),2882 TestCase::new(1, 1, 1, 1, 0),2883 ];28842885 pub fn check_token_permissions<T: Config>(2886 collection_admin_permitted: bool,2887 token_owner_permitted: bool,2888 is_collection_admin: &mut LazyValue<bool>,2889 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>>,2890 check_token_existence: &mut LazyValue<bool>,2891 ) -> DispatchResult {2892 crate::check_token_permissions::<T>(2893 collection_admin_permitted,2894 token_owner_permitted,2895 is_collection_admin,2896 check_token_ownership,2897 check_token_existence,2898 )2899 }2900}