12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use alloc::boxed::Box;57use core::{58 marker::PhantomData,59 ops::{Deref, DerefMut},60 slice::from_ref,61 unreachable,62};6364use evm_coder::ToLog;65use frame_support::{66 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Pays, PostDispatchInfo},67 ensure, fail,68 traits::{69 fungible::{Balanced, Debt, Inspect},70 tokens::{Imbalance, Precision, Preservation},71 Get,72 },73 transactional,74};75pub use pallet::*;76use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};77use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};78use sp_core::H160;79use sp_runtime::{traits::Zero, ArithmeticError, DispatchError, DispatchResult};80use sp_std::vec::Vec;81use sp_weights::Weight;82use up_data_structs::{83 budget::Budget, AccessMode, Collection, CollectionId, CollectionLimits, CollectionMode,84 CollectionPermissions, CollectionProperties as CollectionPropertiesT, CollectionStats,85 CreateCollectionData, CreateItemData, CreateItemExData, PhantomType, PropertiesError,86 PropertiesPermissionMap, Property, PropertyKey, PropertyKeyPermission, PropertyPermission,87 PropertyScope, PropertyValue, RpcCollection, RpcCollectionFlags, SponsoringRateLimit,88 SponsorshipState, TokenChild, TokenData, TokenId, TokenOwnerError, TokenProperties,89 TrySetProperty, COLLECTION_ADMINS_LIMIT, COLLECTION_NUMBER_LIMIT, CUSTOM_DATA_LIMIT,90 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, MAX_TOKEN_OWNERSHIP,91 MAX_TOKEN_PREFIX_LENGTH, NFT_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,92};93use up_pov_estimate_rpc::PovInfo;9495#[cfg(feature = "runtime-benchmarks")]96pub mod benchmarking;97pub mod dispatch;98pub mod erc;99pub mod eth;100pub mod helpers;101#[allow(missing_docs)]102pub mod weights;103104use weights::WeightInfo;105106107pub type SelfWeightOf<T> = <T as Config>::WeightInfo;108109110111112113114115#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]116pub struct CollectionHandle<T: Config> {117 118 pub id: CollectionId,119 collection: Collection<T::AccountId>,120 121 pub recorder: SubstrateRecorder<T>,122}123124impl<T: Config> WithRecorder<T> for CollectionHandle<T> {125 fn recorder(&self) -> &SubstrateRecorder<T> {126 &self.recorder127 }128 fn into_recorder(self) -> SubstrateRecorder<T> {129 self.recorder130 }131}132133impl<T: Config> CollectionHandle<T> {134 135 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {136 Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))137 }138139 140 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {141 <CollectionById<T>>::get(id).map(|collection| Self {142 id,143 collection,144 recorder,145 })146 }147148 149 150 pub fn new(id: CollectionId) -> Option<Self> {151 Self::new_with_gas_limit(id, u64::MAX)152 }153154 155 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {156 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)157 }158159 160 pub fn consume_store_reads(161 &self,162 reads: u64,163 ) -> pallet_evm_coder_substrate::execution::Result<()> {164 self.recorder().consume_store_reads(reads)165 }166167 168 pub fn consume_store_writes(169 &self,170 writes: u64,171 ) -> pallet_evm_coder_substrate::execution::Result<()> {172 self.recorder().consume_store_writes(writes)173 }174175 176 pub fn consume_store_reads_and_writes(177 &self,178 reads: u64,179 writes: u64,180 ) -> pallet_evm_coder_substrate::execution::Result<()> {181 self.recorder()182 .consume_store_reads_and_writes(reads, writes)183 }184185 186 pub fn save(&self) -> DispatchResult {187 <CollectionById<T>>::insert(self.id, &self.collection);188 Ok(())189 }190191 192 193 194 195 196 pub fn set_sponsor(197 &mut self,198 sender: &T::CrossAccountId,199 sponsor: T::AccountId,200 ) -> DispatchResult {201 self.check_is_internal()?;202 self.check_is_owner_or_admin(sender)?;203204 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());205206 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));207 <PalletEvm<T>>::deposit_log(208 erc::CollectionHelpersEvents::CollectionChanged {209 collection_id: eth::collection_id_to_address(self.id),210 }211 .to_log(T::ContractAddress::get()),212 );213214 self.save()215 }216217 218 219 220 221 222 223 224 225 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {226 self.check_is_internal()?;227228 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());229230 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));231 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));232 <PalletEvm<T>>::deposit_log(233 erc::CollectionHelpersEvents::CollectionChanged {234 collection_id: eth::collection_id_to_address(self.id),235 }236 .to_log(T::ContractAddress::get()),237 );238239 self.save()240 }241242 243 244 245 246 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {247 self.check_is_internal()?;248 ensure!(249 self.collection.sponsorship.pending_sponsor() == Some(sender),250 Error::<T>::ConfirmSponsorshipFail251 );252253 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());254255 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));256 <PalletEvm<T>>::deposit_log(257 erc::CollectionHelpersEvents::CollectionChanged {258 collection_id: eth::collection_id_to_address(self.id),259 }260 .to_log(T::ContractAddress::get()),261 );262263 self.save()264 }265266 267 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {268 self.check_is_internal()?;269 self.check_is_owner_or_admin(sender)?;270271 self.collection.sponsorship = SponsorshipState::Disabled;272273 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));274 <PalletEvm<T>>::deposit_log(275 erc::CollectionHelpersEvents::CollectionChanged {276 collection_id: eth::collection_id_to_address(self.id),277 }278 .to_log(T::ContractAddress::get()),279 );280 self.save()281 }282283 284 285 286 287 pub fn force_remove_sponsor(&mut self) -> DispatchResult {288 self.check_is_internal()?;289290 self.collection.sponsorship = SponsorshipState::Disabled;291292 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));293 <PalletEvm<T>>::deposit_log(294 erc::CollectionHelpersEvents::CollectionChanged {295 collection_id: eth::collection_id_to_address(self.id),296 }297 .to_log(T::ContractAddress::get()),298 );299 self.save()300 }301302 303 304 pub fn check_is_internal(&self) -> DispatchResult {305 if self.flags.external {306 return Err(<Error<T>>::CollectionIsExternal)?;307 }308309 Ok(())310 }311312 313 314 pub fn check_is_external(&self) -> DispatchResult {315 if !self.flags.external {316 return Err(<Error<T>>::CollectionIsInternal)?;317 }318319 Ok(())320 }321}322323impl<T: Config> Deref for CollectionHandle<T> {324 type Target = Collection<T::AccountId>;325326 fn deref(&self) -> &Self::Target {327 &self.collection328 }329}330331impl<T: Config> DerefMut for CollectionHandle<T> {332 fn deref_mut(&mut self) -> &mut Self::Target {333 &mut self.collection334 }335}336337impl<T: Config> CollectionHandle<T> {338 339 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {340 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);341 Ok(())342 }343344 345 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {346 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))347 }348349 350 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {351 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);352 Ok(())353 }354355 356 357 358 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {359 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)360 }361362 363 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {364 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)365 }366367 368 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {369 ensure!(370 <Allowlist<T>>::get((self.id, user)),371 <Error<T>>::AddressNotInAllowlist372 );373 Ok(())374 }375376 377 378 379 pub fn change_owner(380 &mut self,381 caller: T::CrossAccountId,382 new_owner: T::CrossAccountId,383 ) -> DispatchResult {384 self.check_is_internal()?;385 self.check_is_owner(&caller)?;386 self.collection.owner = new_owner.as_sub().clone();387388 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(389 self.id,390 new_owner.as_sub().clone(),391 ));392 <PalletEvm<T>>::deposit_log(393 erc::CollectionHelpersEvents::CollectionChanged {394 collection_id: eth::collection_id_to_address(self.id),395 }396 .to_log(T::ContractAddress::get()),397 );398399 self.save()400 }401}402403#[frame_support::pallet]404pub mod pallet {405406 use dispatch::CollectionDispatch;407 use frame_support::{408 pallet_prelude::*, storage::Key, traits::StorageVersion, Blake2_128Concat,409 };410 use scale_info::TypeInfo;411 use up_data_structs::{mapping::TokenAddressMapping, TokenId};412 use weights::WeightInfo;413414 use super::*;415416 #[pallet::config]417 pub trait Config:418 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo419 {420 421 type WeightInfo: WeightInfo;422423 424 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;425426 427 type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;428429 430 #[pallet::constant]431 type CollectionCreationPrice: Get<432 <<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,433 >;434435 436 type CollectionDispatch: CollectionDispatch<Self>;437438 439 type TreasuryAccountId: Get<Self::AccountId>;440441 442 #[pallet::constant]443 type ContractAddress: Get<H160>;444445 446 type EvmTokenAddressMapping: TokenAddressMapping<H160>;447448 449 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;450 }451452 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);453 454 pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);455456 #[pallet::pallet]457 #[pallet::storage_version(STORAGE_VERSION)]458 pub struct Pallet<T>(_);459460 #[pallet::extra_constants]461 impl<T: Config> Pallet<T> {462 463 pub fn collection_admins_limit() -> u32 {464 COLLECTION_ADMINS_LIMIT465 }466 }467468 #[pallet::genesis_config]469 pub struct GenesisConfig<T>(PhantomData<T>);470471 impl<T: Config> Default for GenesisConfig<T> {472 fn default() -> Self {473 Self(Default::default())474 }475 }476477 #[pallet::genesis_build]478 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {479 fn build(&self) {480 StorageVersion::new(1).put::<Pallet<T>>();481 }482 }483484 impl<T: Config> Pallet<T> {485 486 pub fn deposit_event(event: Event<T>) {487 let event = <T as Config>::RuntimeEvent::from(event);488 let event = event.into();489 <frame_system::Pallet<T>>::deposit_event(event)490 }491 }492493 #[pallet::event]494 pub enum Event<T: Config> {495 496 CollectionCreated(497 498 CollectionId,499 500 u8,501 502 T::AccountId,503 ),504505 506 CollectionDestroyed(507 508 CollectionId,509 ),510511 512 ItemCreated(513 514 CollectionId,515 516 TokenId,517 518 T::CrossAccountId,519 520 u128,521 ),522523 524 ItemDestroyed(525 526 CollectionId,527 528 TokenId,529 530 T::CrossAccountId,531 532 u128,533 ),534535 536 Transfer(537 538 CollectionId,539 540 TokenId,541 542 T::CrossAccountId,543 544 T::CrossAccountId,545 546 u128,547 ),548549 550 Approved(551 552 CollectionId,553 554 TokenId,555 556 T::CrossAccountId,557 558 T::CrossAccountId,559 560 u128,561 ),562563 564 ApprovedForAll(565 566 CollectionId,567 568 T::CrossAccountId,569 570 T::CrossAccountId,571 572 bool,573 ),574575 576 CollectionPropertySet(577 578 CollectionId,579 580 PropertyKey,581 ),582583 584 CollectionPropertyDeleted(585 586 CollectionId,587 588 PropertyKey,589 ),590591 592 TokenPropertySet(593 594 CollectionId,595 596 TokenId,597 598 PropertyKey,599 ),600601 602 TokenPropertyDeleted(603 604 CollectionId,605 606 TokenId,607 608 PropertyKey,609 ),610611 612 PropertyPermissionSet(613 614 CollectionId,615 616 PropertyKey,617 ),618619 620 AllowListAddressAdded(621 622 CollectionId,623 624 T::CrossAccountId,625 ),626627 628 AllowListAddressRemoved(629 630 CollectionId,631 632 T::CrossAccountId,633 ),634635 636 CollectionAdminAdded(637 638 CollectionId,639 640 T::CrossAccountId,641 ),642643 644 CollectionAdminRemoved(645 646 CollectionId,647 648 T::CrossAccountId,649 ),650651 652 CollectionLimitSet(653 654 CollectionId,655 ),656657 658 CollectionOwnerChanged(659 660 CollectionId,661 662 T::AccountId,663 ),664665 666 CollectionPermissionSet(667 668 CollectionId,669 ),670671 672 CollectionSponsorSet(673 674 CollectionId,675 676 T::AccountId,677 ),678679 680 SponsorshipConfirmed(681 682 CollectionId,683 684 T::AccountId,685 ),686687 688 CollectionSponsorRemoved(689 690 CollectionId,691 ),692 }693694 #[pallet::error]695 pub enum Error<T> {696 697 CollectionNotFound,698 699 MustBeTokenOwner,700 701 NoPermission,702 703 CantDestroyNotEmptyCollection,704 705 PublicMintingNotAllowed,706 707 AddressNotInAllowlist,708709 710 CollectionNameLimitExceeded,711 712 CollectionDescriptionLimitExceeded,713 714 CollectionTokenPrefixLimitExceeded,715 716 TotalCollectionsLimitExceeded,717 718 CollectionAdminCountExceeded,719 720 CollectionLimitBoundsExceeded,721 722 OwnerPermissionsCantBeReverted,723 724 TransferNotAllowed,725 726 AccountTokenLimitExceeded,727 728 CollectionTokenLimitExceeded,729 730 MetadataFlagFrozen,731732 733 TokenNotFound,734 735 TokenValueTooLow,736 737 ApprovedValueTooLow,738 739 CantApproveMoreThanOwned,740 741 AddressIsNotEthMirror,742743 744 AddressIsZero,745746 747 UnsupportedOperation,748749 750 NotSufficientFounds,751752 753 UserIsNotAllowedToNest,754 755 SourceCollectionIsNotAllowedToNest,756757 758 CollectionFieldSizeExceeded,759760 761 NoSpaceForProperty,762763 764 PropertyLimitReached,765766 767 PropertyKeyIsTooLong,768769 770 InvalidCharacterInPropertyKey,771772 773 EmptyPropertyKey,774775 776 CollectionIsExternal,777778 779 CollectionIsInternal,780781 782 ConfirmSponsorshipFail,783784 785 UserIsNotCollectionAdmin,786 }787788 789 #[pallet::storage]790 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;791792 793 #[pallet::storage]794 pub type DestroyedCollectionCount<T> =795 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;796797 798 #[pallet::storage]799 pub type CollectionById<T> = StorageMap<800 Hasher = Blake2_128Concat,801 Key = CollectionId,802 Value = Collection<<T as frame_system::Config>::AccountId>,803 QueryKind = OptionQuery,804 >;805806 807 #[pallet::storage]808 #[pallet::getter(fn collection_properties)]809 pub type CollectionProperties<T> = StorageMap<810 Hasher = Blake2_128Concat,811 Key = CollectionId,812 Value = CollectionPropertiesT,813 QueryKind = ValueQuery,814 >;815816 817 #[pallet::storage]818 #[pallet::getter(fn property_permissions)]819 pub type CollectionPropertyPermissions<T> = StorageMap<820 Hasher = Blake2_128Concat,821 Key = CollectionId,822 Value = PropertiesPermissionMap,823 QueryKind = ValueQuery,824 >;825826 827 #[pallet::storage]828 pub type AdminAmount<T> = StorageMap<829 Hasher = Blake2_128Concat,830 Key = CollectionId,831 Value = u32,832 QueryKind = ValueQuery,833 >;834835 836 #[pallet::storage]837 pub type IsAdmin<T: Config> = StorageNMap<838 Key = (839 Key<Blake2_128Concat, CollectionId>,840 Key<Blake2_128Concat, T::CrossAccountId>,841 ),842 Value = bool,843 QueryKind = ValueQuery,844 >;845846 847 #[pallet::storage]848 pub type Allowlist<T: Config> = StorageNMap<849 Key = (850 Key<Blake2_128Concat, CollectionId>,851 Key<Blake2_128Concat, T::CrossAccountId>,852 ),853 Value = bool,854 QueryKind = ValueQuery,855 >;856857 858 #[pallet::storage]859 pub type DummyStorageValue<T: Config> = StorageValue<860 Value = (861 CollectionStats,862 CollectionId,863 TokenId,864 TokenChild,865 PhantomType<(866 TokenData<T::CrossAccountId>,867 RpcCollection<T::AccountId>,868 869 PovInfo,870 )>,871 ),872 QueryKind = OptionQuery,873 >;874}875876enum LazyValueState<'a, T> {877 Pending(Box<dyn FnOnce() -> T + 'a>),878 InProgress(PhantomData<sp_std::cell::Cell<T>>),879 Computed(T),880}881882883pub struct LazyValue<'a, T> {884 state: LazyValueState<'a, T>,885}886887impl<'a, T> LazyValue<'a, T> {888 889 pub fn new(f: impl FnOnce() -> T + 'a) -> Self {890 Self {891 state: LazyValueState::Pending(Box::new(f)),892 }893 }894895 896 pub fn value(&mut self) -> &T {897 self.force_value();898 self.value_mut()899 }900901 902 pub fn value_mut(&mut self) -> &mut T {903 self.force_value();904905 if let LazyValueState::Computed(value) = &mut self.state {906 value907 } else {908 unreachable!()909 }910 }911912 fn into_inner(mut self) -> T {913 self.force_value();914 if let LazyValueState::Computed(value) = self.state {915 value916 } else {917 unreachable!()918 }919 }920921 922 pub fn has_value(&self) -> bool {923 matches!(self.state, LazyValueState::Computed(_))924 }925926 fn force_value(&mut self) {927 use LazyValueState::*;928929 if self.has_value() {930 return;931 }932933 match sp_std::mem::replace(&mut self.state, InProgress(PhantomData)) {934 Pending(f) => self.state = Computed(f()),935 _ => {936 937 938 unreachable!()939 }940 }941 }942}943944fn check_token_permissions<T: Config>(945 collection_admin_permitted: bool,946 token_owner_permitted: bool,947 is_collection_admin: &mut LazyValue<bool>,948 is_token_owner: &mut LazyValue<Result<bool, DispatchError>>,949 is_token_exist: &mut LazyValue<bool>,950) -> DispatchResult {951 if !(collection_admin_permitted && *is_collection_admin.value()952 || token_owner_permitted && (*is_token_owner.value())?)953 {954 fail!(<Error<T>>::NoPermission);955 }956957 let token_exist_due_to_owner_check_success =958 is_token_owner.has_value() && (*is_token_owner.value())?;959960 961 962 if !token_exist_due_to_owner_check_success {963 964 965 if !is_token_exist.value() {966 fail!(<Error<T>>::TokenNotFound);967 }968 }969970 Ok(())971}972973impl<T: Config> Pallet<T> {974 975 976 977 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {978 ensure!(979 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,980 <Error<T>>::AddressIsZero981 );982 Ok(())983 }984985 986 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {987 <IsAdmin<T>>::iter_prefix((collection,))988 .map(|(a, _)| a)989 .collect()990 }991992 993 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {994 <Allowlist<T>>::iter_prefix((collection,))995 .map(|(a, _)| a)996 .collect()997 }998999 1000 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {1001 <Allowlist<T>>::get((collection, user))1002 }10031004 1005 pub fn collection_stats() -> CollectionStats {1006 let created = <CreatedCollectionCount<T>>::get();1007 let destroyed = <DestroyedCollectionCount<T>>::get();1008 CollectionStats {1009 created: created.0,1010 destroyed: destroyed.0,1011 alive: created.0 - destroyed.0,1012 }1013 }10141015 1016 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {1017 let collection = <CollectionById<T>>::get(collection)?;1018 let limits = collection.limits;1019 let effective_limits = CollectionLimits {1020 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),1021 sponsored_data_size: Some(limits.sponsored_data_size()),1022 sponsored_data_rate_limit: Some(1023 limits1024 .sponsored_data_rate_limit1025 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),1026 ),1027 token_limit: Some(limits.token_limit()),1028 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(1029 match collection.mode {1030 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1031 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1032 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1033 },1034 )),1035 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),1036 owner_can_transfer: Some(limits.owner_can_transfer()),1037 owner_can_destroy: Some(limits.owner_can_destroy()),1038 transfers_enabled: Some(limits.transfers_enabled()),1039 };10401041 Some(effective_limits)1042 }10431044 1045 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1046 let Collection {1047 name,1048 description,1049 owner,1050 mode,1051 token_prefix,1052 sponsorship,1053 limits,1054 permissions,1055 flags,1056 } = <CollectionById<T>>::get(collection)?;10571058 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1059 .into_iter()1060 .map(|(key, permission)| PropertyKeyPermission { key, permission })1061 .collect();10621063 let properties = <CollectionProperties<T>>::get(collection)1064 .into_iter()1065 .map(|(key, value)| Property { key, value })1066 .collect();10671068 let permissions = CollectionPermissions {1069 access: Some(permissions.access()),1070 mint_mode: Some(permissions.mint_mode()),1071 nesting: Some(permissions.nesting().clone()),1072 };10731074 Some(RpcCollection {1075 name: name.into_inner(),1076 description: description.into_inner(),1077 owner,1078 mode,1079 token_prefix: token_prefix.into_inner(),1080 sponsorship,1081 limits,1082 permissions,1083 token_property_permissions,1084 properties,1085 read_only: flags.external,10861087 flags: RpcCollectionFlags {1088 foreign: flags.foreign,1089 erc721metadata: flags.erc721metadata,1090 },1091 })1092 }1093}10941095macro_rules! limit_default {1096 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1097 $(1098 if let Some($new) = $new.$field {1099 let $old = $old.$field($($arg)?);1100 let _ = $new;1101 let _ = $old;1102 $check1103 } else {1104 $new.$field = $old.$field1105 }1106 )*1107 }};1108}1109macro_rules! limit_default_clone {1110 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1111 $(1112 if let Some($new) = $new.$field.clone() {1113 let $old = $old.$field($($arg)?);1114 let _ = $new;1115 let _ = $old;1116 $check1117 } else {1118 $new.$field = $old.$field.clone()1119 }1120 )*1121 }};1122}11231124impl<T: Config> Pallet<T> {1125 1126 1127 1128 1129 1130 pub fn init_collection(1131 owner: T::CrossAccountId,1132 payer: T::CrossAccountId,1133 data: CreateCollectionData<T::CrossAccountId>,1134 ) -> Result<CollectionId, DispatchError> {1135 ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);1136 Self::init_collection_internal(owner, payer, data)1137 }11381139 1140 pub fn init_foreign_collection(1141 owner: T::CrossAccountId,1142 payer: T::CrossAccountId,1143 mut data: CreateCollectionData<T::CrossAccountId>,1144 ) -> Result<CollectionId, DispatchError> {1145 data.flags.foreign = true;1146 let id = Self::init_collection_internal(owner, payer, data)?;1147 Ok(id)1148 }11491150 fn init_collection_internal(1151 owner: T::CrossAccountId,1152 payer: T::CrossAccountId,1153 data: CreateCollectionData<T::CrossAccountId>,1154 ) -> Result<CollectionId, DispatchError> {1155 {1156 ensure!(1157 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1158 Error::<T>::CollectionTokenPrefixLimitExceeded1159 );1160 }11611162 let created_count = <CreatedCollectionCount<T>>::get()1163 .01164 .checked_add(1)1165 .ok_or(ArithmeticError::Overflow)?;1166 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1167 let id = CollectionId(created_count);11681169 1170 ensure!(1171 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1172 <Error<T>>::TotalCollectionsLimitExceeded1173 );11741175 11761177 let collection = Collection {1178 owner: owner.as_sub().clone(),1179 name: data.name,1180 mode: data.mode.clone(),1181 description: data.description,1182 token_prefix: data.token_prefix,1183 sponsorship: data1184 .pending_sponsor1185 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1186 .unwrap_or_default(),1187 limits: data1188 .limits1189 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1190 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1191 permissions: data1192 .permissions1193 .map(|permissions| {1194 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1195 })1196 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1197 flags: data.flags,1198 };11991200 let mut collection_properties = CollectionPropertiesT::new();1201 collection_properties1202 .try_set_from_iter(data.properties.into_iter())1203 .map_err(<Error<T>>::from)?;12041205 CollectionProperties::<T>::insert(id, collection_properties);12061207 let mut token_props_permissions = PropertiesPermissionMap::new();1208 token_props_permissions1209 .try_set_from_iter(data.token_property_permissions.into_iter())1210 .map_err(<Error<T>>::from)?;12111212 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);12131214 let mut admin_amount = 0u32;1215 for admin in data.admin_list.iter() {1216 if !<IsAdmin<T>>::get((id, admin)) {1217 <IsAdmin<T>>::insert((id, admin), true);1218 admin_amount = admin_amount1219 .checked_add(1)1220 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1221 }1222 }1223 ensure!(1224 admin_amount <= Self::collection_admins_limit(),1225 <Error<T>>::CollectionAdminCountExceeded,1226 );1227 <AdminAmount<T>>::insert(id, admin_amount);12281229 1230 {1231 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1232 imbalance.subsume(<T as Config>::Currency::deposit(1233 &T::TreasuryAccountId::get(),1234 T::CollectionCreationPrice::get(),1235 Precision::Exact,1236 )?);1237 let credit =1238 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1239 .map_err(|_| Error::<T>::NotSufficientFounds)?;12401241 debug_assert!(credit.peek().is_zero())1242 }12431244 <CreatedCollectionCount<T>>::put(created_count);1245 <Pallet<T>>::deposit_event(Event::CollectionCreated(1246 id,1247 data.mode.id(),1248 owner.as_sub().clone(),1249 ));1250 <PalletEvm<T>>::deposit_log(1251 erc::CollectionHelpersEvents::CollectionCreated {1252 owner: *owner.as_eth(),1253 collection_id: eth::collection_id_to_address(id),1254 }1255 .to_log(T::ContractAddress::get()),1256 );1257 <CollectionById<T>>::insert(id, collection);1258 Ok(id)1259 }12601261 1262 1263 1264 1265 pub fn destroy_collection(1266 collection: CollectionHandle<T>,1267 sender: &T::CrossAccountId,1268 ) -> DispatchResult {1269 ensure!(1270 collection.limits.owner_can_destroy(),1271 <Error<T>>::NoPermission,1272 );1273 collection.check_is_owner(sender)?;12741275 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1276 .01277 .checked_add(1)1278 .ok_or(ArithmeticError::Overflow)?;12791280 12811282 <DestroyedCollectionCount<T>>::put(destroyed_collections);1283 <CollectionById<T>>::remove(collection.id);1284 <AdminAmount<T>>::remove(collection.id);1285 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1286 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1287 <CollectionProperties<T>>::remove(collection.id);12881289 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12901291 <PalletEvm<T>>::deposit_log(1292 erc::CollectionHelpersEvents::CollectionDestroyed {1293 collection_id: eth::collection_id_to_address(collection.id),1294 }1295 .to_log(T::ContractAddress::get()),1296 );1297 Ok(())1298 }12991300 1301 1302 1303 1304 1305 1306 1307 1308 #[transactional]1309 fn modify_collection_properties(1310 collection: &CollectionHandle<T>,1311 sender: &T::CrossAccountId,1312 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1313 ) -> DispatchResult {1314 collection.check_is_owner_or_admin(sender)?;13151316 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);13171318 for (key, value) in properties_updates {1319 match value {1320 Some(value) => {1321 stored_properties1322 .try_set(key.clone(), value)1323 .map_err(<Error<T>>::from)?;13241325 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1326 <PalletEvm<T>>::deposit_log(1327 erc::CollectionHelpersEvents::CollectionChanged {1328 collection_id: eth::collection_id_to_address(collection.id),1329 }1330 .to_log(T::ContractAddress::get()),1331 );1332 }1333 None => {1334 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13351336 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1337 <PalletEvm<T>>::deposit_log(1338 erc::CollectionHelpersEvents::CollectionChanged {1339 collection_id: eth::collection_id_to_address(collection.id),1340 }1341 .to_log(T::ContractAddress::get()),1342 );1343 }1344 }1345 }13461347 <CollectionProperties<T>>::set(collection.id, stored_properties);13481349 Ok(())1350 }13511352 1353 1354 1355 1356 1357 1358 pub fn set_allowance_for_all(1359 collection: &CollectionHandle<T>,1360 owner: &T::CrossAccountId,1361 operator: &T::CrossAccountId,1362 approve: bool,1363 set_allowance: impl FnOnce(),1364 log: evm_coder::ethereum::Log,1365 ) -> DispatchResult {1366 if collection.permissions.access() == AccessMode::AllowList {1367 collection.check_allowlist(owner)?;1368 collection.check_allowlist(operator)?;1369 }13701371 Self::ensure_correct_receiver(operator)?;13721373 set_allowance();13741375 <PalletEvm<T>>::deposit_log(log);1376 Self::deposit_event(Event::ApprovedForAll(1377 collection.id,1378 owner.clone(),1379 operator.clone(),1380 approve,1381 ));1382 Ok(())1383 }13841385 1386 1387 1388 1389 1390 pub fn set_collection_property(1391 collection: &CollectionHandle<T>,1392 sender: &T::CrossAccountId,1393 property: Property,1394 ) -> DispatchResult {1395 Self::set_collection_properties(collection, sender, [property].into_iter())1396 }13971398 1399 1400 1401 1402 1403 1404 pub fn set_scoped_collection_property(1405 collection_id: CollectionId,1406 scope: PropertyScope,1407 property: Property,1408 ) -> DispatchResult {1409 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1410 properties.try_scoped_set(scope, property.key, property.value)1411 })1412 .map_err(<Error<T>>::from)?;14131414 Ok(())1415 }14161417 1418 1419 1420 1421 1422 1423 pub fn set_scoped_collection_properties(1424 collection_id: CollectionId,1425 scope: PropertyScope,1426 properties: impl Iterator<Item = Property>,1427 ) -> DispatchResult {1428 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1429 stored_properties.try_scoped_set_from_iter(scope, properties)1430 })1431 .map_err(<Error<T>>::from)?;14321433 Ok(())1434 }14351436 1437 1438 1439 1440 1441 pub fn set_collection_properties(1442 collection: &CollectionHandle<T>,1443 sender: &T::CrossAccountId,1444 properties: impl Iterator<Item = Property>,1445 ) -> DispatchResult {1446 Self::modify_collection_properties(1447 collection,1448 sender,1449 properties.map(|property| (property.key, Some(property.value))),1450 )1451 }14521453 1454 1455 1456 1457 1458 pub fn delete_collection_property(1459 collection: &CollectionHandle<T>,1460 sender: &T::CrossAccountId,1461 property_key: PropertyKey,1462 ) -> DispatchResult {1463 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1464 }14651466 1467 1468 1469 1470 1471 pub fn delete_collection_properties(1472 collection: &CollectionHandle<T>,1473 sender: &T::CrossAccountId,1474 property_keys: impl Iterator<Item = PropertyKey>,1475 ) -> DispatchResult {1476 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1477 }14781479 1480 1481 1482 1483 1484 1485 pub fn set_property_permission_unchecked(1486 collection: CollectionId,1487 property_permission: PropertyKeyPermission,1488 ) -> DispatchResult {1489 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1490 permissions.try_set(property_permission.key, property_permission.permission)1491 })1492 .map_err(<Error<T>>::from)?;1493 Ok(())1494 }14951496 1497 1498 1499 1500 1501 pub fn set_property_permission(1502 collection: &CollectionHandle<T>,1503 sender: &T::CrossAccountId,1504 property_permission: PropertyKeyPermission,1505 ) -> DispatchResult {1506 Self::set_scoped_property_permission(1507 collection,1508 sender,1509 PropertyScope::None,1510 property_permission,1511 )1512 }15131514 1515 1516 1517 1518 1519 1520 pub fn set_scoped_property_permission(1521 collection: &CollectionHandle<T>,1522 sender: &T::CrossAccountId,1523 scope: PropertyScope,1524 property_permission: PropertyKeyPermission,1525 ) -> DispatchResult {1526 collection.check_is_owner_or_admin(sender)?;15271528 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1529 let current_permission = all_permissions.get(&property_permission.key);1530 if matches![1531 current_permission,1532 Some(PropertyPermission { mutable: false, .. })1533 ] {1534 return Err(<Error<T>>::NoPermission.into());1535 }15361537 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1538 let property_permission = property_permission.clone();1539 permissions.try_scoped_set(1540 scope,1541 property_permission.key,1542 property_permission.permission,1543 )1544 })1545 .map_err(<Error<T>>::from)?;15461547 Self::deposit_event(Event::PropertyPermissionSet(1548 collection.id,1549 property_permission.key,1550 ));1551 <PalletEvm<T>>::deposit_log(1552 erc::CollectionHelpersEvents::CollectionChanged {1553 collection_id: eth::collection_id_to_address(collection.id),1554 }1555 .to_log(T::ContractAddress::get()),1556 );15571558 Ok(())1559 }15601561 1562 1563 1564 1565 1566 #[transactional]1567 pub fn set_token_property_permissions(1568 collection: &CollectionHandle<T>,1569 sender: &T::CrossAccountId,1570 property_permissions: Vec<PropertyKeyPermission>,1571 ) -> DispatchResult {1572 Self::set_scoped_token_property_permissions(1573 collection,1574 sender,1575 PropertyScope::None,1576 property_permissions,1577 )1578 }15791580 1581 1582 1583 1584 1585 1586 #[transactional]1587 pub fn set_scoped_token_property_permissions(1588 collection: &CollectionHandle<T>,1589 sender: &T::CrossAccountId,1590 scope: PropertyScope,1591 property_permissions: Vec<PropertyKeyPermission>,1592 ) -> DispatchResult {1593 for prop_pemission in property_permissions {1594 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1595 }15961597 Ok(())1598 }15991600 1601 pub fn get_collection_property(1602 collection_id: CollectionId,1603 key: &PropertyKey,1604 ) -> Option<PropertyValue> {1605 Self::collection_properties(collection_id).get(key).cloned()1606 }16071608 1609 pub fn bytes_keys_to_property_keys(1610 keys: Vec<Vec<u8>>,1611 ) -> Result<Vec<PropertyKey>, DispatchError> {1612 keys.into_iter()1613 .map(|key| -> Result<PropertyKey, DispatchError> {1614 key.try_into()1615 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1616 })1617 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1618 }16191620 1621 pub fn filter_collection_properties(1622 collection_id: CollectionId,1623 keys: Option<Vec<PropertyKey>>,1624 ) -> Result<Vec<Property>, DispatchError> {1625 let properties = Self::collection_properties(collection_id);16261627 let properties = keys1628 .map(|keys| {1629 keys.into_iter()1630 .filter_map(|key| {1631 properties.get(&key).map(|value| Property {1632 key,1633 value: value.clone(),1634 })1635 })1636 .collect()1637 })1638 .unwrap_or_else(|| {1639 properties1640 .into_iter()1641 .map(|(key, value)| Property { key, value })1642 .collect()1643 });16441645 Ok(properties)1646 }16471648 1649 pub fn filter_property_permissions(1650 collection_id: CollectionId,1651 keys: Option<Vec<PropertyKey>>,1652 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1653 let permissions = Self::property_permissions(collection_id);16541655 let key_permissions = keys1656 .map(|keys| {1657 keys.into_iter()1658 .filter_map(|key| {1659 permissions1660 .get(&key)1661 .map(|permission| PropertyKeyPermission {1662 key,1663 permission: permission.clone(),1664 })1665 })1666 .collect()1667 })1668 .unwrap_or_else(|| {1669 permissions1670 .into_iter()1671 .map(|(key, permission)| PropertyKeyPermission { key, permission })1672 .collect()1673 });16741675 Ok(key_permissions)1676 }16771678 1679 1680 1681 pub fn toggle_allowlist(1682 collection: &CollectionHandle<T>,1683 sender: &T::CrossAccountId,1684 user: &T::CrossAccountId,1685 allowed: bool,1686 ) -> DispatchResult {1687 collection.check_is_owner_or_admin(sender)?;16881689 16901691 if allowed {1692 <Allowlist<T>>::insert((collection.id, user), true);1693 Self::deposit_event(Event::<T>::AllowListAddressAdded(1694 collection.id,1695 user.clone(),1696 ));1697 } else {1698 <Allowlist<T>>::remove((collection.id, user));1699 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1700 collection.id,1701 user.clone(),1702 ));1703 }17041705 <PalletEvm<T>>::deposit_log(1706 erc::CollectionHelpersEvents::CollectionChanged {1707 collection_id: eth::collection_id_to_address(collection.id),1708 }1709 .to_log(T::ContractAddress::get()),1710 );17111712 Ok(())1713 }17141715 1716 1717 1718 pub fn toggle_admin(1719 collection: &CollectionHandle<T>,1720 sender: &T::CrossAccountId,1721 user: &T::CrossAccountId,1722 admin: bool,1723 ) -> DispatchResult {1724 collection.check_is_internal()?;1725 collection.check_is_owner(sender)?;17261727 let is_admin = <IsAdmin<T>>::get((collection.id, user));1728 if is_admin == admin {1729 if admin {1730 return Ok(());1731 } else {1732 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1733 }1734 }1735 let amount = <AdminAmount<T>>::get(collection.id);17361737 17381739 if admin {1740 let amount = amount1741 .checked_add(1)1742 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1743 ensure!(1744 amount <= Self::collection_admins_limit(),1745 <Error<T>>::CollectionAdminCountExceeded,1746 );17471748 <AdminAmount<T>>::insert(collection.id, amount);1749 <IsAdmin<T>>::insert((collection.id, user), true);17501751 Self::deposit_event(Event::<T>::CollectionAdminAdded(1752 collection.id,1753 user.clone(),1754 ));1755 } else {1756 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1757 <IsAdmin<T>>::remove((collection.id, user));17581759 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1760 collection.id,1761 user.clone(),1762 ));1763 }17641765 <PalletEvm<T>>::deposit_log(1766 erc::CollectionHelpersEvents::CollectionChanged {1767 collection_id: eth::collection_id_to_address(collection.id),1768 }1769 .to_log(T::ContractAddress::get()),1770 );17711772 Ok(())1773 }17741775 1776 pub fn update_limits(1777 user: &T::CrossAccountId,1778 collection: &mut CollectionHandle<T>,1779 new_limit: CollectionLimits,1780 ) -> DispatchResult {1781 collection.check_is_internal()?;1782 collection.check_is_owner_or_admin(user)?;17831784 collection.limits =1785 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17861787 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1788 <PalletEvm<T>>::deposit_log(1789 erc::CollectionHelpersEvents::CollectionChanged {1790 collection_id: eth::collection_id_to_address(collection.id),1791 }1792 .to_log(T::ContractAddress::get()),1793 );17941795 collection.save()1796 }17971798 1799 fn clamp_limits(1800 mode: CollectionMode,1801 old_limit: &CollectionLimits,1802 mut new_limit: CollectionLimits,1803 ) -> Result<CollectionLimits, DispatchError> {1804 let limits = old_limit;1805 limit_default!(old_limit, new_limit,1806 account_token_ownership_limit => ensure!(1807 new_limit <= MAX_TOKEN_OWNERSHIP,1808 <Error<T>>::CollectionLimitBoundsExceeded,1809 ),1810 sponsored_data_size => ensure!(1811 new_limit <= CUSTOM_DATA_LIMIT,1812 <Error<T>>::CollectionLimitBoundsExceeded,1813 ),18141815 sponsored_data_rate_limit => {},1816 token_limit => ensure!(1817 old_limit >= new_limit && new_limit > 0,1818 <Error<T>>::CollectionTokenLimitExceeded1819 ),18201821 sponsor_transfer_timeout(match mode {1822 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1823 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1824 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1825 }) => ensure!(1826 new_limit <= MAX_SPONSOR_TIMEOUT,1827 <Error<T>>::CollectionLimitBoundsExceeded,1828 ),1829 sponsor_approve_timeout => {},1830 owner_can_transfer => ensure!(1831 !limits.owner_can_transfer_instaled() ||1832 old_limit || !new_limit,1833 <Error<T>>::OwnerPermissionsCantBeReverted,1834 ),1835 owner_can_destroy => ensure!(1836 old_limit || !new_limit,1837 <Error<T>>::OwnerPermissionsCantBeReverted,1838 ),1839 transfers_enabled => {},1840 );1841 Ok(new_limit)1842 }18431844 1845 pub fn update_permissions(1846 user: &T::CrossAccountId,1847 collection: &mut CollectionHandle<T>,1848 new_permission: CollectionPermissions,1849 ) -> DispatchResult {1850 collection.check_is_internal()?;1851 collection.check_is_owner_or_admin(user)?;1852 collection.permissions = Self::clamp_permissions(1853 collection.mode.clone(),1854 &collection.permissions,1855 new_permission,1856 )?;18571858 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1859 <PalletEvm<T>>::deposit_log(1860 erc::CollectionHelpersEvents::CollectionChanged {1861 collection_id: eth::collection_id_to_address(collection.id),1862 }1863 .to_log(T::ContractAddress::get()),1864 );18651866 collection.save()1867 }18681869 1870 fn clamp_permissions(1871 _mode: CollectionMode,1872 old_permission: &CollectionPermissions,1873 mut new_permission: CollectionPermissions,1874 ) -> Result<CollectionPermissions, DispatchError> {1875 limit_default_clone!(old_permission, new_permission,1876 access => {},1877 mint_mode => {},1878 nesting => { },1879 );1880 Ok(new_permission)1881 }18821883 1884 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1885 CollectionProperties::<T>::mutate(collection_id, |properties| {1886 properties.recompute_consumed_space();1887 });18881889 Ok(())1890 }1891}189218931894#[macro_export]1895macro_rules! unsupported {1896 ($runtime:path) => {1897 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1898 };1899}190019011902pub trait CommonWeightInfo<CrossAccountId> {1903 1904 fn create_item(data: &CreateItemData) -> Weight {1905 Self::create_multiple_items(from_ref(data))1906 }19071908 1909 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19101911 1912 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19131914 1915 fn burn_item() -> Weight;19161917 1918 1919 1920 fn set_collection_properties(amount: u32) -> Weight;19211922 1923 1924 1925 fn delete_collection_properties(amount: u32) -> Weight {1926 Self::set_collection_properties(amount)1927 }19281929 1930 1931 1932 fn set_token_properties(amount: u32) -> Weight;19331934 1935 1936 1937 fn delete_token_properties(amount: u32) -> Weight {1938 Self::set_token_properties(amount)1939 }19401941 1942 1943 1944 fn set_token_property_permissions(amount: u32) -> Weight;19451946 1947 fn transfer() -> Weight;19481949 1950 fn approve() -> Weight;19511952 1953 fn approve_from() -> Weight;19541955 1956 fn transfer_from() -> Weight;19571958 1959 fn burn_from() -> 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 fn set_collection_properties(2037 &self,2038 sender: T::CrossAccountId,2039 properties: Vec<Property>,2040 ) -> DispatchResultWithPostInfo;20412042 2043 2044 2045 2046 fn delete_collection_properties(2047 &self,2048 sender: &T::CrossAccountId,2049 property_keys: Vec<PropertyKey>,2050 ) -> DispatchResultWithPostInfo;20512052 2053 2054 2055 2056 2057 2058 2059 2060 2061 fn set_token_properties(2062 &self,2063 sender: T::CrossAccountId,2064 token_id: TokenId,2065 properties: Vec<Property>,2066 budget: &dyn Budget,2067 ) -> DispatchResultWithPostInfo;20682069 2070 2071 2072 2073 2074 2075 2076 2077 2078 fn delete_token_properties(2079 &self,2080 sender: T::CrossAccountId,2081 token_id: TokenId,2082 property_keys: Vec<PropertyKey>,2083 budget: &dyn Budget,2084 ) -> DispatchResultWithPostInfo;20852086 2087 2088 2089 fn get_token_properties_raw(&self, token_id: TokenId) -> Option<TokenProperties>;20902091 2092 2093 2094 2095 fn set_token_properties_raw(&self, token_id: TokenId, map: TokenProperties);20962097 2098 2099 2100 2101 2102 2103 fn set_token_property_permissions(2104 &self,2105 sender: &T::CrossAccountId,2106 property_permissions: Vec<PropertyKeyPermission>,2107 ) -> DispatchResultWithPostInfo;21082109 2110 2111 2112 2113 2114 2115 2116 fn transfer(2117 &self,2118 sender: T::CrossAccountId,2119 to: T::CrossAccountId,2120 token: TokenId,2121 amount: u128,2122 budget: &dyn Budget,2123 ) -> DispatchResultWithPostInfo;21242125 2126 2127 2128 2129 2130 2131 fn approve(2132 &self,2133 sender: T::CrossAccountId,2134 spender: T::CrossAccountId,2135 token: TokenId,2136 amount: u128,2137 ) -> DispatchResultWithPostInfo;21382139 2140 2141 2142 2143 2144 2145 2146 fn approve_from(2147 &self,2148 sender: T::CrossAccountId,2149 from: T::CrossAccountId,2150 to: T::CrossAccountId,2151 token: TokenId,2152 amount: u128,2153 ) -> DispatchResultWithPostInfo;21542155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 fn transfer_from(2166 &self,2167 sender: T::CrossAccountId,2168 from: T::CrossAccountId,2169 to: T::CrossAccountId,2170 token: TokenId,2171 amount: u128,2172 budget: &dyn Budget,2173 ) -> DispatchResultWithPostInfo;21742175 2176 2177 2178 2179 2180 2181 2182 2183 2184 fn burn_from(2185 &self,2186 sender: T::CrossAccountId,2187 from: T::CrossAccountId,2188 token: TokenId,2189 amount: u128,2190 budget: &dyn Budget,2191 ) -> DispatchResultWithPostInfo;21922193 2194 2195 2196 2197 2198 2199 fn check_nesting(2200 &self,2201 sender: T::CrossAccountId,2202 from: (CollectionId, TokenId),2203 under: TokenId,2204 budget: &dyn Budget,2205 ) -> DispatchResult;22062207 2208 2209 2210 2211 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22122213 2214 2215 2216 2217 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22182219 2220 2221 2222 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22232224 2225 fn collection_tokens(&self) -> Vec<TokenId>;22262227 2228 2229 2230 fn token_exists(&self, token: TokenId) -> bool;22312232 2233 fn last_token_id(&self) -> TokenId;22342235 2236 2237 2238 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22392240 2241 2242 2243 2244 2245 fn check_token_indirect_owner(2246 &self,2247 token: TokenId,2248 maybe_owner: &T::CrossAccountId,2249 nesting_budget: &dyn Budget,2250 ) -> Result<bool, DispatchError>;22512252 2253 2254 2255 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22562257 2258 2259 2260 2261 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22622263 2264 2265 2266 2267 2268 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22692270 2271 fn total_supply(&self) -> u32;22722273 2274 2275 2276 fn account_balance(&self, account: T::CrossAccountId) -> u32;22772278 2279 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22802281 2282 fn total_pieces(&self, token: TokenId) -> Option<u128>;22832284 2285 2286 2287 2288 2289 fn allowance(2290 &self,2291 sender: T::CrossAccountId,2292 spender: T::CrossAccountId,2293 token: TokenId,2294 ) -> u128;22952296 2297 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22982299 2300 2301 2302 2303 fn set_allowance_for_all(2304 &self,2305 owner: T::CrossAccountId,2306 operator: T::CrossAccountId,2307 approve: bool,2308 ) -> DispatchResultWithPostInfo;23092310 2311 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23122313 2314 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2315}231623172318pub trait RefungibleExtensions<T>2319where2320 T: Config,2321{2322 2323 2324 2325 2326 2327 2328 2329 fn repartition(2330 &self,2331 sender: &T::CrossAccountId,2332 token: TokenId,2333 amount: u128,2334 ) -> DispatchResultWithPostInfo;2335}23362337233823392340pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2341 let post_info = PostDispatchInfo {2342 actual_weight: Some(weight),2343 pays_fee: Pays::Yes,2344 };2345 match res {2346 Ok(()) => Ok(post_info),2347 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2348 }2349}23502351impl<T: Config> From<PropertiesError> for Error<T> {2352 fn from(error: PropertiesError) -> Self {2353 match error {2354 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2355 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2356 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2357 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2358 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2359 }2360 }2361}23622363236423652366236723682369pub struct PropertyWriter<'a, WriterVariant, T, Handle> {2370 collection: &'a Handle,2371 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2372 _phantom: PhantomData<(T, WriterVariant)>,2373}23742375impl<'a, T, Handle, WriterVariant> PropertyWriter<'a, WriterVariant, T, Handle>2376where2377 T: Config,2378 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2379{2380 fn internal_write_token_properties(2381 &mut self,2382 token_id: TokenId,2383 mut token_lazy_info: PropertyWriterLazyTokenInfo,2384 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2385 log: evm_coder::ethereum::Log,2386 ) -> DispatchResult {2387 for (key, value) in properties_updates {2388 let permission = self2389 .collection_lazy_info2390 .property_permissions2391 .value()2392 .get(&key)2393 .cloned()2394 .unwrap_or_else(PropertyPermission::none);23952396 match permission {2397 PropertyPermission { mutable: false, .. }2398 if token_lazy_info2399 .stored_properties2400 .value()2401 .get(&key)2402 .is_some() =>2403 {2404 return Err(<Error<T>>::NoPermission.into());2405 }24062407 PropertyPermission {2408 collection_admin,2409 token_owner,2410 ..2411 } => check_token_permissions::<T>(2412 collection_admin,2413 token_owner,2414 &mut self.collection_lazy_info.is_collection_admin,2415 &mut token_lazy_info.is_token_owner,2416 &mut token_lazy_info.is_token_exist,2417 )?,2418 }24192420 match value {2421 Some(value) => {2422 token_lazy_info2423 .stored_properties2424 .value_mut()2425 .try_set(key.clone(), value)2426 .map_err(<Error<T>>::from)?;24272428 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2429 self.collection.id,2430 token_id,2431 key,2432 ));2433 }2434 None => {2435 token_lazy_info2436 .stored_properties2437 .value_mut()2438 .remove(&key)2439 .map_err(<Error<T>>::from)?;24402441 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2442 self.collection.id,2443 token_id,2444 key,2445 ));2446 }2447 }2448 }24492450 let properties_changed = token_lazy_info.stored_properties.has_value();2451 if properties_changed {2452 <PalletEvm<T>>::deposit_log(log);24532454 self.collection2455 .set_token_properties_raw(token_id, token_lazy_info.stored_properties.into_inner());2456 }24572458 Ok(())2459 }2460}24612462246324642465pub struct PropertyWriterLazyCollectionInfo<'a> {2466 is_collection_admin: LazyValue<'a, bool>,2467 property_permissions: LazyValue<'a, PropertiesPermissionMap>,2468}2469247024712472pub struct PropertyWriterLazyTokenInfo<'a> {2473 is_token_exist: LazyValue<'a, bool>,2474 is_token_owner: LazyValue<'a, Result<bool, DispatchError>>,2475 stored_properties: LazyValue<'a, TokenProperties>,2476}24772478impl<'a> PropertyWriterLazyTokenInfo<'a> {2479 2480 pub fn new(2481 check_token_exist: impl FnOnce() -> bool + 'a,2482 check_token_owner: impl FnOnce() -> Result<bool, DispatchError> + 'a,2483 get_token_properties: impl FnOnce() -> TokenProperties + 'a,2484 ) -> Self {2485 Self {2486 is_token_exist: LazyValue::new(check_token_exist),2487 is_token_owner: LazyValue::new(check_token_owner),2488 stored_properties: LazyValue::new(get_token_properties),2489 }2490 }2491}2492249324942495pub struct NewTokenPropertyWriter<T>(PhantomData<T>);2496impl<T: Config> NewTokenPropertyWriter<T> {2497 2498 pub fn new<'a, Handle>(2499 collection: &'a Handle,2500 sender: &'a T::CrossAccountId,2501 ) -> PropertyWriter<'a, Self, T, Handle>2502 where2503 T: Config,2504 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2505 {2506 PropertyWriter {2507 collection,2508 collection_lazy_info: PropertyWriterLazyCollectionInfo {2509 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2510 property_permissions: LazyValue::new(|| {2511 <Pallet<T>>::property_permissions(collection.id)2512 }),2513 },2514 _phantom: PhantomData,2515 }2516 }2517}25182519impl<'a, T, Handle> PropertyWriter<'a, NewTokenPropertyWriter<T>, T, Handle>2520where2521 T: Config,2522 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2523{2524 2525 pub fn write_token_properties(2526 &mut self,2527 mint_target_is_sender: bool,2528 token_id: TokenId,2529 properties_updates: impl Iterator<Item = Property>,2530 log: evm_coder::ethereum::Log,2531 ) -> DispatchResult {2532 let check_token_exist = || {2533 debug_assert!(self.collection.token_exists(token_id));2534 true2535 };25362537 let check_token_owner = || Ok(mint_target_is_sender);25382539 let get_token_properties = || {2540 debug_assert!(self.collection.get_token_properties_raw(token_id).is_none());2541 TokenProperties::new()2542 };25432544 self.internal_write_token_properties(2545 token_id,2546 PropertyWriterLazyTokenInfo::new(2547 check_token_exist,2548 check_token_owner,2549 get_token_properties,2550 ),2551 properties_updates.map(|p| (p.key, Some(p.value))),2552 log,2553 )2554 }2555}2556255725582559pub struct ExistingTokenPropertyWriter<T>(PhantomData<T>);2560impl<T: Config> ExistingTokenPropertyWriter<T> {2561 2562 pub fn new<'a, Handle>(2563 collection: &'a Handle,2564 sender: &'a T::CrossAccountId,2565 ) -> PropertyWriter<'a, Self, T, Handle>2566 where2567 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2568 {2569 PropertyWriter {2570 collection,2571 collection_lazy_info: PropertyWriterLazyCollectionInfo {2572 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2573 property_permissions: LazyValue::new(|| {2574 <Pallet<T>>::property_permissions(collection.id)2575 }),2576 },2577 _phantom: PhantomData,2578 }2579 }2580}25812582impl<'a, T, Handle> PropertyWriter<'a, ExistingTokenPropertyWriter<T>, T, Handle>2583where2584 T: Config,2585 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2586{2587 2588 pub fn write_token_properties(2589 &mut self,2590 sender: &T::CrossAccountId,2591 token_id: TokenId,2592 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2593 nesting_budget: &dyn Budget,2594 log: evm_coder::ethereum::Log,2595 ) -> DispatchResult {2596 let check_token_exist = || self.collection.token_exists(token_id);2597 let check_token_owner = || {2598 self.collection2599 .check_token_indirect_owner(token_id, sender, nesting_budget)2600 };2601 let get_token_properties = || {2602 self.collection2603 .get_token_properties_raw(token_id)2604 .unwrap_or_default()2605 };26062607 self.internal_write_token_properties(2608 token_id,2609 PropertyWriterLazyTokenInfo::new(2610 check_token_exist,2611 check_token_owner,2612 get_token_properties,2613 ),2614 properties_updates,2615 log,2616 )2617 }2618}2619262026212622#[cfg(feature = "runtime-benchmarks")]2623pub struct BenchmarkPropertyWriter<T>(PhantomData<T>);26242625#[cfg(feature = "runtime-benchmarks")]2626impl<T: Config> BenchmarkPropertyWriter<T> {2627 2628 pub fn new<'a, Handle>(2629 collection: &'a Handle,2630 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2631 ) -> PropertyWriter<'a, Self, T, Handle>2632 where2633 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2634 {2635 PropertyWriter {2636 collection,2637 collection_lazy_info,2638 _phantom: PhantomData,2639 }2640 }26412642 2643 pub fn load_collection_info<Handle>(2644 collection_handle: &Handle,2645 sender: &T::CrossAccountId,2646 ) -> PropertyWriterLazyCollectionInfo<'static>2647 where2648 Handle: Deref<Target = CollectionHandle<T>>,2649 {2650 let is_collection_admin = collection_handle.is_owner_or_admin(sender);2651 let property_permissions = <Pallet<T>>::property_permissions(collection_handle.id);26522653 PropertyWriterLazyCollectionInfo {2654 is_collection_admin: LazyValue::new(move || is_collection_admin),2655 property_permissions: LazyValue::new(move || property_permissions),2656 }2657 }26582659 2660 pub fn load_token_properties<Handle>(2661 collection: &Handle,2662 token_id: TokenId,2663 ) -> PropertyWriterLazyTokenInfo2664 where2665 Handle: CommonCollectionOperations<T>,2666 {2667 let stored_properties = collection2668 .get_token_properties_raw(token_id)2669 .unwrap_or_default();26702671 PropertyWriterLazyTokenInfo {2672 is_token_exist: LazyValue::new(|| true),2673 is_token_owner: LazyValue::new(|| Ok(true)),2674 stored_properties: LazyValue::new(move || stored_properties),2675 }2676 }2677}26782679#[cfg(feature = "runtime-benchmarks")]2680impl<'a, T, Handle> PropertyWriter<'a, BenchmarkPropertyWriter<T>, T, Handle>2681where2682 T: Config,2683 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2684{2685 2686 pub fn write_token_properties(2687 &mut self,2688 token_id: TokenId,2689 properties_updates: impl Iterator<Item = Property>,2690 log: evm_coder::ethereum::Log,2691 ) -> DispatchResult {2692 let check_token_exist = || true;2693 let check_token_owner = || Ok(true);2694 let get_token_properties = || TokenProperties::new();26952696 self.internal_write_token_properties(2697 token_id,2698 PropertyWriterLazyTokenInfo::new(2699 check_token_exist,2700 check_token_owner,2701 get_token_properties,2702 ),2703 properties_updates.map(|p| (p.key, Some(p.value))),2704 log,2705 )2706 }2707}270827092710271127122713pub fn write_token_properties_total_weight<T: Config, I: Fn(u32) -> Weight>(2714 properties_nums: impl Iterator<Item = u32>,2715 per_token_weight: I,2716) -> Weight {2717 let mut weight = properties_nums2718 .filter_map(|properties_num| {2719 if properties_num > 0 {2720 Some(per_token_weight(properties_num))2721 } else {2722 None2723 }2724 })2725 .fold(Weight::zero(), |a, b| a.saturating_add(b));27262727 if !weight.is_zero() {2728 2729 2730 27312732 weight = weight.saturating_add(<SelfWeightOf<T>>::property_writer_load_collection_info());2733 }27342735 weight2736}27372738#[cfg(any(feature = "tests", test))]2739#[allow(missing_docs)]2740pub mod tests {2741 use crate::{Config, DispatchError, DispatchResult, LazyValue};27422743 const fn to_bool(u: u8) -> bool {2744 u != 02745 }27462747 #[derive(Debug)]2748 pub struct TestCase {2749 pub collection_admin: bool,2750 pub is_collection_admin: bool,2751 pub token_owner: bool,2752 pub is_token_owner: bool,2753 pub no_permission: bool,2754 }27552756 impl TestCase {2757 const fn new(2758 collection_admin: u8,2759 is_collection_admin: u8,2760 token_owner: u8,2761 is_token_owner: u8,2762 no_permission: u8,2763 ) -> Self {2764 Self {2765 collection_admin: to_bool(collection_admin),2766 is_collection_admin: to_bool(is_collection_admin),2767 token_owner: to_bool(token_owner),2768 is_token_owner: to_bool(is_token_owner),2769 no_permission: to_bool(no_permission),2770 }2771 }2772 }27732774 #[rustfmt::skip]2775 pub const TABLE: [TestCase; 16] = [2776 2777 2778 2779 2780 2781 TestCase::new(0, 0, 0, 0, 1),2782 TestCase::new(0, 0, 0, 1, 1),2783 TestCase::new(0, 0, 1, 0, 1),2784 TestCase::new(0, 0, 1, 1, 0),2785 TestCase::new(0, 1, 0, 0, 1),2786 TestCase::new(0, 1, 0, 1, 1),2787 TestCase::new(0, 1, 1, 0, 1),2788 TestCase::new(0, 1, 1, 1, 0),2789 TestCase::new(1, 0, 0, 0, 1),2790 TestCase::new(1, 0, 0, 1, 1),2791 TestCase::new(1, 0, 1, 0, 1),2792 TestCase::new(1, 0, 1, 1, 0),2793 TestCase::new(1, 1, 0, 0, 0),2794 TestCase::new(1, 1, 0, 1, 0),2795 TestCase::new(1, 1, 1, 0, 0),2796 TestCase::new(1, 1, 1, 1, 0),2797 ];27982799 pub fn check_token_permissions<T: Config>(2800 collection_admin_permitted: bool,2801 token_owner_permitted: bool,2802 is_collection_admin: &mut LazyValue<bool>,2803 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>>,2804 check_token_existence: &mut LazyValue<bool>,2805 ) -> DispatchResult {2806 crate::check_token_permissions::<T>(2807 collection_admin_permitted,2808 token_owner_permitted,2809 is_collection_admin,2810 check_token_ownership,2811 check_token_existence,2812 )2813 }2814}