12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::{57 ops::{Deref, DerefMut},58 slice::from_ref,59 marker::PhantomData,60};61use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};62use sp_std::vec::Vec;63use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};64use evm_coder::ToLog;65use frame_support::{66 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},67 ensure,68 traits::{69 Get,70 fungible::{Balanced, Debt, Inspect},71 tokens::{Imbalance, Precision, Preservation},72 },73 dispatch::Pays,74 transactional, fail,75};76use up_data_structs::{77 AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, RpcCollectionFlags,78 CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, TokenId,79 TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,80 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,81 CUSTOM_DATA_LIMIT, CollectionLimits, CreateCollectionData, SponsorshipState, CreateItemExData,82 SponsoringRateLimit, budget::Budget, PhantomType, Property,83 CollectionProperties as CollectionPropertiesT, TokenProperties, PropertiesPermissionMap,84 PropertyKey, PropertyValue, PropertyPermission, PropertiesError, TokenOwnerError,85 PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope, CollectionPermissions,86};87use up_pov_estimate_rpc::PovInfo;8889pub use pallet::*;90use sp_core::H160;91use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, traits::Zero};9293#[cfg(feature = "runtime-benchmarks")]94pub mod benchmarking;95pub mod dispatch;96pub mod erc;97pub mod eth;98pub mod helpers;99#[allow(missing_docs)]100pub mod weights;101102use weights::WeightInfo;103104105pub type SelfWeightOf<T> = <T as Config>::WeightInfo;106107108109110111112113#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]114pub struct CollectionHandle<T: Config> {115 116 pub id: CollectionId,117 collection: Collection<T::AccountId>,118 119 pub recorder: SubstrateRecorder<T>,120}121122impl<T: Config> WithRecorder<T> for CollectionHandle<T> {123 fn recorder(&self) -> &SubstrateRecorder<T> {124 &self.recorder125 }126 fn into_recorder(self) -> SubstrateRecorder<T> {127 self.recorder128 }129}130131impl<T: Config> CollectionHandle<T> {132 133 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {134 Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))135 }136137 138 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {139 <CollectionById<T>>::get(id).map(|collection| Self {140 id,141 collection,142 recorder,143 })144 }145146 147 148 pub fn new(id: CollectionId) -> Option<Self> {149 Self::new_with_gas_limit(id, u64::MAX)150 }151152 153 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {154 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)155 }156157 158 pub fn consume_store_reads(159 &self,160 reads: u64,161 ) -> pallet_evm_coder_substrate::execution::Result<()> {162 self.recorder().consume_store_reads(reads)163 }164165 166 pub fn consume_store_writes(167 &self,168 writes: u64,169 ) -> pallet_evm_coder_substrate::execution::Result<()> {170 self.recorder().consume_store_writes(writes)171 }172173 174 pub fn consume_store_reads_and_writes(175 &self,176 reads: u64,177 writes: u64,178 ) -> pallet_evm_coder_substrate::execution::Result<()> {179 self.recorder()180 .consume_store_reads_and_writes(reads, writes)181 }182183 184 pub fn save(&self) -> DispatchResult {185 <CollectionById<T>>::insert(self.id, &self.collection);186 Ok(())187 }188189 190 191 192 193 194 pub fn set_sponsor(195 &mut self,196 sender: &T::CrossAccountId,197 sponsor: T::AccountId,198 ) -> DispatchResult {199 self.check_is_internal()?;200 self.check_is_owner_or_admin(sender)?;201202 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());203204 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));205 <PalletEvm<T>>::deposit_log(206 erc::CollectionHelpersEvents::CollectionChanged {207 collection_id: eth::collection_id_to_address(self.id),208 }209 .to_log(T::ContractAddress::get()),210 );211212 self.save()213 }214215 216 217 218 219 220 221 222 223 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {224 self.check_is_internal()?;225226 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());227228 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));229 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));230 <PalletEvm<T>>::deposit_log(231 erc::CollectionHelpersEvents::CollectionChanged {232 collection_id: eth::collection_id_to_address(self.id),233 }234 .to_log(T::ContractAddress::get()),235 );236237 self.save()238 }239240 241 242 243 244 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {245 self.check_is_internal()?;246 ensure!(247 self.collection.sponsorship.pending_sponsor() == Some(sender),248 Error::<T>::ConfirmSponsorshipFail249 );250251 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());252253 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));254 <PalletEvm<T>>::deposit_log(255 erc::CollectionHelpersEvents::CollectionChanged {256 collection_id: eth::collection_id_to_address(self.id),257 }258 .to_log(T::ContractAddress::get()),259 );260261 self.save()262 }263264 265 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {266 self.check_is_internal()?;267 self.check_is_owner_or_admin(sender)?;268269 self.collection.sponsorship = SponsorshipState::Disabled;270271 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));272 <PalletEvm<T>>::deposit_log(273 erc::CollectionHelpersEvents::CollectionChanged {274 collection_id: eth::collection_id_to_address(self.id),275 }276 .to_log(T::ContractAddress::get()),277 );278 self.save()279 }280281 282 283 284 285 pub fn force_remove_sponsor(&mut self) -> DispatchResult {286 self.check_is_internal()?;287288 self.collection.sponsorship = SponsorshipState::Disabled;289290 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));291 <PalletEvm<T>>::deposit_log(292 erc::CollectionHelpersEvents::CollectionChanged {293 collection_id: eth::collection_id_to_address(self.id),294 }295 .to_log(T::ContractAddress::get()),296 );297 self.save()298 }299300 301 302 pub fn check_is_internal(&self) -> DispatchResult {303 if self.flags.external {304 return Err(<Error<T>>::CollectionIsExternal)?;305 }306307 Ok(())308 }309310 311 312 pub fn check_is_external(&self) -> DispatchResult {313 if !self.flags.external {314 return Err(<Error<T>>::CollectionIsInternal)?;315 }316317 Ok(())318 }319}320321impl<T: Config> Deref for CollectionHandle<T> {322 type Target = Collection<T::AccountId>;323324 fn deref(&self) -> &Self::Target {325 &self.collection326 }327}328329impl<T: Config> DerefMut for CollectionHandle<T> {330 fn deref_mut(&mut self) -> &mut Self::Target {331 &mut self.collection332 }333}334335impl<T: Config> CollectionHandle<T> {336 337 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {338 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);339 Ok(())340 }341342 343 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {344 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))345 }346347 348 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {349 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);350 Ok(())351 }352353 354 355 356 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {357 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)358 }359360 361 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {362 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)363 }364365 366 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {367 ensure!(368 <Allowlist<T>>::get((self.id, user)),369 <Error<T>>::AddressNotInAllowlist370 );371 Ok(())372 }373374 375 376 377 pub fn change_owner(378 &mut self,379 caller: T::CrossAccountId,380 new_owner: T::CrossAccountId,381 ) -> DispatchResult {382 self.check_is_internal()?;383 self.check_is_owner(&caller)?;384 self.collection.owner = new_owner.as_sub().clone();385386 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(387 self.id,388 new_owner.as_sub().clone(),389 ));390 <PalletEvm<T>>::deposit_log(391 erc::CollectionHelpersEvents::CollectionChanged {392 collection_id: eth::collection_id_to_address(self.id),393 }394 .to_log(T::ContractAddress::get()),395 );396397 self.save()398 }399}400401#[frame_support::pallet]402pub mod pallet {403404 use super::*;405 use dispatch::CollectionDispatch;406 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};407 use up_data_structs::{TokenId, mapping::TokenAddressMapping};408 use scale_info::TypeInfo;409 use weights::WeightInfo;410411 #[pallet::config]412 pub trait Config:413 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo414 {415 416 type WeightInfo: WeightInfo;417418 419 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;420421 422 type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;423424 425 #[pallet::constant]426 type CollectionCreationPrice: Get<427 <<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,428 >;429430 431 type CollectionDispatch: CollectionDispatch<Self>;432433 434 type TreasuryAccountId: Get<Self::AccountId>;435436 437 #[pallet::constant]438 type ContractAddress: Get<H160>;439440 441 type EvmTokenAddressMapping: TokenAddressMapping<H160>;442443 444 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;445 }446447 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);448 449 pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);450451 #[pallet::pallet]452 #[pallet::storage_version(STORAGE_VERSION)]453 pub struct Pallet<T>(_);454455 #[pallet::extra_constants]456 impl<T: Config> Pallet<T> {457 458 pub fn collection_admins_limit() -> u32 {459 COLLECTION_ADMINS_LIMIT460 }461 }462463 #[pallet::genesis_config]464 pub struct GenesisConfig<T>(PhantomData<T>);465466 #[cfg(feature = "std")]467 impl<T: Config> Default for GenesisConfig<T> {468 fn default() -> Self {469 Self(Default::default())470 }471 }472473 #[pallet::genesis_build]474 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {475 fn build(&self) {476 StorageVersion::new(1).put::<Pallet<T>>();477 }478 }479480 impl<T: Config> Pallet<T> {481 482 pub fn deposit_event(event: Event<T>) {483 let event = <T as Config>::RuntimeEvent::from(event);484 let event = event.into();485 <frame_system::Pallet<T>>::deposit_event(event)486 }487 }488489 #[pallet::event]490 pub enum Event<T: Config> {491 492 CollectionCreated(493 494 CollectionId,495 496 u8,497 498 T::AccountId,499 ),500501 502 CollectionDestroyed(503 504 CollectionId,505 ),506507 508 ItemCreated(509 510 CollectionId,511 512 TokenId,513 514 T::CrossAccountId,515 516 u128,517 ),518519 520 ItemDestroyed(521 522 CollectionId,523 524 TokenId,525 526 T::CrossAccountId,527 528 u128,529 ),530531 532 Transfer(533 534 CollectionId,535 536 TokenId,537 538 T::CrossAccountId,539 540 T::CrossAccountId,541 542 u128,543 ),544545 546 Approved(547 548 CollectionId,549 550 TokenId,551 552 T::CrossAccountId,553 554 T::CrossAccountId,555 556 u128,557 ),558559 560 ApprovedForAll(561 562 CollectionId,563 564 T::CrossAccountId,565 566 T::CrossAccountId,567 568 bool,569 ),570571 572 CollectionPropertySet(573 574 CollectionId,575 576 PropertyKey,577 ),578579 580 CollectionPropertyDeleted(581 582 CollectionId,583 584 PropertyKey,585 ),586587 588 TokenPropertySet(589 590 CollectionId,591 592 TokenId,593 594 PropertyKey,595 ),596597 598 TokenPropertyDeleted(599 600 CollectionId,601 602 TokenId,603 604 PropertyKey,605 ),606607 608 PropertyPermissionSet(609 610 CollectionId,611 612 PropertyKey,613 ),614615 616 AllowListAddressAdded(617 618 CollectionId,619 620 T::CrossAccountId,621 ),622623 624 AllowListAddressRemoved(625 626 CollectionId,627 628 T::CrossAccountId,629 ),630631 632 CollectionAdminAdded(633 634 CollectionId,635 636 T::CrossAccountId,637 ),638639 640 CollectionAdminRemoved(641 642 CollectionId,643 644 T::CrossAccountId,645 ),646647 648 CollectionLimitSet(649 650 CollectionId,651 ),652653 654 CollectionOwnerChanged(655 656 CollectionId,657 658 T::AccountId,659 ),660661 662 CollectionPermissionSet(663 664 CollectionId,665 ),666667 668 CollectionSponsorSet(669 670 CollectionId,671 672 T::AccountId,673 ),674675 676 SponsorshipConfirmed(677 678 CollectionId,679 680 T::AccountId,681 ),682683 684 CollectionSponsorRemoved(685 686 CollectionId,687 ),688 }689690 #[pallet::error]691 pub enum Error<T> {692 693 CollectionNotFound,694 695 MustBeTokenOwner,696 697 NoPermission,698 699 CantDestroyNotEmptyCollection,700 701 PublicMintingNotAllowed,702 703 AddressNotInAllowlist,704705 706 CollectionNameLimitExceeded,707 708 CollectionDescriptionLimitExceeded,709 710 CollectionTokenPrefixLimitExceeded,711 712 TotalCollectionsLimitExceeded,713 714 CollectionAdminCountExceeded,715 716 CollectionLimitBoundsExceeded,717 718 OwnerPermissionsCantBeReverted,719 720 TransferNotAllowed,721 722 AccountTokenLimitExceeded,723 724 CollectionTokenLimitExceeded,725 726 MetadataFlagFrozen,727728 729 TokenNotFound,730 731 TokenValueTooLow,732 733 ApprovedValueTooLow,734 735 CantApproveMoreThanOwned,736 737 AddressIsNotEthMirror,738739 740 AddressIsZero,741742 743 UnsupportedOperation,744745 746 NotSufficientFounds,747748 749 UserIsNotAllowedToNest,750 751 SourceCollectionIsNotAllowedToNest,752753 754 CollectionFieldSizeExceeded,755756 757 NoSpaceForProperty,758759 760 PropertyLimitReached,761762 763 PropertyKeyIsTooLong,764765 766 InvalidCharacterInPropertyKey,767768 769 EmptyPropertyKey,770771 772 CollectionIsExternal,773774 775 CollectionIsInternal,776777 778 ConfirmSponsorshipFail,779780 781 UserIsNotCollectionAdmin,782 }783784 785 #[pallet::storage]786 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;787788 789 #[pallet::storage]790 pub type DestroyedCollectionCount<T> =791 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;792793 794 #[pallet::storage]795 pub type CollectionById<T> = StorageMap<796 Hasher = Blake2_128Concat,797 Key = CollectionId,798 Value = Collection<<T as frame_system::Config>::AccountId>,799 QueryKind = OptionQuery,800 >;801802 803 #[pallet::storage]804 #[pallet::getter(fn collection_properties)]805 pub type CollectionProperties<T> = StorageMap<806 Hasher = Blake2_128Concat,807 Key = CollectionId,808 Value = CollectionPropertiesT,809 QueryKind = ValueQuery,810 >;811812 813 #[pallet::storage]814 #[pallet::getter(fn property_permissions)]815 pub type CollectionPropertyPermissions<T> = StorageMap<816 Hasher = Blake2_128Concat,817 Key = CollectionId,818 Value = PropertiesPermissionMap,819 QueryKind = ValueQuery,820 >;821822 823 #[pallet::storage]824 pub type AdminAmount<T> = StorageMap<825 Hasher = Blake2_128Concat,826 Key = CollectionId,827 Value = u32,828 QueryKind = ValueQuery,829 >;830831 832 #[pallet::storage]833 pub type IsAdmin<T: Config> = StorageNMap<834 Key = (835 Key<Blake2_128Concat, CollectionId>,836 Key<Blake2_128Concat, T::CrossAccountId>,837 ),838 Value = bool,839 QueryKind = ValueQuery,840 >;841842 843 #[pallet::storage]844 pub type Allowlist<T: Config> = StorageNMap<845 Key = (846 Key<Blake2_128Concat, CollectionId>,847 Key<Blake2_128Concat, T::CrossAccountId>,848 ),849 Value = bool,850 QueryKind = ValueQuery,851 >;852853 854 #[pallet::storage]855 pub type DummyStorageValue<T: Config> = StorageValue<856 Value = (857 CollectionStats,858 CollectionId,859 TokenId,860 TokenChild,861 PhantomType<(862 TokenData<T::CrossAccountId>,863 RpcCollection<T::AccountId>,864 865 PovInfo,866 )>,867 ),868 QueryKind = OptionQuery,869 >;870}871872873pub struct LazyValue<T, F: FnOnce() -> T> {874 value: Option<T>,875 f: Option<F>,876}877878impl<T, F: FnOnce() -> T> LazyValue<T, F> {879 880 pub fn new(f: F) -> Self {881 Self {882 value: None,883 f: Some(f),884 }885 }886887 888 pub fn value(&mut self) -> &T {889 self.compute_value_if_not_already();890 self.value.as_ref().unwrap()891 }892893 894 pub fn value_mut(&mut self) -> &mut T {895 self.compute_value_if_not_already();896 self.value.as_mut().unwrap()897 }898899 fn into_inner(mut self) -> T {900 self.compute_value_if_not_already();901 self.value.unwrap()902 }903904 905 pub fn has_value(&self) -> bool {906 self.value.is_some()907 }908909 fn compute_value_if_not_already(&mut self) {910 if self.value.is_none() {911 self.value = Some(self.f.take().unwrap()())912 }913 }914}915916fn check_token_permissions<T, FCA, FTO, FTE>(917 collection_admin_permitted: bool,918 token_owner_permitted: bool,919 is_collection_admin: &mut LazyValue<bool, FCA>,920 is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,921 is_token_exist: &mut LazyValue<bool, FTE>,922) -> DispatchResult923where924 T: Config,925 FCA: FnOnce() -> bool,926 FTO: FnOnce() -> Result<bool, DispatchError>,927 FTE: FnOnce() -> bool,928{929 if !(collection_admin_permitted && *is_collection_admin.value()930 || token_owner_permitted && (*is_token_owner.value())?)931 {932 fail!(<Error<T>>::NoPermission);933 }934935 let token_exist_due_to_owner_check_success =936 is_token_owner.has_value() && (*is_token_owner.value())?;937938 939 940 if !token_exist_due_to_owner_check_success {941 942 943 if !is_token_exist.value() {944 fail!(<Error<T>>::TokenNotFound);945 }946 }947948 Ok(())949}950951impl<T: Config> Pallet<T> {952 953 954 955 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {956 ensure!(957 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,958 <Error<T>>::AddressIsZero959 );960 Ok(())961 }962963 964 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {965 <IsAdmin<T>>::iter_prefix((collection,))966 .map(|(a, _)| a)967 .collect()968 }969970 971 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {972 <Allowlist<T>>::iter_prefix((collection,))973 .map(|(a, _)| a)974 .collect()975 }976977 978 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {979 <Allowlist<T>>::get((collection, user))980 }981982 983 pub fn collection_stats() -> CollectionStats {984 let created = <CreatedCollectionCount<T>>::get();985 let destroyed = <DestroyedCollectionCount<T>>::get();986 CollectionStats {987 created: created.0,988 destroyed: destroyed.0,989 alive: created.0 - destroyed.0,990 }991 }992993 994 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {995 let collection = <CollectionById<T>>::get(collection)?;996 let limits = collection.limits;997 let effective_limits = CollectionLimits {998 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),999 sponsored_data_size: Some(limits.sponsored_data_size()),1000 sponsored_data_rate_limit: Some(1001 limits1002 .sponsored_data_rate_limit1003 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),1004 ),1005 token_limit: Some(limits.token_limit()),1006 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(1007 match collection.mode {1008 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1009 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1010 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1011 },1012 )),1013 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),1014 owner_can_transfer: Some(limits.owner_can_transfer()),1015 owner_can_destroy: Some(limits.owner_can_destroy()),1016 transfers_enabled: Some(limits.transfers_enabled()),1017 };10181019 Some(effective_limits)1020 }10211022 1023 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1024 let Collection {1025 name,1026 description,1027 owner,1028 mode,1029 token_prefix,1030 sponsorship,1031 limits,1032 permissions,1033 flags,1034 } = <CollectionById<T>>::get(collection)?;10351036 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1037 .into_iter()1038 .map(|(key, permission)| PropertyKeyPermission { key, permission })1039 .collect();10401041 let properties = <CollectionProperties<T>>::get(collection)1042 .into_iter()1043 .map(|(key, value)| Property { key, value })1044 .collect();10451046 let permissions = CollectionPermissions {1047 access: Some(permissions.access()),1048 mint_mode: Some(permissions.mint_mode()),1049 nesting: Some(permissions.nesting().clone()),1050 };10511052 Some(RpcCollection {1053 name: name.into_inner(),1054 description: description.into_inner(),1055 owner,1056 mode,1057 token_prefix: token_prefix.into_inner(),1058 sponsorship,1059 limits,1060 permissions,1061 token_property_permissions,1062 properties,1063 read_only: flags.external,10641065 flags: RpcCollectionFlags {1066 foreign: flags.foreign,1067 erc721metadata: flags.erc721metadata,1068 },1069 })1070 }1071}10721073macro_rules! limit_default {1074 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1075 $(1076 if let Some($new) = $new.$field {1077 let $old = $old.$field($($arg)?);1078 let _ = $new;1079 let _ = $old;1080 $check1081 } else {1082 $new.$field = $old.$field1083 }1084 )*1085 }};1086}1087macro_rules! limit_default_clone {1088 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1089 $(1090 if let Some($new) = $new.$field.clone() {1091 let $old = $old.$field($($arg)?);1092 let _ = $new;1093 let _ = $old;1094 $check1095 } else {1096 $new.$field = $old.$field.clone()1097 }1098 )*1099 }};1100}11011102impl<T: Config> Pallet<T> {1103 1104 1105 1106 1107 1108 pub fn init_collection(1109 owner: T::CrossAccountId,1110 payer: T::CrossAccountId,1111 data: CreateCollectionData<T::CrossAccountId>,1112 ) -> Result<CollectionId, DispatchError> {1113 ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);1114 Self::init_collection_internal(owner, payer, data)1115 }11161117 1118 pub fn init_foreign_collection(1119 owner: T::CrossAccountId,1120 payer: T::CrossAccountId,1121 mut data: CreateCollectionData<T::CrossAccountId>,1122 ) -> Result<CollectionId, DispatchError> {1123 data.flags.foreign = true;1124 let id = Self::init_collection_internal(owner, payer, data)?;1125 Ok(id)1126 }11271128 fn init_collection_internal(1129 owner: T::CrossAccountId,1130 payer: T::CrossAccountId,1131 data: CreateCollectionData<T::CrossAccountId>,1132 ) -> Result<CollectionId, DispatchError> {1133 {1134 ensure!(1135 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1136 Error::<T>::CollectionTokenPrefixLimitExceeded1137 );1138 }11391140 let created_count = <CreatedCollectionCount<T>>::get()1141 .01142 .checked_add(1)1143 .ok_or(ArithmeticError::Overflow)?;1144 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1145 let id = CollectionId(created_count);11461147 1148 ensure!(1149 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1150 <Error<T>>::TotalCollectionsLimitExceeded1151 );11521153 11541155 let collection = Collection {1156 owner: owner.as_sub().clone(),1157 name: data.name,1158 mode: data.mode.clone(),1159 description: data.description,1160 token_prefix: data.token_prefix,1161 sponsorship: data1162 .pending_sponsor1163 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1164 .unwrap_or_default(),1165 limits: data1166 .limits1167 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1168 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1169 permissions: data1170 .permissions1171 .map(|permissions| {1172 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1173 })1174 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1175 flags: data.flags,1176 };11771178 let mut collection_properties = CollectionPropertiesT::new();1179 collection_properties1180 .try_set_from_iter(data.properties.into_iter())1181 .map_err(<Error<T>>::from)?;11821183 CollectionProperties::<T>::insert(id, collection_properties);11841185 let mut token_props_permissions = PropertiesPermissionMap::new();1186 token_props_permissions1187 .try_set_from_iter(data.token_property_permissions.into_iter())1188 .map_err(<Error<T>>::from)?;11891190 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11911192 let mut admin_amount = 0u32;1193 for admin in data.admin_list.iter() {1194 if !<IsAdmin<T>>::get((id, admin)) {1195 <IsAdmin<T>>::insert((id, admin), true);1196 admin_amount = admin_amount1197 .checked_add(1)1198 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1199 }1200 }1201 ensure!(1202 admin_amount <= Self::collection_admins_limit(),1203 <Error<T>>::CollectionAdminCountExceeded,1204 );1205 <AdminAmount<T>>::insert(id, admin_amount);12061207 1208 {1209 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1210 imbalance.subsume(<T as Config>::Currency::deposit(1211 &T::TreasuryAccountId::get(),1212 T::CollectionCreationPrice::get(),1213 Precision::Exact,1214 )?);1215 let credit =1216 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1217 .map_err(|_| Error::<T>::NotSufficientFounds)?;12181219 debug_assert!(credit.peek().is_zero())1220 }12211222 <CreatedCollectionCount<T>>::put(created_count);1223 <Pallet<T>>::deposit_event(Event::CollectionCreated(1224 id,1225 data.mode.id(),1226 owner.as_sub().clone(),1227 ));1228 <PalletEvm<T>>::deposit_log(1229 erc::CollectionHelpersEvents::CollectionCreated {1230 owner: *owner.as_eth(),1231 collection_id: eth::collection_id_to_address(id),1232 }1233 .to_log(T::ContractAddress::get()),1234 );1235 <CollectionById<T>>::insert(id, collection);1236 Ok(id)1237 }12381239 1240 1241 1242 1243 pub fn destroy_collection(1244 collection: CollectionHandle<T>,1245 sender: &T::CrossAccountId,1246 ) -> DispatchResult {1247 ensure!(1248 collection.limits.owner_can_destroy(),1249 <Error<T>>::NoPermission,1250 );1251 collection.check_is_owner(sender)?;12521253 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1254 .01255 .checked_add(1)1256 .ok_or(ArithmeticError::Overflow)?;12571258 12591260 <DestroyedCollectionCount<T>>::put(destroyed_collections);1261 <CollectionById<T>>::remove(collection.id);1262 <AdminAmount<T>>::remove(collection.id);1263 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1264 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1265 <CollectionProperties<T>>::remove(collection.id);12661267 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12681269 <PalletEvm<T>>::deposit_log(1270 erc::CollectionHelpersEvents::CollectionDestroyed {1271 collection_id: eth::collection_id_to_address(collection.id),1272 }1273 .to_log(T::ContractAddress::get()),1274 );1275 Ok(())1276 }12771278 1279 1280 1281 1282 1283 1284 1285 1286 #[transactional]1287 fn modify_collection_properties(1288 collection: &CollectionHandle<T>,1289 sender: &T::CrossAccountId,1290 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1291 ) -> DispatchResult {1292 collection.check_is_owner_or_admin(sender)?;12931294 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12951296 for (key, value) in properties_updates {1297 match value {1298 Some(value) => {1299 stored_properties1300 .try_set(key.clone(), value)1301 .map_err(<Error<T>>::from)?;13021303 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1304 <PalletEvm<T>>::deposit_log(1305 erc::CollectionHelpersEvents::CollectionChanged {1306 collection_id: eth::collection_id_to_address(collection.id),1307 }1308 .to_log(T::ContractAddress::get()),1309 );1310 }1311 None => {1312 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13131314 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1315 <PalletEvm<T>>::deposit_log(1316 erc::CollectionHelpersEvents::CollectionChanged {1317 collection_id: eth::collection_id_to_address(collection.id),1318 }1319 .to_log(T::ContractAddress::get()),1320 );1321 }1322 }1323 }13241325 <CollectionProperties<T>>::set(collection.id, stored_properties);13261327 Ok(())1328 }13291330 1331 1332 1333 1334 1335 1336 pub fn set_allowance_for_all(1337 collection: &CollectionHandle<T>,1338 owner: &T::CrossAccountId,1339 operator: &T::CrossAccountId,1340 approve: bool,1341 set_allowance: impl FnOnce(),1342 log: evm_coder::ethereum::Log,1343 ) -> DispatchResult {1344 if collection.permissions.access() == AccessMode::AllowList {1345 collection.check_allowlist(owner)?;1346 collection.check_allowlist(operator)?;1347 }13481349 Self::ensure_correct_receiver(operator)?;13501351 set_allowance();13521353 <PalletEvm<T>>::deposit_log(log);1354 Self::deposit_event(Event::ApprovedForAll(1355 collection.id,1356 owner.clone(),1357 operator.clone(),1358 approve,1359 ));1360 Ok(())1361 }13621363 1364 1365 1366 1367 1368 pub fn set_collection_property(1369 collection: &CollectionHandle<T>,1370 sender: &T::CrossAccountId,1371 property: Property,1372 ) -> DispatchResult {1373 Self::set_collection_properties(collection, sender, [property].into_iter())1374 }13751376 1377 1378 1379 1380 1381 1382 pub fn set_scoped_collection_property(1383 collection_id: CollectionId,1384 scope: PropertyScope,1385 property: Property,1386 ) -> DispatchResult {1387 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1388 properties.try_scoped_set(scope, property.key, property.value)1389 })1390 .map_err(<Error<T>>::from)?;13911392 Ok(())1393 }13941395 1396 1397 1398 1399 1400 1401 pub fn set_scoped_collection_properties(1402 collection_id: CollectionId,1403 scope: PropertyScope,1404 properties: impl Iterator<Item = Property>,1405 ) -> DispatchResult {1406 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1407 stored_properties.try_scoped_set_from_iter(scope, properties)1408 })1409 .map_err(<Error<T>>::from)?;14101411 Ok(())1412 }14131414 1415 1416 1417 1418 1419 pub fn set_collection_properties(1420 collection: &CollectionHandle<T>,1421 sender: &T::CrossAccountId,1422 properties: impl Iterator<Item = Property>,1423 ) -> DispatchResult {1424 Self::modify_collection_properties(1425 collection,1426 sender,1427 properties.map(|property| (property.key, Some(property.value))),1428 )1429 }14301431 1432 1433 1434 1435 1436 pub fn delete_collection_property(1437 collection: &CollectionHandle<T>,1438 sender: &T::CrossAccountId,1439 property_key: PropertyKey,1440 ) -> DispatchResult {1441 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1442 }14431444 1445 1446 1447 1448 1449 pub fn delete_collection_properties(1450 collection: &CollectionHandle<T>,1451 sender: &T::CrossAccountId,1452 property_keys: impl Iterator<Item = PropertyKey>,1453 ) -> DispatchResult {1454 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1455 }14561457 1458 1459 1460 1461 1462 1463 pub fn set_property_permission_unchecked(1464 collection: CollectionId,1465 property_permission: PropertyKeyPermission,1466 ) -> DispatchResult {1467 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1468 permissions.try_set(property_permission.key, property_permission.permission)1469 })1470 .map_err(<Error<T>>::from)?;1471 Ok(())1472 }14731474 1475 1476 1477 1478 1479 pub fn set_property_permission(1480 collection: &CollectionHandle<T>,1481 sender: &T::CrossAccountId,1482 property_permission: PropertyKeyPermission,1483 ) -> DispatchResult {1484 Self::set_scoped_property_permission(1485 collection,1486 sender,1487 PropertyScope::None,1488 property_permission,1489 )1490 }14911492 1493 1494 1495 1496 1497 1498 pub fn set_scoped_property_permission(1499 collection: &CollectionHandle<T>,1500 sender: &T::CrossAccountId,1501 scope: PropertyScope,1502 property_permission: PropertyKeyPermission,1503 ) -> DispatchResult {1504 collection.check_is_owner_or_admin(sender)?;15051506 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1507 let current_permission = all_permissions.get(&property_permission.key);1508 if matches![1509 current_permission,1510 Some(PropertyPermission { mutable: false, .. })1511 ] {1512 return Err(<Error<T>>::NoPermission.into());1513 }15141515 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1516 let property_permission = property_permission.clone();1517 permissions.try_scoped_set(1518 scope,1519 property_permission.key,1520 property_permission.permission,1521 )1522 })1523 .map_err(<Error<T>>::from)?;15241525 Self::deposit_event(Event::PropertyPermissionSet(1526 collection.id,1527 property_permission.key,1528 ));1529 <PalletEvm<T>>::deposit_log(1530 erc::CollectionHelpersEvents::CollectionChanged {1531 collection_id: eth::collection_id_to_address(collection.id),1532 }1533 .to_log(T::ContractAddress::get()),1534 );15351536 Ok(())1537 }15381539 1540 1541 1542 1543 1544 #[transactional]1545 pub fn set_token_property_permissions(1546 collection: &CollectionHandle<T>,1547 sender: &T::CrossAccountId,1548 property_permissions: Vec<PropertyKeyPermission>,1549 ) -> DispatchResult {1550 Self::set_scoped_token_property_permissions(1551 collection,1552 sender,1553 PropertyScope::None,1554 property_permissions,1555 )1556 }15571558 1559 1560 1561 1562 1563 1564 #[transactional]1565 pub fn set_scoped_token_property_permissions(1566 collection: &CollectionHandle<T>,1567 sender: &T::CrossAccountId,1568 scope: PropertyScope,1569 property_permissions: Vec<PropertyKeyPermission>,1570 ) -> DispatchResult {1571 for prop_pemission in property_permissions {1572 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1573 }15741575 Ok(())1576 }15771578 1579 pub fn get_collection_property(1580 collection_id: CollectionId,1581 key: &PropertyKey,1582 ) -> Option<PropertyValue> {1583 Self::collection_properties(collection_id).get(key).cloned()1584 }15851586 1587 pub fn bytes_keys_to_property_keys(1588 keys: Vec<Vec<u8>>,1589 ) -> Result<Vec<PropertyKey>, DispatchError> {1590 keys.into_iter()1591 .map(|key| -> Result<PropertyKey, DispatchError> {1592 key.try_into()1593 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1594 })1595 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1596 }15971598 1599 pub fn filter_collection_properties(1600 collection_id: CollectionId,1601 keys: Option<Vec<PropertyKey>>,1602 ) -> Result<Vec<Property>, DispatchError> {1603 let properties = Self::collection_properties(collection_id);16041605 let properties = keys1606 .map(|keys| {1607 keys.into_iter()1608 .filter_map(|key| {1609 properties.get(&key).map(|value| Property {1610 key,1611 value: value.clone(),1612 })1613 })1614 .collect()1615 })1616 .unwrap_or_else(|| {1617 properties1618 .into_iter()1619 .map(|(key, value)| Property { key, value })1620 .collect()1621 });16221623 Ok(properties)1624 }16251626 1627 pub fn filter_property_permissions(1628 collection_id: CollectionId,1629 keys: Option<Vec<PropertyKey>>,1630 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1631 let permissions = Self::property_permissions(collection_id);16321633 let key_permissions = keys1634 .map(|keys| {1635 keys.into_iter()1636 .filter_map(|key| {1637 permissions1638 .get(&key)1639 .map(|permission| PropertyKeyPermission {1640 key,1641 permission: permission.clone(),1642 })1643 })1644 .collect()1645 })1646 .unwrap_or_else(|| {1647 permissions1648 .into_iter()1649 .map(|(key, permission)| PropertyKeyPermission { key, permission })1650 .collect()1651 });16521653 Ok(key_permissions)1654 }16551656 1657 1658 1659 pub fn toggle_allowlist(1660 collection: &CollectionHandle<T>,1661 sender: &T::CrossAccountId,1662 user: &T::CrossAccountId,1663 allowed: bool,1664 ) -> DispatchResult {1665 collection.check_is_owner_or_admin(sender)?;16661667 16681669 if allowed {1670 <Allowlist<T>>::insert((collection.id, user), true);1671 Self::deposit_event(Event::<T>::AllowListAddressAdded(1672 collection.id,1673 user.clone(),1674 ));1675 } else {1676 <Allowlist<T>>::remove((collection.id, user));1677 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1678 collection.id,1679 user.clone(),1680 ));1681 }16821683 <PalletEvm<T>>::deposit_log(1684 erc::CollectionHelpersEvents::CollectionChanged {1685 collection_id: eth::collection_id_to_address(collection.id),1686 }1687 .to_log(T::ContractAddress::get()),1688 );16891690 Ok(())1691 }16921693 1694 1695 1696 pub fn toggle_admin(1697 collection: &CollectionHandle<T>,1698 sender: &T::CrossAccountId,1699 user: &T::CrossAccountId,1700 admin: bool,1701 ) -> DispatchResult {1702 collection.check_is_internal()?;1703 collection.check_is_owner(sender)?;17041705 let is_admin = <IsAdmin<T>>::get((collection.id, user));1706 if is_admin == admin {1707 if admin {1708 return Ok(());1709 } else {1710 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1711 }1712 }1713 let amount = <AdminAmount<T>>::get(collection.id);17141715 17161717 if admin {1718 let amount = amount1719 .checked_add(1)1720 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1721 ensure!(1722 amount <= Self::collection_admins_limit(),1723 <Error<T>>::CollectionAdminCountExceeded,1724 );17251726 <AdminAmount<T>>::insert(collection.id, amount);1727 <IsAdmin<T>>::insert((collection.id, user), true);17281729 Self::deposit_event(Event::<T>::CollectionAdminAdded(1730 collection.id,1731 user.clone(),1732 ));1733 } else {1734 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1735 <IsAdmin<T>>::remove((collection.id, user));17361737 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1738 collection.id,1739 user.clone(),1740 ));1741 }17421743 <PalletEvm<T>>::deposit_log(1744 erc::CollectionHelpersEvents::CollectionChanged {1745 collection_id: eth::collection_id_to_address(collection.id),1746 }1747 .to_log(T::ContractAddress::get()),1748 );17491750 Ok(())1751 }17521753 1754 pub fn update_limits(1755 user: &T::CrossAccountId,1756 collection: &mut CollectionHandle<T>,1757 new_limit: CollectionLimits,1758 ) -> DispatchResult {1759 collection.check_is_internal()?;1760 collection.check_is_owner_or_admin(user)?;17611762 collection.limits =1763 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17641765 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1766 <PalletEvm<T>>::deposit_log(1767 erc::CollectionHelpersEvents::CollectionChanged {1768 collection_id: eth::collection_id_to_address(collection.id),1769 }1770 .to_log(T::ContractAddress::get()),1771 );17721773 collection.save()1774 }17751776 1777 fn clamp_limits(1778 mode: CollectionMode,1779 old_limit: &CollectionLimits,1780 mut new_limit: CollectionLimits,1781 ) -> Result<CollectionLimits, DispatchError> {1782 let limits = old_limit;1783 limit_default!(old_limit, new_limit,1784 account_token_ownership_limit => ensure!(1785 new_limit <= MAX_TOKEN_OWNERSHIP,1786 <Error<T>>::CollectionLimitBoundsExceeded,1787 ),1788 sponsored_data_size => ensure!(1789 new_limit <= CUSTOM_DATA_LIMIT,1790 <Error<T>>::CollectionLimitBoundsExceeded,1791 ),17921793 sponsored_data_rate_limit => {},1794 token_limit => ensure!(1795 old_limit >= new_limit && new_limit > 0,1796 <Error<T>>::CollectionTokenLimitExceeded1797 ),17981799 sponsor_transfer_timeout(match mode {1800 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1801 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1802 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1803 }) => ensure!(1804 new_limit <= MAX_SPONSOR_TIMEOUT,1805 <Error<T>>::CollectionLimitBoundsExceeded,1806 ),1807 sponsor_approve_timeout => {},1808 owner_can_transfer => ensure!(1809 !limits.owner_can_transfer_instaled() ||1810 old_limit || !new_limit,1811 <Error<T>>::OwnerPermissionsCantBeReverted,1812 ),1813 owner_can_destroy => ensure!(1814 old_limit || !new_limit,1815 <Error<T>>::OwnerPermissionsCantBeReverted,1816 ),1817 transfers_enabled => {},1818 );1819 Ok(new_limit)1820 }18211822 1823 pub fn update_permissions(1824 user: &T::CrossAccountId,1825 collection: &mut CollectionHandle<T>,1826 new_permission: CollectionPermissions,1827 ) -> DispatchResult {1828 collection.check_is_internal()?;1829 collection.check_is_owner_or_admin(user)?;1830 collection.permissions = Self::clamp_permissions(1831 collection.mode.clone(),1832 &collection.permissions,1833 new_permission,1834 )?;18351836 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1837 <PalletEvm<T>>::deposit_log(1838 erc::CollectionHelpersEvents::CollectionChanged {1839 collection_id: eth::collection_id_to_address(collection.id),1840 }1841 .to_log(T::ContractAddress::get()),1842 );18431844 collection.save()1845 }18461847 1848 fn clamp_permissions(1849 _mode: CollectionMode,1850 old_permission: &CollectionPermissions,1851 mut new_permission: CollectionPermissions,1852 ) -> Result<CollectionPermissions, DispatchError> {1853 limit_default_clone!(old_permission, new_permission,1854 access => {},1855 mint_mode => {},1856 nesting => { },1857 );1858 Ok(new_permission)1859 }18601861 1862 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1863 CollectionProperties::<T>::mutate(collection_id, |properties| {1864 properties.recompute_consumed_space();1865 });18661867 Ok(())1868 }1869}187018711872#[macro_export]1873macro_rules! unsupported {1874 ($runtime:path) => {1875 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1876 };1877}187818791880pub trait CommonWeightInfo<CrossAccountId> {1881 1882 fn create_item(data: &CreateItemData) -> Weight {1883 Self::create_multiple_items(from_ref(data))1884 }18851886 1887 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18881889 1890 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18911892 1893 fn burn_item() -> Weight;18941895 1896 1897 1898 fn set_collection_properties(amount: u32) -> Weight;18991900 1901 1902 1903 fn delete_collection_properties(amount: u32) -> Weight;19041905 1906 1907 1908 fn set_token_properties(amount: u32) -> Weight;19091910 1911 1912 1913 fn delete_token_properties(amount: u32) -> Weight;19141915 1916 1917 1918 fn set_token_property_permissions(amount: u32) -> Weight;19191920 1921 fn transfer() -> Weight;19221923 1924 fn approve() -> Weight;19251926 1927 fn approve_from() -> Weight;19281929 1930 fn transfer_from() -> Weight;19311932 1933 fn burn_from() -> Weight;19341935 1936 1937 1938 1939 fn burn_recursively_self_raw() -> Weight;19401941 1942 1943 1944 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19451946 1947 1948 1949 1950 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1951 Self::burn_recursively_self_raw()1952 .saturating_mul(max_selfs.max(1) as u64)1953 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1954 }19551956 1957 fn token_owner() -> 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 2035 2036 fn burn_item_recursively(2037 &self,2038 sender: T::CrossAccountId,2039 token: TokenId,2040 self_budget: &dyn Budget,2041 breadth_budget: &dyn Budget,2042 ) -> DispatchResultWithPostInfo;20432044 2045 2046 2047 2048 fn set_collection_properties(2049 &self,2050 sender: T::CrossAccountId,2051 properties: Vec<Property>,2052 ) -> DispatchResultWithPostInfo;20532054 2055 2056 2057 2058 fn delete_collection_properties(2059 &self,2060 sender: &T::CrossAccountId,2061 property_keys: Vec<PropertyKey>,2062 ) -> DispatchResultWithPostInfo;20632064 2065 2066 2067 2068 2069 2070 2071 2072 2073 fn set_token_properties(2074 &self,2075 sender: T::CrossAccountId,2076 token_id: TokenId,2077 properties: Vec<Property>,2078 budget: &dyn Budget,2079 ) -> DispatchResultWithPostInfo;20802081 2082 2083 2084 2085 2086 2087 2088 2089 2090 fn delete_token_properties(2091 &self,2092 sender: T::CrossAccountId,2093 token_id: TokenId,2094 property_keys: Vec<PropertyKey>,2095 budget: &dyn Budget,2096 ) -> DispatchResultWithPostInfo;20972098 2099 2100 2101 fn get_token_properties_map(&self, token_id: TokenId) -> TokenProperties;21022103 2104 2105 2106 2107 fn set_token_properties_map(&self, token_id: TokenId, map: TokenProperties);21082109 2110 2111 2112 fn properties_exist(&self, token: TokenId) -> bool;21132114 2115 2116 2117 2118 2119 2120 fn set_token_property_permissions(2121 &self,2122 sender: &T::CrossAccountId,2123 property_permissions: Vec<PropertyKeyPermission>,2124 ) -> DispatchResultWithPostInfo;21252126 2127 2128 2129 2130 2131 2132 2133 fn transfer(2134 &self,2135 sender: T::CrossAccountId,2136 to: T::CrossAccountId,2137 token: TokenId,2138 amount: u128,2139 budget: &dyn Budget,2140 ) -> DispatchResultWithPostInfo;21412142 2143 2144 2145 2146 2147 2148 fn approve(2149 &self,2150 sender: T::CrossAccountId,2151 spender: T::CrossAccountId,2152 token: TokenId,2153 amount: u128,2154 ) -> DispatchResultWithPostInfo;21552156 2157 2158 2159 2160 2161 2162 2163 fn approve_from(2164 &self,2165 sender: T::CrossAccountId,2166 from: T::CrossAccountId,2167 to: T::CrossAccountId,2168 token: TokenId,2169 amount: u128,2170 ) -> DispatchResultWithPostInfo;21712172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 fn transfer_from(2183 &self,2184 sender: T::CrossAccountId,2185 from: T::CrossAccountId,2186 to: T::CrossAccountId,2187 token: TokenId,2188 amount: u128,2189 budget: &dyn Budget,2190 ) -> DispatchResultWithPostInfo;21912192 2193 2194 2195 2196 2197 2198 2199 2200 2201 fn burn_from(2202 &self,2203 sender: T::CrossAccountId,2204 from: T::CrossAccountId,2205 token: TokenId,2206 amount: u128,2207 budget: &dyn Budget,2208 ) -> DispatchResultWithPostInfo;22092210 2211 2212 2213 2214 2215 2216 fn check_nesting(2217 &self,2218 sender: T::CrossAccountId,2219 from: (CollectionId, TokenId),2220 under: TokenId,2221 budget: &dyn Budget,2222 ) -> DispatchResult;22232224 2225 2226 2227 2228 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22292230 2231 2232 2233 2234 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22352236 2237 2238 2239 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22402241 2242 fn collection_tokens(&self) -> Vec<TokenId>;22432244 2245 2246 2247 fn token_exists(&self, token: TokenId) -> bool;22482249 2250 fn last_token_id(&self) -> TokenId;22512252 2253 2254 2255 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22562257 2258 2259 2260 2261 2262 fn check_token_indirect_owner(2263 &self,2264 token: TokenId,2265 maybe_owner: &T::CrossAccountId,2266 nesting_budget: &dyn Budget,2267 ) -> Result<bool, DispatchError>;22682269 2270 2271 2272 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22732274 2275 2276 2277 2278 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22792280 2281 2282 2283 2284 2285 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22862287 2288 fn total_supply(&self) -> u32;22892290 2291 2292 2293 fn account_balance(&self, account: T::CrossAccountId) -> u32;22942295 2296 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22972298 2299 fn total_pieces(&self, token: TokenId) -> Option<u128>;23002301 2302 2303 2304 2305 2306 fn allowance(2307 &self,2308 sender: T::CrossAccountId,2309 spender: T::CrossAccountId,2310 token: TokenId,2311 ) -> u128;23122313 2314 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;23152316 2317 2318 2319 2320 fn set_allowance_for_all(2321 &self,2322 owner: T::CrossAccountId,2323 operator: T::CrossAccountId,2324 approve: bool,2325 ) -> DispatchResultWithPostInfo;23262327 2328 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23292330 2331 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2332}233323342335pub trait RefungibleExtensions<T>2336where2337 T: Config,2338{2339 2340 2341 2342 2343 2344 2345 2346 fn repartition(2347 &self,2348 sender: &T::CrossAccountId,2349 token: TokenId,2350 amount: u128,2351 ) -> DispatchResultWithPostInfo;2352}23532354235523562357pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2358 let post_info = PostDispatchInfo {2359 actual_weight: Some(weight),2360 pays_fee: Pays::Yes,2361 };2362 match res {2363 Ok(()) => Ok(post_info),2364 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2365 }2366}23672368impl<T: Config> From<PropertiesError> for Error<T> {2369 fn from(error: PropertiesError) -> Self {2370 match error {2371 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2372 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2373 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2374 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2375 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2376 }2377 }2378}2379238023812382pub struct NewTokenPropertyWriter;2383238423852386pub struct ExistingTokenPropertyWriter;23872388238923902391239223932394pub struct PropertyWriter<2395 'a,2396 T,2397 Handle,2398 WriterVariant,2399 FIsAdmin,2400 FPropertyPermissions,2401 FCheckTokenExist,2402 FGetProperties,2403> where2404 T: Config,2405 FIsAdmin: FnOnce() -> bool,2406 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2407{2408 collection: &'a Handle,2409 is_collection_admin: LazyValue<bool, FIsAdmin>,2410 property_permissions: LazyValue<PropertiesPermissionMap, FPropertyPermissions>,2411 check_token_exist: FCheckTokenExist,2412 get_properties: FGetProperties,2413 _phantom: PhantomData<(T, WriterVariant)>,2414}24152416impl<'a, T, Handle, FIsAdmin, FPropertyPermissions, FCheckTokenExist, FGetProperties>2417 PropertyWriter<2418 'a,2419 T,2420 Handle,2421 NewTokenPropertyWriter,2422 FIsAdmin,2423 FPropertyPermissions,2424 FCheckTokenExist,2425 FGetProperties,2426 > where2427 T: Config,2428 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2429 FIsAdmin: FnOnce() -> bool,2430 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2431 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,2432 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,2433{2434 2435 pub fn write_token_properties(2436 &mut self,2437 mint_target_is_sender: bool,2438 token_id: TokenId,2439 properties_updates: impl Iterator<Item = Property>,2440 log: evm_coder::ethereum::Log,2441 ) -> DispatchResult {2442 self.internal_write_token_properties(2443 token_id,2444 properties_updates.map(|p| (p.key, Some(p.value))),2445 |_| Ok(mint_target_is_sender),2446 log,2447 )2448 }2449}24502451impl<'a, T, Handle, FIsAdmin, FPropertyPermissions, FCheckTokenExist, FGetProperties>2452 PropertyWriter<2453 'a,2454 T,2455 Handle,2456 ExistingTokenPropertyWriter,2457 FIsAdmin,2458 FPropertyPermissions,2459 FCheckTokenExist,2460 FGetProperties,2461 > where2462 T: Config,2463 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2464 FIsAdmin: FnOnce() -> bool,2465 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2466 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,2467 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,2468{2469 2470 pub fn write_token_properties(2471 &mut self,2472 sender: &T::CrossAccountId,2473 token_id: TokenId,2474 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2475 nesting_budget: &dyn Budget,2476 log: evm_coder::ethereum::Log,2477 ) -> DispatchResult {2478 self.internal_write_token_properties(2479 token_id,2480 properties_updates,2481 |collection| collection.check_token_indirect_owner(token_id, sender, nesting_budget),2482 log,2483 )2484 }2485}24862487impl<2488 'a,2489 T,2490 Handle,2491 WriterVariant,2492 FIsAdmin,2493 FPropertyPermissions,2494 FCheckTokenExist,2495 FGetProperties,2496 >2497 PropertyWriter<2498 'a,2499 T,2500 Handle,2501 WriterVariant,2502 FIsAdmin,2503 FPropertyPermissions,2504 FCheckTokenExist,2505 FGetProperties,2506 > where2507 T: Config,2508 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2509 FIsAdmin: FnOnce() -> bool,2510 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2511 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,2512 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,2513{2514 fn internal_write_token_properties<FCheckTokenOwner>(2515 &mut self,2516 token_id: TokenId,2517 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2518 check_token_owner: FCheckTokenOwner,2519 log: evm_coder::ethereum::Log,2520 ) -> DispatchResult2521 where2522 FCheckTokenOwner: FnOnce(&Handle) -> Result<bool, DispatchError>,2523 {2524 let get_properties = self.get_properties;2525 let mut stored_properties = LazyValue::new(move || get_properties(token_id));25262527 let mut is_token_owner = LazyValue::new(|| check_token_owner(self.collection));25282529 let check_token_exist = self.check_token_exist;2530 let mut is_token_exist = LazyValue::new(move || check_token_exist(token_id));25312532 for (key, value) in properties_updates {2533 let permission = self2534 .property_permissions2535 .value()2536 .get(&key)2537 .cloned()2538 .unwrap_or_else(PropertyPermission::none);25392540 match permission {2541 PropertyPermission { mutable: false, .. }2542 if stored_properties.value().get(&key).is_some() =>2543 {2544 return Err(<Error<T>>::NoPermission.into());2545 }25462547 PropertyPermission {2548 collection_admin,2549 token_owner,2550 ..2551 } => check_token_permissions::<T, _, _, _>(2552 collection_admin,2553 token_owner,2554 &mut self.is_collection_admin,2555 &mut is_token_owner,2556 &mut is_token_exist,2557 )?,2558 }25592560 match value {2561 Some(value) => {2562 stored_properties2563 .value_mut()2564 .try_set(key.clone(), value)2565 .map_err(<Error<T>>::from)?;25662567 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2568 self.collection.id,2569 token_id,2570 key,2571 ));2572 }2573 None => {2574 stored_properties2575 .value_mut()2576 .remove(&key)2577 .map_err(<Error<T>>::from)?;25782579 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2580 self.collection.id,2581 token_id,2582 key,2583 ));2584 }2585 }2586 }25872588 let properties_changed = stored_properties.has_value();2589 if properties_changed {2590 <PalletEvm<T>>::deposit_log(log);25912592 self.collection2593 .set_token_properties_map(token_id, stored_properties.into_inner());2594 }25952596 Ok(())2597 }2598}259926002601pub fn property_writer_for_new_token<'a, T, Handle>(2602 collection: &'a Handle,2603 sender: &'a T::CrossAccountId,2604) -> PropertyWriter<2605 'a,2606 T,2607 Handle,2608 NewTokenPropertyWriter,2609 impl FnOnce() -> bool + 'a,2610 impl FnOnce() -> PropertiesPermissionMap + 'a,2611 impl Copy + FnOnce(TokenId) -> bool + 'a,2612 impl Copy + FnOnce(TokenId) -> TokenProperties + 'a,2613>2614where2615 T: Config,2616 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2617{2618 PropertyWriter {2619 collection,2620 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2621 property_permissions: LazyValue::new(|| <Pallet<T>>::property_permissions(collection.id)),2622 check_token_exist: |token_id| {2623 debug_assert!(collection.token_exists(token_id));2624 true2625 },2626 get_properties: |token_id| {2627 debug_assert!(!collection.properties_exist(token_id));2628 TokenProperties::new()2629 },2630 _phantom: PhantomData,2631 }2632}26332634#[cfg(feature = "runtime-benchmarks")]26352636263726382639pub fn collection_info_loaded_property_writer<T, Handle>(2640 collection: &Handle,2641 is_collection_admin: bool,2642 property_permissions: PropertiesPermissionMap,2643) -> PropertyWriter<2644 T,2645 Handle,2646 NewTokenPropertyWriter,2647 impl FnOnce() -> bool,2648 impl FnOnce() -> PropertiesPermissionMap,2649 impl Copy + FnOnce(TokenId) -> bool,2650 impl Copy + FnOnce(TokenId) -> TokenProperties,2651>2652where2653 T: Config,2654 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2655{2656 PropertyWriter {2657 collection,2658 is_collection_admin: LazyValue::new(move || is_collection_admin),2659 property_permissions: LazyValue::new(move || property_permissions),2660 check_token_exist: |_token_id| true,2661 get_properties: |_token_id| TokenProperties::new(),2662 _phantom: PhantomData,2663 }2664}266526662667pub fn property_writer_for_existing_token<'a, T, Handle>(2668 collection: &'a Handle,2669 sender: &'a T::CrossAccountId,2670) -> PropertyWriter<2671 'a,2672 T,2673 Handle,2674 ExistingTokenPropertyWriter,2675 impl FnOnce() -> bool + 'a,2676 impl FnOnce() -> PropertiesPermissionMap + 'a,2677 impl Copy + FnOnce(TokenId) -> bool + 'a,2678 impl Copy + FnOnce(TokenId) -> TokenProperties + 'a,2679>2680where2681 T: Config,2682 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2683{2684 PropertyWriter {2685 collection,2686 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2687 property_permissions: LazyValue::new(|| <Pallet<T>>::property_permissions(collection.id)),2688 check_token_exist: |token_id| collection.token_exists(token_id),2689 get_properties: |token_id| collection.get_token_properties_map(token_id),2690 _phantom: PhantomData,2691 }2692}26932694269526962697pub fn init_token_properties_delta<T: Config, I: Fn(u32) -> Weight>(2698 properties_nums: impl Iterator<Item = u32>,2699 init_token_properties: I,2700) -> Weight {2701 let mut delta = properties_nums2702 .filter_map(|properties_num| {2703 if properties_num > 0 {2704 Some(init_token_properties(properties_num))2705 } else {2706 None2707 }2708 })2709 .fold(Weight::zero(), |a, b| a.saturating_add(b));27102711 2712 2713 2714 2715 if !delta.is_zero() {2716 delta = delta.saturating_add(<SelfWeightOf<T>>::init_token_properties_common())2717 }27182719 delta2720}27212722#[cfg(any(feature = "tests", test))]2723#[allow(missing_docs)]2724pub mod tests {2725 use crate::{DispatchResult, DispatchError, LazyValue, Config};27262727 const fn to_bool(u: u8) -> bool {2728 u != 02729 }27302731 #[derive(Debug)]2732 pub struct TestCase {2733 pub collection_admin: bool,2734 pub is_collection_admin: bool,2735 pub token_owner: bool,2736 pub is_token_owner: bool,2737 pub no_permission: bool,2738 }27392740 impl TestCase {2741 const fn new(2742 collection_admin: u8,2743 is_collection_admin: u8,2744 token_owner: u8,2745 is_token_owner: u8,2746 no_permission: u8,2747 ) -> Self {2748 Self {2749 collection_admin: to_bool(collection_admin),2750 is_collection_admin: to_bool(is_collection_admin),2751 token_owner: to_bool(token_owner),2752 is_token_owner: to_bool(is_token_owner),2753 no_permission: to_bool(no_permission),2754 }2755 }2756 }27572758 #[rustfmt::skip]2759 pub const TABLE: [TestCase; 16] = [2760 2761 2762 2763 2764 2765 TestCase::new(0, 0, 0, 0, 1),2766 TestCase::new(0, 0, 0, 1, 1),2767 TestCase::new(0, 0, 1, 0, 1),2768 TestCase::new(0, 0, 1, 1, 0),2769 TestCase::new(0, 1, 0, 0, 1),2770 TestCase::new(0, 1, 0, 1, 1),2771 TestCase::new(0, 1, 1, 0, 1),2772 TestCase::new(0, 1, 1, 1, 0),2773 TestCase::new(1, 0, 0, 0, 1),2774 TestCase::new(1, 0, 0, 1, 1),2775 TestCase::new(1, 0, 1, 0, 1),2776 TestCase::new(1, 0, 1, 1, 0),2777 TestCase::new(1, 1, 0, 0, 0),2778 TestCase::new(1, 1, 0, 1, 0),2779 TestCase::new(1, 1, 1, 0, 0),2780 TestCase::new(1, 1, 1, 1, 0),2781 ];27822783 pub fn check_token_permissions<T, FCA, FTO, FTE>(2784 collection_admin_permitted: bool,2785 token_owner_permitted: bool,2786 is_collection_admin: &mut LazyValue<bool, FCA>,2787 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>, FTO>,2788 check_token_existence: &mut LazyValue<bool, FTE>,2789 ) -> DispatchResult2790 where2791 T: Config,2792 FCA: FnOnce() -> bool,2793 FTO: FnOnce() -> Result<bool, DispatchError>,2794 FTE: FnOnce() -> bool,2795 {2796 crate::check_token_permissions::<T, FCA, FTO, FTE>(2797 collection_admin_permitted,2798 token_owner_permitted,2799 is_collection_admin,2800 check_token_ownership,2801 check_token_existence,2802 )2803 }2804}