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_raw(&self, token_id: TokenId) -> Option<TokenProperties>;21022103 2104 2105 2106 2107 fn set_token_properties_raw(&self, token_id: TokenId, map: TokenProperties);21082109 2110 2111 2112 2113 2114 2115 fn set_token_property_permissions(2116 &self,2117 sender: &T::CrossAccountId,2118 property_permissions: Vec<PropertyKeyPermission>,2119 ) -> DispatchResultWithPostInfo;21202121 2122 2123 2124 2125 2126 2127 2128 fn transfer(2129 &self,2130 sender: T::CrossAccountId,2131 to: T::CrossAccountId,2132 token: TokenId,2133 amount: u128,2134 budget: &dyn Budget,2135 ) -> DispatchResultWithPostInfo;21362137 2138 2139 2140 2141 2142 2143 fn approve(2144 &self,2145 sender: T::CrossAccountId,2146 spender: T::CrossAccountId,2147 token: TokenId,2148 amount: u128,2149 ) -> DispatchResultWithPostInfo;21502151 2152 2153 2154 2155 2156 2157 2158 fn approve_from(2159 &self,2160 sender: T::CrossAccountId,2161 from: T::CrossAccountId,2162 to: T::CrossAccountId,2163 token: TokenId,2164 amount: u128,2165 ) -> DispatchResultWithPostInfo;21662167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 fn transfer_from(2178 &self,2179 sender: T::CrossAccountId,2180 from: T::CrossAccountId,2181 to: T::CrossAccountId,2182 token: TokenId,2183 amount: u128,2184 budget: &dyn Budget,2185 ) -> DispatchResultWithPostInfo;21862187 2188 2189 2190 2191 2192 2193 2194 2195 2196 fn burn_from(2197 &self,2198 sender: T::CrossAccountId,2199 from: T::CrossAccountId,2200 token: TokenId,2201 amount: u128,2202 budget: &dyn Budget,2203 ) -> DispatchResultWithPostInfo;22042205 2206 2207 2208 2209 2210 2211 fn check_nesting(2212 &self,2213 sender: T::CrossAccountId,2214 from: (CollectionId, TokenId),2215 under: TokenId,2216 budget: &dyn Budget,2217 ) -> DispatchResult;22182219 2220 2221 2222 2223 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22242225 2226 2227 2228 2229 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22302231 2232 2233 2234 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22352236 2237 fn collection_tokens(&self) -> Vec<TokenId>;22382239 2240 2241 2242 fn token_exists(&self, token: TokenId) -> bool;22432244 2245 fn last_token_id(&self) -> TokenId;22462247 2248 2249 2250 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22512252 2253 2254 2255 2256 2257 fn check_token_indirect_owner(2258 &self,2259 token: TokenId,2260 maybe_owner: &T::CrossAccountId,2261 nesting_budget: &dyn Budget,2262 ) -> Result<bool, DispatchError>;22632264 2265 2266 2267 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22682269 2270 2271 2272 2273 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22742275 2276 2277 2278 2279 2280 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22812282 2283 fn total_supply(&self) -> u32;22842285 2286 2287 2288 fn account_balance(&self, account: T::CrossAccountId) -> u32;22892290 2291 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22922293 2294 fn total_pieces(&self, token: TokenId) -> Option<u128>;22952296 2297 2298 2299 2300 2301 fn allowance(2302 &self,2303 sender: T::CrossAccountId,2304 spender: T::CrossAccountId,2305 token: TokenId,2306 ) -> u128;23072308 2309 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;23102311 2312 2313 2314 2315 fn set_allowance_for_all(2316 &self,2317 owner: T::CrossAccountId,2318 operator: T::CrossAccountId,2319 approve: bool,2320 ) -> DispatchResultWithPostInfo;23212322 2323 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23242325 2326 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2327}232823292330pub trait RefungibleExtensions<T>2331where2332 T: Config,2333{2334 2335 2336 2337 2338 2339 2340 2341 fn repartition(2342 &self,2343 sender: &T::CrossAccountId,2344 token: TokenId,2345 amount: u128,2346 ) -> DispatchResultWithPostInfo;2347}23482349235023512352pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2353 let post_info = PostDispatchInfo {2354 actual_weight: Some(weight),2355 pays_fee: Pays::Yes,2356 };2357 match res {2358 Ok(()) => Ok(post_info),2359 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2360 }2361}23622363impl<T: Config> From<PropertiesError> for Error<T> {2364 fn from(error: PropertiesError) -> Self {2365 match error {2366 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2367 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2368 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2369 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2370 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2371 }2372 }2373}2374237523762377pub struct NewTokenPropertyWriter;2378237923802381pub struct ExistingTokenPropertyWriter;23822383238423852386238723882389pub struct PropertyWriter<2390 'a,2391 T,2392 Handle,2393 WriterVariant,2394 FIsAdmin,2395 FPropertyPermissions,2396 FCheckTokenExist,2397 FGetProperties,2398> where2399 T: Config,2400 FIsAdmin: FnOnce() -> bool,2401 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2402{2403 collection: &'a Handle,2404 is_collection_admin: LazyValue<bool, FIsAdmin>,2405 property_permissions: LazyValue<PropertiesPermissionMap, FPropertyPermissions>,2406 check_token_exist: FCheckTokenExist,2407 get_properties: FGetProperties,2408 _phantom: PhantomData<(T, WriterVariant)>,2409}24102411impl<'a, T, Handle, FIsAdmin, FPropertyPermissions, FCheckTokenExist, FGetProperties>2412 PropertyWriter<2413 'a,2414 T,2415 Handle,2416 NewTokenPropertyWriter,2417 FIsAdmin,2418 FPropertyPermissions,2419 FCheckTokenExist,2420 FGetProperties,2421 > where2422 T: Config,2423 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2424 FIsAdmin: FnOnce() -> bool,2425 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2426 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,2427 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,2428{2429 2430 pub fn write_token_properties(2431 &mut self,2432 mint_target_is_sender: bool,2433 token_id: TokenId,2434 properties_updates: impl Iterator<Item = Property>,2435 log: evm_coder::ethereum::Log,2436 ) -> DispatchResult {2437 self.internal_write_token_properties(2438 token_id,2439 properties_updates.map(|p| (p.key, Some(p.value))),2440 |_| Ok(mint_target_is_sender),2441 log,2442 )2443 }2444}24452446impl<'a, T, Handle, FIsAdmin, FPropertyPermissions, FCheckTokenExist, FGetProperties>2447 PropertyWriter<2448 'a,2449 T,2450 Handle,2451 ExistingTokenPropertyWriter,2452 FIsAdmin,2453 FPropertyPermissions,2454 FCheckTokenExist,2455 FGetProperties,2456 > where2457 T: Config,2458 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2459 FIsAdmin: FnOnce() -> bool,2460 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2461 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,2462 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,2463{2464 2465 pub fn write_token_properties(2466 &mut self,2467 sender: &T::CrossAccountId,2468 token_id: TokenId,2469 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2470 nesting_budget: &dyn Budget,2471 log: evm_coder::ethereum::Log,2472 ) -> DispatchResult {2473 self.internal_write_token_properties(2474 token_id,2475 properties_updates,2476 |collection| collection.check_token_indirect_owner(token_id, sender, nesting_budget),2477 log,2478 )2479 }2480}24812482impl<2483 'a,2484 T,2485 Handle,2486 WriterVariant,2487 FIsAdmin,2488 FPropertyPermissions,2489 FCheckTokenExist,2490 FGetProperties,2491 >2492 PropertyWriter<2493 'a,2494 T,2495 Handle,2496 WriterVariant,2497 FIsAdmin,2498 FPropertyPermissions,2499 FCheckTokenExist,2500 FGetProperties,2501 > where2502 T: Config,2503 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2504 FIsAdmin: FnOnce() -> bool,2505 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2506 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,2507 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,2508{2509 fn internal_write_token_properties<FCheckTokenOwner>(2510 &mut self,2511 token_id: TokenId,2512 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2513 check_token_owner: FCheckTokenOwner,2514 log: evm_coder::ethereum::Log,2515 ) -> DispatchResult2516 where2517 FCheckTokenOwner: FnOnce(&Handle) -> Result<bool, DispatchError>,2518 {2519 let get_properties = self.get_properties;2520 let mut stored_properties = LazyValue::new(move || get_properties(token_id));25212522 let mut is_token_owner = LazyValue::new(|| check_token_owner(self.collection));25232524 let check_token_exist = self.check_token_exist;2525 let mut is_token_exist = LazyValue::new(move || check_token_exist(token_id));25262527 for (key, value) in properties_updates {2528 let permission = self2529 .property_permissions2530 .value()2531 .get(&key)2532 .cloned()2533 .unwrap_or_else(PropertyPermission::none);25342535 match permission {2536 PropertyPermission { mutable: false, .. }2537 if stored_properties.value().get(&key).is_some() =>2538 {2539 return Err(<Error<T>>::NoPermission.into());2540 }25412542 PropertyPermission {2543 collection_admin,2544 token_owner,2545 ..2546 } => check_token_permissions::<T, _, _, _>(2547 collection_admin,2548 token_owner,2549 &mut self.is_collection_admin,2550 &mut is_token_owner,2551 &mut is_token_exist,2552 )?,2553 }25542555 match value {2556 Some(value) => {2557 stored_properties2558 .value_mut()2559 .try_set(key.clone(), value)2560 .map_err(<Error<T>>::from)?;25612562 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2563 self.collection.id,2564 token_id,2565 key,2566 ));2567 }2568 None => {2569 stored_properties2570 .value_mut()2571 .remove(&key)2572 .map_err(<Error<T>>::from)?;25732574 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2575 self.collection.id,2576 token_id,2577 key,2578 ));2579 }2580 }2581 }25822583 let properties_changed = stored_properties.has_value();2584 if properties_changed {2585 <PalletEvm<T>>::deposit_log(log);25862587 self.collection2588 .set_token_properties_raw(token_id, stored_properties.into_inner());2589 }25902591 Ok(())2592 }2593}259425952596pub fn property_writer_for_new_token<'a, T, Handle>(2597 collection: &'a Handle,2598 sender: &'a T::CrossAccountId,2599) -> PropertyWriter<2600 'a,2601 T,2602 Handle,2603 NewTokenPropertyWriter,2604 impl FnOnce() -> bool + 'a,2605 impl FnOnce() -> PropertiesPermissionMap + 'a,2606 impl Copy + FnOnce(TokenId) -> bool + 'a,2607 impl Copy + FnOnce(TokenId) -> TokenProperties + 'a,2608>2609where2610 T: Config,2611 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2612{2613 PropertyWriter {2614 collection,2615 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2616 property_permissions: LazyValue::new(|| <Pallet<T>>::property_permissions(collection.id)),2617 check_token_exist: |token_id| {2618 debug_assert!(collection.token_exists(token_id));2619 true2620 },2621 get_properties: |token_id| {2622 debug_assert!(collection.get_token_properties_raw(token_id).is_none());2623 TokenProperties::new()2624 },2625 _phantom: PhantomData,2626 }2627}26282629#[cfg(feature = "runtime-benchmarks")]26302631263226332634pub fn collection_info_loaded_property_writer<T, Handle>(2635 collection: &Handle,2636 is_collection_admin: bool,2637 property_permissions: PropertiesPermissionMap,2638) -> PropertyWriter<2639 T,2640 Handle,2641 NewTokenPropertyWriter,2642 impl FnOnce() -> bool,2643 impl FnOnce() -> PropertiesPermissionMap,2644 impl Copy + FnOnce(TokenId) -> bool,2645 impl Copy + FnOnce(TokenId) -> TokenProperties,2646>2647where2648 T: Config,2649 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2650{2651 PropertyWriter {2652 collection,2653 is_collection_admin: LazyValue::new(move || is_collection_admin),2654 property_permissions: LazyValue::new(move || property_permissions),2655 check_token_exist: |_token_id| true,2656 get_properties: |_token_id| TokenProperties::new(),2657 _phantom: PhantomData,2658 }2659}266026612662pub fn property_writer_for_existing_token<'a, T, Handle>(2663 collection: &'a Handle,2664 sender: &'a T::CrossAccountId,2665) -> PropertyWriter<2666 'a,2667 T,2668 Handle,2669 ExistingTokenPropertyWriter,2670 impl FnOnce() -> bool + 'a,2671 impl FnOnce() -> PropertiesPermissionMap + 'a,2672 impl Copy + FnOnce(TokenId) -> bool + 'a,2673 impl Copy + FnOnce(TokenId) -> TokenProperties + 'a,2674>2675where2676 T: Config,2677 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2678{2679 PropertyWriter {2680 collection,2681 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2682 property_permissions: LazyValue::new(|| <Pallet<T>>::property_permissions(collection.id)),2683 check_token_exist: |token_id| collection.token_exists(token_id),2684 get_properties: |token_id| {2685 collection2686 .get_token_properties_raw(token_id)2687 .unwrap_or_default()2688 },2689 _phantom: PhantomData,2690 }2691}26922693269426952696pub fn init_token_properties_delta<T: Config, I: Fn(u32) -> Weight>(2697 properties_nums: impl Iterator<Item = u32>,2698 init_token_properties: I,2699) -> Weight {2700 let mut delta = properties_nums2701 .filter_map(|properties_num| {2702 if properties_num > 0 {2703 Some(init_token_properties(properties_num))2704 } else {2705 None2706 }2707 })2708 .fold(Weight::zero(), |a, b| a.saturating_add(b));27092710 2711 2712 2713 2714 if !delta.is_zero() {2715 delta = delta.saturating_add(<SelfWeightOf<T>>::init_token_properties_common())2716 }27172718 delta2719}27202721#[cfg(any(feature = "tests", test))]2722#[allow(missing_docs)]2723pub mod tests {2724 use crate::{DispatchResult, DispatchError, LazyValue, Config};27252726 const fn to_bool(u: u8) -> bool {2727 u != 02728 }27292730 #[derive(Debug)]2731 pub struct TestCase {2732 pub collection_admin: bool,2733 pub is_collection_admin: bool,2734 pub token_owner: bool,2735 pub is_token_owner: bool,2736 pub no_permission: bool,2737 }27382739 impl TestCase {2740 const fn new(2741 collection_admin: u8,2742 is_collection_admin: u8,2743 token_owner: u8,2744 is_token_owner: u8,2745 no_permission: u8,2746 ) -> Self {2747 Self {2748 collection_admin: to_bool(collection_admin),2749 is_collection_admin: to_bool(is_collection_admin),2750 token_owner: to_bool(token_owner),2751 is_token_owner: to_bool(is_token_owner),2752 no_permission: to_bool(no_permission),2753 }2754 }2755 }27562757 #[rustfmt::skip]2758 pub const TABLE: [TestCase; 16] = [2759 2760 2761 2762 2763 2764 TestCase::new(0, 0, 0, 0, 1),2765 TestCase::new(0, 0, 0, 1, 1),2766 TestCase::new(0, 0, 1, 0, 1),2767 TestCase::new(0, 0, 1, 1, 0),2768 TestCase::new(0, 1, 0, 0, 1),2769 TestCase::new(0, 1, 0, 1, 1),2770 TestCase::new(0, 1, 1, 0, 1),2771 TestCase::new(0, 1, 1, 1, 0),2772 TestCase::new(1, 0, 0, 0, 1),2773 TestCase::new(1, 0, 0, 1, 1),2774 TestCase::new(1, 0, 1, 0, 1),2775 TestCase::new(1, 0, 1, 1, 0),2776 TestCase::new(1, 1, 0, 0, 0),2777 TestCase::new(1, 1, 0, 1, 0),2778 TestCase::new(1, 1, 1, 0, 0),2779 TestCase::new(1, 1, 1, 1, 0),2780 ];27812782 pub fn check_token_permissions<T, FCA, FTO, FTE>(2783 collection_admin_permitted: bool,2784 token_owner_permitted: bool,2785 is_collection_admin: &mut LazyValue<bool, FCA>,2786 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>, FTO>,2787 check_token_existence: &mut LazyValue<bool, FTE>,2788 ) -> DispatchResult2789 where2790 T: Config,2791 FCA: FnOnce() -> bool,2792 FTO: FnOnce() -> Result<bool, DispatchError>,2793 FTE: FnOnce() -> bool,2794 {2795 crate::check_token_permissions::<T, FCA, FTO, FTE>(2796 collection_admin_permitted,2797 token_owner_permitted,2798 is_collection_admin,2799 check_token_ownership,2800 check_token_existence,2801 )2802 }2803}