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,786 }787788 789 #[pallet::storage]790 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;791792 793 #[pallet::storage]794 pub type DestroyedCollectionCount<T> =795 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;796797 798 #[pallet::storage]799 pub type CollectionById<T> = StorageMap<800 Hasher = Blake2_128Concat,801 Key = CollectionId,802 Value = Collection<<T as frame_system::Config>::AccountId>,803 QueryKind = OptionQuery,804 >;805806 807 #[pallet::storage]808 #[pallet::getter(fn collection_properties)]809 pub type CollectionProperties<T> = StorageMap<810 Hasher = Blake2_128Concat,811 Key = CollectionId,812 Value = CollectionPropertiesT,813 QueryKind = ValueQuery,814 >;815816 817 #[pallet::storage]818 #[pallet::getter(fn property_permissions)]819 pub type CollectionPropertyPermissions<T> = StorageMap<820 Hasher = Blake2_128Concat,821 Key = CollectionId,822 Value = PropertiesPermissionMap,823 QueryKind = ValueQuery,824 >;825826 827 #[pallet::storage]828 pub type AdminAmount<T> = StorageMap<829 Hasher = Blake2_128Concat,830 Key = CollectionId,831 Value = u32,832 QueryKind = ValueQuery,833 >;834835 836 #[pallet::storage]837 pub type IsAdmin<T: Config> = StorageNMap<838 Key = (839 Key<Blake2_128Concat, CollectionId>,840 Key<Blake2_128Concat, T::CrossAccountId>,841 ),842 Value = bool,843 QueryKind = ValueQuery,844 >;845846 847 #[pallet::storage]848 pub type Allowlist<T: Config> = StorageNMap<849 Key = (850 Key<Blake2_128Concat, CollectionId>,851 Key<Blake2_128Concat, T::CrossAccountId>,852 ),853 Value = bool,854 QueryKind = ValueQuery,855 >;856857 858 #[pallet::storage]859 pub type DummyStorageValue<T: Config> = StorageValue<860 Value = (861 CollectionStats,862 CollectionId,863 TokenId,864 TokenChild,865 PhantomType<(866 TokenData<T::CrossAccountId>,867 RpcCollection<T::AccountId>,868 869 PovInfo,870 )>,871 ),872 QueryKind = OptionQuery,873 >;874}875876enum LazyValueState<'a, T> {877 Pending(Box<dyn FnOnce() -> T + 'a>),878 InProgress,879 Computed(T),880}881882883pub struct LazyValue<'a, T> {884 state: LazyValueState<'a, T>,885}886887impl<'a, T> LazyValue<'a, T> {888 889 pub fn new(f: impl FnOnce() -> T + 'a) -> Self {890 Self {891 state: LazyValueState::Pending(Box::new(f)),892 }893 }894895 896 pub fn value(&mut self) -> &T {897 self.force_value();898 self.value_mut()899 }900901 902 pub fn value_mut(&mut self) -> &mut T {903 self.force_value();904905 if let LazyValueState::Computed(value) = &mut self.state {906 value907 } else {908 unreachable!()909 }910 }911912 fn into_inner(mut self) -> T {913 self.force_value();914 if let LazyValueState::Computed(value) = self.state {915 value916 } else {917 unreachable!()918 }919 }920921 922 pub fn has_value(&self) -> bool {923 matches!(self.state, LazyValueState::Computed(_))924 }925926 fn force_value(&mut self) {927 use LazyValueState::*;928929 if self.has_value() {930 return;931 }932933 match sp_std::mem::replace(&mut self.state, InProgress) {934 Pending(f) => self.state = Computed(f()),935 _ => panic!("recursion isn't supported"),936 }937 }938}939940fn check_token_permissions<T: Config>(941 collection_admin_permitted: bool,942 token_owner_permitted: bool,943 is_collection_admin: &mut LazyValue<bool>,944 is_token_owner: &mut LazyValue<Result<bool, DispatchError>>,945 is_token_exist: &mut LazyValue<bool>,946) -> DispatchResult {947 if !(collection_admin_permitted && *is_collection_admin.value()948 || token_owner_permitted && (*is_token_owner.value())?)949 {950 fail!(<Error<T>>::NoPermission);951 }952953 let token_exist_due_to_owner_check_success =954 is_token_owner.has_value() && (*is_token_owner.value())?;955956 957 958 if !token_exist_due_to_owner_check_success {959 960 961 if !is_token_exist.value() {962 fail!(<Error<T>>::TokenNotFound);963 }964 }965966 Ok(())967}968969impl<T: Config> Pallet<T> {970 971 972 973 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {974 ensure!(975 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,976 <Error<T>>::AddressIsZero977 );978 Ok(())979 }980981 982 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {983 <IsAdmin<T>>::iter_prefix((collection,))984 .map(|(a, _)| a)985 .collect()986 }987988 989 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {990 <Allowlist<T>>::iter_prefix((collection,))991 .map(|(a, _)| a)992 .collect()993 }994995 996 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {997 <Allowlist<T>>::get((collection, user))998 }9991000 1001 pub fn collection_stats() -> CollectionStats {1002 let created = <CreatedCollectionCount<T>>::get();1003 let destroyed = <DestroyedCollectionCount<T>>::get();1004 CollectionStats {1005 created: created.0,1006 destroyed: destroyed.0,1007 alive: created.0 - destroyed.0,1008 }1009 }10101011 1012 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {1013 let collection = <CollectionById<T>>::get(collection)?;1014 let limits = collection.limits;1015 let effective_limits = CollectionLimits {1016 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),1017 sponsored_data_size: Some(limits.sponsored_data_size()),1018 sponsored_data_rate_limit: Some(1019 limits1020 .sponsored_data_rate_limit1021 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),1022 ),1023 token_limit: Some(limits.token_limit()),1024 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(1025 match collection.mode {1026 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1027 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1028 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1029 },1030 )),1031 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),1032 owner_can_transfer: Some(limits.owner_can_transfer()),1033 owner_can_destroy: Some(limits.owner_can_destroy()),1034 transfers_enabled: Some(limits.transfers_enabled()),1035 };10361037 Some(effective_limits)1038 }10391040 1041 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1042 let Collection {1043 name,1044 description,1045 owner,1046 mode,1047 token_prefix,1048 sponsorship,1049 limits,1050 permissions,1051 flags,1052 } = <CollectionById<T>>::get(collection)?;10531054 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1055 .into_iter()1056 .map(|(key, permission)| PropertyKeyPermission { key, permission })1057 .collect();10581059 let properties = <CollectionProperties<T>>::get(collection)1060 .into_iter()1061 .map(|(key, value)| Property { key, value })1062 .collect();10631064 let permissions = CollectionPermissions {1065 access: Some(permissions.access()),1066 mint_mode: Some(permissions.mint_mode()),1067 nesting: Some(permissions.nesting().clone()),1068 };10691070 Some(RpcCollection {1071 name: name.into_inner(),1072 description: description.into_inner(),1073 owner,1074 mode,1075 token_prefix: token_prefix.into_inner(),1076 sponsorship,1077 limits,1078 permissions,1079 token_property_permissions,1080 properties,1081 read_only: flags.external,10821083 flags: RpcCollectionFlags {1084 foreign: flags.foreign,1085 erc721metadata: flags.erc721metadata,1086 },1087 })1088 }1089}10901091macro_rules! limit_default {1092 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1093 $(1094 if let Some($new) = $new.$field {1095 let $old = $old.$field($($arg)?);1096 let _ = $new;1097 let _ = $old;1098 $check1099 } else {1100 $new.$field = $old.$field1101 }1102 )*1103 }};1104}1105macro_rules! limit_default_clone {1106 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1107 $(1108 if let Some($new) = $new.$field.clone() {1109 let $old = $old.$field($($arg)?);1110 let _ = $new;1111 let _ = $old;1112 $check1113 } else {1114 $new.$field = $old.$field.clone()1115 }1116 )*1117 }};1118}11191120impl<T: Config> Pallet<T> {1121 1122 1123 1124 1125 1126 pub fn init_collection(1127 owner: T::CrossAccountId,1128 payer: T::CrossAccountId,1129 data: CreateCollectionData<T::CrossAccountId>,1130 ) -> Result<CollectionId, DispatchError> {1131 ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);1132 Self::init_collection_internal(owner, payer, data)1133 }11341135 1136 pub fn init_foreign_collection(1137 owner: T::CrossAccountId,1138 payer: T::CrossAccountId,1139 mut data: CreateCollectionData<T::CrossAccountId>,1140 ) -> Result<CollectionId, DispatchError> {1141 data.flags.foreign = true;1142 let id = Self::init_collection_internal(owner, payer, data)?;1143 Ok(id)1144 }11451146 fn init_collection_internal(1147 owner: T::CrossAccountId,1148 payer: T::CrossAccountId,1149 data: CreateCollectionData<T::CrossAccountId>,1150 ) -> Result<CollectionId, DispatchError> {1151 {1152 ensure!(1153 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1154 Error::<T>::CollectionTokenPrefixLimitExceeded1155 );1156 }11571158 let created_count = <CreatedCollectionCount<T>>::get()1159 .01160 .checked_add(1)1161 .ok_or(ArithmeticError::Overflow)?;1162 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1163 let id = CollectionId(created_count);11641165 1166 ensure!(1167 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1168 <Error<T>>::TotalCollectionsLimitExceeded1169 );11701171 11721173 let collection = Collection {1174 owner: owner.as_sub().clone(),1175 name: data.name,1176 mode: data.mode.clone(),1177 description: data.description,1178 token_prefix: data.token_prefix,1179 sponsorship: data1180 .pending_sponsor1181 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1182 .unwrap_or_default(),1183 limits: data1184 .limits1185 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1186 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1187 permissions: data1188 .permissions1189 .map(|permissions| {1190 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1191 })1192 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1193 flags: data.flags,1194 };11951196 let mut collection_properties = CollectionPropertiesT::new();1197 collection_properties1198 .try_set_from_iter(data.properties.into_iter())1199 .map_err(<Error<T>>::from)?;12001201 CollectionProperties::<T>::insert(id, collection_properties);12021203 let mut token_props_permissions = PropertiesPermissionMap::new();1204 token_props_permissions1205 .try_set_from_iter(data.token_property_permissions.into_iter())1206 .map_err(<Error<T>>::from)?;12071208 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);12091210 let mut admin_amount = 0u32;1211 for admin in data.admin_list.iter() {1212 if !<IsAdmin<T>>::get((id, admin)) {1213 <IsAdmin<T>>::insert((id, admin), true);1214 admin_amount = admin_amount1215 .checked_add(1)1216 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1217 }1218 }1219 ensure!(1220 admin_amount <= Self::collection_admins_limit(),1221 <Error<T>>::CollectionAdminCountExceeded,1222 );1223 <AdminAmount<T>>::insert(id, admin_amount);12241225 1226 {1227 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1228 imbalance.subsume(<T as Config>::Currency::deposit(1229 &T::TreasuryAccountId::get(),1230 T::CollectionCreationPrice::get(),1231 Precision::Exact,1232 )?);1233 let credit =1234 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1235 .map_err(|_| Error::<T>::NotSufficientFounds)?;12361237 debug_assert!(credit.peek().is_zero())1238 }12391240 <CreatedCollectionCount<T>>::put(created_count);1241 <Pallet<T>>::deposit_event(Event::CollectionCreated(1242 id,1243 data.mode.id(),1244 owner.as_sub().clone(),1245 ));1246 <PalletEvm<T>>::deposit_log(1247 erc::CollectionHelpersEvents::CollectionCreated {1248 owner: *owner.as_eth(),1249 collection_id: eth::collection_id_to_address(id),1250 }1251 .to_log(T::ContractAddress::get()),1252 );1253 <CollectionById<T>>::insert(id, collection);1254 Ok(id)1255 }12561257 1258 1259 1260 1261 pub fn destroy_collection(1262 collection: CollectionHandle<T>,1263 sender: &T::CrossAccountId,1264 ) -> DispatchResult {1265 ensure!(1266 collection.limits.owner_can_destroy(),1267 <Error<T>>::NoPermission,1268 );1269 collection.check_is_owner(sender)?;12701271 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1272 .01273 .checked_add(1)1274 .ok_or(ArithmeticError::Overflow)?;12751276 12771278 <DestroyedCollectionCount<T>>::put(destroyed_collections);1279 <CollectionById<T>>::remove(collection.id);1280 <AdminAmount<T>>::remove(collection.id);1281 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1282 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1283 <CollectionProperties<T>>::remove(collection.id);12841285 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12861287 <PalletEvm<T>>::deposit_log(1288 erc::CollectionHelpersEvents::CollectionDestroyed {1289 collection_id: eth::collection_id_to_address(collection.id),1290 }1291 .to_log(T::ContractAddress::get()),1292 );1293 Ok(())1294 }12951296 1297 1298 1299 1300 1301 1302 1303 1304 #[transactional]1305 fn modify_collection_properties(1306 collection: &CollectionHandle<T>,1307 sender: &T::CrossAccountId,1308 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1309 ) -> DispatchResult {1310 collection.check_is_owner_or_admin(sender)?;13111312 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);13131314 for (key, value) in properties_updates {1315 match value {1316 Some(value) => {1317 stored_properties1318 .try_set(key.clone(), value)1319 .map_err(<Error<T>>::from)?;13201321 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1322 <PalletEvm<T>>::deposit_log(1323 erc::CollectionHelpersEvents::CollectionChanged {1324 collection_id: eth::collection_id_to_address(collection.id),1325 }1326 .to_log(T::ContractAddress::get()),1327 );1328 }1329 None => {1330 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13311332 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1333 <PalletEvm<T>>::deposit_log(1334 erc::CollectionHelpersEvents::CollectionChanged {1335 collection_id: eth::collection_id_to_address(collection.id),1336 }1337 .to_log(T::ContractAddress::get()),1338 );1339 }1340 }1341 }13421343 <CollectionProperties<T>>::set(collection.id, stored_properties);13441345 Ok(())1346 }13471348 1349 1350 1351 1352 1353 1354 pub fn set_allowance_for_all(1355 collection: &CollectionHandle<T>,1356 owner: &T::CrossAccountId,1357 operator: &T::CrossAccountId,1358 approve: bool,1359 set_allowance: impl FnOnce(),1360 log: evm_coder::ethereum::Log,1361 ) -> DispatchResult {1362 if collection.permissions.access() == AccessMode::AllowList {1363 collection.check_allowlist(owner)?;1364 collection.check_allowlist(operator)?;1365 }13661367 Self::ensure_correct_receiver(operator)?;13681369 set_allowance();13701371 <PalletEvm<T>>::deposit_log(log);1372 Self::deposit_event(Event::ApprovedForAll(1373 collection.id,1374 owner.clone(),1375 operator.clone(),1376 approve,1377 ));1378 Ok(())1379 }13801381 1382 1383 1384 1385 1386 pub fn set_collection_property(1387 collection: &CollectionHandle<T>,1388 sender: &T::CrossAccountId,1389 property: Property,1390 ) -> DispatchResult {1391 Self::set_collection_properties(collection, sender, [property].into_iter())1392 }13931394 1395 1396 1397 1398 1399 1400 pub fn set_scoped_collection_property(1401 collection_id: CollectionId,1402 scope: PropertyScope,1403 property: Property,1404 ) -> DispatchResult {1405 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1406 properties.try_scoped_set(scope, property.key, property.value)1407 })1408 .map_err(<Error<T>>::from)?;14091410 Ok(())1411 }14121413 1414 1415 1416 1417 1418 1419 pub fn set_scoped_collection_properties(1420 collection_id: CollectionId,1421 scope: PropertyScope,1422 properties: impl Iterator<Item = Property>,1423 ) -> DispatchResult {1424 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1425 stored_properties.try_scoped_set_from_iter(scope, properties)1426 })1427 .map_err(<Error<T>>::from)?;14281429 Ok(())1430 }14311432 1433 1434 1435 1436 1437 pub fn set_collection_properties(1438 collection: &CollectionHandle<T>,1439 sender: &T::CrossAccountId,1440 properties: impl Iterator<Item = Property>,1441 ) -> DispatchResult {1442 Self::modify_collection_properties(1443 collection,1444 sender,1445 properties.map(|property| (property.key, Some(property.value))),1446 )1447 }14481449 1450 1451 1452 1453 1454 pub fn delete_collection_property(1455 collection: &CollectionHandle<T>,1456 sender: &T::CrossAccountId,1457 property_key: PropertyKey,1458 ) -> DispatchResult {1459 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1460 }14611462 1463 1464 1465 1466 1467 pub fn delete_collection_properties(1468 collection: &CollectionHandle<T>,1469 sender: &T::CrossAccountId,1470 property_keys: impl Iterator<Item = PropertyKey>,1471 ) -> DispatchResult {1472 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1473 }14741475 1476 1477 1478 1479 1480 1481 pub fn set_property_permission_unchecked(1482 collection: CollectionId,1483 property_permission: PropertyKeyPermission,1484 ) -> DispatchResult {1485 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1486 permissions.try_set(property_permission.key, property_permission.permission)1487 })1488 .map_err(<Error<T>>::from)?;1489 Ok(())1490 }14911492 1493 1494 1495 1496 1497 pub fn set_property_permission(1498 collection: &CollectionHandle<T>,1499 sender: &T::CrossAccountId,1500 property_permission: PropertyKeyPermission,1501 ) -> DispatchResult {1502 Self::set_scoped_property_permission(1503 collection,1504 sender,1505 PropertyScope::None,1506 property_permission,1507 )1508 }15091510 1511 1512 1513 1514 1515 1516 pub fn set_scoped_property_permission(1517 collection: &CollectionHandle<T>,1518 sender: &T::CrossAccountId,1519 scope: PropertyScope,1520 property_permission: PropertyKeyPermission,1521 ) -> DispatchResult {1522 collection.check_is_owner_or_admin(sender)?;15231524 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1525 let current_permission = all_permissions.get(&property_permission.key);1526 if matches![1527 current_permission,1528 Some(PropertyPermission { mutable: false, .. })1529 ] {1530 return Err(<Error<T>>::NoPermission.into());1531 }15321533 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1534 let property_permission = property_permission.clone();1535 permissions.try_scoped_set(1536 scope,1537 property_permission.key,1538 property_permission.permission,1539 )1540 })1541 .map_err(<Error<T>>::from)?;15421543 Self::deposit_event(Event::PropertyPermissionSet(1544 collection.id,1545 property_permission.key,1546 ));1547 <PalletEvm<T>>::deposit_log(1548 erc::CollectionHelpersEvents::CollectionChanged {1549 collection_id: eth::collection_id_to_address(collection.id),1550 }1551 .to_log(T::ContractAddress::get()),1552 );15531554 Ok(())1555 }15561557 1558 1559 1560 1561 1562 #[transactional]1563 pub fn set_token_property_permissions(1564 collection: &CollectionHandle<T>,1565 sender: &T::CrossAccountId,1566 property_permissions: Vec<PropertyKeyPermission>,1567 ) -> DispatchResult {1568 Self::set_scoped_token_property_permissions(1569 collection,1570 sender,1571 PropertyScope::None,1572 property_permissions,1573 )1574 }15751576 1577 1578 1579 1580 1581 1582 #[transactional]1583 pub fn set_scoped_token_property_permissions(1584 collection: &CollectionHandle<T>,1585 sender: &T::CrossAccountId,1586 scope: PropertyScope,1587 property_permissions: Vec<PropertyKeyPermission>,1588 ) -> DispatchResult {1589 for prop_pemission in property_permissions {1590 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1591 }15921593 Ok(())1594 }15951596 1597 pub fn get_collection_property(1598 collection_id: CollectionId,1599 key: &PropertyKey,1600 ) -> Option<PropertyValue> {1601 Self::collection_properties(collection_id).get(key).cloned()1602 }16031604 1605 pub fn bytes_keys_to_property_keys(1606 keys: Vec<Vec<u8>>,1607 ) -> Result<Vec<PropertyKey>, DispatchError> {1608 keys.into_iter()1609 .map(|key| -> Result<PropertyKey, DispatchError> {1610 key.try_into()1611 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1612 })1613 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1614 }16151616 1617 pub fn filter_collection_properties(1618 collection_id: CollectionId,1619 keys: Option<Vec<PropertyKey>>,1620 ) -> Result<Vec<Property>, DispatchError> {1621 let properties = Self::collection_properties(collection_id);16221623 let properties = keys1624 .map(|keys| {1625 keys.into_iter()1626 .filter_map(|key| {1627 properties.get(&key).map(|value| Property {1628 key,1629 value: value.clone(),1630 })1631 })1632 .collect()1633 })1634 .unwrap_or_else(|| {1635 properties1636 .into_iter()1637 .map(|(key, value)| Property { key, value })1638 .collect()1639 });16401641 Ok(properties)1642 }16431644 1645 pub fn filter_property_permissions(1646 collection_id: CollectionId,1647 keys: Option<Vec<PropertyKey>>,1648 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1649 let permissions = Self::property_permissions(collection_id);16501651 let key_permissions = keys1652 .map(|keys| {1653 keys.into_iter()1654 .filter_map(|key| {1655 permissions1656 .get(&key)1657 .map(|permission| PropertyKeyPermission {1658 key,1659 permission: permission.clone(),1660 })1661 })1662 .collect()1663 })1664 .unwrap_or_else(|| {1665 permissions1666 .into_iter()1667 .map(|(key, permission)| PropertyKeyPermission { key, permission })1668 .collect()1669 });16701671 Ok(key_permissions)1672 }16731674 1675 1676 1677 pub fn toggle_allowlist(1678 collection: &CollectionHandle<T>,1679 sender: &T::CrossAccountId,1680 user: &T::CrossAccountId,1681 allowed: bool,1682 ) -> DispatchResult {1683 collection.check_is_owner_or_admin(sender)?;16841685 16861687 if allowed {1688 <Allowlist<T>>::insert((collection.id, user), true);1689 Self::deposit_event(Event::<T>::AllowListAddressAdded(1690 collection.id,1691 user.clone(),1692 ));1693 } else {1694 <Allowlist<T>>::remove((collection.id, user));1695 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1696 collection.id,1697 user.clone(),1698 ));1699 }17001701 <PalletEvm<T>>::deposit_log(1702 erc::CollectionHelpersEvents::CollectionChanged {1703 collection_id: eth::collection_id_to_address(collection.id),1704 }1705 .to_log(T::ContractAddress::get()),1706 );17071708 Ok(())1709 }17101711 1712 1713 1714 pub fn toggle_admin(1715 collection: &CollectionHandle<T>,1716 sender: &T::CrossAccountId,1717 user: &T::CrossAccountId,1718 admin: bool,1719 ) -> DispatchResult {1720 collection.check_is_internal()?;1721 collection.check_is_owner(sender)?;17221723 let is_admin = <IsAdmin<T>>::get((collection.id, user));1724 if is_admin == admin {1725 if admin {1726 return Ok(());1727 } else {1728 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1729 }1730 }1731 let amount = <AdminAmount<T>>::get(collection.id);17321733 17341735 if admin {1736 let amount = amount1737 .checked_add(1)1738 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1739 ensure!(1740 amount <= Self::collection_admins_limit(),1741 <Error<T>>::CollectionAdminCountExceeded,1742 );17431744 <AdminAmount<T>>::insert(collection.id, amount);1745 <IsAdmin<T>>::insert((collection.id, user), true);17461747 Self::deposit_event(Event::<T>::CollectionAdminAdded(1748 collection.id,1749 user.clone(),1750 ));1751 } else {1752 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1753 <IsAdmin<T>>::remove((collection.id, user));17541755 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1756 collection.id,1757 user.clone(),1758 ));1759 }17601761 <PalletEvm<T>>::deposit_log(1762 erc::CollectionHelpersEvents::CollectionChanged {1763 collection_id: eth::collection_id_to_address(collection.id),1764 }1765 .to_log(T::ContractAddress::get()),1766 );17671768 Ok(())1769 }17701771 1772 pub fn update_limits(1773 user: &T::CrossAccountId,1774 collection: &mut CollectionHandle<T>,1775 new_limit: CollectionLimits,1776 ) -> DispatchResult {1777 collection.check_is_internal()?;1778 collection.check_is_owner_or_admin(user)?;17791780 collection.limits =1781 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17821783 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1784 <PalletEvm<T>>::deposit_log(1785 erc::CollectionHelpersEvents::CollectionChanged {1786 collection_id: eth::collection_id_to_address(collection.id),1787 }1788 .to_log(T::ContractAddress::get()),1789 );17901791 collection.save()1792 }17931794 1795 fn clamp_limits(1796 mode: CollectionMode,1797 old_limit: &CollectionLimits,1798 mut new_limit: CollectionLimits,1799 ) -> Result<CollectionLimits, DispatchError> {1800 let limits = old_limit;1801 limit_default!(old_limit, new_limit,1802 account_token_ownership_limit => ensure!(1803 new_limit <= MAX_TOKEN_OWNERSHIP,1804 <Error<T>>::CollectionLimitBoundsExceeded,1805 ),1806 sponsored_data_size => ensure!(1807 new_limit <= CUSTOM_DATA_LIMIT,1808 <Error<T>>::CollectionLimitBoundsExceeded,1809 ),18101811 sponsored_data_rate_limit => {},1812 token_limit => ensure!(1813 old_limit >= new_limit && new_limit > 0,1814 <Error<T>>::CollectionTokenLimitExceeded1815 ),18161817 sponsor_transfer_timeout(match mode {1818 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1819 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1820 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1821 }) => ensure!(1822 new_limit <= MAX_SPONSOR_TIMEOUT,1823 <Error<T>>::CollectionLimitBoundsExceeded,1824 ),1825 sponsor_approve_timeout => {},1826 owner_can_transfer => ensure!(1827 !limits.owner_can_transfer_instaled() ||1828 old_limit || !new_limit,1829 <Error<T>>::OwnerPermissionsCantBeReverted,1830 ),1831 owner_can_destroy => ensure!(1832 old_limit || !new_limit,1833 <Error<T>>::OwnerPermissionsCantBeReverted,1834 ),1835 transfers_enabled => {},1836 );1837 Ok(new_limit)1838 }18391840 1841 pub fn update_permissions(1842 user: &T::CrossAccountId,1843 collection: &mut CollectionHandle<T>,1844 new_permission: CollectionPermissions,1845 ) -> DispatchResult {1846 collection.check_is_internal()?;1847 collection.check_is_owner_or_admin(user)?;1848 collection.permissions = Self::clamp_permissions(1849 collection.mode.clone(),1850 &collection.permissions,1851 new_permission,1852 )?;18531854 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1855 <PalletEvm<T>>::deposit_log(1856 erc::CollectionHelpersEvents::CollectionChanged {1857 collection_id: eth::collection_id_to_address(collection.id),1858 }1859 .to_log(T::ContractAddress::get()),1860 );18611862 collection.save()1863 }18641865 1866 fn clamp_permissions(1867 _mode: CollectionMode,1868 old_permission: &CollectionPermissions,1869 mut new_permission: CollectionPermissions,1870 ) -> Result<CollectionPermissions, DispatchError> {1871 limit_default_clone!(old_permission, new_permission,1872 access => {},1873 mint_mode => {},1874 nesting => { },1875 );1876 Ok(new_permission)1877 }18781879 1880 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1881 CollectionProperties::<T>::mutate(collection_id, |properties| {1882 properties.recompute_consumed_space();1883 });18841885 Ok(())1886 }1887}188818891890#[macro_export]1891macro_rules! unsupported {1892 ($runtime:path) => {1893 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1894 };1895}189618971898pub trait CommonWeightInfo<CrossAccountId> {1899 1900 fn create_item(data: &CreateItemData) -> Weight {1901 Self::create_multiple_items(from_ref(data))1902 }19031904 1905 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19061907 1908 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19091910 1911 fn burn_item() -> Weight;19121913 1914 1915 1916 fn set_collection_properties(amount: u32) -> Weight;19171918 1919 1920 1921 fn delete_collection_properties(amount: u32) -> Weight {1922 Self::set_collection_properties(amount)1923 }19241925 1926 1927 1928 fn set_token_properties(amount: u32) -> Weight;19291930 1931 1932 1933 fn delete_token_properties(amount: u32) -> Weight {1934 Self::set_token_properties(amount)1935 }19361937 1938 1939 1940 fn set_token_property_permissions(amount: u32) -> Weight;19411942 1943 fn transfer() -> Weight;19441945 1946 fn approve() -> Weight;19471948 1949 fn approve_from() -> Weight;19501951 1952 fn transfer_from() -> Weight;19531954 1955 fn burn_from() -> Weight;19561957 1958 fn set_allowance_for_all() -> Weight;19591960 1961 fn force_repair_item() -> Weight;1962}196319641965pub trait RefungibleExtensionsWeightInfo {1966 1967 fn repartition() -> Weight;1968}196919701971197219731974pub trait CommonCollectionOperations<T: Config> {1975 1976 1977 1978 1979 1980 1981 fn create_item(1982 &self,1983 sender: T::CrossAccountId,1984 to: T::CrossAccountId,1985 data: CreateItemData,1986 nesting_budget: &dyn Budget,1987 ) -> DispatchResultWithPostInfo;19881989 1990 1991 1992 1993 1994 1995 fn create_multiple_items(1996 &self,1997 sender: T::CrossAccountId,1998 to: T::CrossAccountId,1999 data: Vec<CreateItemData>,2000 nesting_budget: &dyn Budget,2001 ) -> DispatchResultWithPostInfo;20022003 2004 2005 2006 2007 2008 2009 fn create_multiple_items_ex(2010 &self,2011 sender: T::CrossAccountId,2012 data: CreateItemExData<T::CrossAccountId>,2013 nesting_budget: &dyn Budget,2014 ) -> DispatchResultWithPostInfo;20152016 2017 2018 2019 2020 2021 fn burn_item(2022 &self,2023 sender: T::CrossAccountId,2024 token: TokenId,2025 amount: u128,2026 ) -> DispatchResultWithPostInfo;20272028 2029 2030 2031 2032 fn set_collection_properties(2033 &self,2034 sender: T::CrossAccountId,2035 properties: Vec<Property>,2036 ) -> DispatchResultWithPostInfo;20372038 2039 2040 2041 2042 fn delete_collection_properties(2043 &self,2044 sender: &T::CrossAccountId,2045 property_keys: Vec<PropertyKey>,2046 ) -> DispatchResultWithPostInfo;20472048 2049 2050 2051 2052 2053 2054 2055 2056 2057 fn set_token_properties(2058 &self,2059 sender: T::CrossAccountId,2060 token_id: TokenId,2061 properties: Vec<Property>,2062 budget: &dyn Budget,2063 ) -> DispatchResultWithPostInfo;20642065 2066 2067 2068 2069 2070 2071 2072 2073 2074 fn delete_token_properties(2075 &self,2076 sender: T::CrossAccountId,2077 token_id: TokenId,2078 property_keys: Vec<PropertyKey>,2079 budget: &dyn Budget,2080 ) -> DispatchResultWithPostInfo;20812082 2083 2084 2085 fn get_token_properties_raw(&self, token_id: TokenId) -> Option<TokenProperties>;20862087 2088 2089 2090 2091 fn set_token_properties_raw(&self, token_id: TokenId, map: TokenProperties);20922093 2094 2095 2096 2097 2098 2099 fn set_token_property_permissions(2100 &self,2101 sender: &T::CrossAccountId,2102 property_permissions: Vec<PropertyKeyPermission>,2103 ) -> DispatchResultWithPostInfo;21042105 2106 2107 2108 2109 2110 2111 2112 fn transfer(2113 &self,2114 sender: T::CrossAccountId,2115 to: T::CrossAccountId,2116 token: TokenId,2117 amount: u128,2118 budget: &dyn Budget,2119 ) -> DispatchResultWithPostInfo;21202121 2122 2123 2124 2125 2126 2127 fn approve(2128 &self,2129 sender: T::CrossAccountId,2130 spender: T::CrossAccountId,2131 token: TokenId,2132 amount: u128,2133 ) -> DispatchResultWithPostInfo;21342135 2136 2137 2138 2139 2140 2141 2142 fn approve_from(2143 &self,2144 sender: T::CrossAccountId,2145 from: T::CrossAccountId,2146 to: T::CrossAccountId,2147 token: TokenId,2148 amount: u128,2149 ) -> DispatchResultWithPostInfo;21502151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 fn transfer_from(2162 &self,2163 sender: T::CrossAccountId,2164 from: T::CrossAccountId,2165 to: T::CrossAccountId,2166 token: TokenId,2167 amount: u128,2168 budget: &dyn Budget,2169 ) -> DispatchResultWithPostInfo;21702171 2172 2173 2174 2175 2176 2177 2178 2179 2180 fn burn_from(2181 &self,2182 sender: T::CrossAccountId,2183 from: T::CrossAccountId,2184 token: TokenId,2185 amount: u128,2186 budget: &dyn Budget,2187 ) -> DispatchResultWithPostInfo;21882189 2190 2191 2192 2193 2194 2195 fn check_nesting(2196 &self,2197 sender: T::CrossAccountId,2198 from: (CollectionId, TokenId),2199 under: TokenId,2200 budget: &dyn Budget,2201 ) -> DispatchResult;22022203 2204 2205 2206 2207 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22082209 2210 2211 2212 2213 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22142215 2216 2217 2218 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22192220 2221 fn collection_tokens(&self) -> Vec<TokenId>;22222223 2224 2225 2226 fn token_exists(&self, token: TokenId) -> bool;22272228 2229 fn last_token_id(&self) -> TokenId;22302231 2232 2233 2234 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22352236 2237 2238 2239 2240 2241 fn check_token_indirect_owner(2242 &self,2243 token: TokenId,2244 maybe_owner: &T::CrossAccountId,2245 nesting_budget: &dyn Budget,2246 ) -> Result<bool, DispatchError>;22472248 2249 2250 2251 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22522253 2254 2255 2256 2257 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22582259 2260 2261 2262 2263 2264 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22652266 2267 fn total_supply(&self) -> u32;22682269 2270 2271 2272 fn account_balance(&self, account: T::CrossAccountId) -> u32;22732274 2275 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22762277 2278 fn total_pieces(&self, token: TokenId) -> Option<u128>;22792280 2281 2282 2283 2284 2285 fn allowance(2286 &self,2287 sender: T::CrossAccountId,2288 spender: T::CrossAccountId,2289 token: TokenId,2290 ) -> u128;22912292 2293 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22942295 2296 2297 2298 2299 fn set_allowance_for_all(2300 &self,2301 owner: T::CrossAccountId,2302 operator: T::CrossAccountId,2303 approve: bool,2304 ) -> DispatchResultWithPostInfo;23052306 2307 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23082309 2310 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2311}231223132314pub trait RefungibleExtensions<T>2315where2316 T: Config,2317{2318 2319 2320 2321 2322 2323 2324 2325 fn repartition(2326 &self,2327 sender: &T::CrossAccountId,2328 token: TokenId,2329 amount: u128,2330 ) -> DispatchResultWithPostInfo;2331}23322333233423352336pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2337 let post_info = PostDispatchInfo {2338 actual_weight: Some(weight),2339 pays_fee: Pays::Yes,2340 };2341 match res {2342 Ok(()) => Ok(post_info),2343 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2344 }2345}23462347impl<T: Config> From<PropertiesError> for Error<T> {2348 fn from(error: PropertiesError) -> Self {2349 match error {2350 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2351 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2352 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2353 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2354 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2355 }2356 }2357}23582359236023612362236323642365pub struct PropertyWriter<'a, WriterVariant, T, Handle> {2366 collection: &'a Handle,2367 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2368 _phantom: PhantomData<(T, WriterVariant)>,2369}23702371impl<'a, T, Handle, WriterVariant> PropertyWriter<'a, WriterVariant, T, Handle>2372where2373 T: Config,2374 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2375{2376 fn internal_write_token_properties(2377 &mut self,2378 token_id: TokenId,2379 mut token_lazy_info: PropertyWriterLazyTokenInfo,2380 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2381 log: evm_coder::ethereum::Log,2382 ) -> DispatchResult {2383 for (key, value) in properties_updates {2384 let permission = self2385 .collection_lazy_info2386 .property_permissions2387 .value()2388 .get(&key)2389 .cloned()2390 .unwrap_or_else(PropertyPermission::none);23912392 match permission {2393 PropertyPermission { mutable: false, .. }2394 if token_lazy_info2395 .stored_properties2396 .value()2397 .get(&key)2398 .is_some() =>2399 {2400 return Err(<Error<T>>::NoPermission.into());2401 }24022403 PropertyPermission {2404 collection_admin,2405 token_owner,2406 ..2407 } => check_token_permissions::<T>(2408 collection_admin,2409 token_owner,2410 &mut self.collection_lazy_info.is_collection_admin,2411 &mut token_lazy_info.is_token_owner,2412 &mut token_lazy_info.is_token_exist,2413 )?,2414 }24152416 match value {2417 Some(value) => {2418 token_lazy_info2419 .stored_properties2420 .value_mut()2421 .try_set(key.clone(), value)2422 .map_err(<Error<T>>::from)?;24232424 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2425 self.collection.id,2426 token_id,2427 key,2428 ));2429 }2430 None => {2431 token_lazy_info2432 .stored_properties2433 .value_mut()2434 .remove(&key)2435 .map_err(<Error<T>>::from)?;24362437 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2438 self.collection.id,2439 token_id,2440 key,2441 ));2442 }2443 }2444 }24452446 let properties_changed = token_lazy_info.stored_properties.has_value();2447 if properties_changed {2448 <PalletEvm<T>>::deposit_log(log);24492450 self.collection2451 .set_token_properties_raw(token_id, token_lazy_info.stored_properties.into_inner());2452 }24532454 Ok(())2455 }2456}24572458245924602461pub struct PropertyWriterLazyCollectionInfo<'a> {2462 is_collection_admin: LazyValue<'a, bool>,2463 property_permissions: LazyValue<'a, PropertiesPermissionMap>,2464}2465246624672468pub struct PropertyWriterLazyTokenInfo<'a> {2469 is_token_exist: LazyValue<'a, bool>,2470 is_token_owner: LazyValue<'a, Result<bool, DispatchError>>,2471 stored_properties: LazyValue<'a, TokenProperties>,2472}24732474impl<'a> PropertyWriterLazyTokenInfo<'a> {2475 2476 pub fn new(2477 check_token_exist: impl FnOnce() -> bool + 'a,2478 check_token_owner: impl FnOnce() -> Result<bool, DispatchError> + 'a,2479 get_token_properties: impl FnOnce() -> TokenProperties + 'a,2480 ) -> Self {2481 Self {2482 is_token_exist: LazyValue::new(check_token_exist),2483 is_token_owner: LazyValue::new(check_token_owner),2484 stored_properties: LazyValue::new(get_token_properties),2485 }2486 }2487}2488248924902491pub struct NewTokenPropertyWriter<T>(PhantomData<T>);2492impl<T: Config> NewTokenPropertyWriter<T> {2493 2494 pub fn new<'a, Handle>(2495 collection: &'a Handle,2496 sender: &'a T::CrossAccountId,2497 ) -> PropertyWriter<'a, Self, T, Handle>2498 where2499 T: Config,2500 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2501 {2502 PropertyWriter {2503 collection,2504 collection_lazy_info: PropertyWriterLazyCollectionInfo {2505 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2506 property_permissions: LazyValue::new(|| {2507 <Pallet<T>>::property_permissions(collection.id)2508 }),2509 },2510 _phantom: PhantomData,2511 }2512 }2513}25142515impl<'a, T, Handle> PropertyWriter<'a, NewTokenPropertyWriter<T>, T, Handle>2516where2517 T: Config,2518 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2519{2520 2521 pub fn write_token_properties(2522 &mut self,2523 mint_target_is_sender: bool,2524 token_id: TokenId,2525 properties_updates: impl Iterator<Item = Property>,2526 log: evm_coder::ethereum::Log,2527 ) -> DispatchResult {2528 let check_token_exist = || {2529 debug_assert!(self.collection.token_exists(token_id));2530 true2531 };25322533 let check_token_owner = || Ok(mint_target_is_sender);25342535 let get_token_properties = || {2536 debug_assert!(self.collection.get_token_properties_raw(token_id).is_none());2537 TokenProperties::new()2538 };25392540 self.internal_write_token_properties(2541 token_id,2542 PropertyWriterLazyTokenInfo::new(2543 check_token_exist,2544 check_token_owner,2545 get_token_properties,2546 ),2547 properties_updates.map(|p| (p.key, Some(p.value))),2548 log,2549 )2550 }2551}2552255325542555pub struct ExistingTokenPropertyWriter<T>(PhantomData<T>);2556impl<T: Config> ExistingTokenPropertyWriter<T> {2557 2558 pub fn new<'a, Handle>(2559 collection: &'a Handle,2560 sender: &'a T::CrossAccountId,2561 ) -> PropertyWriter<'a, Self, T, Handle>2562 where2563 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2564 {2565 PropertyWriter {2566 collection,2567 collection_lazy_info: PropertyWriterLazyCollectionInfo {2568 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2569 property_permissions: LazyValue::new(|| {2570 <Pallet<T>>::property_permissions(collection.id)2571 }),2572 },2573 _phantom: PhantomData,2574 }2575 }2576}25772578impl<'a, T, Handle> PropertyWriter<'a, ExistingTokenPropertyWriter<T>, T, Handle>2579where2580 T: Config,2581 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2582{2583 2584 pub fn write_token_properties(2585 &mut self,2586 sender: &T::CrossAccountId,2587 token_id: TokenId,2588 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2589 nesting_budget: &dyn Budget,2590 log: evm_coder::ethereum::Log,2591 ) -> DispatchResult {2592 let check_token_exist = || self.collection.token_exists(token_id);2593 let check_token_owner = || {2594 self.collection2595 .check_token_indirect_owner(token_id, sender, nesting_budget)2596 };2597 let get_token_properties = || {2598 self.collection2599 .get_token_properties_raw(token_id)2600 .unwrap_or_default()2601 };26022603 self.internal_write_token_properties(2604 token_id,2605 PropertyWriterLazyTokenInfo::new(2606 check_token_exist,2607 check_token_owner,2608 get_token_properties,2609 ),2610 properties_updates,2611 log,2612 )2613 }2614}2615261626172618#[cfg(feature = "runtime-benchmarks")]2619pub struct BenchmarkPropertyWriter<T>(PhantomData<T>);26202621#[cfg(feature = "runtime-benchmarks")]2622impl<T: Config> BenchmarkPropertyWriter<T> {2623 2624 pub fn new<'a, Handle>(2625 collection: &'a Handle,2626 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2627 ) -> PropertyWriter<'a, Self, T, Handle>2628 where2629 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2630 {2631 PropertyWriter {2632 collection,2633 collection_lazy_info,2634 _phantom: PhantomData,2635 }2636 }26372638 2639 pub fn load_collection_info<Handle>(2640 collection_handle: &Handle,2641 sender: &T::CrossAccountId,2642 ) -> PropertyWriterLazyCollectionInfo<'static>2643 where2644 Handle: Deref<Target = CollectionHandle<T>>,2645 {2646 let is_collection_admin = collection_handle.is_owner_or_admin(sender);2647 let property_permissions = <Pallet<T>>::property_permissions(collection_handle.id);26482649 PropertyWriterLazyCollectionInfo {2650 is_collection_admin: LazyValue::new(move || is_collection_admin),2651 property_permissions: LazyValue::new(move || property_permissions),2652 }2653 }26542655 2656 pub fn load_token_properties<Handle>(2657 collection: &Handle,2658 token_id: TokenId,2659 ) -> PropertyWriterLazyTokenInfo2660 where2661 Handle: CommonCollectionOperations<T>,2662 {2663 let stored_properties = collection2664 .get_token_properties_raw(token_id)2665 .unwrap_or_default();26662667 PropertyWriterLazyTokenInfo {2668 is_token_exist: LazyValue::new(|| true),2669 is_token_owner: LazyValue::new(|| Ok(true)),2670 stored_properties: LazyValue::new(move || stored_properties),2671 }2672 }2673}26742675#[cfg(feature = "runtime-benchmarks")]2676impl<'a, T, Handle> PropertyWriter<'a, BenchmarkPropertyWriter<T>, T, Handle>2677where2678 T: Config,2679 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2680{2681 2682 pub fn write_token_properties(2683 &mut self,2684 token_id: TokenId,2685 properties_updates: impl Iterator<Item = Property>,2686 log: evm_coder::ethereum::Log,2687 ) -> DispatchResult {2688 let check_token_exist = || true;2689 let check_token_owner = || Ok(true);2690 let get_token_properties = TokenProperties::new;26912692 self.internal_write_token_properties(2693 token_id,2694 PropertyWriterLazyTokenInfo::new(2695 check_token_exist,2696 check_token_owner,2697 get_token_properties,2698 ),2699 properties_updates.map(|p| (p.key, Some(p.value))),2700 log,2701 )2702 }2703}270427052706270727082709pub fn write_token_properties_total_weight<T: Config, I: Fn(u32) -> Weight>(2710 properties_nums: impl Iterator<Item = u32>,2711 per_token_weight: I,2712) -> Weight {2713 let mut weight = properties_nums2714 .filter_map(|properties_num| {2715 if properties_num > 0 {2716 Some(per_token_weight(properties_num))2717 } else {2718 None2719 }2720 })2721 .fold(Weight::zero(), |a, b| a.saturating_add(b));27222723 if !weight.is_zero() {2724 2725 2726 27272728 weight = weight.saturating_add(<SelfWeightOf<T>>::property_writer_load_collection_info());2729 }27302731 weight2732}27332734#[cfg(any(feature = "tests", test))]2735#[allow(missing_docs)]2736pub mod tests {2737 use crate::{Config, DispatchError, DispatchResult, LazyValue};27382739 const fn to_bool(u: u8) -> bool {2740 u != 02741 }27422743 #[derive(Debug)]2744 pub struct TestCase {2745 pub collection_admin: bool,2746 pub is_collection_admin: bool,2747 pub token_owner: bool,2748 pub is_token_owner: bool,2749 pub no_permission: bool,2750 }27512752 impl TestCase {2753 const fn new(2754 collection_admin: u8,2755 is_collection_admin: u8,2756 token_owner: u8,2757 is_token_owner: u8,2758 no_permission: u8,2759 ) -> Self {2760 Self {2761 collection_admin: to_bool(collection_admin),2762 is_collection_admin: to_bool(is_collection_admin),2763 token_owner: to_bool(token_owner),2764 is_token_owner: to_bool(is_token_owner),2765 no_permission: to_bool(no_permission),2766 }2767 }2768 }27692770 #[rustfmt::skip]2771 pub const TABLE: [TestCase; 16] = [2772 2773 2774 2775 2776 2777 TestCase::new(0, 0, 0, 0, 1),2778 TestCase::new(0, 0, 0, 1, 1),2779 TestCase::new(0, 0, 1, 0, 1),2780 TestCase::new(0, 0, 1, 1, 0),2781 TestCase::new(0, 1, 0, 0, 1),2782 TestCase::new(0, 1, 0, 1, 1),2783 TestCase::new(0, 1, 1, 0, 1),2784 TestCase::new(0, 1, 1, 1, 0),2785 TestCase::new(1, 0, 0, 0, 1),2786 TestCase::new(1, 0, 0, 1, 1),2787 TestCase::new(1, 0, 1, 0, 1),2788 TestCase::new(1, 0, 1, 1, 0),2789 TestCase::new(1, 1, 0, 0, 0),2790 TestCase::new(1, 1, 0, 1, 0),2791 TestCase::new(1, 1, 1, 0, 0),2792 TestCase::new(1, 1, 1, 1, 0),2793 ];27942795 pub fn check_token_permissions<T: Config>(2796 collection_admin_permitted: bool,2797 token_owner_permitted: bool,2798 is_collection_admin: &mut LazyValue<bool>,2799 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>>,2800 check_token_existence: &mut LazyValue<bool>,2801 ) -> DispatchResult {2802 crate::check_token_permissions::<T>(2803 collection_admin_permitted,2804 token_owner_permitted,2805 is_collection_admin,2806 check_token_ownership,2807 check_token_existence,2808 )2809 }2810}