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, AccessMode, Collection, CollectionId, CollectionLimits, CollectionMode,84 CollectionPermissions, CollectionProperties as CollectionPropertiesT, CollectionStats,85 CreateCollectionData, CreateItemData, CreateItemExData, PhantomType, PropertiesError,86 PropertiesPermissionMap, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,87 PropertyScope, PropertyValue, RpcCollection, RpcCollectionFlags, SponsoringRateLimit,88 SponsorshipState, TokenChild, TokenData, TokenId, TokenOwnerError, TokenProperties,89 TrySetProperty, COLLECTION_ADMINS_LIMIT, COLLECTION_NUMBER_LIMIT, CUSTOM_DATA_LIMIT,90 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP,91 MAX_TOKEN_PREFIX_LENGTH, NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,92};93use up_pov_estimate_rpc::PovInfo;9495#[cfg(feature = "runtime-benchmarks")]96pub mod benchmarking;97pub mod dispatch;98pub mod erc;99pub mod eth;100pub mod helpers;101#[allow(missing_docs)]102pub mod weights;103104use weights::WeightInfo;105106107pub type SelfWeightOf<T> = <T as Config>::WeightInfo;108109110111112113114115#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]116pub struct CollectionHandle<T: Config> {117 118 pub id: CollectionId,119 collection: Collection<T::AccountId>,120 121 pub recorder: SubstrateRecorder<T>,122}123124impl<T: Config> WithRecorder<T> for CollectionHandle<T> {125 fn recorder(&self) -> &SubstrateRecorder<T> {126 &self.recorder127 }128 fn into_recorder(self) -> SubstrateRecorder<T> {129 self.recorder130 }131}132133impl<T: Config> CollectionHandle<T> {134 135 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {136 Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))137 }138139 140 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {141 <CollectionById<T>>::get(id).map(|collection| Self {142 id,143 collection,144 recorder,145 })146 }147148 149 150 pub fn new(id: CollectionId) -> Option<Self> {151 Self::new_with_gas_limit(id, u64::MAX)152 }153154 155 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {156 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)157 }158159 160 pub fn consume_store_reads(161 &self,162 reads: u64,163 ) -> pallet_evm_coder_substrate::execution::Result<()> {164 self.recorder().consume_store_reads(reads)165 }166167 168 pub fn consume_store_writes(169 &self,170 writes: u64,171 ) -> pallet_evm_coder_substrate::execution::Result<()> {172 self.recorder().consume_store_writes(writes)173 }174175 176 pub fn consume_store_reads_and_writes(177 &self,178 reads: u64,179 writes: u64,180 ) -> pallet_evm_coder_substrate::execution::Result<()> {181 self.recorder()182 .consume_store_reads_and_writes(reads, writes)183 }184185 186 pub fn save(&self) -> DispatchResult {187 <CollectionById<T>>::insert(self.id, &self.collection);188 Ok(())189 }190191 192 193 194 195 196 pub fn set_sponsor(197 &mut self,198 sender: &T::CrossAccountId,199 sponsor: T::AccountId,200 ) -> DispatchResult {201 self.check_is_internal()?;202 self.check_is_owner_or_admin(sender)?;203204 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());205206 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));207 <PalletEvm<T>>::deposit_log(208 erc::CollectionHelpersEvents::CollectionChanged {209 collection_id: eth::collection_id_to_address(self.id),210 }211 .to_log(T::ContractAddress::get()),212 );213214 self.save()215 }216217 218 219 220 221 222 223 224 225 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {226 self.check_is_internal()?;227228 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());229230 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));231 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));232 <PalletEvm<T>>::deposit_log(233 erc::CollectionHelpersEvents::CollectionChanged {234 collection_id: eth::collection_id_to_address(self.id),235 }236 .to_log(T::ContractAddress::get()),237 );238239 self.save()240 }241242 243 244 245 246 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {247 self.check_is_internal()?;248 ensure!(249 self.collection.sponsorship.pending_sponsor() == Some(sender),250 Error::<T>::ConfirmSponsorshipFail251 );252253 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());254255 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));256 <PalletEvm<T>>::deposit_log(257 erc::CollectionHelpersEvents::CollectionChanged {258 collection_id: eth::collection_id_to_address(self.id),259 }260 .to_log(T::ContractAddress::get()),261 );262263 self.save()264 }265266 267 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {268 self.check_is_internal()?;269 self.check_is_owner_or_admin(sender)?;270271 self.collection.sponsorship = SponsorshipState::Disabled;272273 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));274 <PalletEvm<T>>::deposit_log(275 erc::CollectionHelpersEvents::CollectionChanged {276 collection_id: eth::collection_id_to_address(self.id),277 }278 .to_log(T::ContractAddress::get()),279 );280 self.save()281 }282283 284 285 286 287 pub fn force_remove_sponsor(&mut self) -> DispatchResult {288 self.check_is_internal()?;289290 self.collection.sponsorship = SponsorshipState::Disabled;291292 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));293 <PalletEvm<T>>::deposit_log(294 erc::CollectionHelpersEvents::CollectionChanged {295 collection_id: eth::collection_id_to_address(self.id),296 }297 .to_log(T::ContractAddress::get()),298 );299 self.save()300 }301302 303 304 pub fn check_is_internal(&self) -> DispatchResult {305 if self.flags.external {306 return Err(<Error<T>>::CollectionIsExternal)?;307 }308309 Ok(())310 }311312 313 314 pub fn check_is_external(&self) -> DispatchResult {315 if !self.flags.external {316 return Err(<Error<T>>::CollectionIsInternal)?;317 }318319 Ok(())320 }321}322323impl<T: Config> Deref for CollectionHandle<T> {324 type Target = Collection<T::AccountId>;325326 fn deref(&self) -> &Self::Target {327 &self.collection328 }329}330331impl<T: Config> DerefMut for CollectionHandle<T> {332 fn deref_mut(&mut self) -> &mut Self::Target {333 &mut self.collection334 }335}336337impl<T: Config> CollectionHandle<T> {338 339 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {340 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);341 Ok(())342 }343344 345 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {346 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))347 }348349 350 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {351 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);352 Ok(())353 }354355 356 357 358 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {359 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)360 }361362 363 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {364 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)365 }366367 368 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {369 ensure!(370 <Allowlist<T>>::get((self.id, user)),371 <Error<T>>::AddressNotInAllowlist372 );373 Ok(())374 }375376 377 378 379 pub fn change_owner(380 &mut self,381 caller: T::CrossAccountId,382 new_owner: T::CrossAccountId,383 ) -> DispatchResult {384 self.check_is_internal()?;385 self.check_is_owner(&caller)?;386 self.collection.owner = new_owner.as_sub().clone();387388 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(389 self.id,390 new_owner.as_sub().clone(),391 ));392 <PalletEvm<T>>::deposit_log(393 erc::CollectionHelpersEvents::CollectionChanged {394 collection_id: eth::collection_id_to_address(self.id),395 }396 .to_log(T::ContractAddress::get()),397 );398399 self.save()400 }401}402403#[frame_support::pallet]404pub mod pallet {405406 use dispatch::CollectionDispatch;407 use frame_support::{408 pallet_prelude::*, storage::Key, traits::StorageVersion, Blake2_128Concat,409 };410 use scale_info::TypeInfo;411 use up_data_structs::{mapping::TokenAddressMapping, TokenId};412 use weights::WeightInfo;413414 use super::*;415416 #[pallet::config]417 pub trait Config:418 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo419 {420 421 type WeightInfo: WeightInfo;422423 424 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;425426 427 type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;428429 430 #[pallet::constant]431 type CollectionCreationPrice: Get<432 <<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,433 >;434435 436 type CollectionDispatch: CollectionDispatch<Self>;437438 439 type TreasuryAccountId: Get<Self::AccountId>;440441 442 #[pallet::constant]443 type ContractAddress: Get<H160>;444445 446 type EvmTokenAddressMapping: TokenAddressMapping<H160>;447448 449 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;450 }451452 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);453 454 pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);455456 #[pallet::pallet]457 #[pallet::storage_version(STORAGE_VERSION)]458 pub struct Pallet<T>(_);459460 #[pallet::extra_constants]461 impl<T: Config> Pallet<T> {462 463 pub fn collection_admins_limit() -> u32 {464 COLLECTION_ADMINS_LIMIT465 }466 }467468 #[pallet::genesis_config]469 pub struct GenesisConfig<T>(PhantomData<T>);470471 impl<T: Config> Default for GenesisConfig<T> {472 fn default() -> Self {473 Self(Default::default())474 }475 }476477 #[pallet::genesis_build]478 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {479 fn build(&self) {480 StorageVersion::new(1).put::<Pallet<T>>();481 }482 }483484 impl<T: Config> Pallet<T> {485 486 pub fn deposit_event(event: Event<T>) {487 let event = <T as Config>::RuntimeEvent::from(event);488 let event = event.into();489 <frame_system::Pallet<T>>::deposit_event(event)490 }491 }492493 #[pallet::event]494 pub enum Event<T: Config> {495 496 CollectionCreated(497 498 CollectionId,499 500 u8,501 502 T::AccountId,503 ),504505 506 CollectionDestroyed(507 508 CollectionId,509 ),510511 512 ItemCreated(513 514 CollectionId,515 516 TokenId,517 518 T::CrossAccountId,519 520 u128,521 ),522523 524 ItemDestroyed(525 526 CollectionId,527 528 TokenId,529 530 T::CrossAccountId,531 532 u128,533 ),534535 536 Transfer(537 538 CollectionId,539 540 TokenId,541 542 T::CrossAccountId,543 544 T::CrossAccountId,545 546 u128,547 ),548549 550 Approved(551 552 CollectionId,553 554 TokenId,555 556 T::CrossAccountId,557 558 T::CrossAccountId,559 560 u128,561 ),562563 564 ApprovedForAll(565 566 CollectionId,567 568 T::CrossAccountId,569 570 T::CrossAccountId,571 572 bool,573 ),574575 576 CollectionPropertySet(577 578 CollectionId,579 580 PropertyKey,581 ),582583 584 CollectionPropertyDeleted(585 586 CollectionId,587 588 PropertyKey,589 ),590591 592 TokenPropertySet(593 594 CollectionId,595 596 TokenId,597 598 PropertyKey,599 ),600601 602 TokenPropertyDeleted(603 604 CollectionId,605 606 TokenId,607 608 PropertyKey,609 ),610611 612 PropertyPermissionSet(613 614 CollectionId,615 616 PropertyKey,617 ),618619 620 AllowListAddressAdded(621 622 CollectionId,623 624 T::CrossAccountId,625 ),626627 628 AllowListAddressRemoved(629 630 CollectionId,631 632 T::CrossAccountId,633 ),634635 636 CollectionAdminAdded(637 638 CollectionId,639 640 T::CrossAccountId,641 ),642643 644 CollectionAdminRemoved(645 646 CollectionId,647 648 T::CrossAccountId,649 ),650651 652 CollectionLimitSet(653 654 CollectionId,655 ),656657 658 CollectionOwnerChanged(659 660 CollectionId,661 662 T::AccountId,663 ),664665 666 CollectionPermissionSet(667 668 CollectionId,669 ),670671 672 CollectionSponsorSet(673 674 CollectionId,675 676 T::AccountId,677 ),678679 680 SponsorshipConfirmed(681 682 CollectionId,683 684 T::AccountId,685 ),686687 688 CollectionSponsorRemoved(689 690 CollectionId,691 ),692 }693694 #[pallet::error]695 pub enum Error<T> {696 697 CollectionNotFound,698 699 MustBeTokenOwner,700 701 NoPermission,702 703 CantDestroyNotEmptyCollection,704 705 PublicMintingNotAllowed,706 707 AddressNotInAllowlist,708709 710 CollectionNameLimitExceeded,711 712 CollectionDescriptionLimitExceeded,713 714 CollectionTokenPrefixLimitExceeded,715 716 TotalCollectionsLimitExceeded,717 718 CollectionAdminCountExceeded,719 720 CollectionLimitBoundsExceeded,721 722 OwnerPermissionsCantBeReverted,723 724 TransferNotAllowed,725 726 AccountTokenLimitExceeded,727 728 CollectionTokenLimitExceeded,729 730 MetadataFlagFrozen,731732 733 TokenNotFound,734 735 TokenValueTooLow,736 737 ApprovedValueTooLow,738 739 CantApproveMoreThanOwned,740 741 AddressIsNotEthMirror,742743 744 AddressIsZero,745746 747 UnsupportedOperation,748749 750 NotSufficientFounds,751752 753 UserIsNotAllowedToNest,754 755 SourceCollectionIsNotAllowedToNest,756757 758 CollectionFieldSizeExceeded,759760 761 NoSpaceForProperty,762763 764 PropertyLimitReached,765766 767 PropertyKeyIsTooLong,768769 770 InvalidCharacterInPropertyKey,771772 773 EmptyPropertyKey,774775 776 CollectionIsExternal,777778 779 CollectionIsInternal,780781 782 ConfirmSponsorshipFail,783784 785 UserIsNotCollectionAdmin,786787 788 FungibleItemsHaveNoId,789 }790791 792 #[pallet::storage]793 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;794795 796 #[pallet::storage]797 pub type DestroyedCollectionCount<T> =798 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;799800 801 #[pallet::storage]802 pub type CollectionById<T> = StorageMap<803 Hasher = Blake2_128Concat,804 Key = CollectionId,805 Value = Collection<<T as frame_system::Config>::AccountId>,806 QueryKind = OptionQuery,807 >;808809 810 #[pallet::storage]811 #[pallet::getter(fn collection_properties)]812 pub type CollectionProperties<T> = StorageMap<813 Hasher = Blake2_128Concat,814 Key = CollectionId,815 Value = CollectionPropertiesT,816 QueryKind = ValueQuery,817 >;818819 820 #[pallet::storage]821 #[pallet::getter(fn property_permissions)]822 pub type CollectionPropertyPermissions<T> = StorageMap<823 Hasher = Blake2_128Concat,824 Key = CollectionId,825 Value = PropertiesPermissionMap,826 QueryKind = ValueQuery,827 >;828829 830 #[pallet::storage]831 pub type AdminAmount<T> = StorageMap<832 Hasher = Blake2_128Concat,833 Key = CollectionId,834 Value = u32,835 QueryKind = ValueQuery,836 >;837838 839 #[pallet::storage]840 pub type IsAdmin<T: Config> = StorageNMap<841 Key = (842 Key<Blake2_128Concat, CollectionId>,843 Key<Blake2_128Concat, T::CrossAccountId>,844 ),845 Value = bool,846 QueryKind = ValueQuery,847 >;848849 850 #[pallet::storage]851 pub type Allowlist<T: Config> = StorageNMap<852 Key = (853 Key<Blake2_128Concat, CollectionId>,854 Key<Blake2_128Concat, T::CrossAccountId>,855 ),856 Value = bool,857 QueryKind = ValueQuery,858 >;859860 861 #[pallet::storage]862 pub type DummyStorageValue<T: Config> = StorageValue<863 Value = (864 CollectionStats,865 CollectionId,866 TokenId,867 TokenChild,868 PhantomType<(869 TokenData<T::CrossAccountId>,870 RpcCollection<T::AccountId>,871 872 PovInfo,873 )>,874 ),875 QueryKind = OptionQuery,876 >;877}878879enum LazyValueState<'a, T> {880 Pending(Box<dyn FnOnce() -> T + 'a>),881 InProgress,882 Computed(T),883}884885886pub struct LazyValue<'a, T> {887 state: LazyValueState<'a, T>,888}889890impl<'a, T> LazyValue<'a, T> {891 892 pub fn new(f: impl FnOnce() -> T + 'a) -> Self {893 Self {894 state: LazyValueState::Pending(Box::new(f)),895 }896 }897898 899 pub fn value(&mut self) -> &T {900 self.force_value();901 self.value_mut()902 }903904 905 pub fn value_mut(&mut self) -> &mut T {906 self.force_value();907908 if let LazyValueState::Computed(value) = &mut self.state {909 value910 } else {911 unreachable!()912 }913 }914915 fn into_inner(mut self) -> T {916 self.force_value();917 if let LazyValueState::Computed(value) = self.state {918 value919 } else {920 unreachable!()921 }922 }923924 925 pub fn has_value(&self) -> bool {926 matches!(self.state, LazyValueState::Computed(_))927 }928929 fn force_value(&mut self) {930 use LazyValueState::*;931932 if self.has_value() {933 return;934 }935936 match sp_std::mem::replace(&mut self.state, InProgress) {937 Pending(f) => self.state = Computed(f()),938 _ => panic!("recursion isn't supported"),939 }940 }941}942943fn check_token_permissions<T: Config>(944 collection_admin_permitted: bool,945 token_owner_permitted: bool,946 is_collection_admin: &mut LazyValue<bool>,947 is_token_owner: &mut LazyValue<Result<bool, DispatchError>>,948 is_token_exist: &mut LazyValue<bool>,949) -> DispatchResult {950 if !(collection_admin_permitted && *is_collection_admin.value()951 || token_owner_permitted && (*is_token_owner.value())?)952 {953 fail!(<Error<T>>::NoPermission);954 }955956 let token_exist_due_to_owner_check_success =957 is_token_owner.has_value() && (*is_token_owner.value())?;958959 960 961 if !token_exist_due_to_owner_check_success {962 963 964 if !is_token_exist.value() {965 fail!(<Error<T>>::TokenNotFound);966 }967 }968969 Ok(())970}971972impl<T: Config> Pallet<T> {973 974 975 976 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {977 ensure!(978 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,979 <Error<T>>::AddressIsZero980 );981 Ok(())982 }983984 985 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {986 <IsAdmin<T>>::iter_prefix((collection,))987 .map(|(a, _)| a)988 .collect()989 }990991 992 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {993 <Allowlist<T>>::iter_prefix((collection,))994 .map(|(a, _)| a)995 .collect()996 }997998 999 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {1000 <Allowlist<T>>::get((collection, user))1001 }10021003 1004 pub fn collection_stats() -> CollectionStats {1005 let created = <CreatedCollectionCount<T>>::get();1006 let destroyed = <DestroyedCollectionCount<T>>::get();1007 CollectionStats {1008 created: created.0,1009 destroyed: destroyed.0,1010 alive: created.0 - destroyed.0,1011 }1012 }10131014 1015 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {1016 let collection = <CollectionById<T>>::get(collection)?;1017 let limits = collection.limits;1018 let effective_limits = CollectionLimits {1019 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),1020 sponsored_data_size: Some(limits.sponsored_data_size()),1021 sponsored_data_rate_limit: Some(1022 limits1023 .sponsored_data_rate_limit1024 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),1025 ),1026 token_limit: Some(limits.token_limit()),1027 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(1028 match collection.mode {1029 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1030 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1031 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1032 },1033 )),1034 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),1035 owner_can_transfer: Some(limits.owner_can_transfer()),1036 owner_can_destroy: Some(limits.owner_can_destroy()),1037 transfers_enabled: Some(limits.transfers_enabled()),1038 };10391040 Some(effective_limits)1041 }10421043 1044 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1045 let Collection {1046 name,1047 description,1048 owner,1049 mode,1050 token_prefix,1051 sponsorship,1052 limits,1053 permissions,1054 flags,1055 } = <CollectionById<T>>::get(collection)?;10561057 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1058 .into_iter()1059 .map(|(key, permission)| PropertyKeyPermission { key, permission })1060 .collect();10611062 let properties = <CollectionProperties<T>>::get(collection)1063 .into_iter()1064 .map(|(key, value)| Property { key, value })1065 .collect();10661067 let permissions = CollectionPermissions {1068 access: Some(permissions.access()),1069 mint_mode: Some(permissions.mint_mode()),1070 nesting: Some(permissions.nesting().clone()),1071 };10721073 Some(RpcCollection {1074 name: name.into_inner(),1075 description: description.into_inner(),1076 owner,1077 mode,1078 token_prefix: token_prefix.into_inner(),1079 sponsorship,1080 limits,1081 permissions,1082 token_property_permissions,1083 properties,1084 read_only: flags.external,10851086 flags: RpcCollectionFlags {1087 foreign: flags.foreign,1088 erc721metadata: flags.erc721metadata,1089 },1090 })1091 }1092}10931094macro_rules! limit_default {1095 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1096 $(1097 if let Some($new) = $new.$field {1098 let $old = $old.$field($($arg)?);1099 let _ = $new;1100 let _ = $old;1101 $check1102 } else {1103 $new.$field = $old.$field1104 }1105 )*1106 }};1107}1108macro_rules! limit_default_clone {1109 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1110 $(1111 if let Some($new) = $new.$field.clone() {1112 let $old = $old.$field($($arg)?);1113 let _ = $new;1114 let _ = $old;1115 $check1116 } else {1117 $new.$field = $old.$field.clone()1118 }1119 )*1120 }};1121}11221123impl<T: Config> Pallet<T> {1124 1125 1126 1127 1128 1129 pub fn init_collection(1130 owner: T::CrossAccountId,1131 payer: T::CrossAccountId,1132 data: CreateCollectionData<T::CrossAccountId>,1133 ) -> Result<CollectionId, DispatchError> {1134 ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);11351136 1137 {1138 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1139 imbalance.subsume(<T as Config>::Currency::deposit(1140 &T::TreasuryAccountId::get(),1141 T::CollectionCreationPrice::get(),1142 Precision::Exact,1143 )?);1144 let credit =1145 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1146 .map_err(|_| Error::<T>::NotSufficientFounds)?;11471148 debug_assert!(credit.peek().is_zero())1149 }11501151 Self::init_collection_internal(owner, data)1152 }11531154 1155 pub fn init_foreign_collection(1156 owner: T::CrossAccountId,1157 mut data: CreateCollectionData<T::CrossAccountId>,1158 ) -> Result<CollectionId, DispatchError> {1159 data.flags.foreign = true;1160 let id = Self::init_collection_internal(owner, data)?;1161 Ok(id)1162 }11631164 fn init_collection_internal(1165 owner: T::CrossAccountId,1166 data: CreateCollectionData<T::CrossAccountId>,1167 ) -> Result<CollectionId, DispatchError> {1168 {1169 ensure!(1170 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1171 Error::<T>::CollectionTokenPrefixLimitExceeded1172 );1173 }11741175 let created_count = <CreatedCollectionCount<T>>::get()1176 .01177 .checked_add(1)1178 .ok_or(ArithmeticError::Overflow)?;1179 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1180 let id = CollectionId(created_count);11811182 1183 ensure!(1184 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1185 <Error<T>>::TotalCollectionsLimitExceeded1186 );11871188 11891190 let collection = Collection {1191 owner: owner.as_sub().clone(),1192 name: data.name,1193 mode: data.mode.clone(),1194 description: data.description,1195 token_prefix: data.token_prefix,1196 sponsorship: data1197 .pending_sponsor1198 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1199 .unwrap_or_default(),1200 limits: data1201 .limits1202 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1203 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1204 permissions: data1205 .permissions1206 .map(|permissions| {1207 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1208 })1209 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1210 flags: data.flags,1211 };12121213 let mut collection_properties = CollectionPropertiesT::new();1214 collection_properties1215 .try_set_from_iter(data.properties.into_iter())1216 .map_err(<Error<T>>::from)?;12171218 CollectionProperties::<T>::insert(id, collection_properties);12191220 let mut token_props_permissions = PropertiesPermissionMap::new();1221 token_props_permissions1222 .try_set_from_iter(data.token_property_permissions.into_iter())1223 .map_err(<Error<T>>::from)?;12241225 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);12261227 let mut admin_amount = 0u32;1228 for admin in data.admin_list.iter() {1229 if !<IsAdmin<T>>::get((id, admin)) {1230 <IsAdmin<T>>::insert((id, admin), true);1231 admin_amount = admin_amount1232 .checked_add(1)1233 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1234 }1235 }1236 ensure!(1237 admin_amount <= Self::collection_admins_limit(),1238 <Error<T>>::CollectionAdminCountExceeded,1239 );1240 <AdminAmount<T>>::insert(id, admin_amount);12411242 <CreatedCollectionCount<T>>::put(created_count);1243 <Pallet<T>>::deposit_event(Event::CollectionCreated(1244 id,1245 data.mode.id(),1246 owner.as_sub().clone(),1247 ));1248 <PalletEvm<T>>::deposit_log(1249 erc::CollectionHelpersEvents::CollectionCreated {1250 owner: *owner.as_eth(),1251 collection_id: eth::collection_id_to_address(id),1252 }1253 .to_log(T::ContractAddress::get()),1254 );1255 <CollectionById<T>>::insert(id, collection);1256 Ok(id)1257 }12581259 1260 1261 1262 1263 pub fn destroy_collection(1264 collection: CollectionHandle<T>,1265 sender: &T::CrossAccountId,1266 ) -> DispatchResult {1267 ensure!(1268 collection.limits.owner_can_destroy(),1269 <Error<T>>::NoPermission,1270 );1271 collection.check_is_owner(sender)?;12721273 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1274 .01275 .checked_add(1)1276 .ok_or(ArithmeticError::Overflow)?;12771278 12791280 <DestroyedCollectionCount<T>>::put(destroyed_collections);1281 <CollectionById<T>>::remove(collection.id);1282 <AdminAmount<T>>::remove(collection.id);1283 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1284 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1285 <CollectionProperties<T>>::remove(collection.id);12861287 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12881289 <PalletEvm<T>>::deposit_log(1290 erc::CollectionHelpersEvents::CollectionDestroyed {1291 collection_id: eth::collection_id_to_address(collection.id),1292 }1293 .to_log(T::ContractAddress::get()),1294 );1295 Ok(())1296 }12971298 1299 1300 1301 1302 1303 1304 1305 1306 #[transactional]1307 fn modify_collection_properties(1308 collection: &CollectionHandle<T>,1309 sender: &T::CrossAccountId,1310 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1311 ) -> DispatchResult {1312 collection.check_is_owner_or_admin(sender)?;13131314 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);13151316 for (key, value) in properties_updates {1317 match value {1318 Some(value) => {1319 stored_properties1320 .try_set(key.clone(), value)1321 .map_err(<Error<T>>::from)?;13221323 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1324 <PalletEvm<T>>::deposit_log(1325 erc::CollectionHelpersEvents::CollectionChanged {1326 collection_id: eth::collection_id_to_address(collection.id),1327 }1328 .to_log(T::ContractAddress::get()),1329 );1330 }1331 None => {1332 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13331334 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1335 <PalletEvm<T>>::deposit_log(1336 erc::CollectionHelpersEvents::CollectionChanged {1337 collection_id: eth::collection_id_to_address(collection.id),1338 }1339 .to_log(T::ContractAddress::get()),1340 );1341 }1342 }1343 }13441345 <CollectionProperties<T>>::set(collection.id, stored_properties);13461347 Ok(())1348 }13491350 1351 1352 1353 1354 1355 1356 pub fn set_allowance_for_all(1357 collection: &CollectionHandle<T>,1358 owner: &T::CrossAccountId,1359 operator: &T::CrossAccountId,1360 approve: bool,1361 set_allowance: impl FnOnce(),1362 log: evm_coder::ethereum::Log,1363 ) -> DispatchResult {1364 if collection.permissions.access() == AccessMode::AllowList {1365 collection.check_allowlist(owner)?;1366 collection.check_allowlist(operator)?;1367 }13681369 Self::ensure_correct_receiver(operator)?;13701371 set_allowance();13721373 <PalletEvm<T>>::deposit_log(log);1374 Self::deposit_event(Event::ApprovedForAll(1375 collection.id,1376 owner.clone(),1377 operator.clone(),1378 approve,1379 ));1380 Ok(())1381 }13821383 1384 1385 1386 1387 1388 pub fn set_collection_property(1389 collection: &CollectionHandle<T>,1390 sender: &T::CrossAccountId,1391 property: Property,1392 ) -> DispatchResult {1393 Self::set_collection_properties(collection, sender, [property].into_iter())1394 }13951396 1397 1398 1399 1400 1401 1402 pub fn set_scoped_collection_property(1403 collection_id: CollectionId,1404 scope: PropertyScope,1405 property: Property,1406 ) -> DispatchResult {1407 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1408 properties.try_scoped_set(scope, property.key, property.value)1409 })1410 .map_err(<Error<T>>::from)?;14111412 Ok(())1413 }14141415 1416 1417 1418 1419 1420 1421 pub fn set_scoped_collection_properties(1422 collection_id: CollectionId,1423 scope: PropertyScope,1424 properties: impl Iterator<Item = Property>,1425 ) -> DispatchResult {1426 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1427 stored_properties.try_scoped_set_from_iter(scope, properties)1428 })1429 .map_err(<Error<T>>::from)?;14301431 Ok(())1432 }14331434 1435 1436 1437 1438 1439 pub fn set_collection_properties(1440 collection: &CollectionHandle<T>,1441 sender: &T::CrossAccountId,1442 properties: impl Iterator<Item = Property>,1443 ) -> DispatchResult {1444 Self::modify_collection_properties(1445 collection,1446 sender,1447 properties.map(|property| (property.key, Some(property.value))),1448 )1449 }14501451 1452 1453 1454 1455 1456 pub fn delete_collection_property(1457 collection: &CollectionHandle<T>,1458 sender: &T::CrossAccountId,1459 property_key: PropertyKey,1460 ) -> DispatchResult {1461 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1462 }14631464 1465 1466 1467 1468 1469 pub fn delete_collection_properties(1470 collection: &CollectionHandle<T>,1471 sender: &T::CrossAccountId,1472 property_keys: impl Iterator<Item = PropertyKey>,1473 ) -> DispatchResult {1474 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1475 }14761477 1478 1479 1480 1481 1482 1483 pub fn set_property_permission_unchecked(1484 collection: CollectionId,1485 property_permission: PropertyKeyPermission,1486 ) -> DispatchResult {1487 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1488 permissions.try_set(property_permission.key, property_permission.permission)1489 })1490 .map_err(<Error<T>>::from)?;1491 Ok(())1492 }14931494 1495 1496 1497 1498 1499 pub fn set_property_permission(1500 collection: &CollectionHandle<T>,1501 sender: &T::CrossAccountId,1502 property_permission: PropertyKeyPermission,1503 ) -> DispatchResult {1504 Self::set_scoped_property_permission(1505 collection,1506 sender,1507 PropertyScope::None,1508 property_permission,1509 )1510 }15111512 1513 1514 1515 1516 1517 1518 pub fn set_scoped_property_permission(1519 collection: &CollectionHandle<T>,1520 sender: &T::CrossAccountId,1521 scope: PropertyScope,1522 property_permission: PropertyKeyPermission,1523 ) -> DispatchResult {1524 collection.check_is_owner_or_admin(sender)?;15251526 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1527 let current_permission = all_permissions.get(&property_permission.key);1528 if matches![1529 current_permission,1530 Some(PropertyPermission { mutable: false, .. })1531 ] {1532 return Err(<Error<T>>::NoPermission.into());1533 }15341535 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1536 let property_permission = property_permission.clone();1537 permissions.try_scoped_set(1538 scope,1539 property_permission.key,1540 property_permission.permission,1541 )1542 })1543 .map_err(<Error<T>>::from)?;15441545 Self::deposit_event(Event::PropertyPermissionSet(1546 collection.id,1547 property_permission.key,1548 ));1549 <PalletEvm<T>>::deposit_log(1550 erc::CollectionHelpersEvents::CollectionChanged {1551 collection_id: eth::collection_id_to_address(collection.id),1552 }1553 .to_log(T::ContractAddress::get()),1554 );15551556 Ok(())1557 }15581559 1560 1561 1562 1563 1564 #[transactional]1565 pub fn set_token_property_permissions(1566 collection: &CollectionHandle<T>,1567 sender: &T::CrossAccountId,1568 property_permissions: Vec<PropertyKeyPermission>,1569 ) -> DispatchResult {1570 Self::set_scoped_token_property_permissions(1571 collection,1572 sender,1573 PropertyScope::None,1574 property_permissions,1575 )1576 }15771578 1579 1580 1581 1582 1583 1584 #[transactional]1585 pub fn set_scoped_token_property_permissions(1586 collection: &CollectionHandle<T>,1587 sender: &T::CrossAccountId,1588 scope: PropertyScope,1589 property_permissions: Vec<PropertyKeyPermission>,1590 ) -> DispatchResult {1591 for prop_pemission in property_permissions {1592 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1593 }15941595 Ok(())1596 }15971598 1599 pub fn get_collection_property(1600 collection_id: CollectionId,1601 key: &PropertyKey,1602 ) -> Option<PropertyValue> {1603 Self::collection_properties(collection_id).get(key).cloned()1604 }16051606 1607 pub fn bytes_keys_to_property_keys(1608 keys: Vec<Vec<u8>>,1609 ) -> Result<Vec<PropertyKey>, DispatchError> {1610 keys.into_iter()1611 .map(|key| -> Result<PropertyKey, DispatchError> {1612 key.try_into()1613 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1614 })1615 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1616 }16171618 1619 pub fn filter_collection_properties(1620 collection_id: CollectionId,1621 keys: Option<Vec<PropertyKey>>,1622 ) -> Result<Vec<Property>, DispatchError> {1623 let properties = Self::collection_properties(collection_id);16241625 let properties = keys1626 .map(|keys| {1627 keys.into_iter()1628 .filter_map(|key| {1629 properties.get(&key).map(|value| Property {1630 key,1631 value: value.clone(),1632 })1633 })1634 .collect()1635 })1636 .unwrap_or_else(|| {1637 properties1638 .into_iter()1639 .map(|(key, value)| Property { key, value })1640 .collect()1641 });16421643 Ok(properties)1644 }16451646 1647 pub fn filter_property_permissions(1648 collection_id: CollectionId,1649 keys: Option<Vec<PropertyKey>>,1650 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1651 let permissions = Self::property_permissions(collection_id);16521653 let key_permissions = keys1654 .map(|keys| {1655 keys.into_iter()1656 .filter_map(|key| {1657 permissions1658 .get(&key)1659 .map(|permission| PropertyKeyPermission {1660 key,1661 permission: permission.clone(),1662 })1663 })1664 .collect()1665 })1666 .unwrap_or_else(|| {1667 permissions1668 .into_iter()1669 .map(|(key, permission)| PropertyKeyPermission { key, permission })1670 .collect()1671 });16721673 Ok(key_permissions)1674 }16751676 1677 1678 1679 pub fn toggle_allowlist(1680 collection: &CollectionHandle<T>,1681 sender: &T::CrossAccountId,1682 user: &T::CrossAccountId,1683 allowed: bool,1684 ) -> DispatchResult {1685 collection.check_is_owner_or_admin(sender)?;16861687 16881689 if allowed {1690 <Allowlist<T>>::insert((collection.id, user), true);1691 Self::deposit_event(Event::<T>::AllowListAddressAdded(1692 collection.id,1693 user.clone(),1694 ));1695 } else {1696 <Allowlist<T>>::remove((collection.id, user));1697 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1698 collection.id,1699 user.clone(),1700 ));1701 }17021703 <PalletEvm<T>>::deposit_log(1704 erc::CollectionHelpersEvents::CollectionChanged {1705 collection_id: eth::collection_id_to_address(collection.id),1706 }1707 .to_log(T::ContractAddress::get()),1708 );17091710 Ok(())1711 }17121713 1714 1715 1716 pub fn toggle_admin(1717 collection: &CollectionHandle<T>,1718 sender: &T::CrossAccountId,1719 user: &T::CrossAccountId,1720 admin: bool,1721 ) -> DispatchResult {1722 collection.check_is_internal()?;1723 collection.check_is_owner(sender)?;17241725 let is_admin = <IsAdmin<T>>::get((collection.id, user));1726 if is_admin == admin {1727 if admin {1728 return Ok(());1729 } else {1730 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1731 }1732 }1733 let amount = <AdminAmount<T>>::get(collection.id);17341735 17361737 if admin {1738 let amount = amount1739 .checked_add(1)1740 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1741 ensure!(1742 amount <= Self::collection_admins_limit(),1743 <Error<T>>::CollectionAdminCountExceeded,1744 );17451746 <AdminAmount<T>>::insert(collection.id, amount);1747 <IsAdmin<T>>::insert((collection.id, user), true);17481749 Self::deposit_event(Event::<T>::CollectionAdminAdded(1750 collection.id,1751 user.clone(),1752 ));1753 } else {1754 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1755 <IsAdmin<T>>::remove((collection.id, user));17561757 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1758 collection.id,1759 user.clone(),1760 ));1761 }17621763 <PalletEvm<T>>::deposit_log(1764 erc::CollectionHelpersEvents::CollectionChanged {1765 collection_id: eth::collection_id_to_address(collection.id),1766 }1767 .to_log(T::ContractAddress::get()),1768 );17691770 Ok(())1771 }17721773 1774 pub fn update_limits(1775 user: &T::CrossAccountId,1776 collection: &mut CollectionHandle<T>,1777 new_limit: CollectionLimits,1778 ) -> DispatchResult {1779 collection.check_is_internal()?;1780 collection.check_is_owner_or_admin(user)?;17811782 collection.limits =1783 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17841785 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1786 <PalletEvm<T>>::deposit_log(1787 erc::CollectionHelpersEvents::CollectionChanged {1788 collection_id: eth::collection_id_to_address(collection.id),1789 }1790 .to_log(T::ContractAddress::get()),1791 );17921793 collection.save()1794 }17951796 1797 fn clamp_limits(1798 mode: CollectionMode,1799 old_limit: &CollectionLimits,1800 mut new_limit: CollectionLimits,1801 ) -> Result<CollectionLimits, DispatchError> {1802 let limits = old_limit;1803 limit_default!(old_limit, new_limit,1804 account_token_ownership_limit => ensure!(1805 new_limit <= MAX_TOKEN_OWNERSHIP,1806 <Error<T>>::CollectionLimitBoundsExceeded,1807 ),1808 sponsored_data_size => ensure!(1809 new_limit <= CUSTOM_DATA_LIMIT,1810 <Error<T>>::CollectionLimitBoundsExceeded,1811 ),18121813 sponsored_data_rate_limit => {},1814 token_limit => ensure!(1815 old_limit >= new_limit && new_limit > 0,1816 <Error<T>>::CollectionTokenLimitExceeded1817 ),18181819 sponsor_transfer_timeout(match mode {1820 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1821 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1822 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1823 }) => ensure!(1824 new_limit <= MAX_SPONSOR_TIMEOUT,1825 <Error<T>>::CollectionLimitBoundsExceeded,1826 ),1827 sponsor_approve_timeout => {},1828 owner_can_transfer => ensure!(1829 !limits.owner_can_transfer_instaled() ||1830 old_limit || !new_limit,1831 <Error<T>>::OwnerPermissionsCantBeReverted,1832 ),1833 owner_can_destroy => ensure!(1834 old_limit || !new_limit,1835 <Error<T>>::OwnerPermissionsCantBeReverted,1836 ),1837 transfers_enabled => {},1838 );1839 Ok(new_limit)1840 }18411842 1843 pub fn update_permissions(1844 user: &T::CrossAccountId,1845 collection: &mut CollectionHandle<T>,1846 new_permission: CollectionPermissions,1847 ) -> DispatchResult {1848 collection.check_is_internal()?;1849 collection.check_is_owner_or_admin(user)?;1850 collection.permissions = Self::clamp_permissions(1851 collection.mode.clone(),1852 &collection.permissions,1853 new_permission,1854 )?;18551856 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1857 <PalletEvm<T>>::deposit_log(1858 erc::CollectionHelpersEvents::CollectionChanged {1859 collection_id: eth::collection_id_to_address(collection.id),1860 }1861 .to_log(T::ContractAddress::get()),1862 );18631864 collection.save()1865 }18661867 1868 fn clamp_permissions(1869 _mode: CollectionMode,1870 old_permission: &CollectionPermissions,1871 mut new_permission: CollectionPermissions,1872 ) -> Result<CollectionPermissions, DispatchError> {1873 limit_default_clone!(old_permission, new_permission,1874 access => {},1875 mint_mode => {},1876 nesting => { },1877 );1878 Ok(new_permission)1879 }18801881 1882 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1883 CollectionProperties::<T>::mutate(collection_id, |properties| {1884 properties.recompute_consumed_space();1885 });18861887 Ok(())1888 }1889}189018911892#[macro_export]1893macro_rules! unsupported {1894 ($runtime:path) => {1895 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1896 };1897}189818991900pub trait CommonWeightInfo<CrossAccountId> {1901 1902 fn create_item(data: &CreateItemData) -> Weight {1903 Self::create_multiple_items(from_ref(data))1904 }19051906 1907 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19081909 1910 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19111912 1913 fn burn_item() -> Weight;19141915 1916 1917 1918 fn set_collection_properties(amount: u32) -> Weight;19191920 1921 1922 1923 fn delete_collection_properties(amount: u32) -> Weight {1924 Self::set_collection_properties(amount)1925 }19261927 1928 1929 1930 fn set_token_properties(amount: u32) -> Weight;19311932 1933 1934 1935 fn delete_token_properties(amount: u32) -> Weight {1936 Self::set_token_properties(amount)1937 }19381939 1940 1941 1942 fn set_token_property_permissions(amount: u32) -> Weight;19431944 1945 fn transfer() -> Weight;19461947 1948 fn approve() -> Weight;19491950 1951 fn approve_from() -> Weight;19521953 1954 fn transfer_from() -> Weight;19551956 1957 fn burn_from() -> Weight;19581959 1960 fn set_allowance_for_all() -> Weight;19611962 1963 fn force_repair_item() -> Weight;1964}196519661967pub trait RefungibleExtensionsWeightInfo {1968 1969 fn repartition() -> Weight;1970}197119721973197419751976pub trait CommonCollectionOperations<T: Config> {1977 1978 1979 1980 1981 1982 1983 fn create_item(1984 &self,1985 sender: T::CrossAccountId,1986 to: T::CrossAccountId,1987 data: CreateItemData,1988 nesting_budget: &dyn Budget,1989 ) -> DispatchResultWithPostInfo;19901991 1992 1993 1994 1995 1996 1997 fn create_multiple_items(1998 &self,1999 sender: T::CrossAccountId,2000 to: T::CrossAccountId,2001 data: Vec<CreateItemData>,2002 nesting_budget: &dyn Budget,2003 ) -> DispatchResultWithPostInfo;20042005 2006 2007 2008 2009 2010 2011 fn create_multiple_items_ex(2012 &self,2013 sender: T::CrossAccountId,2014 data: CreateItemExData<T::CrossAccountId>,2015 nesting_budget: &dyn Budget,2016 ) -> DispatchResultWithPostInfo;20172018 2019 2020 2021 2022 2023 fn burn_item(2024 &self,2025 sender: T::CrossAccountId,2026 token: TokenId,2027 amount: u128,2028 ) -> DispatchResultWithPostInfo;20292030 2031 2032 2033 2034 fn set_collection_properties(2035 &self,2036 sender: T::CrossAccountId,2037 properties: Vec<Property>,2038 ) -> DispatchResultWithPostInfo;20392040 2041 2042 2043 2044 fn delete_collection_properties(2045 &self,2046 sender: &T::CrossAccountId,2047 property_keys: Vec<PropertyKey>,2048 ) -> DispatchResultWithPostInfo;20492050 2051 2052 2053 2054 2055 2056 2057 2058 2059 fn set_token_properties(2060 &self,2061 sender: T::CrossAccountId,2062 token_id: TokenId,2063 properties: Vec<Property>,2064 budget: &dyn Budget,2065 ) -> DispatchResultWithPostInfo;20662067 2068 2069 2070 2071 2072 2073 2074 2075 2076 fn delete_token_properties(2077 &self,2078 sender: T::CrossAccountId,2079 token_id: TokenId,2080 property_keys: Vec<PropertyKey>,2081 budget: &dyn Budget,2082 ) -> DispatchResultWithPostInfo;20832084 2085 2086 2087 fn get_token_properties_raw(&self, token_id: TokenId) -> Option<TokenProperties>;20882089 2090 2091 2092 2093 fn set_token_properties_raw(&self, token_id: TokenId, map: TokenProperties);20942095 2096 2097 2098 2099 2100 2101 fn set_token_property_permissions(2102 &self,2103 sender: &T::CrossAccountId,2104 property_permissions: Vec<PropertyKeyPermission>,2105 ) -> DispatchResultWithPostInfo;21062107 2108 2109 2110 2111 2112 2113 2114 fn transfer(2115 &self,2116 sender: T::CrossAccountId,2117 to: T::CrossAccountId,2118 token: TokenId,2119 amount: u128,2120 budget: &dyn Budget,2121 ) -> DispatchResultWithPostInfo;21222123 2124 2125 2126 2127 2128 2129 fn approve(2130 &self,2131 sender: T::CrossAccountId,2132 spender: T::CrossAccountId,2133 token: TokenId,2134 amount: u128,2135 ) -> DispatchResultWithPostInfo;21362137 2138 2139 2140 2141 2142 2143 2144 fn approve_from(2145 &self,2146 sender: T::CrossAccountId,2147 from: T::CrossAccountId,2148 to: T::CrossAccountId,2149 token: TokenId,2150 amount: u128,2151 ) -> DispatchResultWithPostInfo;21522153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 fn transfer_from(2164 &self,2165 sender: T::CrossAccountId,2166 from: T::CrossAccountId,2167 to: T::CrossAccountId,2168 token: TokenId,2169 amount: u128,2170 budget: &dyn Budget,2171 ) -> DispatchResultWithPostInfo;21722173 2174 2175 2176 2177 2178 2179 2180 2181 2182 fn burn_from(2183 &self,2184 sender: T::CrossAccountId,2185 from: T::CrossAccountId,2186 token: TokenId,2187 amount: u128,2188 budget: &dyn Budget,2189 ) -> DispatchResultWithPostInfo;21902191 2192 2193 2194 2195 2196 2197 fn check_nesting(2198 &self,2199 sender: &T::CrossAccountId,2200 from: (CollectionId, TokenId),2201 under: TokenId,2202 budget: &dyn Budget,2203 ) -> DispatchResult;22042205 2206 2207 2208 2209 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22102211 2212 2213 2214 2215 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22162217 2218 2219 2220 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22212222 2223 fn collection_tokens(&self) -> Vec<TokenId>;22242225 2226 2227 2228 fn token_exists(&self, token: TokenId) -> bool;22292230 2231 fn last_token_id(&self) -> TokenId;22322233 2234 2235 2236 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22372238 2239 2240 2241 2242 2243 fn check_token_indirect_owner(2244 &self,2245 token: TokenId,2246 maybe_owner: &T::CrossAccountId,2247 nesting_budget: &dyn Budget,2248 ) -> Result<bool, DispatchError>;22492250 2251 2252 2253 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22542255 2256 2257 2258 2259 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22602261 2262 2263 2264 2265 2266 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22672268 2269 fn total_supply(&self) -> u32;22702271 2272 2273 2274 fn account_balance(&self, account: T::CrossAccountId) -> u32;22752276 2277 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22782279 2280 fn total_pieces(&self, token: TokenId) -> Option<u128>;22812282 2283 2284 2285 2286 2287 fn allowance(2288 &self,2289 sender: T::CrossAccountId,2290 spender: T::CrossAccountId,2291 token: TokenId,2292 ) -> u128;22932294 2295 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>> {2296 None2297 }22982299 2300 fn xcm_extensions(&self) -> Option<&dyn XcmExtensions<T>> {2301 None2302 }23032304 2305 2306 2307 2308 fn set_allowance_for_all(2309 &self,2310 owner: T::CrossAccountId,2311 operator: T::CrossAccountId,2312 approve: bool,2313 ) -> DispatchResultWithPostInfo;23142315 2316 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23172318 2319 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2320}232123222323pub trait RefungibleExtensions<T>2324where2325 T: Config,2326{2327 2328 2329 2330 2331 2332 2333 2334 fn repartition(2335 &self,2336 sender: &T::CrossAccountId,2337 token: TokenId,2338 amount: u128,2339 ) -> DispatchResultWithPostInfo;2340}234123422343pub trait XcmExtensions<T>2344where2345 T: Config,2346{2347 2348 fn is_foreign(&self) -> bool;23492350 2351 fn create_item(2352 &self,2353 to: T::CrossAccountId,2354 data: CreateItemData,2355 ) -> Result<TokenId, DispatchError>;23562357 2358 fn transfer(2359 &self,2360 from: T::CrossAccountId,2361 to: T::CrossAccountId,2362 token: TokenId,2363 amount: u128,2364 ) -> DispatchResult;23652366 2367 fn burn(&self, from: T::CrossAccountId, token: TokenId, amount: u128) -> DispatchResult;2368}23692370237123722373pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2374 let post_info = PostDispatchInfo {2375 actual_weight: Some(weight),2376 pays_fee: Pays::Yes,2377 };2378 match res {2379 Ok(()) => Ok(post_info),2380 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2381 }2382}23832384impl<T: Config> From<PropertiesError> for Error<T> {2385 fn from(error: PropertiesError) -> Self {2386 match error {2387 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2388 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2389 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2390 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2391 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2392 }2393 }2394}23952396239723982399240024012402pub struct PropertyWriter<'a, WriterVariant, T, Handle> {2403 collection: &'a Handle,2404 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2405 _phantom: PhantomData<(T, WriterVariant)>,2406}24072408impl<'a, T, Handle, WriterVariant> PropertyWriter<'a, WriterVariant, T, Handle>2409where2410 T: Config,2411 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2412{2413 fn internal_write_token_properties(2414 &mut self,2415 token_id: TokenId,2416 mut token_lazy_info: PropertyWriterLazyTokenInfo,2417 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2418 log: evm_coder::ethereum::Log,2419 ) -> DispatchResult {2420 for (key, value) in properties_updates {2421 let permission = self2422 .collection_lazy_info2423 .property_permissions2424 .value()2425 .get(&key)2426 .cloned()2427 .unwrap_or_else(PropertyPermission::none);24282429 match permission {2430 PropertyPermission { mutable: false, .. }2431 if token_lazy_info2432 .stored_properties2433 .value()2434 .get(&key)2435 .is_some() =>2436 {2437 return Err(<Error<T>>::NoPermission.into());2438 }24392440 PropertyPermission {2441 collection_admin,2442 token_owner,2443 ..2444 } => check_token_permissions::<T>(2445 collection_admin,2446 token_owner,2447 &mut self.collection_lazy_info.is_collection_admin,2448 &mut token_lazy_info.is_token_owner,2449 &mut token_lazy_info.is_token_exist,2450 )?,2451 }24522453 match value {2454 Some(value) => {2455 token_lazy_info2456 .stored_properties2457 .value_mut()2458 .try_set(key.clone(), value)2459 .map_err(<Error<T>>::from)?;24602461 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2462 self.collection.id,2463 token_id,2464 key,2465 ));2466 }2467 None => {2468 token_lazy_info2469 .stored_properties2470 .value_mut()2471 .remove(&key)2472 .map_err(<Error<T>>::from)?;24732474 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2475 self.collection.id,2476 token_id,2477 key,2478 ));2479 }2480 }2481 }24822483 let properties_changed = token_lazy_info.stored_properties.has_value();2484 if properties_changed {2485 <PalletEvm<T>>::deposit_log(log);24862487 self.collection2488 .set_token_properties_raw(token_id, token_lazy_info.stored_properties.into_inner());2489 }24902491 Ok(())2492 }2493}24942495249624972498pub struct PropertyWriterLazyCollectionInfo<'a> {2499 is_collection_admin: LazyValue<'a, bool>,2500 property_permissions: LazyValue<'a, PropertiesPermissionMap>,2501}2502250325042505pub struct PropertyWriterLazyTokenInfo<'a> {2506 is_token_exist: LazyValue<'a, bool>,2507 is_token_owner: LazyValue<'a, Result<bool, DispatchError>>,2508 stored_properties: LazyValue<'a, TokenProperties>,2509}25102511impl<'a> PropertyWriterLazyTokenInfo<'a> {2512 2513 pub fn new(2514 check_token_exist: impl FnOnce() -> bool + 'a,2515 check_token_owner: impl FnOnce() -> Result<bool, DispatchError> + 'a,2516 get_token_properties: impl FnOnce() -> TokenProperties + 'a,2517 ) -> Self {2518 Self {2519 is_token_exist: LazyValue::new(check_token_exist),2520 is_token_owner: LazyValue::new(check_token_owner),2521 stored_properties: LazyValue::new(get_token_properties),2522 }2523 }2524}2525252625272528pub struct NewTokenPropertyWriter<T>(PhantomData<T>);2529impl<T: Config> NewTokenPropertyWriter<T> {2530 2531 pub fn new<'a, Handle>(2532 collection: &'a Handle,2533 sender: &'a T::CrossAccountId,2534 ) -> PropertyWriter<'a, Self, T, Handle>2535 where2536 T: Config,2537 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2538 {2539 PropertyWriter {2540 collection,2541 collection_lazy_info: PropertyWriterLazyCollectionInfo {2542 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2543 property_permissions: LazyValue::new(|| {2544 <Pallet<T>>::property_permissions(collection.id)2545 }),2546 },2547 _phantom: PhantomData,2548 }2549 }2550}25512552impl<'a, T, Handle> PropertyWriter<'a, NewTokenPropertyWriter<T>, T, Handle>2553where2554 T: Config,2555 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2556{2557 2558 pub fn write_token_properties(2559 &mut self,2560 mint_target_is_sender: bool,2561 token_id: TokenId,2562 properties_updates: impl Iterator<Item = Property>,2563 log: evm_coder::ethereum::Log,2564 ) -> DispatchResult {2565 let check_token_exist = || {2566 debug_assert!(self.collection.token_exists(token_id));2567 true2568 };25692570 let check_token_owner = || Ok(mint_target_is_sender);25712572 let get_token_properties = || {2573 debug_assert!(self.collection.get_token_properties_raw(token_id).is_none());2574 TokenProperties::new()2575 };25762577 self.internal_write_token_properties(2578 token_id,2579 PropertyWriterLazyTokenInfo::new(2580 check_token_exist,2581 check_token_owner,2582 get_token_properties,2583 ),2584 properties_updates.map(|p| (p.key, Some(p.value))),2585 log,2586 )2587 }2588}2589259025912592pub struct ExistingTokenPropertyWriter<T>(PhantomData<T>);2593impl<T: Config> ExistingTokenPropertyWriter<T> {2594 2595 pub fn new<'a, Handle>(2596 collection: &'a Handle,2597 sender: &'a T::CrossAccountId,2598 ) -> PropertyWriter<'a, Self, T, Handle>2599 where2600 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2601 {2602 PropertyWriter {2603 collection,2604 collection_lazy_info: PropertyWriterLazyCollectionInfo {2605 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2606 property_permissions: LazyValue::new(|| {2607 <Pallet<T>>::property_permissions(collection.id)2608 }),2609 },2610 _phantom: PhantomData,2611 }2612 }2613}26142615impl<'a, T, Handle> PropertyWriter<'a, ExistingTokenPropertyWriter<T>, T, Handle>2616where2617 T: Config,2618 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2619{2620 2621 pub fn write_token_properties(2622 &mut self,2623 sender: &T::CrossAccountId,2624 token_id: TokenId,2625 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2626 nesting_budget: &dyn Budget,2627 log: evm_coder::ethereum::Log,2628 ) -> DispatchResult {2629 let check_token_exist = || self.collection.token_exists(token_id);2630 let check_token_owner = || {2631 self.collection2632 .check_token_indirect_owner(token_id, sender, nesting_budget)2633 };2634 let get_token_properties = || {2635 self.collection2636 .get_token_properties_raw(token_id)2637 .unwrap_or_default()2638 };26392640 self.internal_write_token_properties(2641 token_id,2642 PropertyWriterLazyTokenInfo::new(2643 check_token_exist,2644 check_token_owner,2645 get_token_properties,2646 ),2647 properties_updates,2648 log,2649 )2650 }2651}2652265326542655#[cfg(feature = "runtime-benchmarks")]2656pub struct BenchmarkPropertyWriter<T>(PhantomData<T>);26572658#[cfg(feature = "runtime-benchmarks")]2659impl<T: Config> BenchmarkPropertyWriter<T> {2660 2661 pub fn new<'a, Handle>(2662 collection: &'a Handle,2663 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2664 ) -> PropertyWriter<'a, Self, T, Handle>2665 where2666 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2667 {2668 PropertyWriter {2669 collection,2670 collection_lazy_info,2671 _phantom: PhantomData,2672 }2673 }26742675 2676 pub fn load_collection_info<Handle>(2677 collection_handle: &Handle,2678 sender: &T::CrossAccountId,2679 ) -> PropertyWriterLazyCollectionInfo<'static>2680 where2681 Handle: Deref<Target = CollectionHandle<T>>,2682 {2683 let is_collection_admin = collection_handle.is_owner_or_admin(sender);2684 let property_permissions = <Pallet<T>>::property_permissions(collection_handle.id);26852686 PropertyWriterLazyCollectionInfo {2687 is_collection_admin: LazyValue::new(move || is_collection_admin),2688 property_permissions: LazyValue::new(move || property_permissions),2689 }2690 }26912692 2693 pub fn load_token_properties<Handle>(2694 collection: &Handle,2695 token_id: TokenId,2696 ) -> PropertyWriterLazyTokenInfo2697 where2698 Handle: CommonCollectionOperations<T>,2699 {2700 let stored_properties = collection2701 .get_token_properties_raw(token_id)2702 .unwrap_or_default();27032704 PropertyWriterLazyTokenInfo {2705 is_token_exist: LazyValue::new(|| true),2706 is_token_owner: LazyValue::new(|| Ok(true)),2707 stored_properties: LazyValue::new(move || stored_properties),2708 }2709 }2710}27112712#[cfg(feature = "runtime-benchmarks")]2713impl<'a, T, Handle> PropertyWriter<'a, BenchmarkPropertyWriter<T>, T, Handle>2714where2715 T: Config,2716 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2717{2718 2719 pub fn write_token_properties(2720 &mut self,2721 token_id: TokenId,2722 properties_updates: impl Iterator<Item = Property>,2723 log: evm_coder::ethereum::Log,2724 ) -> DispatchResult {2725 let check_token_exist = || true;2726 let check_token_owner = || Ok(true);2727 let get_token_properties = TokenProperties::new;27282729 self.internal_write_token_properties(2730 token_id,2731 PropertyWriterLazyTokenInfo::new(2732 check_token_exist,2733 check_token_owner,2734 get_token_properties,2735 ),2736 properties_updates.map(|p| (p.key, Some(p.value))),2737 log,2738 )2739 }2740}274127422743274427452746pub fn write_token_properties_total_weight<T: Config, I: Fn(u32) -> Weight>(2747 properties_nums: impl Iterator<Item = u32>,2748 per_token_weight: I,2749) -> Weight {2750 let mut weight = properties_nums2751 .filter_map(|properties_num| {2752 if properties_num > 0 {2753 Some(per_token_weight(properties_num))2754 } else {2755 None2756 }2757 })2758 .fold(Weight::zero(), |a, b| a.saturating_add(b));27592760 if !weight.is_zero() {2761 2762 2763 27642765 weight = weight.saturating_add(<SelfWeightOf<T>>::property_writer_load_collection_info());2766 }27672768 weight2769}27702771#[cfg(any(feature = "tests", test))]2772#[allow(missing_docs)]2773pub mod tests {2774 use crate::{Config, DispatchError, DispatchResult, LazyValue};27752776 const fn to_bool(u: u8) -> bool {2777 u != 02778 }27792780 #[derive(Debug)]2781 pub struct TestCase {2782 pub collection_admin: bool,2783 pub is_collection_admin: bool,2784 pub token_owner: bool,2785 pub is_token_owner: bool,2786 pub no_permission: bool,2787 }27882789 impl TestCase {2790 const fn new(2791 collection_admin: u8,2792 is_collection_admin: u8,2793 token_owner: u8,2794 is_token_owner: u8,2795 no_permission: u8,2796 ) -> Self {2797 Self {2798 collection_admin: to_bool(collection_admin),2799 is_collection_admin: to_bool(is_collection_admin),2800 token_owner: to_bool(token_owner),2801 is_token_owner: to_bool(is_token_owner),2802 no_permission: to_bool(no_permission),2803 }2804 }2805 }28062807 #[rustfmt::skip]2808 pub const TABLE: [TestCase; 16] = [2809 2810 2811 2812 2813 2814 TestCase::new(0, 0, 0, 0, 1),2815 TestCase::new(0, 0, 0, 1, 1),2816 TestCase::new(0, 0, 1, 0, 1),2817 TestCase::new(0, 0, 1, 1, 0),2818 TestCase::new(0, 1, 0, 0, 1),2819 TestCase::new(0, 1, 0, 1, 1),2820 TestCase::new(0, 1, 1, 0, 1),2821 TestCase::new(0, 1, 1, 1, 0),2822 TestCase::new(1, 0, 0, 0, 1),2823 TestCase::new(1, 0, 0, 1, 1),2824 TestCase::new(1, 0, 1, 0, 1),2825 TestCase::new(1, 0, 1, 1, 0),2826 TestCase::new(1, 1, 0, 0, 0),2827 TestCase::new(1, 1, 0, 1, 0),2828 TestCase::new(1, 1, 1, 0, 0),2829 TestCase::new(1, 1, 1, 1, 0),2830 ];28312832 pub fn check_token_permissions<T: Config>(2833 collection_admin_permitted: bool,2834 token_owner_permitted: bool,2835 is_collection_admin: &mut LazyValue<bool>,2836 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>>,2837 check_token_existence: &mut LazyValue<bool>,2838 ) -> DispatchResult {2839 crate::check_token_permissions::<T>(2840 collection_admin_permitted,2841 token_owner_permitted,2842 is_collection_admin,2843 check_token_ownership,2844 check_token_existence,2845 )2846 }2847}