12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::{57 marker::PhantomData,58 ops::{Deref, DerefMut},59 slice::from_ref,60};6162use evm_coder::ToLog;63use frame_support::{64 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Pays, PostDispatchInfo},65 ensure, fail,66 traits::{67 fungible::{Balanced, Debt, Inspect},68 tokens::{Imbalance, Precision, Preservation},69 Get,70 },71 transactional,72};73pub use pallet::*;74use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};75use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};76use sp_core::H160;77use sp_runtime::{traits::Zero, ArithmeticError, DispatchError, DispatchResult};78use sp_std::vec::Vec;79use sp_weights::Weight;80use up_data_structs::{81 budget::Budget, AccessMode, Collection, CollectionId, CollectionLimits, CollectionMode,82 CollectionPermissions, CollectionProperties as CollectionPropertiesT, CollectionStats,83 CreateCollectionData, CreateItemData, CreateItemExData, PhantomType, PropertiesError,84 PropertiesPermissionMap, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,85 PropertyScope, PropertyValue, RpcCollection, RpcCollectionFlags, SponsoringRateLimit,86 SponsorshipState, TokenChild, TokenData, TokenId, TokenOwnerError, TokenProperties,87 TrySetProperty, COLLECTION_ADMINS_LIMIT, COLLECTION_NUMBER_LIMIT, CUSTOM_DATA_LIMIT,88 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP,89 MAX_TOKEN_PREFIX_LENGTH, NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,90};91use up_pov_estimate_rpc::PovInfo;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 dispatch::CollectionDispatch;405 use frame_support::{406 pallet_prelude::*, storage::Key, traits::StorageVersion, Blake2_128Concat,407 };408 use scale_info::TypeInfo;409 use up_data_structs::{mapping::TokenAddressMapping, TokenId};410 use weights::WeightInfo;411412 use super::*;413414 #[pallet::config]415 pub trait Config:416 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo417 {418 419 type WeightInfo: WeightInfo;420421 422 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;423424 425 type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;426427 428 #[pallet::constant]429 type CollectionCreationPrice: Get<430 <<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,431 >;432433 434 type CollectionDispatch: CollectionDispatch<Self>;435436 437 type TreasuryAccountId: Get<Self::AccountId>;438439 440 #[pallet::constant]441 type ContractAddress: Get<H160>;442443 444 type EvmTokenAddressMapping: TokenAddressMapping<H160>;445446 447 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;448 }449450 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);451 452 pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);453454 #[pallet::pallet]455 #[pallet::storage_version(STORAGE_VERSION)]456 pub struct Pallet<T>(_);457458 #[pallet::extra_constants]459 impl<T: Config> Pallet<T> {460 461 pub fn collection_admins_limit() -> u32 {462 COLLECTION_ADMINS_LIMIT463 }464 }465466 #[pallet::genesis_config]467 pub struct GenesisConfig<T>(PhantomData<T>);468469 impl<T: Config> Default for GenesisConfig<T> {470 fn default() -> Self {471 Self(Default::default())472 }473 }474475 #[pallet::genesis_build]476 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {477 fn build(&self) {478 StorageVersion::new(1).put::<Pallet<T>>();479 }480 }481482 impl<T: Config> Pallet<T> {483 484 pub fn deposit_event(event: Event<T>) {485 let event = <T as Config>::RuntimeEvent::from(event);486 let event = event.into();487 <frame_system::Pallet<T>>::deposit_event(event)488 }489 }490491 #[pallet::event]492 pub enum Event<T: Config> {493 494 CollectionCreated(495 496 CollectionId,497 498 u8,499 500 T::AccountId,501 ),502503 504 CollectionDestroyed(505 506 CollectionId,507 ),508509 510 ItemCreated(511 512 CollectionId,513 514 TokenId,515 516 T::CrossAccountId,517 518 u128,519 ),520521 522 ItemDestroyed(523 524 CollectionId,525 526 TokenId,527 528 T::CrossAccountId,529 530 u128,531 ),532533 534 Transfer(535 536 CollectionId,537 538 TokenId,539 540 T::CrossAccountId,541 542 T::CrossAccountId,543 544 u128,545 ),546547 548 Approved(549 550 CollectionId,551 552 TokenId,553 554 T::CrossAccountId,555 556 T::CrossAccountId,557 558 u128,559 ),560561 562 ApprovedForAll(563 564 CollectionId,565 566 T::CrossAccountId,567 568 T::CrossAccountId,569 570 bool,571 ),572573 574 CollectionPropertySet(575 576 CollectionId,577 578 PropertyKey,579 ),580581 582 CollectionPropertyDeleted(583 584 CollectionId,585 586 PropertyKey,587 ),588589 590 TokenPropertySet(591 592 CollectionId,593 594 TokenId,595 596 PropertyKey,597 ),598599 600 TokenPropertyDeleted(601 602 CollectionId,603 604 TokenId,605 606 PropertyKey,607 ),608609 610 PropertyPermissionSet(611 612 CollectionId,613 614 PropertyKey,615 ),616617 618 AllowListAddressAdded(619 620 CollectionId,621 622 T::CrossAccountId,623 ),624625 626 AllowListAddressRemoved(627 628 CollectionId,629 630 T::CrossAccountId,631 ),632633 634 CollectionAdminAdded(635 636 CollectionId,637 638 T::CrossAccountId,639 ),640641 642 CollectionAdminRemoved(643 644 CollectionId,645 646 T::CrossAccountId,647 ),648649 650 CollectionLimitSet(651 652 CollectionId,653 ),654655 656 CollectionOwnerChanged(657 658 CollectionId,659 660 T::AccountId,661 ),662663 664 CollectionPermissionSet(665 666 CollectionId,667 ),668669 670 CollectionSponsorSet(671 672 CollectionId,673 674 T::AccountId,675 ),676677 678 SponsorshipConfirmed(679 680 CollectionId,681 682 T::AccountId,683 ),684685 686 CollectionSponsorRemoved(687 688 CollectionId,689 ),690 }691692 #[pallet::error]693 pub enum Error<T> {694 695 CollectionNotFound,696 697 MustBeTokenOwner,698 699 NoPermission,700 701 CantDestroyNotEmptyCollection,702 703 PublicMintingNotAllowed,704 705 AddressNotInAllowlist,706707 708 CollectionNameLimitExceeded,709 710 CollectionDescriptionLimitExceeded,711 712 CollectionTokenPrefixLimitExceeded,713 714 TotalCollectionsLimitExceeded,715 716 CollectionAdminCountExceeded,717 718 CollectionLimitBoundsExceeded,719 720 OwnerPermissionsCantBeReverted,721 722 TransferNotAllowed,723 724 AccountTokenLimitExceeded,725 726 CollectionTokenLimitExceeded,727 728 MetadataFlagFrozen,729730 731 TokenNotFound,732 733 TokenValueTooLow,734 735 ApprovedValueTooLow,736 737 CantApproveMoreThanOwned,738 739 AddressIsNotEthMirror,740741 742 AddressIsZero,743744 745 UnsupportedOperation,746747 748 NotSufficientFounds,749750 751 UserIsNotAllowedToNest,752 753 SourceCollectionIsNotAllowedToNest,754755 756 CollectionFieldSizeExceeded,757758 759 NoSpaceForProperty,760761 762 PropertyLimitReached,763764 765 PropertyKeyIsTooLong,766767 768 InvalidCharacterInPropertyKey,769770 771 EmptyPropertyKey,772773 774 CollectionIsExternal,775776 777 CollectionIsInternal,778779 780 ConfirmSponsorshipFail,781782 783 UserIsNotCollectionAdmin,784 }785786 787 #[pallet::storage]788 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;789790 791 #[pallet::storage]792 pub type DestroyedCollectionCount<T> =793 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;794795 796 #[pallet::storage]797 pub type CollectionById<T> = StorageMap<798 Hasher = Blake2_128Concat,799 Key = CollectionId,800 Value = Collection<<T as frame_system::Config>::AccountId>,801 QueryKind = OptionQuery,802 >;803804 805 #[pallet::storage]806 #[pallet::getter(fn collection_properties)]807 pub type CollectionProperties<T> = StorageMap<808 Hasher = Blake2_128Concat,809 Key = CollectionId,810 Value = CollectionPropertiesT,811 QueryKind = ValueQuery,812 >;813814 815 #[pallet::storage]816 #[pallet::getter(fn property_permissions)]817 pub type CollectionPropertyPermissions<T> = StorageMap<818 Hasher = Blake2_128Concat,819 Key = CollectionId,820 Value = PropertiesPermissionMap,821 QueryKind = ValueQuery,822 >;823824 825 #[pallet::storage]826 pub type AdminAmount<T> = StorageMap<827 Hasher = Blake2_128Concat,828 Key = CollectionId,829 Value = u32,830 QueryKind = ValueQuery,831 >;832833 834 #[pallet::storage]835 pub type IsAdmin<T: Config> = StorageNMap<836 Key = (837 Key<Blake2_128Concat, CollectionId>,838 Key<Blake2_128Concat, T::CrossAccountId>,839 ),840 Value = bool,841 QueryKind = ValueQuery,842 >;843844 845 #[pallet::storage]846 pub type Allowlist<T: Config> = StorageNMap<847 Key = (848 Key<Blake2_128Concat, CollectionId>,849 Key<Blake2_128Concat, T::CrossAccountId>,850 ),851 Value = bool,852 QueryKind = ValueQuery,853 >;854855 856 #[pallet::storage]857 pub type DummyStorageValue<T: Config> = StorageValue<858 Value = (859 CollectionStats,860 CollectionId,861 TokenId,862 TokenChild,863 PhantomType<(864 TokenData<T::CrossAccountId>,865 RpcCollection<T::AccountId>,866 867 PovInfo,868 )>,869 ),870 QueryKind = OptionQuery,871 >;872}873874875pub struct LazyValue<T, F: FnOnce() -> T> {876 value: Option<T>,877 f: Option<F>,878}879880impl<T, F: FnOnce() -> T> LazyValue<T, F> {881 882 pub fn new(f: F) -> Self {883 Self {884 value: None,885 f: Some(f),886 }887 }888889 890 pub fn value(&mut self) -> &T {891 self.force_value();892 self.value.as_ref().unwrap()893 }894895 896 pub fn value_mut(&mut self) -> &mut T {897 self.force_value();898 self.value.as_mut().unwrap()899 }900901 fn into_inner(mut self) -> T {902 self.force_value();903 self.value.unwrap()904 }905906 907 pub fn has_value(&self) -> bool {908 self.value.is_some()909 }910911 fn force_value(&mut self) {912 if self.value.is_none() {913 self.value = Some(self.f.take().unwrap()())914 }915 }916}917918fn check_token_permissions<T, FCA, FTO, FTE>(919 collection_admin_permitted: bool,920 token_owner_permitted: bool,921 is_collection_admin: &mut LazyValue<bool, FCA>,922 is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,923 is_token_exist: &mut LazyValue<bool, FTE>,924) -> DispatchResult925where926 T: Config,927 FCA: FnOnce() -> bool,928 FTO: FnOnce() -> Result<bool, DispatchError>,929 FTE: FnOnce() -> bool,930{931 if !(collection_admin_permitted && *is_collection_admin.value()932 || token_owner_permitted && (*is_token_owner.value())?)933 {934 fail!(<Error<T>>::NoPermission);935 }936937 let token_exist_due_to_owner_check_success =938 is_token_owner.has_value() && (*is_token_owner.value())?;939940 941 942 if !token_exist_due_to_owner_check_success {943 944 945 if !is_token_exist.value() {946 fail!(<Error<T>>::TokenNotFound);947 }948 }949950 Ok(())951}952953impl<T: Config> Pallet<T> {954 955 956 957 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {958 ensure!(959 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,960 <Error<T>>::AddressIsZero961 );962 Ok(())963 }964965 966 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {967 <IsAdmin<T>>::iter_prefix((collection,))968 .map(|(a, _)| a)969 .collect()970 }971972 973 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {974 <Allowlist<T>>::iter_prefix((collection,))975 .map(|(a, _)| a)976 .collect()977 }978979 980 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {981 <Allowlist<T>>::get((collection, user))982 }983984 985 pub fn collection_stats() -> CollectionStats {986 let created = <CreatedCollectionCount<T>>::get();987 let destroyed = <DestroyedCollectionCount<T>>::get();988 CollectionStats {989 created: created.0,990 destroyed: destroyed.0,991 alive: created.0 - destroyed.0,992 }993 }994995 996 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {997 let collection = <CollectionById<T>>::get(collection)?;998 let limits = collection.limits;999 let effective_limits = CollectionLimits {1000 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),1001 sponsored_data_size: Some(limits.sponsored_data_size()),1002 sponsored_data_rate_limit: Some(1003 limits1004 .sponsored_data_rate_limit1005 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),1006 ),1007 token_limit: Some(limits.token_limit()),1008 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(1009 match collection.mode {1010 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1011 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1012 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1013 },1014 )),1015 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),1016 owner_can_transfer: Some(limits.owner_can_transfer()),1017 owner_can_destroy: Some(limits.owner_can_destroy()),1018 transfers_enabled: Some(limits.transfers_enabled()),1019 };10201021 Some(effective_limits)1022 }10231024 1025 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1026 let Collection {1027 name,1028 description,1029 owner,1030 mode,1031 token_prefix,1032 sponsorship,1033 limits,1034 permissions,1035 flags,1036 } = <CollectionById<T>>::get(collection)?;10371038 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1039 .into_iter()1040 .map(|(key, permission)| PropertyKeyPermission { key, permission })1041 .collect();10421043 let properties = <CollectionProperties<T>>::get(collection)1044 .into_iter()1045 .map(|(key, value)| Property { key, value })1046 .collect();10471048 let permissions = CollectionPermissions {1049 access: Some(permissions.access()),1050 mint_mode: Some(permissions.mint_mode()),1051 nesting: Some(permissions.nesting().clone()),1052 };10531054 Some(RpcCollection {1055 name: name.into_inner(),1056 description: description.into_inner(),1057 owner,1058 mode,1059 token_prefix: token_prefix.into_inner(),1060 sponsorship,1061 limits,1062 permissions,1063 token_property_permissions,1064 properties,1065 read_only: flags.external,10661067 flags: RpcCollectionFlags {1068 foreign: flags.foreign,1069 erc721metadata: flags.erc721metadata,1070 },1071 })1072 }1073}10741075macro_rules! limit_default {1076 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1077 $(1078 if let Some($new) = $new.$field {1079 let $old = $old.$field($($arg)?);1080 let _ = $new;1081 let _ = $old;1082 $check1083 } else {1084 $new.$field = $old.$field1085 }1086 )*1087 }};1088}1089macro_rules! limit_default_clone {1090 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1091 $(1092 if let Some($new) = $new.$field.clone() {1093 let $old = $old.$field($($arg)?);1094 let _ = $new;1095 let _ = $old;1096 $check1097 } else {1098 $new.$field = $old.$field.clone()1099 }1100 )*1101 }};1102}11031104impl<T: Config> Pallet<T> {1105 1106 1107 1108 1109 1110 pub fn init_collection(1111 owner: T::CrossAccountId,1112 payer: T::CrossAccountId,1113 data: CreateCollectionData<T::CrossAccountId>,1114 ) -> Result<CollectionId, DispatchError> {1115 ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);1116 Self::init_collection_internal(owner, payer, data)1117 }11181119 1120 pub fn init_foreign_collection(1121 owner: T::CrossAccountId,1122 payer: T::CrossAccountId,1123 mut data: CreateCollectionData<T::CrossAccountId>,1124 ) -> Result<CollectionId, DispatchError> {1125 data.flags.foreign = true;1126 let id = Self::init_collection_internal(owner, payer, data)?;1127 Ok(id)1128 }11291130 fn init_collection_internal(1131 owner: T::CrossAccountId,1132 payer: T::CrossAccountId,1133 data: CreateCollectionData<T::CrossAccountId>,1134 ) -> Result<CollectionId, DispatchError> {1135 {1136 ensure!(1137 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1138 Error::<T>::CollectionTokenPrefixLimitExceeded1139 );1140 }11411142 let created_count = <CreatedCollectionCount<T>>::get()1143 .01144 .checked_add(1)1145 .ok_or(ArithmeticError::Overflow)?;1146 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1147 let id = CollectionId(created_count);11481149 1150 ensure!(1151 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1152 <Error<T>>::TotalCollectionsLimitExceeded1153 );11541155 11561157 let collection = Collection {1158 owner: owner.as_sub().clone(),1159 name: data.name,1160 mode: data.mode.clone(),1161 description: data.description,1162 token_prefix: data.token_prefix,1163 sponsorship: data1164 .pending_sponsor1165 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1166 .unwrap_or_default(),1167 limits: data1168 .limits1169 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1170 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1171 permissions: data1172 .permissions1173 .map(|permissions| {1174 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1175 })1176 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1177 flags: data.flags,1178 };11791180 let mut collection_properties = CollectionPropertiesT::new();1181 collection_properties1182 .try_set_from_iter(data.properties.into_iter())1183 .map_err(<Error<T>>::from)?;11841185 CollectionProperties::<T>::insert(id, collection_properties);11861187 let mut token_props_permissions = PropertiesPermissionMap::new();1188 token_props_permissions1189 .try_set_from_iter(data.token_property_permissions.into_iter())1190 .map_err(<Error<T>>::from)?;11911192 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11931194 let mut admin_amount = 0u32;1195 for admin in data.admin_list.iter() {1196 if !<IsAdmin<T>>::get((id, admin)) {1197 <IsAdmin<T>>::insert((id, admin), true);1198 admin_amount = admin_amount1199 .checked_add(1)1200 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1201 }1202 }1203 ensure!(1204 admin_amount <= Self::collection_admins_limit(),1205 <Error<T>>::CollectionAdminCountExceeded,1206 );1207 <AdminAmount<T>>::insert(id, admin_amount);12081209 1210 {1211 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1212 imbalance.subsume(<T as Config>::Currency::deposit(1213 &T::TreasuryAccountId::get(),1214 T::CollectionCreationPrice::get(),1215 Precision::Exact,1216 )?);1217 let credit =1218 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1219 .map_err(|_| Error::<T>::NotSufficientFounds)?;12201221 debug_assert!(credit.peek().is_zero())1222 }12231224 <CreatedCollectionCount<T>>::put(created_count);1225 <Pallet<T>>::deposit_event(Event::CollectionCreated(1226 id,1227 data.mode.id(),1228 owner.as_sub().clone(),1229 ));1230 <PalletEvm<T>>::deposit_log(1231 erc::CollectionHelpersEvents::CollectionCreated {1232 owner: *owner.as_eth(),1233 collection_id: eth::collection_id_to_address(id),1234 }1235 .to_log(T::ContractAddress::get()),1236 );1237 <CollectionById<T>>::insert(id, collection);1238 Ok(id)1239 }12401241 1242 1243 1244 1245 pub fn destroy_collection(1246 collection: CollectionHandle<T>,1247 sender: &T::CrossAccountId,1248 ) -> DispatchResult {1249 ensure!(1250 collection.limits.owner_can_destroy(),1251 <Error<T>>::NoPermission,1252 );1253 collection.check_is_owner(sender)?;12541255 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1256 .01257 .checked_add(1)1258 .ok_or(ArithmeticError::Overflow)?;12591260 12611262 <DestroyedCollectionCount<T>>::put(destroyed_collections);1263 <CollectionById<T>>::remove(collection.id);1264 <AdminAmount<T>>::remove(collection.id);1265 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1266 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1267 <CollectionProperties<T>>::remove(collection.id);12681269 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12701271 <PalletEvm<T>>::deposit_log(1272 erc::CollectionHelpersEvents::CollectionDestroyed {1273 collection_id: eth::collection_id_to_address(collection.id),1274 }1275 .to_log(T::ContractAddress::get()),1276 );1277 Ok(())1278 }12791280 1281 1282 1283 1284 1285 1286 1287 1288 #[transactional]1289 fn modify_collection_properties(1290 collection: &CollectionHandle<T>,1291 sender: &T::CrossAccountId,1292 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1293 ) -> DispatchResult {1294 collection.check_is_owner_or_admin(sender)?;12951296 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12971298 for (key, value) in properties_updates {1299 match value {1300 Some(value) => {1301 stored_properties1302 .try_set(key.clone(), value)1303 .map_err(<Error<T>>::from)?;13041305 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1306 <PalletEvm<T>>::deposit_log(1307 erc::CollectionHelpersEvents::CollectionChanged {1308 collection_id: eth::collection_id_to_address(collection.id),1309 }1310 .to_log(T::ContractAddress::get()),1311 );1312 }1313 None => {1314 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13151316 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1317 <PalletEvm<T>>::deposit_log(1318 erc::CollectionHelpersEvents::CollectionChanged {1319 collection_id: eth::collection_id_to_address(collection.id),1320 }1321 .to_log(T::ContractAddress::get()),1322 );1323 }1324 }1325 }13261327 <CollectionProperties<T>>::set(collection.id, stored_properties);13281329 Ok(())1330 }13311332 1333 1334 1335 1336 1337 1338 pub fn set_allowance_for_all(1339 collection: &CollectionHandle<T>,1340 owner: &T::CrossAccountId,1341 operator: &T::CrossAccountId,1342 approve: bool,1343 set_allowance: impl FnOnce(),1344 log: evm_coder::ethereum::Log,1345 ) -> DispatchResult {1346 if collection.permissions.access() == AccessMode::AllowList {1347 collection.check_allowlist(owner)?;1348 collection.check_allowlist(operator)?;1349 }13501351 Self::ensure_correct_receiver(operator)?;13521353 set_allowance();13541355 <PalletEvm<T>>::deposit_log(log);1356 Self::deposit_event(Event::ApprovedForAll(1357 collection.id,1358 owner.clone(),1359 operator.clone(),1360 approve,1361 ));1362 Ok(())1363 }13641365 1366 1367 1368 1369 1370 pub fn set_collection_property(1371 collection: &CollectionHandle<T>,1372 sender: &T::CrossAccountId,1373 property: Property,1374 ) -> DispatchResult {1375 Self::set_collection_properties(collection, sender, [property].into_iter())1376 }13771378 1379 1380 1381 1382 1383 1384 pub fn set_scoped_collection_property(1385 collection_id: CollectionId,1386 scope: PropertyScope,1387 property: Property,1388 ) -> DispatchResult {1389 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1390 properties.try_scoped_set(scope, property.key, property.value)1391 })1392 .map_err(<Error<T>>::from)?;13931394 Ok(())1395 }13961397 1398 1399 1400 1401 1402 1403 pub fn set_scoped_collection_properties(1404 collection_id: CollectionId,1405 scope: PropertyScope,1406 properties: impl Iterator<Item = Property>,1407 ) -> DispatchResult {1408 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1409 stored_properties.try_scoped_set_from_iter(scope, properties)1410 })1411 .map_err(<Error<T>>::from)?;14121413 Ok(())1414 }14151416 1417 1418 1419 1420 1421 pub fn set_collection_properties(1422 collection: &CollectionHandle<T>,1423 sender: &T::CrossAccountId,1424 properties: impl Iterator<Item = Property>,1425 ) -> DispatchResult {1426 Self::modify_collection_properties(1427 collection,1428 sender,1429 properties.map(|property| (property.key, Some(property.value))),1430 )1431 }14321433 1434 1435 1436 1437 1438 pub fn delete_collection_property(1439 collection: &CollectionHandle<T>,1440 sender: &T::CrossAccountId,1441 property_key: PropertyKey,1442 ) -> DispatchResult {1443 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1444 }14451446 1447 1448 1449 1450 1451 pub fn delete_collection_properties(1452 collection: &CollectionHandle<T>,1453 sender: &T::CrossAccountId,1454 property_keys: impl Iterator<Item = PropertyKey>,1455 ) -> DispatchResult {1456 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1457 }14581459 1460 1461 1462 1463 1464 1465 pub fn set_property_permission_unchecked(1466 collection: CollectionId,1467 property_permission: PropertyKeyPermission,1468 ) -> DispatchResult {1469 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1470 permissions.try_set(property_permission.key, property_permission.permission)1471 })1472 .map_err(<Error<T>>::from)?;1473 Ok(())1474 }14751476 1477 1478 1479 1480 1481 pub fn set_property_permission(1482 collection: &CollectionHandle<T>,1483 sender: &T::CrossAccountId,1484 property_permission: PropertyKeyPermission,1485 ) -> DispatchResult {1486 Self::set_scoped_property_permission(1487 collection,1488 sender,1489 PropertyScope::None,1490 property_permission,1491 )1492 }14931494 1495 1496 1497 1498 1499 1500 pub fn set_scoped_property_permission(1501 collection: &CollectionHandle<T>,1502 sender: &T::CrossAccountId,1503 scope: PropertyScope,1504 property_permission: PropertyKeyPermission,1505 ) -> DispatchResult {1506 collection.check_is_owner_or_admin(sender)?;15071508 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1509 let current_permission = all_permissions.get(&property_permission.key);1510 if matches![1511 current_permission,1512 Some(PropertyPermission { mutable: false, .. })1513 ] {1514 return Err(<Error<T>>::NoPermission.into());1515 }15161517 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1518 let property_permission = property_permission.clone();1519 permissions.try_scoped_set(1520 scope,1521 property_permission.key,1522 property_permission.permission,1523 )1524 })1525 .map_err(<Error<T>>::from)?;15261527 Self::deposit_event(Event::PropertyPermissionSet(1528 collection.id,1529 property_permission.key,1530 ));1531 <PalletEvm<T>>::deposit_log(1532 erc::CollectionHelpersEvents::CollectionChanged {1533 collection_id: eth::collection_id_to_address(collection.id),1534 }1535 .to_log(T::ContractAddress::get()),1536 );15371538 Ok(())1539 }15401541 1542 1543 1544 1545 1546 #[transactional]1547 pub fn set_token_property_permissions(1548 collection: &CollectionHandle<T>,1549 sender: &T::CrossAccountId,1550 property_permissions: Vec<PropertyKeyPermission>,1551 ) -> DispatchResult {1552 Self::set_scoped_token_property_permissions(1553 collection,1554 sender,1555 PropertyScope::None,1556 property_permissions,1557 )1558 }15591560 1561 1562 1563 1564 1565 1566 #[transactional]1567 pub fn set_scoped_token_property_permissions(1568 collection: &CollectionHandle<T>,1569 sender: &T::CrossAccountId,1570 scope: PropertyScope,1571 property_permissions: Vec<PropertyKeyPermission>,1572 ) -> DispatchResult {1573 for prop_pemission in property_permissions {1574 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1575 }15761577 Ok(())1578 }15791580 1581 pub fn get_collection_property(1582 collection_id: CollectionId,1583 key: &PropertyKey,1584 ) -> Option<PropertyValue> {1585 Self::collection_properties(collection_id).get(key).cloned()1586 }15871588 1589 pub fn bytes_keys_to_property_keys(1590 keys: Vec<Vec<u8>>,1591 ) -> Result<Vec<PropertyKey>, DispatchError> {1592 keys.into_iter()1593 .map(|key| -> Result<PropertyKey, DispatchError> {1594 key.try_into()1595 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1596 })1597 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1598 }15991600 1601 pub fn filter_collection_properties(1602 collection_id: CollectionId,1603 keys: Option<Vec<PropertyKey>>,1604 ) -> Result<Vec<Property>, DispatchError> {1605 let properties = Self::collection_properties(collection_id);16061607 let properties = keys1608 .map(|keys| {1609 keys.into_iter()1610 .filter_map(|key| {1611 properties.get(&key).map(|value| Property {1612 key,1613 value: value.clone(),1614 })1615 })1616 .collect()1617 })1618 .unwrap_or_else(|| {1619 properties1620 .into_iter()1621 .map(|(key, value)| Property { key, value })1622 .collect()1623 });16241625 Ok(properties)1626 }16271628 1629 pub fn filter_property_permissions(1630 collection_id: CollectionId,1631 keys: Option<Vec<PropertyKey>>,1632 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1633 let permissions = Self::property_permissions(collection_id);16341635 let key_permissions = keys1636 .map(|keys| {1637 keys.into_iter()1638 .filter_map(|key| {1639 permissions1640 .get(&key)1641 .map(|permission| PropertyKeyPermission {1642 key,1643 permission: permission.clone(),1644 })1645 })1646 .collect()1647 })1648 .unwrap_or_else(|| {1649 permissions1650 .into_iter()1651 .map(|(key, permission)| PropertyKeyPermission { key, permission })1652 .collect()1653 });16541655 Ok(key_permissions)1656 }16571658 1659 1660 1661 pub fn toggle_allowlist(1662 collection: &CollectionHandle<T>,1663 sender: &T::CrossAccountId,1664 user: &T::CrossAccountId,1665 allowed: bool,1666 ) -> DispatchResult {1667 collection.check_is_owner_or_admin(sender)?;16681669 16701671 if allowed {1672 <Allowlist<T>>::insert((collection.id, user), true);1673 Self::deposit_event(Event::<T>::AllowListAddressAdded(1674 collection.id,1675 user.clone(),1676 ));1677 } else {1678 <Allowlist<T>>::remove((collection.id, user));1679 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1680 collection.id,1681 user.clone(),1682 ));1683 }16841685 <PalletEvm<T>>::deposit_log(1686 erc::CollectionHelpersEvents::CollectionChanged {1687 collection_id: eth::collection_id_to_address(collection.id),1688 }1689 .to_log(T::ContractAddress::get()),1690 );16911692 Ok(())1693 }16941695 1696 1697 1698 pub fn toggle_admin(1699 collection: &CollectionHandle<T>,1700 sender: &T::CrossAccountId,1701 user: &T::CrossAccountId,1702 admin: bool,1703 ) -> DispatchResult {1704 collection.check_is_internal()?;1705 collection.check_is_owner(sender)?;17061707 let is_admin = <IsAdmin<T>>::get((collection.id, user));1708 if is_admin == admin {1709 if admin {1710 return Ok(());1711 } else {1712 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1713 }1714 }1715 let amount = <AdminAmount<T>>::get(collection.id);17161717 17181719 if admin {1720 let amount = amount1721 .checked_add(1)1722 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1723 ensure!(1724 amount <= Self::collection_admins_limit(),1725 <Error<T>>::CollectionAdminCountExceeded,1726 );17271728 <AdminAmount<T>>::insert(collection.id, amount);1729 <IsAdmin<T>>::insert((collection.id, user), true);17301731 Self::deposit_event(Event::<T>::CollectionAdminAdded(1732 collection.id,1733 user.clone(),1734 ));1735 } else {1736 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1737 <IsAdmin<T>>::remove((collection.id, user));17381739 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1740 collection.id,1741 user.clone(),1742 ));1743 }17441745 <PalletEvm<T>>::deposit_log(1746 erc::CollectionHelpersEvents::CollectionChanged {1747 collection_id: eth::collection_id_to_address(collection.id),1748 }1749 .to_log(T::ContractAddress::get()),1750 );17511752 Ok(())1753 }17541755 1756 pub fn update_limits(1757 user: &T::CrossAccountId,1758 collection: &mut CollectionHandle<T>,1759 new_limit: CollectionLimits,1760 ) -> DispatchResult {1761 collection.check_is_internal()?;1762 collection.check_is_owner_or_admin(user)?;17631764 collection.limits =1765 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17661767 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1768 <PalletEvm<T>>::deposit_log(1769 erc::CollectionHelpersEvents::CollectionChanged {1770 collection_id: eth::collection_id_to_address(collection.id),1771 }1772 .to_log(T::ContractAddress::get()),1773 );17741775 collection.save()1776 }17771778 1779 fn clamp_limits(1780 mode: CollectionMode,1781 old_limit: &CollectionLimits,1782 mut new_limit: CollectionLimits,1783 ) -> Result<CollectionLimits, DispatchError> {1784 let limits = old_limit;1785 limit_default!(old_limit, new_limit,1786 account_token_ownership_limit => ensure!(1787 new_limit <= MAX_TOKEN_OWNERSHIP,1788 <Error<T>>::CollectionLimitBoundsExceeded,1789 ),1790 sponsored_data_size => ensure!(1791 new_limit <= CUSTOM_DATA_LIMIT,1792 <Error<T>>::CollectionLimitBoundsExceeded,1793 ),17941795 sponsored_data_rate_limit => {},1796 token_limit => ensure!(1797 old_limit >= new_limit && new_limit > 0,1798 <Error<T>>::CollectionTokenLimitExceeded1799 ),18001801 sponsor_transfer_timeout(match mode {1802 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1803 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1804 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1805 }) => ensure!(1806 new_limit <= MAX_SPONSOR_TIMEOUT,1807 <Error<T>>::CollectionLimitBoundsExceeded,1808 ),1809 sponsor_approve_timeout => {},1810 owner_can_transfer => ensure!(1811 !limits.owner_can_transfer_instaled() ||1812 old_limit || !new_limit,1813 <Error<T>>::OwnerPermissionsCantBeReverted,1814 ),1815 owner_can_destroy => ensure!(1816 old_limit || !new_limit,1817 <Error<T>>::OwnerPermissionsCantBeReverted,1818 ),1819 transfers_enabled => {},1820 );1821 Ok(new_limit)1822 }18231824 1825 pub fn update_permissions(1826 user: &T::CrossAccountId,1827 collection: &mut CollectionHandle<T>,1828 new_permission: CollectionPermissions,1829 ) -> DispatchResult {1830 collection.check_is_internal()?;1831 collection.check_is_owner_or_admin(user)?;1832 collection.permissions = Self::clamp_permissions(1833 collection.mode.clone(),1834 &collection.permissions,1835 new_permission,1836 )?;18371838 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1839 <PalletEvm<T>>::deposit_log(1840 erc::CollectionHelpersEvents::CollectionChanged {1841 collection_id: eth::collection_id_to_address(collection.id),1842 }1843 .to_log(T::ContractAddress::get()),1844 );18451846 collection.save()1847 }18481849 1850 fn clamp_permissions(1851 _mode: CollectionMode,1852 old_permission: &CollectionPermissions,1853 mut new_permission: CollectionPermissions,1854 ) -> Result<CollectionPermissions, DispatchError> {1855 limit_default_clone!(old_permission, new_permission,1856 access => {},1857 mint_mode => {},1858 nesting => { },1859 );1860 Ok(new_permission)1861 }18621863 1864 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1865 CollectionProperties::<T>::mutate(collection_id, |properties| {1866 properties.recompute_consumed_space();1867 });18681869 Ok(())1870 }1871}187218731874#[macro_export]1875macro_rules! unsupported {1876 ($runtime:path) => {1877 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1878 };1879}188018811882pub trait CommonWeightInfo<CrossAccountId> {1883 1884 fn create_item(data: &CreateItemData) -> Weight {1885 Self::create_multiple_items(from_ref(data))1886 }18871888 1889 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18901891 1892 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18931894 1895 fn burn_item() -> Weight;18961897 1898 1899 1900 fn set_collection_properties(amount: u32) -> Weight;19011902 1903 1904 1905 fn delete_collection_properties(amount: u32) -> Weight;19061907 1908 1909 1910 fn set_token_properties(amount: u32) -> Weight;19111912 1913 1914 1915 fn delete_token_properties(amount: u32) -> Weight;19161917 1918 1919 1920 fn set_token_property_permissions(amount: u32) -> Weight;19211922 1923 fn transfer() -> Weight;19241925 1926 fn approve() -> Weight;19271928 1929 fn approve_from() -> Weight;19301931 1932 fn transfer_from() -> Weight;19331934 1935 fn burn_from() -> Weight;19361937 1938 1939 1940 1941 fn burn_recursively_self_raw() -> Weight;19421943 1944 1945 1946 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19471948 1949 1950 1951 1952 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1953 Self::burn_recursively_self_raw()1954 .saturating_mul(max_selfs.max(1) as u64)1955 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1956 }19571958 1959 fn token_owner() -> Weight;19601961 1962 fn set_allowance_for_all() -> Weight;19631964 1965 fn force_repair_item() -> Weight;1966}196719681969pub trait RefungibleExtensionsWeightInfo {1970 1971 fn repartition() -> Weight;1972}197319741975197619771978pub trait CommonCollectionOperations<T: Config> {1979 1980 1981 1982 1983 1984 1985 fn create_item(1986 &self,1987 sender: T::CrossAccountId,1988 to: T::CrossAccountId,1989 data: CreateItemData,1990 nesting_budget: &dyn Budget,1991 ) -> DispatchResultWithPostInfo;19921993 1994 1995 1996 1997 1998 1999 fn create_multiple_items(2000 &self,2001 sender: T::CrossAccountId,2002 to: T::CrossAccountId,2003 data: Vec<CreateItemData>,2004 nesting_budget: &dyn Budget,2005 ) -> DispatchResultWithPostInfo;20062007 2008 2009 2010 2011 2012 2013 fn create_multiple_items_ex(2014 &self,2015 sender: T::CrossAccountId,2016 data: CreateItemExData<T::CrossAccountId>,2017 nesting_budget: &dyn Budget,2018 ) -> DispatchResultWithPostInfo;20192020 2021 2022 2023 2024 2025 fn burn_item(2026 &self,2027 sender: T::CrossAccountId,2028 token: TokenId,2029 amount: u128,2030 ) -> DispatchResultWithPostInfo;20312032 2033 2034 2035 2036 2037 2038 fn burn_item_recursively(2039 &self,2040 sender: T::CrossAccountId,2041 token: TokenId,2042 self_budget: &dyn Budget,2043 breadth_budget: &dyn Budget,2044 ) -> DispatchResultWithPostInfo;20452046 2047 2048 2049 2050 fn set_collection_properties(2051 &self,2052 sender: T::CrossAccountId,2053 properties: Vec<Property>,2054 ) -> DispatchResultWithPostInfo;20552056 2057 2058 2059 2060 fn delete_collection_properties(2061 &self,2062 sender: &T::CrossAccountId,2063 property_keys: Vec<PropertyKey>,2064 ) -> DispatchResultWithPostInfo;20652066 2067 2068 2069 2070 2071 2072 2073 2074 2075 fn set_token_properties(2076 &self,2077 sender: T::CrossAccountId,2078 token_id: TokenId,2079 properties: Vec<Property>,2080 budget: &dyn Budget,2081 ) -> DispatchResultWithPostInfo;20822083 2084 2085 2086 2087 2088 2089 2090 2091 2092 fn delete_token_properties(2093 &self,2094 sender: T::CrossAccountId,2095 token_id: TokenId,2096 property_keys: Vec<PropertyKey>,2097 budget: &dyn Budget,2098 ) -> DispatchResultWithPostInfo;20992100 2101 2102 2103 fn get_token_properties_raw(&self, token_id: TokenId) -> Option<TokenProperties>;21042105 2106 2107 2108 2109 fn set_token_properties_raw(&self, token_id: TokenId, map: TokenProperties);21102111 2112 2113 2114 2115 2116 2117 fn set_token_property_permissions(2118 &self,2119 sender: &T::CrossAccountId,2120 property_permissions: Vec<PropertyKeyPermission>,2121 ) -> DispatchResultWithPostInfo;21222123 2124 2125 2126 2127 2128 2129 2130 fn transfer(2131 &self,2132 sender: T::CrossAccountId,2133 to: T::CrossAccountId,2134 token: TokenId,2135 amount: u128,2136 budget: &dyn Budget,2137 ) -> DispatchResultWithPostInfo;21382139 2140 2141 2142 2143 2144 2145 fn approve(2146 &self,2147 sender: T::CrossAccountId,2148 spender: T::CrossAccountId,2149 token: TokenId,2150 amount: u128,2151 ) -> DispatchResultWithPostInfo;21522153 2154 2155 2156 2157 2158 2159 2160 fn approve_from(2161 &self,2162 sender: T::CrossAccountId,2163 from: T::CrossAccountId,2164 to: T::CrossAccountId,2165 token: TokenId,2166 amount: u128,2167 ) -> DispatchResultWithPostInfo;21682169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 fn transfer_from(2180 &self,2181 sender: T::CrossAccountId,2182 from: T::CrossAccountId,2183 to: T::CrossAccountId,2184 token: TokenId,2185 amount: u128,2186 budget: &dyn Budget,2187 ) -> DispatchResultWithPostInfo;21882189 2190 2191 2192 2193 2194 2195 2196 2197 2198 fn burn_from(2199 &self,2200 sender: T::CrossAccountId,2201 from: T::CrossAccountId,2202 token: TokenId,2203 amount: u128,2204 budget: &dyn Budget,2205 ) -> DispatchResultWithPostInfo;22062207 2208 2209 2210 2211 2212 2213 fn check_nesting(2214 &self,2215 sender: T::CrossAccountId,2216 from: (CollectionId, TokenId),2217 under: TokenId,2218 budget: &dyn Budget,2219 ) -> DispatchResult;22202221 2222 2223 2224 2225 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22262227 2228 2229 2230 2231 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22322233 2234 2235 2236 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22372238 2239 fn collection_tokens(&self) -> Vec<TokenId>;22402241 2242 2243 2244 fn token_exists(&self, token: TokenId) -> bool;22452246 2247 fn last_token_id(&self) -> TokenId;22482249 2250 2251 2252 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22532254 2255 2256 2257 2258 2259 fn check_token_indirect_owner(2260 &self,2261 token: TokenId,2262 maybe_owner: &T::CrossAccountId,2263 nesting_budget: &dyn Budget,2264 ) -> Result<bool, DispatchError>;22652266 2267 2268 2269 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22702271 2272 2273 2274 2275 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22762277 2278 2279 2280 2281 2282 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22832284 2285 fn total_supply(&self) -> u32;22862287 2288 2289 2290 fn account_balance(&self, account: T::CrossAccountId) -> u32;22912292 2293 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22942295 2296 fn total_pieces(&self, token: TokenId) -> Option<u128>;22972298 2299 2300 2301 2302 2303 fn allowance(2304 &self,2305 sender: T::CrossAccountId,2306 spender: T::CrossAccountId,2307 token: TokenId,2308 ) -> u128;23092310 2311 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;23122313 2314 2315 2316 2317 fn set_allowance_for_all(2318 &self,2319 owner: T::CrossAccountId,2320 operator: T::CrossAccountId,2321 approve: bool,2322 ) -> DispatchResultWithPostInfo;23232324 2325 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23262327 2328 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2329}233023312332pub trait RefungibleExtensions<T>2333where2334 T: Config,2335{2336 2337 2338 2339 2340 2341 2342 2343 fn repartition(2344 &self,2345 sender: &T::CrossAccountId,2346 token: TokenId,2347 amount: u128,2348 ) -> DispatchResultWithPostInfo;2349}23502351235223532354pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2355 let post_info = PostDispatchInfo {2356 actual_weight: Some(weight),2357 pays_fee: Pays::Yes,2358 };2359 match res {2360 Ok(()) => Ok(post_info),2361 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2362 }2363}23642365impl<T: Config> From<PropertiesError> for Error<T> {2366 fn from(error: PropertiesError) -> Self {2367 match error {2368 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2369 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2370 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2371 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2372 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2373 }2374 }2375}2376237723782379pub struct NewTokenPropertyWriter;2380238123822383pub struct ExistingTokenPropertyWriter;23842385238623872388238923902391pub struct PropertyWriter<2392 'a,2393 T,2394 Handle,2395 WriterVariant,2396 FIsAdmin,2397 FPropertyPermissions,2398 FCheckTokenExist,2399 FGetProperties,2400> where2401 T: Config,2402 FIsAdmin: FnOnce() -> bool,2403 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2404{2405 collection: &'a Handle,2406 is_collection_admin: LazyValue<bool, FIsAdmin>,2407 property_permissions: LazyValue<PropertiesPermissionMap, FPropertyPermissions>,2408 check_token_exist: FCheckTokenExist,2409 get_properties: FGetProperties,2410 _phantom: PhantomData<(T, WriterVariant)>,2411}24122413impl<'a, T, Handle, FIsAdmin, FPropertyPermissions, FCheckTokenExist, FGetProperties>2414 PropertyWriter<2415 'a,2416 T,2417 Handle,2418 NewTokenPropertyWriter,2419 FIsAdmin,2420 FPropertyPermissions,2421 FCheckTokenExist,2422 FGetProperties,2423 > where2424 T: Config,2425 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2426 FIsAdmin: FnOnce() -> bool,2427 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2428 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,2429 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,2430{2431 2432 pub fn write_token_properties(2433 &mut self,2434 mint_target_is_sender: bool,2435 token_id: TokenId,2436 properties_updates: impl Iterator<Item = Property>,2437 log: evm_coder::ethereum::Log,2438 ) -> DispatchResult {2439 self.internal_write_token_properties(2440 token_id,2441 properties_updates.map(|p| (p.key, Some(p.value))),2442 |_| Ok(mint_target_is_sender),2443 log,2444 )2445 }2446}24472448impl<'a, T, Handle, FIsAdmin, FPropertyPermissions, FCheckTokenExist, FGetProperties>2449 PropertyWriter<2450 'a,2451 T,2452 Handle,2453 ExistingTokenPropertyWriter,2454 FIsAdmin,2455 FPropertyPermissions,2456 FCheckTokenExist,2457 FGetProperties,2458 > where2459 T: Config,2460 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2461 FIsAdmin: FnOnce() -> bool,2462 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2463 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,2464 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,2465{2466 2467 pub fn write_token_properties(2468 &mut self,2469 sender: &T::CrossAccountId,2470 token_id: TokenId,2471 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2472 nesting_budget: &dyn Budget,2473 log: evm_coder::ethereum::Log,2474 ) -> DispatchResult {2475 self.internal_write_token_properties(2476 token_id,2477 properties_updates,2478 |collection| collection.check_token_indirect_owner(token_id, sender, nesting_budget),2479 log,2480 )2481 }2482}24832484impl<2485 'a,2486 T,2487 Handle,2488 WriterVariant,2489 FIsAdmin,2490 FPropertyPermissions,2491 FCheckTokenExist,2492 FGetProperties,2493 >2494 PropertyWriter<2495 'a,2496 T,2497 Handle,2498 WriterVariant,2499 FIsAdmin,2500 FPropertyPermissions,2501 FCheckTokenExist,2502 FGetProperties,2503 > where2504 T: Config,2505 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2506 FIsAdmin: FnOnce() -> bool,2507 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2508 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,2509 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,2510{2511 fn internal_write_token_properties<FCheckTokenOwner>(2512 &mut self,2513 token_id: TokenId,2514 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2515 check_token_owner: FCheckTokenOwner,2516 log: evm_coder::ethereum::Log,2517 ) -> DispatchResult2518 where2519 FCheckTokenOwner: FnOnce(&Handle) -> Result<bool, DispatchError>,2520 {2521 let get_properties = self.get_properties;2522 let mut stored_properties = LazyValue::new(move || get_properties(token_id));25232524 let mut is_token_owner = LazyValue::new(|| check_token_owner(self.collection));25252526 let check_token_exist = self.check_token_exist;2527 let mut is_token_exist = LazyValue::new(move || check_token_exist(token_id));25282529 for (key, value) in properties_updates {2530 let permission = self2531 .property_permissions2532 .value()2533 .get(&key)2534 .cloned()2535 .unwrap_or_else(PropertyPermission::none);25362537 match permission {2538 PropertyPermission { mutable: false, .. }2539 if stored_properties.value().get(&key).is_some() =>2540 {2541 return Err(<Error<T>>::NoPermission.into());2542 }25432544 PropertyPermission {2545 collection_admin,2546 token_owner,2547 ..2548 } => check_token_permissions::<T, _, _, _>(2549 collection_admin,2550 token_owner,2551 &mut self.is_collection_admin,2552 &mut is_token_owner,2553 &mut is_token_exist,2554 )?,2555 }25562557 match value {2558 Some(value) => {2559 stored_properties2560 .value_mut()2561 .try_set(key.clone(), value)2562 .map_err(<Error<T>>::from)?;25632564 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2565 self.collection.id,2566 token_id,2567 key,2568 ));2569 }2570 None => {2571 stored_properties2572 .value_mut()2573 .remove(&key)2574 .map_err(<Error<T>>::from)?;25752576 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2577 self.collection.id,2578 token_id,2579 key,2580 ));2581 }2582 }2583 }25842585 let properties_changed = stored_properties.has_value();2586 if properties_changed {2587 <PalletEvm<T>>::deposit_log(log);25882589 self.collection2590 .set_token_properties_raw(token_id, stored_properties.into_inner());2591 }25922593 Ok(())2594 }2595}259625972598pub fn property_writer_for_new_token<'a, T, Handle>(2599 collection: &'a Handle,2600 sender: &'a T::CrossAccountId,2601) -> PropertyWriter<2602 'a,2603 T,2604 Handle,2605 NewTokenPropertyWriter,2606 impl FnOnce() -> bool + 'a,2607 impl FnOnce() -> PropertiesPermissionMap + 'a,2608 impl Copy + FnOnce(TokenId) -> bool + 'a,2609 impl Copy + FnOnce(TokenId) -> TokenProperties + 'a,2610>2611where2612 T: Config,2613 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2614{2615 PropertyWriter {2616 collection,2617 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2618 property_permissions: LazyValue::new(|| <Pallet<T>>::property_permissions(collection.id)),2619 check_token_exist: |token_id| {2620 debug_assert!(collection.token_exists(token_id));2621 true2622 },2623 get_properties: |token_id| {2624 debug_assert!(collection.get_token_properties_raw(token_id).is_none());2625 TokenProperties::new()2626 },2627 _phantom: PhantomData,2628 }2629}26302631#[cfg(feature = "runtime-benchmarks")]26322633263426352636pub fn collection_info_loaded_property_writer<T, Handle>(2637 collection: &Handle,2638 is_collection_admin: bool,2639 property_permissions: PropertiesPermissionMap,2640) -> PropertyWriter<2641 T,2642 Handle,2643 NewTokenPropertyWriter,2644 impl FnOnce() -> bool,2645 impl FnOnce() -> PropertiesPermissionMap,2646 impl Copy + FnOnce(TokenId) -> bool,2647 impl Copy + FnOnce(TokenId) -> TokenProperties,2648>2649where2650 T: Config,2651 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2652{2653 PropertyWriter {2654 collection,2655 is_collection_admin: LazyValue::new(move || is_collection_admin),2656 property_permissions: LazyValue::new(move || property_permissions),2657 check_token_exist: |_token_id| true,2658 get_properties: |_token_id| TokenProperties::new(),2659 _phantom: PhantomData,2660 }2661}266226632664pub fn property_writer_for_existing_token<'a, T, Handle>(2665 collection: &'a Handle,2666 sender: &'a T::CrossAccountId,2667) -> PropertyWriter<2668 'a,2669 T,2670 Handle,2671 ExistingTokenPropertyWriter,2672 impl FnOnce() -> bool + 'a,2673 impl FnOnce() -> PropertiesPermissionMap + 'a,2674 impl Copy + FnOnce(TokenId) -> bool + 'a,2675 impl Copy + FnOnce(TokenId) -> TokenProperties + 'a,2676>2677where2678 T: Config,2679 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2680{2681 PropertyWriter {2682 collection,2683 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2684 property_permissions: LazyValue::new(|| <Pallet<T>>::property_permissions(collection.id)),2685 check_token_exist: |token_id| collection.token_exists(token_id),2686 get_properties: |token_id| {2687 collection2688 .get_token_properties_raw(token_id)2689 .unwrap_or_default()2690 },2691 _phantom: PhantomData,2692 }2693}26942695269626972698pub fn init_token_properties_delta<T: Config, I: Fn(u32) -> Weight>(2699 properties_nums: impl Iterator<Item = u32>,2700 init_token_properties: I,2701) -> Weight {2702 let mut delta = properties_nums2703 .filter_map(|properties_num| {2704 if properties_num > 0 {2705 Some(init_token_properties(properties_num))2706 } else {2707 None2708 }2709 })2710 .fold(Weight::zero(), |a, b| a.saturating_add(b));27112712 2713 2714 2715 2716 if !delta.is_zero() {2717 delta = delta.saturating_add(<SelfWeightOf<T>>::init_token_properties_common())2718 }27192720 delta2721}27222723#[cfg(any(feature = "tests", test))]2724#[allow(missing_docs)]2725pub mod tests {2726 use crate::{Config, DispatchError, DispatchResult, LazyValue};27272728 const fn to_bool(u: u8) -> bool {2729 u != 02730 }27312732 #[derive(Debug)]2733 pub struct TestCase {2734 pub collection_admin: bool,2735 pub is_collection_admin: bool,2736 pub token_owner: bool,2737 pub is_token_owner: bool,2738 pub no_permission: bool,2739 }27402741 impl TestCase {2742 const fn new(2743 collection_admin: u8,2744 is_collection_admin: u8,2745 token_owner: u8,2746 is_token_owner: u8,2747 no_permission: u8,2748 ) -> Self {2749 Self {2750 collection_admin: to_bool(collection_admin),2751 is_collection_admin: to_bool(is_collection_admin),2752 token_owner: to_bool(token_owner),2753 is_token_owner: to_bool(is_token_owner),2754 no_permission: to_bool(no_permission),2755 }2756 }2757 }27582759 #[rustfmt::skip]2760 pub const TABLE: [TestCase; 16] = [2761 2762 2763 2764 2765 2766 TestCase::new(0, 0, 0, 0, 1),2767 TestCase::new(0, 0, 0, 1, 1),2768 TestCase::new(0, 0, 1, 0, 1),2769 TestCase::new(0, 0, 1, 1, 0),2770 TestCase::new(0, 1, 0, 0, 1),2771 TestCase::new(0, 1, 0, 1, 1),2772 TestCase::new(0, 1, 1, 0, 1),2773 TestCase::new(0, 1, 1, 1, 0),2774 TestCase::new(1, 0, 0, 0, 1),2775 TestCase::new(1, 0, 0, 1, 1),2776 TestCase::new(1, 0, 1, 0, 1),2777 TestCase::new(1, 0, 1, 1, 0),2778 TestCase::new(1, 1, 0, 0, 0),2779 TestCase::new(1, 1, 0, 1, 0),2780 TestCase::new(1, 1, 1, 0, 0),2781 TestCase::new(1, 1, 1, 1, 0),2782 ];27832784 pub fn check_token_permissions<T, FCA, FTO, FTE>(2785 collection_admin_permitted: bool,2786 token_owner_permitted: bool,2787 is_collection_admin: &mut LazyValue<bool, FCA>,2788 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>, FTO>,2789 check_token_existence: &mut LazyValue<bool, FTE>,2790 ) -> DispatchResult2791 where2792 T: Config,2793 FCA: FnOnce() -> bool,2794 FTO: FnOnce() -> Result<bool, DispatchError>,2795 FTE: FnOnce() -> bool,2796 {2797 crate::check_token_permissions::<T, FCA, FTO, FTE>(2798 collection_admin_permitted,2799 token_owner_permitted,2800 is_collection_admin,2801 check_token_ownership,2802 check_token_existence,2803 )2804 }2805}