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 pub fn init_collection(1134 owner: T::CrossAccountId,1135 payer: T::CrossAccountId,1136 data: CreateCollectionData<T::CrossAccountId>,1137 ) -> Result<CollectionId, DispatchError> {1138 ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);11391140 1141 {1142 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1143 imbalance.subsume(<T as Config>::Currency::deposit(1144 &T::TreasuryAccountId::get(),1145 T::CollectionCreationPrice::get(),1146 Precision::Exact,1147 )?);1148 let credit =1149 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1150 .map_err(|_| Error::<T>::NotSufficientFounds)?;11511152 debug_assert!(credit.peek().is_zero())1153 }11541155 Self::init_collection_internal(owner, data)1156 }11571158 1159 pub fn init_foreign_collection(1160 owner: T::CrossAccountId,1161 mut data: CreateCollectionData<T::CrossAccountId>,1162 ) -> Result<CollectionId, DispatchError> {1163 data.flags.foreign = true;1164 let id = Self::init_collection_internal(owner, data)?;1165 Ok(id)1166 }11671168 fn init_collection_internal(1169 owner: T::CrossAccountId,1170 data: CreateCollectionData<T::CrossAccountId>,1171 ) -> Result<CollectionId, DispatchError> {1172 {1173 ensure!(1174 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1175 Error::<T>::CollectionTokenPrefixLimitExceeded1176 );1177 }11781179 let created_count = <CreatedCollectionCount<T>>::get()1180 .01181 .checked_add(1)1182 .ok_or(ArithmeticError::Overflow)?;1183 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1184 let id = CollectionId(created_count);11851186 1187 ensure!(1188 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1189 <Error<T>>::TotalCollectionsLimitExceeded1190 );11911192 11931194 let collection = Collection {1195 owner: owner.as_sub().clone(),1196 name: data.name,1197 mode: data.mode.clone(),1198 description: data.description,1199 token_prefix: data.token_prefix,1200 sponsorship: data1201 .pending_sponsor1202 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1203 .unwrap_or_default(),1204 limits: data1205 .limits1206 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1207 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1208 permissions: data1209 .permissions1210 .map(|permissions| {1211 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1212 })1213 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1214 flags: data.flags,1215 };12161217 let mut collection_properties = CollectionPropertiesT::new();1218 collection_properties1219 .try_set_from_iter(data.properties.into_iter())1220 .map_err(<Error<T>>::from)?;12211222 CollectionProperties::<T>::insert(id, collection_properties);12231224 let mut token_props_permissions = PropertiesPermissionMap::new();1225 token_props_permissions1226 .try_set_from_iter(data.token_property_permissions.into_iter())1227 .map_err(<Error<T>>::from)?;12281229 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);12301231 let mut admin_amount = 0u32;1232 for admin in data.admin_list.iter() {1233 if !<IsAdmin<T>>::get((id, admin)) {1234 <IsAdmin<T>>::insert((id, admin), true);1235 admin_amount = admin_amount1236 .checked_add(1)1237 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1238 }1239 }1240 ensure!(1241 admin_amount <= Self::collection_admins_limit(),1242 <Error<T>>::CollectionAdminCountExceeded,1243 );1244 <AdminAmount<T>>::insert(id, admin_amount);12451246 <CreatedCollectionCount<T>>::put(created_count);1247 <Pallet<T>>::deposit_event(Event::CollectionCreated(1248 id,1249 data.mode.id(),1250 owner.as_sub().clone(),1251 ));1252 <PalletEvm<T>>::deposit_log(1253 erc::CollectionHelpersEvents::CollectionCreated {1254 owner: *owner.as_eth(),1255 collection_id: eth::collection_id_to_address(id),1256 }1257 .to_log(T::ContractAddress::get()),1258 );1259 <CollectionById<T>>::insert(id, collection);1260 Ok(id)1261 }12621263 1264 1265 1266 1267 pub fn destroy_collection(1268 collection: CollectionHandle<T>,1269 sender: &T::CrossAccountId,1270 ) -> DispatchResult {1271 ensure!(1272 collection.limits.owner_can_destroy(),1273 <Error<T>>::NoPermission,1274 );1275 collection.check_is_owner(sender)?;12761277 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1278 .01279 .checked_add(1)1280 .ok_or(ArithmeticError::Overflow)?;12811282 12831284 <DestroyedCollectionCount<T>>::put(destroyed_collections);1285 <CollectionById<T>>::remove(collection.id);1286 <AdminAmount<T>>::remove(collection.id);1287 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1288 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1289 <CollectionProperties<T>>::remove(collection.id);12901291 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12921293 <PalletEvm<T>>::deposit_log(1294 erc::CollectionHelpersEvents::CollectionDestroyed {1295 collection_id: eth::collection_id_to_address(collection.id),1296 }1297 .to_log(T::ContractAddress::get()),1298 );1299 Ok(())1300 }13011302 1303 1304 1305 1306 1307 1308 1309 1310 #[transactional]1311 fn modify_collection_properties(1312 collection: &CollectionHandle<T>,1313 sender: &T::CrossAccountId,1314 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1315 ) -> DispatchResult {1316 collection.check_is_owner_or_admin(sender)?;13171318 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);13191320 for (key, value) in properties_updates {1321 match value {1322 Some(value) => {1323 stored_properties1324 .try_set(key.clone(), value)1325 .map_err(<Error<T>>::from)?;13261327 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1328 <PalletEvm<T>>::deposit_log(1329 erc::CollectionHelpersEvents::CollectionChanged {1330 collection_id: eth::collection_id_to_address(collection.id),1331 }1332 .to_log(T::ContractAddress::get()),1333 );1334 }1335 None => {1336 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13371338 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1339 <PalletEvm<T>>::deposit_log(1340 erc::CollectionHelpersEvents::CollectionChanged {1341 collection_id: eth::collection_id_to_address(collection.id),1342 }1343 .to_log(T::ContractAddress::get()),1344 );1345 }1346 }1347 }13481349 <CollectionProperties<T>>::set(collection.id, stored_properties);13501351 Ok(())1352 }13531354 1355 1356 1357 1358 1359 1360 pub fn set_allowance_for_all(1361 collection: &CollectionHandle<T>,1362 owner: &T::CrossAccountId,1363 operator: &T::CrossAccountId,1364 approve: bool,1365 set_allowance: impl FnOnce(),1366 log: evm_coder::ethereum::Log,1367 ) -> DispatchResult {1368 if collection.permissions.access() == AccessMode::AllowList {1369 collection.check_allowlist(owner)?;1370 collection.check_allowlist(operator)?;1371 }13721373 Self::ensure_correct_receiver(operator)?;13741375 set_allowance();13761377 <PalletEvm<T>>::deposit_log(log);1378 Self::deposit_event(Event::ApprovedForAll(1379 collection.id,1380 owner.clone(),1381 operator.clone(),1382 approve,1383 ));1384 Ok(())1385 }13861387 1388 1389 1390 1391 1392 pub fn set_collection_property(1393 collection: &CollectionHandle<T>,1394 sender: &T::CrossAccountId,1395 property: Property,1396 ) -> DispatchResult {1397 Self::set_collection_properties(collection, sender, [property].into_iter())1398 }13991400 1401 1402 1403 1404 1405 1406 pub fn set_scoped_collection_property(1407 collection_id: CollectionId,1408 scope: PropertyScope,1409 property: Property,1410 ) -> DispatchResult {1411 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1412 properties.try_scoped_set(scope, property.key, property.value)1413 })1414 .map_err(<Error<T>>::from)?;14151416 Ok(())1417 }14181419 1420 1421 1422 1423 1424 1425 pub fn set_scoped_collection_properties(1426 collection_id: CollectionId,1427 scope: PropertyScope,1428 properties: impl Iterator<Item = Property>,1429 ) -> DispatchResult {1430 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1431 stored_properties.try_scoped_set_from_iter(scope, properties)1432 })1433 .map_err(<Error<T>>::from)?;14341435 Ok(())1436 }14371438 1439 1440 1441 1442 1443 pub fn set_collection_properties(1444 collection: &CollectionHandle<T>,1445 sender: &T::CrossAccountId,1446 properties: impl Iterator<Item = Property>,1447 ) -> DispatchResult {1448 Self::modify_collection_properties(1449 collection,1450 sender,1451 properties.map(|property| (property.key, Some(property.value))),1452 )1453 }14541455 1456 1457 1458 1459 1460 pub fn delete_collection_property(1461 collection: &CollectionHandle<T>,1462 sender: &T::CrossAccountId,1463 property_key: PropertyKey,1464 ) -> DispatchResult {1465 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1466 }14671468 1469 1470 1471 1472 1473 pub fn delete_collection_properties(1474 collection: &CollectionHandle<T>,1475 sender: &T::CrossAccountId,1476 property_keys: impl Iterator<Item = PropertyKey>,1477 ) -> DispatchResult {1478 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1479 }14801481 1482 1483 1484 1485 1486 1487 pub fn set_property_permission_unchecked(1488 collection: CollectionId,1489 property_permission: PropertyKeyPermission,1490 ) -> DispatchResult {1491 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1492 permissions.try_set(property_permission.key, property_permission.permission)1493 })1494 .map_err(<Error<T>>::from)?;1495 Ok(())1496 }14971498 1499 1500 1501 1502 1503 pub fn set_property_permission(1504 collection: &CollectionHandle<T>,1505 sender: &T::CrossAccountId,1506 property_permission: PropertyKeyPermission,1507 ) -> DispatchResult {1508 Self::set_scoped_property_permission(1509 collection,1510 sender,1511 PropertyScope::None,1512 property_permission,1513 )1514 }15151516 1517 1518 1519 1520 1521 1522 pub fn set_scoped_property_permission(1523 collection: &CollectionHandle<T>,1524 sender: &T::CrossAccountId,1525 scope: PropertyScope,1526 property_permission: PropertyKeyPermission,1527 ) -> DispatchResult {1528 collection.check_is_owner_or_admin(sender)?;15291530 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1531 let current_permission = all_permissions.get(&property_permission.key);1532 if matches![1533 current_permission,1534 Some(PropertyPermission { mutable: false, .. })1535 ] {1536 return Err(<Error<T>>::NoPermission.into());1537 }15381539 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1540 let property_permission = property_permission.clone();1541 permissions.try_scoped_set(1542 scope,1543 property_permission.key,1544 property_permission.permission,1545 )1546 })1547 .map_err(<Error<T>>::from)?;15481549 Self::deposit_event(Event::PropertyPermissionSet(1550 collection.id,1551 property_permission.key,1552 ));1553 <PalletEvm<T>>::deposit_log(1554 erc::CollectionHelpersEvents::CollectionChanged {1555 collection_id: eth::collection_id_to_address(collection.id),1556 }1557 .to_log(T::ContractAddress::get()),1558 );15591560 Ok(())1561 }15621563 1564 1565 1566 1567 1568 #[transactional]1569 pub fn set_token_property_permissions(1570 collection: &CollectionHandle<T>,1571 sender: &T::CrossAccountId,1572 property_permissions: Vec<PropertyKeyPermission>,1573 ) -> DispatchResult {1574 Self::set_scoped_token_property_permissions(1575 collection,1576 sender,1577 PropertyScope::None,1578 property_permissions,1579 )1580 }15811582 1583 1584 1585 1586 1587 1588 #[transactional]1589 pub fn set_scoped_token_property_permissions(1590 collection: &CollectionHandle<T>,1591 sender: &T::CrossAccountId,1592 scope: PropertyScope,1593 property_permissions: Vec<PropertyKeyPermission>,1594 ) -> DispatchResult {1595 for prop_pemission in property_permissions {1596 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1597 }15981599 Ok(())1600 }16011602 1603 pub fn get_collection_property(1604 collection_id: CollectionId,1605 key: &PropertyKey,1606 ) -> Option<PropertyValue> {1607 Self::collection_properties(collection_id).get(key).cloned()1608 }16091610 1611 pub fn bytes_keys_to_property_keys(1612 keys: Vec<Vec<u8>>,1613 ) -> Result<Vec<PropertyKey>, DispatchError> {1614 keys.into_iter()1615 .map(|key| -> Result<PropertyKey, DispatchError> {1616 key.try_into()1617 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1618 })1619 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1620 }16211622 1623 pub fn filter_collection_properties(1624 collection_id: CollectionId,1625 keys: Option<Vec<PropertyKey>>,1626 ) -> Result<Vec<Property>, DispatchError> {1627 let properties = Self::collection_properties(collection_id);16281629 let properties = keys1630 .map(|keys| {1631 keys.into_iter()1632 .filter_map(|key| {1633 properties.get(&key).map(|value| Property {1634 key,1635 value: value.clone(),1636 })1637 })1638 .collect()1639 })1640 .unwrap_or_else(|| {1641 properties1642 .into_iter()1643 .map(|(key, value)| Property { key, value })1644 .collect()1645 });16461647 Ok(properties)1648 }16491650 1651 pub fn filter_property_permissions(1652 collection_id: CollectionId,1653 keys: Option<Vec<PropertyKey>>,1654 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1655 let permissions = Self::property_permissions(collection_id);16561657 let key_permissions = keys1658 .map(|keys| {1659 keys.into_iter()1660 .filter_map(|key| {1661 permissions1662 .get(&key)1663 .map(|permission| PropertyKeyPermission {1664 key,1665 permission: permission.clone(),1666 })1667 })1668 .collect()1669 })1670 .unwrap_or_else(|| {1671 permissions1672 .into_iter()1673 .map(|(key, permission)| PropertyKeyPermission { key, permission })1674 .collect()1675 });16761677 Ok(key_permissions)1678 }16791680 1681 1682 1683 pub fn toggle_allowlist(1684 collection: &CollectionHandle<T>,1685 sender: &T::CrossAccountId,1686 user: &T::CrossAccountId,1687 allowed: bool,1688 ) -> DispatchResult {1689 collection.check_is_owner_or_admin(sender)?;16901691 16921693 if allowed {1694 <Allowlist<T>>::insert((collection.id, user), true);1695 Self::deposit_event(Event::<T>::AllowListAddressAdded(1696 collection.id,1697 user.clone(),1698 ));1699 } else {1700 <Allowlist<T>>::remove((collection.id, user));1701 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1702 collection.id,1703 user.clone(),1704 ));1705 }17061707 <PalletEvm<T>>::deposit_log(1708 erc::CollectionHelpersEvents::CollectionChanged {1709 collection_id: eth::collection_id_to_address(collection.id),1710 }1711 .to_log(T::ContractAddress::get()),1712 );17131714 Ok(())1715 }17161717 1718 1719 1720 pub fn toggle_admin(1721 collection: &CollectionHandle<T>,1722 sender: &T::CrossAccountId,1723 user: &T::CrossAccountId,1724 admin: bool,1725 ) -> DispatchResult {1726 collection.check_is_internal()?;1727 collection.check_is_owner(sender)?;17281729 let is_admin = <IsAdmin<T>>::get((collection.id, user));1730 if is_admin == admin {1731 if admin {1732 return Ok(());1733 } else {1734 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1735 }1736 }1737 let amount = <AdminAmount<T>>::get(collection.id);17381739 17401741 if admin {1742 let amount = amount1743 .checked_add(1)1744 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1745 ensure!(1746 amount <= Self::collection_admins_limit(),1747 <Error<T>>::CollectionAdminCountExceeded,1748 );17491750 <AdminAmount<T>>::insert(collection.id, amount);1751 <IsAdmin<T>>::insert((collection.id, user), true);17521753 Self::deposit_event(Event::<T>::CollectionAdminAdded(1754 collection.id,1755 user.clone(),1756 ));1757 } else {1758 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1759 <IsAdmin<T>>::remove((collection.id, user));17601761 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1762 collection.id,1763 user.clone(),1764 ));1765 }17661767 <PalletEvm<T>>::deposit_log(1768 erc::CollectionHelpersEvents::CollectionChanged {1769 collection_id: eth::collection_id_to_address(collection.id),1770 }1771 .to_log(T::ContractAddress::get()),1772 );17731774 Ok(())1775 }17761777 1778 pub fn update_limits(1779 user: &T::CrossAccountId,1780 collection: &mut CollectionHandle<T>,1781 new_limit: CollectionLimits,1782 ) -> DispatchResult {1783 collection.check_is_internal()?;1784 collection.check_is_owner_or_admin(user)?;17851786 collection.limits =1787 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17881789 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1790 <PalletEvm<T>>::deposit_log(1791 erc::CollectionHelpersEvents::CollectionChanged {1792 collection_id: eth::collection_id_to_address(collection.id),1793 }1794 .to_log(T::ContractAddress::get()),1795 );17961797 collection.save()1798 }17991800 1801 fn clamp_limits(1802 mode: CollectionMode,1803 old_limit: &CollectionLimits,1804 mut new_limit: CollectionLimits,1805 ) -> Result<CollectionLimits, DispatchError> {1806 let limits = old_limit;1807 limit_default!(old_limit, new_limit,1808 account_token_ownership_limit => ensure!(1809 new_limit <= MAX_TOKEN_OWNERSHIP,1810 <Error<T>>::CollectionLimitBoundsExceeded,1811 ),1812 sponsored_data_size => ensure!(1813 new_limit <= CUSTOM_DATA_LIMIT,1814 <Error<T>>::CollectionLimitBoundsExceeded,1815 ),18161817 sponsored_data_rate_limit => {},1818 token_limit => ensure!(1819 old_limit >= new_limit && new_limit > 0,1820 <Error<T>>::CollectionTokenLimitExceeded1821 ),18221823 sponsor_transfer_timeout(match mode {1824 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1825 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1826 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1827 }) => ensure!(1828 new_limit <= MAX_SPONSOR_TIMEOUT,1829 <Error<T>>::CollectionLimitBoundsExceeded,1830 ),1831 sponsor_approve_timeout => {},1832 owner_can_transfer => ensure!(1833 !limits.owner_can_transfer_instaled() ||1834 old_limit || !new_limit,1835 <Error<T>>::OwnerPermissionsCantBeReverted,1836 ),1837 owner_can_destroy => ensure!(1838 old_limit || !new_limit,1839 <Error<T>>::OwnerPermissionsCantBeReverted,1840 ),1841 transfers_enabled => {},1842 );1843 Ok(new_limit)1844 }18451846 1847 pub fn update_permissions(1848 user: &T::CrossAccountId,1849 collection: &mut CollectionHandle<T>,1850 new_permission: CollectionPermissions,1851 ) -> DispatchResult {1852 collection.check_is_internal()?;1853 collection.check_is_owner_or_admin(user)?;1854 collection.permissions = Self::clamp_permissions(1855 collection.mode.clone(),1856 &collection.permissions,1857 new_permission,1858 )?;18591860 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1861 <PalletEvm<T>>::deposit_log(1862 erc::CollectionHelpersEvents::CollectionChanged {1863 collection_id: eth::collection_id_to_address(collection.id),1864 }1865 .to_log(T::ContractAddress::get()),1866 );18671868 collection.save()1869 }18701871 1872 fn clamp_permissions(1873 _mode: CollectionMode,1874 old_permission: &CollectionPermissions,1875 mut new_permission: CollectionPermissions,1876 ) -> Result<CollectionPermissions, DispatchError> {1877 limit_default_clone!(old_permission, new_permission,1878 access => {},1879 mint_mode => {},1880 nesting => { },1881 );1882 Ok(new_permission)1883 }18841885 1886 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1887 CollectionProperties::<T>::mutate(collection_id, |properties| {1888 properties.recompute_consumed_space();1889 });18901891 Ok(())1892 }1893}189418951896#[macro_export]1897macro_rules! unsupported {1898 ($runtime:path) => {1899 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1900 };1901}190219031904pub trait CommonWeightInfo<CrossAccountId> {1905 1906 fn create_item(data: &CreateItemData) -> Weight {1907 Self::create_multiple_items(from_ref(data))1908 }19091910 1911 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19121913 1914 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19151916 1917 fn burn_item() -> Weight;19181919 1920 1921 1922 fn set_collection_properties(amount: u32) -> Weight;19231924 1925 1926 1927 fn delete_collection_properties(amount: u32) -> Weight {1928 Self::set_collection_properties(amount)1929 }19301931 1932 1933 1934 fn set_token_properties(amount: u32) -> Weight;19351936 1937 1938 1939 fn delete_token_properties(amount: u32) -> Weight {1940 Self::set_token_properties(amount)1941 }19421943 1944 1945 1946 fn set_token_property_permissions(amount: u32) -> Weight;19471948 1949 fn transfer() -> Weight;19501951 1952 fn approve() -> Weight;19531954 1955 fn approve_from() -> Weight;19561957 1958 fn transfer_from() -> Weight;19591960 1961 fn burn_from() -> Weight;19621963 1964 fn set_allowance_for_all() -> Weight;19651966 1967 fn force_repair_item() -> Weight;1968}196919701971pub trait RefungibleExtensionsWeightInfo {1972 1973 fn repartition() -> Weight;1974}197519761977197819791980pub trait CommonCollectionOperations<T: Config> {1981 1982 1983 1984 1985 1986 1987 fn create_item(1988 &self,1989 sender: T::CrossAccountId,1990 to: T::CrossAccountId,1991 data: CreateItemData,1992 nesting_budget: &dyn Budget,1993 ) -> DispatchResultWithPostInfo;19941995 1996 1997 1998 1999 2000 2001 fn create_multiple_items(2002 &self,2003 sender: T::CrossAccountId,2004 to: T::CrossAccountId,2005 data: Vec<CreateItemData>,2006 nesting_budget: &dyn Budget,2007 ) -> DispatchResultWithPostInfo;20082009 2010 2011 2012 2013 2014 2015 fn create_multiple_items_ex(2016 &self,2017 sender: T::CrossAccountId,2018 data: CreateItemExData<T::CrossAccountId>,2019 nesting_budget: &dyn Budget,2020 ) -> DispatchResultWithPostInfo;20212022 2023 2024 2025 2026 2027 fn burn_item(2028 &self,2029 sender: T::CrossAccountId,2030 token: TokenId,2031 amount: u128,2032 ) -> DispatchResultWithPostInfo;20332034 2035 2036 2037 2038 fn set_collection_properties(2039 &self,2040 sender: T::CrossAccountId,2041 properties: Vec<Property>,2042 ) -> DispatchResultWithPostInfo;20432044 2045 2046 2047 2048 fn delete_collection_properties(2049 &self,2050 sender: &T::CrossAccountId,2051 property_keys: Vec<PropertyKey>,2052 ) -> DispatchResultWithPostInfo;20532054 2055 2056 2057 2058 2059 2060 2061 2062 2063 fn set_token_properties(2064 &self,2065 sender: T::CrossAccountId,2066 token_id: TokenId,2067 properties: Vec<Property>,2068 budget: &dyn Budget,2069 ) -> DispatchResultWithPostInfo;20702071 2072 2073 2074 2075 2076 2077 2078 2079 2080 fn delete_token_properties(2081 &self,2082 sender: T::CrossAccountId,2083 token_id: TokenId,2084 property_keys: Vec<PropertyKey>,2085 budget: &dyn Budget,2086 ) -> DispatchResultWithPostInfo;20872088 2089 2090 2091 fn get_token_properties_raw(&self, token_id: TokenId) -> Option<TokenProperties>;20922093 2094 2095 2096 2097 fn set_token_properties_raw(&self, token_id: TokenId, map: TokenProperties);20982099 2100 2101 2102 2103 2104 2105 fn set_token_property_permissions(2106 &self,2107 sender: &T::CrossAccountId,2108 property_permissions: Vec<PropertyKeyPermission>,2109 ) -> DispatchResultWithPostInfo;21102111 2112 2113 2114 2115 2116 2117 2118 fn transfer(2119 &self,2120 sender: T::CrossAccountId,2121 to: T::CrossAccountId,2122 token: TokenId,2123 amount: u128,2124 budget: &dyn Budget,2125 ) -> DispatchResultWithPostInfo;21262127 2128 2129 2130 2131 2132 2133 fn approve(2134 &self,2135 sender: T::CrossAccountId,2136 spender: T::CrossAccountId,2137 token: TokenId,2138 amount: u128,2139 ) -> DispatchResultWithPostInfo;21402141 2142 2143 2144 2145 2146 2147 2148 fn approve_from(2149 &self,2150 sender: T::CrossAccountId,2151 from: T::CrossAccountId,2152 to: T::CrossAccountId,2153 token: TokenId,2154 amount: u128,2155 ) -> DispatchResultWithPostInfo;21562157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 fn transfer_from(2168 &self,2169 sender: T::CrossAccountId,2170 from: T::CrossAccountId,2171 to: T::CrossAccountId,2172 token: TokenId,2173 amount: u128,2174 budget: &dyn Budget,2175 ) -> DispatchResultWithPostInfo;21762177 2178 2179 2180 2181 2182 2183 2184 2185 2186 fn burn_from(2187 &self,2188 sender: T::CrossAccountId,2189 from: T::CrossAccountId,2190 token: TokenId,2191 amount: u128,2192 budget: &dyn Budget,2193 ) -> DispatchResultWithPostInfo;21942195 2196 2197 2198 2199 2200 2201 fn check_nesting(2202 &self,2203 sender: &T::CrossAccountId,2204 from: (CollectionId, TokenId),2205 under: TokenId,2206 budget: &dyn Budget,2207 ) -> DispatchResult;22082209 2210 2211 2212 2213 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22142215 2216 2217 2218 2219 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22202221 2222 2223 2224 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22252226 2227 fn collection_tokens(&self) -> Vec<TokenId>;22282229 2230 2231 2232 fn token_exists(&self, token: TokenId) -> bool;22332234 2235 fn last_token_id(&self) -> TokenId;22362237 2238 2239 2240 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22412242 2243 2244 2245 2246 2247 fn check_token_indirect_owner(2248 &self,2249 token: TokenId,2250 maybe_owner: &T::CrossAccountId,2251 nesting_budget: &dyn Budget,2252 ) -> Result<bool, DispatchError>;22532254 2255 2256 2257 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22582259 2260 2261 2262 2263 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22642265 2266 2267 2268 2269 2270 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22712272 2273 fn total_supply(&self) -> u32;22742275 2276 2277 2278 fn account_balance(&self, account: T::CrossAccountId) -> u32;22792280 2281 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22822283 2284 fn total_pieces(&self, token: TokenId) -> Option<u128>;22852286 2287 2288 2289 2290 2291 fn allowance(2292 &self,2293 sender: T::CrossAccountId,2294 spender: T::CrossAccountId,2295 token: TokenId,2296 ) -> u128;22972298 2299 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {2300 None2301 }23022303 2304 fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {2305 None2306 }23072308 2309 2310 2311 2312 fn set_allowance_for_all(2313 &self,2314 owner: T::CrossAccountId,2315 operator: T::CrossAccountId,2316 approve: bool,2317 ) -> DispatchResultWithPostInfo;23182319 2320 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23212322 2323 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2324}232523262327pub trait RefungibleExtensions<T>2328where2329 T: Config,2330{2331 2332 2333 2334 2335 2336 2337 2338 fn repartition(2339 &self,2340 sender: &T::CrossAccountId,2341 token: TokenId,2342 amount: u128,2343 ) -> DispatchResultWithPostInfo;2344}234523462347pub trait XcmExtensions<T>2348where2349 T: Config,2350{2351 2352 fn is_foreign(&self) -> bool;23532354 2355 fn token_has_children(&self, _token: TokenId) -> bool {2356 false2357 }23582359 2360 2361 2362 #[transactional]2363 fn create_item(2364 &self,2365 depositor: &T::CrossAccountId,2366 to: T::CrossAccountId,2367 data: CreateItemData,2368 nesting_budget: &dyn Budget,2369 ) -> Result<TokenId, DispatchError> {2370 if T::CrossTokenAddressMapping::is_token_address(&to) {2371 return unsupported!(T);2372 }23732374 self.create_item_internal(depositor, to, data, nesting_budget)2375 }23762377 2378 fn create_item_internal(2379 &self,2380 depositor: &T::CrossAccountId,2381 to: T::CrossAccountId,2382 data: CreateItemData,2383 nesting_budget: &dyn Budget,2384 ) -> Result<TokenId, DispatchError>;23852386 2387 2388 2389 #[transactional]2390 fn transfer_item(2391 &self,2392 depositor: &T::CrossAccountId,2393 from: &T::CrossAccountId,2394 to: &T::CrossAccountId,2395 token: TokenId,2396 amount: u128,2397 nesting_budget: &dyn Budget,2398 ) -> DispatchResult {2399 if T::CrossTokenAddressMapping::is_token_address(&to) {2400 return unsupported!(T);2401 }24022403 self.transfer_item_internal(depositor, from, to, token, amount, nesting_budget)2404 }24052406 2407 fn transfer_item_internal(2408 &self,2409 depositor: &T::CrossAccountId,2410 from: &T::CrossAccountId,2411 to: &T::CrossAccountId,2412 token: TokenId,2413 amount: u128,2414 nesting_budget: &dyn Budget,2415 ) -> DispatchResult;24162417 2418 #[transactional]2419 fn burn_item(&self, from: T::CrossAccountId, token: TokenId, amount: u128) -> DispatchResult {2420 self.burn_item_internal(from, token, amount)2421 }24222423 2424 fn burn_item_internal(2425 &self,2426 from: T::CrossAccountId,2427 token: TokenId,2428 amount: u128,2429 ) -> DispatchResult;2430}24312432243324342435pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2436 let post_info = PostDispatchInfo {2437 actual_weight: Some(weight),2438 pays_fee: Pays::Yes,2439 };2440 match res {2441 Ok(()) => Ok(post_info),2442 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2443 }2444}24452446impl<T: Config> From<PropertiesError> for Error<T> {2447 fn from(error: PropertiesError) -> Self {2448 match error {2449 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2450 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2451 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2452 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2453 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2454 }2455 }2456}24572458245924602461246224632464pub struct PropertyWriter<'a, WriterVariant, T, Handle> {2465 collection: &'a Handle,2466 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2467 _phantom: PhantomData<(T, WriterVariant)>,2468}24692470impl<'a, T, Handle, WriterVariant> PropertyWriter<'a, WriterVariant, T, Handle>2471where2472 T: Config,2473 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2474{2475 fn internal_write_token_properties(2476 &mut self,2477 token_id: TokenId,2478 mut token_lazy_info: PropertyWriterLazyTokenInfo,2479 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2480 log: evm_coder::ethereum::Log,2481 ) -> DispatchResult {2482 for (key, value) in properties_updates {2483 let permission = self2484 .collection_lazy_info2485 .property_permissions2486 .value()2487 .get(&key)2488 .cloned()2489 .unwrap_or_else(PropertyPermission::none);24902491 match permission {2492 PropertyPermission { mutable: false, .. }2493 if token_lazy_info2494 .stored_properties2495 .value()2496 .get(&key)2497 .is_some() =>2498 {2499 return Err(<Error<T>>::NoPermission.into());2500 }25012502 PropertyPermission {2503 collection_admin,2504 token_owner,2505 ..2506 } => check_token_permissions::<T>(2507 collection_admin,2508 token_owner,2509 &mut self.collection_lazy_info.is_collection_admin,2510 &mut token_lazy_info.is_token_owner,2511 &mut token_lazy_info.is_token_exist,2512 )?,2513 }25142515 match value {2516 Some(value) => {2517 token_lazy_info2518 .stored_properties2519 .value_mut()2520 .try_set(key.clone(), value)2521 .map_err(<Error<T>>::from)?;25222523 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2524 self.collection.id,2525 token_id,2526 key,2527 ));2528 }2529 None => {2530 token_lazy_info2531 .stored_properties2532 .value_mut()2533 .remove(&key)2534 .map_err(<Error<T>>::from)?;25352536 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2537 self.collection.id,2538 token_id,2539 key,2540 ));2541 }2542 }2543 }25442545 let properties_changed = token_lazy_info.stored_properties.has_value();2546 if properties_changed {2547 <PalletEvm<T>>::deposit_log(log);25482549 self.collection2550 .set_token_properties_raw(token_id, token_lazy_info.stored_properties.into_inner());2551 }25522553 Ok(())2554 }2555}25562557255825592560pub struct PropertyWriterLazyCollectionInfo<'a> {2561 is_collection_admin: LazyValue<'a, bool>,2562 property_permissions: LazyValue<'a, PropertiesPermissionMap>,2563}2564256525662567pub struct PropertyWriterLazyTokenInfo<'a> {2568 is_token_exist: LazyValue<'a, bool>,2569 is_token_owner: LazyValue<'a, Result<bool, DispatchError>>,2570 stored_properties: LazyValue<'a, TokenProperties>,2571}25722573impl<'a> PropertyWriterLazyTokenInfo<'a> {2574 2575 pub fn new(2576 check_token_exist: impl FnOnce() -> bool + 'a,2577 check_token_owner: impl FnOnce() -> Result<bool, DispatchError> + 'a,2578 get_token_properties: impl FnOnce() -> TokenProperties + 'a,2579 ) -> Self {2580 Self {2581 is_token_exist: LazyValue::new(check_token_exist),2582 is_token_owner: LazyValue::new(check_token_owner),2583 stored_properties: LazyValue::new(get_token_properties),2584 }2585 }2586}2587258825892590pub struct NewTokenPropertyWriter<T>(PhantomData<T>);2591impl<T: Config> NewTokenPropertyWriter<T> {2592 2593 pub fn new<'a, Handle>(2594 collection: &'a Handle,2595 sender: &'a T::CrossAccountId,2596 ) -> PropertyWriter<'a, Self, T, Handle>2597 where2598 T: Config,2599 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2600 {2601 PropertyWriter {2602 collection,2603 collection_lazy_info: PropertyWriterLazyCollectionInfo {2604 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2605 property_permissions: LazyValue::new(|| {2606 <Pallet<T>>::property_permissions(collection.id)2607 }),2608 },2609 _phantom: PhantomData,2610 }2611 }2612}26132614impl<'a, T, Handle> PropertyWriter<'a, NewTokenPropertyWriter<T>, T, Handle>2615where2616 T: Config,2617 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2618{2619 2620 pub fn write_token_properties(2621 &mut self,2622 mint_target_is_sender: bool,2623 token_id: TokenId,2624 properties_updates: impl Iterator<Item = Property>,2625 log: evm_coder::ethereum::Log,2626 ) -> DispatchResult {2627 let check_token_exist = || {2628 debug_assert!(self.collection.token_exists(token_id));2629 true2630 };26312632 let check_token_owner = || Ok(mint_target_is_sender);26332634 let get_token_properties = || {2635 debug_assert!(self.collection.get_token_properties_raw(token_id).is_none());2636 TokenProperties::new()2637 };26382639 self.internal_write_token_properties(2640 token_id,2641 PropertyWriterLazyTokenInfo::new(2642 check_token_exist,2643 check_token_owner,2644 get_token_properties,2645 ),2646 properties_updates.map(|p| (p.key, Some(p.value))),2647 log,2648 )2649 }2650}2651265226532654pub struct ExistingTokenPropertyWriter<T>(PhantomData<T>);2655impl<T: Config> ExistingTokenPropertyWriter<T> {2656 2657 pub fn new<'a, Handle>(2658 collection: &'a Handle,2659 sender: &'a T::CrossAccountId,2660 ) -> PropertyWriter<'a, Self, T, Handle>2661 where2662 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2663 {2664 PropertyWriter {2665 collection,2666 collection_lazy_info: PropertyWriterLazyCollectionInfo {2667 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2668 property_permissions: LazyValue::new(|| {2669 <Pallet<T>>::property_permissions(collection.id)2670 }),2671 },2672 _phantom: PhantomData,2673 }2674 }2675}26762677impl<'a, T, Handle> PropertyWriter<'a, ExistingTokenPropertyWriter<T>, T, Handle>2678where2679 T: Config,2680 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2681{2682 2683 pub fn write_token_properties(2684 &mut self,2685 sender: &T::CrossAccountId,2686 token_id: TokenId,2687 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2688 nesting_budget: &dyn Budget,2689 log: evm_coder::ethereum::Log,2690 ) -> DispatchResult {2691 let check_token_exist = || self.collection.token_exists(token_id);2692 let check_token_owner = || {2693 self.collection2694 .check_token_indirect_owner(token_id, sender, nesting_budget)2695 };2696 let get_token_properties = || {2697 self.collection2698 .get_token_properties_raw(token_id)2699 .unwrap_or_default()2700 };27012702 self.internal_write_token_properties(2703 token_id,2704 PropertyWriterLazyTokenInfo::new(2705 check_token_exist,2706 check_token_owner,2707 get_token_properties,2708 ),2709 properties_updates,2710 log,2711 )2712 }2713}2714271527162717#[cfg(feature = "runtime-benchmarks")]2718pub struct BenchmarkPropertyWriter<T>(PhantomData<T>);27192720#[cfg(feature = "runtime-benchmarks")]2721impl<T: Config> BenchmarkPropertyWriter<T> {2722 2723 pub fn new<'a, Handle>(2724 collection: &'a Handle,2725 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2726 ) -> PropertyWriter<'a, Self, T, Handle>2727 where2728 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2729 {2730 PropertyWriter {2731 collection,2732 collection_lazy_info,2733 _phantom: PhantomData,2734 }2735 }27362737 2738 pub fn load_collection_info<Handle>(2739 collection_handle: &Handle,2740 sender: &T::CrossAccountId,2741 ) -> PropertyWriterLazyCollectionInfo<'static>2742 where2743 Handle: Deref<Target = CollectionHandle<T>>,2744 {2745 let is_collection_admin = collection_handle.is_owner_or_admin(sender);2746 let property_permissions = <Pallet<T>>::property_permissions(collection_handle.id);27472748 PropertyWriterLazyCollectionInfo {2749 is_collection_admin: LazyValue::new(move || is_collection_admin),2750 property_permissions: LazyValue::new(move || property_permissions),2751 }2752 }27532754 2755 pub fn load_token_properties<Handle>(2756 collection: &Handle,2757 token_id: TokenId,2758 ) -> PropertyWriterLazyTokenInfo2759 where2760 Handle: CommonCollectionOperations<T>,2761 {2762 let stored_properties = collection2763 .get_token_properties_raw(token_id)2764 .unwrap_or_default();27652766 PropertyWriterLazyTokenInfo {2767 is_token_exist: LazyValue::new(|| true),2768 is_token_owner: LazyValue::new(|| Ok(true)),2769 stored_properties: LazyValue::new(move || stored_properties),2770 }2771 }2772}27732774#[cfg(feature = "runtime-benchmarks")]2775impl<'a, T, Handle> PropertyWriter<'a, BenchmarkPropertyWriter<T>, T, Handle>2776where2777 T: Config,2778 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2779{2780 2781 pub fn write_token_properties(2782 &mut self,2783 token_id: TokenId,2784 properties_updates: impl Iterator<Item = Property>,2785 log: evm_coder::ethereum::Log,2786 ) -> DispatchResult {2787 let check_token_exist = || true;2788 let check_token_owner = || Ok(true);2789 let get_token_properties = TokenProperties::new;27902791 self.internal_write_token_properties(2792 token_id,2793 PropertyWriterLazyTokenInfo::new(2794 check_token_exist,2795 check_token_owner,2796 get_token_properties,2797 ),2798 properties_updates.map(|p| (p.key, Some(p.value))),2799 log,2800 )2801 }2802}280328042805280628072808pub fn write_token_properties_total_weight<T: Config, I: Fn(u32) -> Weight>(2809 properties_nums: impl Iterator<Item = u32>,2810 per_token_weight: I,2811) -> Weight {2812 let mut weight = properties_nums2813 .filter_map(|properties_num| {2814 if properties_num > 0 {2815 Some(per_token_weight(properties_num))2816 } else {2817 None2818 }2819 })2820 .fold(Weight::zero(), |a, b| a.saturating_add(b));28212822 if !weight.is_zero() {2823 2824 2825 28262827 weight = weight.saturating_add(<SelfWeightOf<T>>::property_writer_load_collection_info());2828 }28292830 weight2831}28322833#[cfg(any(feature = "tests", test))]2834#[allow(missing_docs)]2835pub mod tests {2836 use crate::{Config, DispatchError, DispatchResult, LazyValue};28372838 const fn to_bool(u: u8) -> bool {2839 u != 02840 }28412842 #[derive(Debug)]2843 pub struct TestCase {2844 pub collection_admin: bool,2845 pub is_collection_admin: bool,2846 pub token_owner: bool,2847 pub is_token_owner: bool,2848 pub no_permission: bool,2849 }28502851 impl TestCase {2852 const fn new(2853 collection_admin: u8,2854 is_collection_admin: u8,2855 token_owner: u8,2856 is_token_owner: u8,2857 no_permission: u8,2858 ) -> Self {2859 Self {2860 collection_admin: to_bool(collection_admin),2861 is_collection_admin: to_bool(is_collection_admin),2862 token_owner: to_bool(token_owner),2863 is_token_owner: to_bool(is_token_owner),2864 no_permission: to_bool(no_permission),2865 }2866 }2867 }28682869 #[rustfmt::skip]2870 pub const TABLE: [TestCase; 16] = [2871 2872 2873 2874 2875 2876 TestCase::new(0, 0, 0, 0, 1),2877 TestCase::new(0, 0, 0, 1, 1),2878 TestCase::new(0, 0, 1, 0, 1),2879 TestCase::new(0, 0, 1, 1, 0),2880 TestCase::new(0, 1, 0, 0, 1),2881 TestCase::new(0, 1, 0, 1, 1),2882 TestCase::new(0, 1, 1, 0, 1),2883 TestCase::new(0, 1, 1, 1, 0),2884 TestCase::new(1, 0, 0, 0, 1),2885 TestCase::new(1, 0, 0, 1, 1),2886 TestCase::new(1, 0, 1, 0, 1),2887 TestCase::new(1, 0, 1, 1, 0),2888 TestCase::new(1, 1, 0, 0, 0),2889 TestCase::new(1, 1, 0, 1, 0),2890 TestCase::new(1, 1, 1, 0, 0),2891 TestCase::new(1, 1, 1, 1, 0),2892 ];28932894 pub fn check_token_permissions<T: Config>(2895 collection_admin_permitted: bool,2896 token_owner_permitted: bool,2897 is_collection_admin: &mut LazyValue<bool>,2898 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>>,2899 check_token_existence: &mut LazyValue<bool>,2900 ) -> DispatchResult {2901 crate::check_token_permissions::<T>(2902 collection_admin_permitted,2903 token_owner_permitted,2904 is_collection_admin,2905 check_token_ownership,2906 check_token_existence,2907 )2908 }2909}