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,786787 788 FungibleItemsHaveNoId,789 }790791 792 #[pallet::storage]793 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;794795 796 #[pallet::storage]797 pub type DestroyedCollectionCount<T> =798 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;799800 801 #[pallet::storage]802 pub type CollectionById<T> = StorageMap<803 Hasher = Blake2_128Concat,804 Key = CollectionId,805 Value = Collection<<T as frame_system::Config>::AccountId>,806 QueryKind = OptionQuery,807 >;808809 810 #[pallet::storage]811 #[pallet::getter(fn collection_properties)]812 pub type CollectionProperties<T> = StorageMap<813 Hasher = Blake2_128Concat,814 Key = CollectionId,815 Value = CollectionPropertiesT,816 QueryKind = ValueQuery,817 >;818819 820 #[pallet::storage]821 #[pallet::getter(fn property_permissions)]822 pub type CollectionPropertyPermissions<T> = StorageMap<823 Hasher = Blake2_128Concat,824 Key = CollectionId,825 Value = PropertiesPermissionMap,826 QueryKind = ValueQuery,827 >;828829 830 #[pallet::storage]831 pub type AdminAmount<T> = StorageMap<832 Hasher = Blake2_128Concat,833 Key = CollectionId,834 Value = u32,835 QueryKind = ValueQuery,836 >;837838 839 #[pallet::storage]840 pub type IsAdmin<T: Config> = StorageNMap<841 Key = (842 Key<Blake2_128Concat, CollectionId>,843 Key<Blake2_128Concat, T::CrossAccountId>,844 ),845 Value = bool,846 QueryKind = ValueQuery,847 >;848849 850 #[pallet::storage]851 pub type Allowlist<T: Config> = StorageNMap<852 Key = (853 Key<Blake2_128Concat, CollectionId>,854 Key<Blake2_128Concat, T::CrossAccountId>,855 ),856 Value = bool,857 QueryKind = ValueQuery,858 >;859860 861 #[pallet::storage]862 pub type DummyStorageValue<T: Config> = StorageValue<863 Value = (864 CollectionStats,865 CollectionId,866 TokenId,867 TokenChild,868 PhantomType<(869 TokenData<T::CrossAccountId>,870 RpcCollection<T::AccountId>,871 872 PovInfo,873 )>,874 ),875 QueryKind = OptionQuery,876 >;877}878879enum LazyValueState<'a, T> {880 Pending(Box<dyn FnOnce() -> T + 'a>),881 InProgress,882 Computed(T),883}884885886pub struct LazyValue<'a, T> {887 state: LazyValueState<'a, T>,888}889890impl<'a, T> LazyValue<'a, T> {891 892 pub fn new(f: impl FnOnce() -> T + 'a) -> Self {893 Self {894 state: LazyValueState::Pending(Box::new(f)),895 }896 }897898 899 pub fn value(&mut self) -> &T {900 self.force_value();901 self.value_mut()902 }903904 905 pub fn value_mut(&mut self) -> &mut T {906 self.force_value();907908 if let LazyValueState::Computed(value) = &mut self.state {909 value910 } else {911 unreachable!()912 }913 }914915 fn into_inner(mut self) -> T {916 self.force_value();917 if let LazyValueState::Computed(value) = self.state {918 value919 } else {920 unreachable!()921 }922 }923924 925 pub fn has_value(&self) -> bool {926 matches!(self.state, LazyValueState::Computed(_))927 }928929 fn force_value(&mut self) {930 use LazyValueState::*;931932 if self.has_value() {933 return;934 }935936 match sp_std::mem::replace(&mut self.state, InProgress) {937 Pending(f) => self.state = Computed(f()),938 _ => panic!("recursion isn't supported"),939 }940 }941}942943fn check_token_permissions<T: Config>(944 collection_admin_permitted: bool,945 token_owner_permitted: bool,946 is_collection_admin: &mut LazyValue<bool>,947 is_token_owner: &mut LazyValue<Result<bool, DispatchError>>,948 is_token_exist: &mut LazyValue<bool>,949) -> DispatchResult {950 if !(collection_admin_permitted && *is_collection_admin.value()951 || token_owner_permitted && (*is_token_owner.value())?)952 {953 fail!(<Error<T>>::NoPermission);954 }955956 let token_exist_due_to_owner_check_success =957 is_token_owner.has_value() && (*is_token_owner.value())?;958959 960 961 if !token_exist_due_to_owner_check_success {962 963 964 if !is_token_exist.value() {965 fail!(<Error<T>>::TokenNotFound);966 }967 }968969 Ok(())970}971972impl<T: Config> Pallet<T> {973 974 975 976 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {977 ensure!(978 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,979 <Error<T>>::AddressIsZero980 );981 Ok(())982 }983984 985 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {986 <IsAdmin<T>>::iter_prefix((collection,))987 .map(|(a, _)| a)988 .collect()989 }990991 992 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {993 <Allowlist<T>>::iter_prefix((collection,))994 .map(|(a, _)| a)995 .collect()996 }997998 999 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {1000 <Allowlist<T>>::get((collection, user))1001 }10021003 1004 pub fn collection_stats() -> CollectionStats {1005 let created = <CreatedCollectionCount<T>>::get();1006 let destroyed = <DestroyedCollectionCount<T>>::get();1007 CollectionStats {1008 created: created.0,1009 destroyed: destroyed.0,1010 alive: created.0 - destroyed.0,1011 }1012 }10131014 1015 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {1016 let collection = <CollectionById<T>>::get(collection)?;1017 let limits = collection.limits;1018 let effective_limits = CollectionLimits {1019 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),1020 sponsored_data_size: Some(limits.sponsored_data_size()),1021 sponsored_data_rate_limit: Some(1022 limits1023 .sponsored_data_rate_limit1024 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),1025 ),1026 token_limit: Some(limits.token_limit()),1027 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(1028 match collection.mode {1029 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1030 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1031 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1032 },1033 )),1034 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),1035 owner_can_transfer: Some(limits.owner_can_transfer()),1036 owner_can_destroy: Some(limits.owner_can_destroy()),1037 transfers_enabled: Some(limits.transfers_enabled()),1038 };10391040 Some(effective_limits)1041 }10421043 1044 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1045 let Collection {1046 name,1047 description,1048 owner,1049 mode,1050 token_prefix,1051 sponsorship,1052 limits,1053 permissions,1054 flags,1055 } = <CollectionById<T>>::get(collection)?;10561057 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1058 .into_iter()1059 .map(|(key, permission)| PropertyKeyPermission { key, permission })1060 .collect();10611062 let properties = <CollectionProperties<T>>::get(collection)1063 .into_iter()1064 .map(|(key, value)| Property { key, value })1065 .collect();10661067 let permissions = CollectionPermissions {1068 access: Some(permissions.access()),1069 mint_mode: Some(permissions.mint_mode()),1070 nesting: Some(permissions.nesting().clone()),1071 };10721073 Some(RpcCollection {1074 name: name.into_inner(),1075 description: description.into_inner(),1076 owner,1077 mode,1078 token_prefix: token_prefix.into_inner(),1079 sponsorship,1080 limits,1081 permissions,1082 token_property_permissions,1083 properties,1084 read_only: flags.external,10851086 flags: RpcCollectionFlags {1087 foreign: flags.foreign,1088 erc721metadata: flags.erc721metadata,1089 },1090 })1091 }1092}10931094macro_rules! limit_default {1095 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1096 $(1097 if let Some($new) = $new.$field {1098 let $old = $old.$field($($arg)?);1099 let _ = $new;1100 let _ = $old;1101 $check1102 } else {1103 $new.$field = $old.$field1104 }1105 )*1106 }};1107}1108macro_rules! limit_default_clone {1109 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1110 $(1111 if let Some($new) = $new.$field.clone() {1112 let $old = $old.$field($($arg)?);1113 let _ = $new;1114 let _ = $old;1115 $check1116 } else {1117 $new.$field = $old.$field.clone()1118 }1119 )*1120 }};1121}11221123impl<T: Config> Pallet<T> {1124 1125 1126 1127 1128 1129 pub fn init_collection(1130 owner: T::CrossAccountId,1131 payer: T::CrossAccountId,1132 data: CreateCollectionData<T::CrossAccountId>,1133 ) -> Result<CollectionId, DispatchError> {1134 ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);1135 Self::init_collection_internal(owner, payer, data)1136 }11371138 1139 pub fn init_foreign_collection(1140 owner: T::CrossAccountId,1141 payer: T::CrossAccountId,1142 mut data: CreateCollectionData<T::CrossAccountId>,1143 ) -> Result<CollectionId, DispatchError> {1144 data.flags.foreign = true;1145 let id = Self::init_collection_internal(owner, payer, data)?;1146 Ok(id)1147 }11481149 fn init_collection_internal(1150 owner: T::CrossAccountId,1151 payer: T::CrossAccountId,1152 data: CreateCollectionData<T::CrossAccountId>,1153 ) -> Result<CollectionId, DispatchError> {1154 {1155 ensure!(1156 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1157 Error::<T>::CollectionTokenPrefixLimitExceeded1158 );1159 }11601161 let created_count = <CreatedCollectionCount<T>>::get()1162 .01163 .checked_add(1)1164 .ok_or(ArithmeticError::Overflow)?;1165 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1166 let id = CollectionId(created_count);11671168 1169 ensure!(1170 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1171 <Error<T>>::TotalCollectionsLimitExceeded1172 );11731174 11751176 let collection = Collection {1177 owner: owner.as_sub().clone(),1178 name: data.name,1179 mode: data.mode.clone(),1180 description: data.description,1181 token_prefix: data.token_prefix,1182 sponsorship: data1183 .pending_sponsor1184 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1185 .unwrap_or_default(),1186 limits: data1187 .limits1188 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1189 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1190 permissions: data1191 .permissions1192 .map(|permissions| {1193 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1194 })1195 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1196 flags: data.flags,1197 };11981199 let mut collection_properties = CollectionPropertiesT::new();1200 collection_properties1201 .try_set_from_iter(data.properties.into_iter())1202 .map_err(<Error<T>>::from)?;12031204 CollectionProperties::<T>::insert(id, collection_properties);12051206 let mut token_props_permissions = PropertiesPermissionMap::new();1207 token_props_permissions1208 .try_set_from_iter(data.token_property_permissions.into_iter())1209 .map_err(<Error<T>>::from)?;12101211 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);12121213 let mut admin_amount = 0u32;1214 for admin in data.admin_list.iter() {1215 if !<IsAdmin<T>>::get((id, admin)) {1216 <IsAdmin<T>>::insert((id, admin), true);1217 admin_amount = admin_amount1218 .checked_add(1)1219 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1220 }1221 }1222 ensure!(1223 admin_amount <= Self::collection_admins_limit(),1224 <Error<T>>::CollectionAdminCountExceeded,1225 );1226 <AdminAmount<T>>::insert(id, admin_amount);12271228 1229 {1230 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1231 imbalance.subsume(<T as Config>::Currency::deposit(1232 &T::TreasuryAccountId::get(),1233 T::CollectionCreationPrice::get(),1234 Precision::Exact,1235 )?);1236 let credit =1237 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1238 .map_err(|_| Error::<T>::NotSufficientFounds)?;12391240 debug_assert!(credit.peek().is_zero())1241 }12421243 <CreatedCollectionCount<T>>::put(created_count);1244 <Pallet<T>>::deposit_event(Event::CollectionCreated(1245 id,1246 data.mode.id(),1247 owner.as_sub().clone(),1248 ));1249 <PalletEvm<T>>::deposit_log(1250 erc::CollectionHelpersEvents::CollectionCreated {1251 owner: *owner.as_eth(),1252 collection_id: eth::collection_id_to_address(id),1253 }1254 .to_log(T::ContractAddress::get()),1255 );1256 <CollectionById<T>>::insert(id, collection);1257 Ok(id)1258 }12591260 1261 1262 1263 1264 pub fn destroy_collection(1265 collection: CollectionHandle<T>,1266 sender: &T::CrossAccountId,1267 ) -> DispatchResult {1268 ensure!(1269 collection.limits.owner_can_destroy(),1270 <Error<T>>::NoPermission,1271 );1272 collection.check_is_owner(sender)?;12731274 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1275 .01276 .checked_add(1)1277 .ok_or(ArithmeticError::Overflow)?;12781279 12801281 <DestroyedCollectionCount<T>>::put(destroyed_collections);1282 <CollectionById<T>>::remove(collection.id);1283 <AdminAmount<T>>::remove(collection.id);1284 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1285 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1286 <CollectionProperties<T>>::remove(collection.id);12871288 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12891290 <PalletEvm<T>>::deposit_log(1291 erc::CollectionHelpersEvents::CollectionDestroyed {1292 collection_id: eth::collection_id_to_address(collection.id),1293 }1294 .to_log(T::ContractAddress::get()),1295 );1296 Ok(())1297 }12981299 1300 1301 1302 1303 1304 1305 1306 1307 #[transactional]1308 fn modify_collection_properties(1309 collection: &CollectionHandle<T>,1310 sender: &T::CrossAccountId,1311 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1312 ) -> DispatchResult {1313 collection.check_is_owner_or_admin(sender)?;13141315 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);13161317 for (key, value) in properties_updates {1318 match value {1319 Some(value) => {1320 stored_properties1321 .try_set(key.clone(), value)1322 .map_err(<Error<T>>::from)?;13231324 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1325 <PalletEvm<T>>::deposit_log(1326 erc::CollectionHelpersEvents::CollectionChanged {1327 collection_id: eth::collection_id_to_address(collection.id),1328 }1329 .to_log(T::ContractAddress::get()),1330 );1331 }1332 None => {1333 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13341335 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1336 <PalletEvm<T>>::deposit_log(1337 erc::CollectionHelpersEvents::CollectionChanged {1338 collection_id: eth::collection_id_to_address(collection.id),1339 }1340 .to_log(T::ContractAddress::get()),1341 );1342 }1343 }1344 }13451346 <CollectionProperties<T>>::set(collection.id, stored_properties);13471348 Ok(())1349 }13501351 1352 1353 1354 1355 1356 1357 pub fn set_allowance_for_all(1358 collection: &CollectionHandle<T>,1359 owner: &T::CrossAccountId,1360 operator: &T::CrossAccountId,1361 approve: bool,1362 set_allowance: impl FnOnce(),1363 log: evm_coder::ethereum::Log,1364 ) -> DispatchResult {1365 if collection.permissions.access() == AccessMode::AllowList {1366 collection.check_allowlist(owner)?;1367 collection.check_allowlist(operator)?;1368 }13691370 Self::ensure_correct_receiver(operator)?;13711372 set_allowance();13731374 <PalletEvm<T>>::deposit_log(log);1375 Self::deposit_event(Event::ApprovedForAll(1376 collection.id,1377 owner.clone(),1378 operator.clone(),1379 approve,1380 ));1381 Ok(())1382 }13831384 1385 1386 1387 1388 1389 pub fn set_collection_property(1390 collection: &CollectionHandle<T>,1391 sender: &T::CrossAccountId,1392 property: Property,1393 ) -> DispatchResult {1394 Self::set_collection_properties(collection, sender, [property].into_iter())1395 }13961397 1398 1399 1400 1401 1402 1403 pub fn set_scoped_collection_property(1404 collection_id: CollectionId,1405 scope: PropertyScope,1406 property: Property,1407 ) -> DispatchResult {1408 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1409 properties.try_scoped_set(scope, property.key, property.value)1410 })1411 .map_err(<Error<T>>::from)?;14121413 Ok(())1414 }14151416 1417 1418 1419 1420 1421 1422 pub fn set_scoped_collection_properties(1423 collection_id: CollectionId,1424 scope: PropertyScope,1425 properties: impl Iterator<Item = Property>,1426 ) -> DispatchResult {1427 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1428 stored_properties.try_scoped_set_from_iter(scope, properties)1429 })1430 .map_err(<Error<T>>::from)?;14311432 Ok(())1433 }14341435 1436 1437 1438 1439 1440 pub fn set_collection_properties(1441 collection: &CollectionHandle<T>,1442 sender: &T::CrossAccountId,1443 properties: impl Iterator<Item = Property>,1444 ) -> DispatchResult {1445 Self::modify_collection_properties(1446 collection,1447 sender,1448 properties.map(|property| (property.key, Some(property.value))),1449 )1450 }14511452 1453 1454 1455 1456 1457 pub fn delete_collection_property(1458 collection: &CollectionHandle<T>,1459 sender: &T::CrossAccountId,1460 property_key: PropertyKey,1461 ) -> DispatchResult {1462 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1463 }14641465 1466 1467 1468 1469 1470 pub fn delete_collection_properties(1471 collection: &CollectionHandle<T>,1472 sender: &T::CrossAccountId,1473 property_keys: impl Iterator<Item = PropertyKey>,1474 ) -> DispatchResult {1475 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1476 }14771478 1479 1480 1481 1482 1483 1484 pub fn set_property_permission_unchecked(1485 collection: CollectionId,1486 property_permission: PropertyKeyPermission,1487 ) -> DispatchResult {1488 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1489 permissions.try_set(property_permission.key, property_permission.permission)1490 })1491 .map_err(<Error<T>>::from)?;1492 Ok(())1493 }14941495 1496 1497 1498 1499 1500 pub fn set_property_permission(1501 collection: &CollectionHandle<T>,1502 sender: &T::CrossAccountId,1503 property_permission: PropertyKeyPermission,1504 ) -> DispatchResult {1505 Self::set_scoped_property_permission(1506 collection,1507 sender,1508 PropertyScope::None,1509 property_permission,1510 )1511 }15121513 1514 1515 1516 1517 1518 1519 pub fn set_scoped_property_permission(1520 collection: &CollectionHandle<T>,1521 sender: &T::CrossAccountId,1522 scope: PropertyScope,1523 property_permission: PropertyKeyPermission,1524 ) -> DispatchResult {1525 collection.check_is_owner_or_admin(sender)?;15261527 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1528 let current_permission = all_permissions.get(&property_permission.key);1529 if matches![1530 current_permission,1531 Some(PropertyPermission { mutable: false, .. })1532 ] {1533 return Err(<Error<T>>::NoPermission.into());1534 }15351536 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1537 let property_permission = property_permission.clone();1538 permissions.try_scoped_set(1539 scope,1540 property_permission.key,1541 property_permission.permission,1542 )1543 })1544 .map_err(<Error<T>>::from)?;15451546 Self::deposit_event(Event::PropertyPermissionSet(1547 collection.id,1548 property_permission.key,1549 ));1550 <PalletEvm<T>>::deposit_log(1551 erc::CollectionHelpersEvents::CollectionChanged {1552 collection_id: eth::collection_id_to_address(collection.id),1553 }1554 .to_log(T::ContractAddress::get()),1555 );15561557 Ok(())1558 }15591560 1561 1562 1563 1564 1565 #[transactional]1566 pub fn set_token_property_permissions(1567 collection: &CollectionHandle<T>,1568 sender: &T::CrossAccountId,1569 property_permissions: Vec<PropertyKeyPermission>,1570 ) -> DispatchResult {1571 Self::set_scoped_token_property_permissions(1572 collection,1573 sender,1574 PropertyScope::None,1575 property_permissions,1576 )1577 }15781579 1580 1581 1582 1583 1584 1585 #[transactional]1586 pub fn set_scoped_token_property_permissions(1587 collection: &CollectionHandle<T>,1588 sender: &T::CrossAccountId,1589 scope: PropertyScope,1590 property_permissions: Vec<PropertyKeyPermission>,1591 ) -> DispatchResult {1592 for prop_pemission in property_permissions {1593 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1594 }15951596 Ok(())1597 }15981599 1600 pub fn get_collection_property(1601 collection_id: CollectionId,1602 key: &PropertyKey,1603 ) -> Option<PropertyValue> {1604 Self::collection_properties(collection_id).get(key).cloned()1605 }16061607 1608 pub fn bytes_keys_to_property_keys(1609 keys: Vec<Vec<u8>>,1610 ) -> Result<Vec<PropertyKey>, DispatchError> {1611 keys.into_iter()1612 .map(|key| -> Result<PropertyKey, DispatchError> {1613 key.try_into()1614 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1615 })1616 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1617 }16181619 1620 pub fn filter_collection_properties(1621 collection_id: CollectionId,1622 keys: Option<Vec<PropertyKey>>,1623 ) -> Result<Vec<Property>, DispatchError> {1624 let properties = Self::collection_properties(collection_id);16251626 let properties = keys1627 .map(|keys| {1628 keys.into_iter()1629 .filter_map(|key| {1630 properties.get(&key).map(|value| Property {1631 key,1632 value: value.clone(),1633 })1634 })1635 .collect()1636 })1637 .unwrap_or_else(|| {1638 properties1639 .into_iter()1640 .map(|(key, value)| Property { key, value })1641 .collect()1642 });16431644 Ok(properties)1645 }16461647 1648 pub fn filter_property_permissions(1649 collection_id: CollectionId,1650 keys: Option<Vec<PropertyKey>>,1651 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1652 let permissions = Self::property_permissions(collection_id);16531654 let key_permissions = keys1655 .map(|keys| {1656 keys.into_iter()1657 .filter_map(|key| {1658 permissions1659 .get(&key)1660 .map(|permission| PropertyKeyPermission {1661 key,1662 permission: permission.clone(),1663 })1664 })1665 .collect()1666 })1667 .unwrap_or_else(|| {1668 permissions1669 .into_iter()1670 .map(|(key, permission)| PropertyKeyPermission { key, permission })1671 .collect()1672 });16731674 Ok(key_permissions)1675 }16761677 1678 1679 1680 pub fn toggle_allowlist(1681 collection: &CollectionHandle<T>,1682 sender: &T::CrossAccountId,1683 user: &T::CrossAccountId,1684 allowed: bool,1685 ) -> DispatchResult {1686 collection.check_is_owner_or_admin(sender)?;16871688 16891690 if allowed {1691 <Allowlist<T>>::insert((collection.id, user), true);1692 Self::deposit_event(Event::<T>::AllowListAddressAdded(1693 collection.id,1694 user.clone(),1695 ));1696 } else {1697 <Allowlist<T>>::remove((collection.id, user));1698 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1699 collection.id,1700 user.clone(),1701 ));1702 }17031704 <PalletEvm<T>>::deposit_log(1705 erc::CollectionHelpersEvents::CollectionChanged {1706 collection_id: eth::collection_id_to_address(collection.id),1707 }1708 .to_log(T::ContractAddress::get()),1709 );17101711 Ok(())1712 }17131714 1715 1716 1717 pub fn toggle_admin(1718 collection: &CollectionHandle<T>,1719 sender: &T::CrossAccountId,1720 user: &T::CrossAccountId,1721 admin: bool,1722 ) -> DispatchResult {1723 collection.check_is_internal()?;1724 collection.check_is_owner(sender)?;17251726 let is_admin = <IsAdmin<T>>::get((collection.id, user));1727 if is_admin == admin {1728 if admin {1729 return Ok(());1730 } else {1731 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1732 }1733 }1734 let amount = <AdminAmount<T>>::get(collection.id);17351736 17371738 if admin {1739 let amount = amount1740 .checked_add(1)1741 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1742 ensure!(1743 amount <= Self::collection_admins_limit(),1744 <Error<T>>::CollectionAdminCountExceeded,1745 );17461747 <AdminAmount<T>>::insert(collection.id, amount);1748 <IsAdmin<T>>::insert((collection.id, user), true);17491750 Self::deposit_event(Event::<T>::CollectionAdminAdded(1751 collection.id,1752 user.clone(),1753 ));1754 } else {1755 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1756 <IsAdmin<T>>::remove((collection.id, user));17571758 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1759 collection.id,1760 user.clone(),1761 ));1762 }17631764 <PalletEvm<T>>::deposit_log(1765 erc::CollectionHelpersEvents::CollectionChanged {1766 collection_id: eth::collection_id_to_address(collection.id),1767 }1768 .to_log(T::ContractAddress::get()),1769 );17701771 Ok(())1772 }17731774 1775 pub fn update_limits(1776 user: &T::CrossAccountId,1777 collection: &mut CollectionHandle<T>,1778 new_limit: CollectionLimits,1779 ) -> DispatchResult {1780 collection.check_is_internal()?;1781 collection.check_is_owner_or_admin(user)?;17821783 collection.limits =1784 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17851786 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1787 <PalletEvm<T>>::deposit_log(1788 erc::CollectionHelpersEvents::CollectionChanged {1789 collection_id: eth::collection_id_to_address(collection.id),1790 }1791 .to_log(T::ContractAddress::get()),1792 );17931794 collection.save()1795 }17961797 1798 fn clamp_limits(1799 mode: CollectionMode,1800 old_limit: &CollectionLimits,1801 mut new_limit: CollectionLimits,1802 ) -> Result<CollectionLimits, DispatchError> {1803 let limits = old_limit;1804 limit_default!(old_limit, new_limit,1805 account_token_ownership_limit => ensure!(1806 new_limit <= MAX_TOKEN_OWNERSHIP,1807 <Error<T>>::CollectionLimitBoundsExceeded,1808 ),1809 sponsored_data_size => ensure!(1810 new_limit <= CUSTOM_DATA_LIMIT,1811 <Error<T>>::CollectionLimitBoundsExceeded,1812 ),18131814 sponsored_data_rate_limit => {},1815 token_limit => ensure!(1816 old_limit >= new_limit && new_limit > 0,1817 <Error<T>>::CollectionTokenLimitExceeded1818 ),18191820 sponsor_transfer_timeout(match mode {1821 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1822 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1823 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1824 }) => ensure!(1825 new_limit <= MAX_SPONSOR_TIMEOUT,1826 <Error<T>>::CollectionLimitBoundsExceeded,1827 ),1828 sponsor_approve_timeout => {},1829 owner_can_transfer => ensure!(1830 !limits.owner_can_transfer_instaled() ||1831 old_limit || !new_limit,1832 <Error<T>>::OwnerPermissionsCantBeReverted,1833 ),1834 owner_can_destroy => ensure!(1835 old_limit || !new_limit,1836 <Error<T>>::OwnerPermissionsCantBeReverted,1837 ),1838 transfers_enabled => {},1839 );1840 Ok(new_limit)1841 }18421843 1844 pub fn update_permissions(1845 user: &T::CrossAccountId,1846 collection: &mut CollectionHandle<T>,1847 new_permission: CollectionPermissions,1848 ) -> DispatchResult {1849 collection.check_is_internal()?;1850 collection.check_is_owner_or_admin(user)?;1851 collection.permissions = Self::clamp_permissions(1852 collection.mode.clone(),1853 &collection.permissions,1854 new_permission,1855 )?;18561857 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1858 <PalletEvm<T>>::deposit_log(1859 erc::CollectionHelpersEvents::CollectionChanged {1860 collection_id: eth::collection_id_to_address(collection.id),1861 }1862 .to_log(T::ContractAddress::get()),1863 );18641865 collection.save()1866 }18671868 1869 fn clamp_permissions(1870 _mode: CollectionMode,1871 old_permission: &CollectionPermissions,1872 mut new_permission: CollectionPermissions,1873 ) -> Result<CollectionPermissions, DispatchError> {1874 limit_default_clone!(old_permission, new_permission,1875 access => {},1876 mint_mode => {},1877 nesting => { },1878 );1879 Ok(new_permission)1880 }18811882 1883 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1884 CollectionProperties::<T>::mutate(collection_id, |properties| {1885 properties.recompute_consumed_space();1886 });18871888 Ok(())1889 }1890}189118921893#[macro_export]1894macro_rules! unsupported {1895 ($runtime:path) => {1896 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1897 };1898}189919001901pub trait CommonWeightInfo<CrossAccountId> {1902 1903 fn create_item(data: &CreateItemData) -> Weight {1904 Self::create_multiple_items(from_ref(data))1905 }19061907 1908 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19091910 1911 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19121913 1914 fn burn_item() -> Weight;19151916 1917 1918 1919 fn set_collection_properties(amount: u32) -> Weight;19201921 1922 1923 1924 fn delete_collection_properties(amount: u32) -> Weight {1925 Self::set_collection_properties(amount)1926 }19271928 1929 1930 1931 fn set_token_properties(amount: u32) -> Weight;19321933 1934 1935 1936 fn delete_token_properties(amount: u32) -> Weight {1937 Self::set_token_properties(amount)1938 }19391940 1941 1942 1943 fn set_token_property_permissions(amount: u32) -> Weight;19441945 1946 fn transfer() -> Weight;19471948 1949 fn approve() -> Weight;19501951 1952 fn approve_from() -> Weight;19531954 1955 fn transfer_from() -> Weight;19561957 1958 fn burn_from() -> Weight;19591960 1961 fn set_allowance_for_all() -> Weight;19621963 1964 fn force_repair_item() -> Weight;1965}196619671968pub trait RefungibleExtensionsWeightInfo {1969 1970 fn repartition() -> Weight;1971}197219731974197519761977pub trait CommonCollectionOperations<T: Config> {1978 1979 1980 1981 1982 1983 1984 fn create_item(1985 &self,1986 sender: T::CrossAccountId,1987 to: T::CrossAccountId,1988 data: CreateItemData,1989 nesting_budget: &dyn Budget,1990 ) -> DispatchResultWithPostInfo;19911992 1993 1994 1995 1996 1997 1998 fn create_multiple_items(1999 &self,2000 sender: T::CrossAccountId,2001 to: T::CrossAccountId,2002 data: Vec<CreateItemData>,2003 nesting_budget: &dyn Budget,2004 ) -> DispatchResultWithPostInfo;20052006 2007 2008 2009 2010 2011 2012 fn create_multiple_items_ex(2013 &self,2014 sender: T::CrossAccountId,2015 data: CreateItemExData<T::CrossAccountId>,2016 nesting_budget: &dyn Budget,2017 ) -> DispatchResultWithPostInfo;20182019 2020 2021 2022 2023 2024 fn burn_item(2025 &self,2026 sender: T::CrossAccountId,2027 token: TokenId,2028 amount: u128,2029 ) -> DispatchResultWithPostInfo;20302031 2032 2033 2034 2035 fn set_collection_properties(2036 &self,2037 sender: T::CrossAccountId,2038 properties: Vec<Property>,2039 ) -> DispatchResultWithPostInfo;20402041 2042 2043 2044 2045 fn delete_collection_properties(2046 &self,2047 sender: &T::CrossAccountId,2048 property_keys: Vec<PropertyKey>,2049 ) -> DispatchResultWithPostInfo;20502051 2052 2053 2054 2055 2056 2057 2058 2059 2060 fn set_token_properties(2061 &self,2062 sender: T::CrossAccountId,2063 token_id: TokenId,2064 properties: Vec<Property>,2065 budget: &dyn Budget,2066 ) -> DispatchResultWithPostInfo;20672068 2069 2070 2071 2072 2073 2074 2075 2076 2077 fn delete_token_properties(2078 &self,2079 sender: T::CrossAccountId,2080 token_id: TokenId,2081 property_keys: Vec<PropertyKey>,2082 budget: &dyn Budget,2083 ) -> DispatchResultWithPostInfo;20842085 2086 2087 2088 fn get_token_properties_raw(&self, token_id: TokenId) -> Option<TokenProperties>;20892090 2091 2092 2093 2094 fn set_token_properties_raw(&self, token_id: TokenId, map: TokenProperties);20952096 2097 2098 2099 2100 2101 2102 fn set_token_property_permissions(2103 &self,2104 sender: &T::CrossAccountId,2105 property_permissions: Vec<PropertyKeyPermission>,2106 ) -> DispatchResultWithPostInfo;21072108 2109 2110 2111 2112 2113 2114 2115 fn transfer(2116 &self,2117 sender: T::CrossAccountId,2118 to: T::CrossAccountId,2119 token: TokenId,2120 amount: u128,2121 budget: &dyn Budget,2122 ) -> DispatchResultWithPostInfo;21232124 2125 2126 2127 2128 2129 2130 fn approve(2131 &self,2132 sender: T::CrossAccountId,2133 spender: T::CrossAccountId,2134 token: TokenId,2135 amount: u128,2136 ) -> DispatchResultWithPostInfo;21372138 2139 2140 2141 2142 2143 2144 2145 fn approve_from(2146 &self,2147 sender: T::CrossAccountId,2148 from: T::CrossAccountId,2149 to: T::CrossAccountId,2150 token: TokenId,2151 amount: u128,2152 ) -> DispatchResultWithPostInfo;21532154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 fn transfer_from(2165 &self,2166 sender: T::CrossAccountId,2167 from: T::CrossAccountId,2168 to: T::CrossAccountId,2169 token: TokenId,2170 amount: u128,2171 budget: &dyn Budget,2172 ) -> DispatchResultWithPostInfo;21732174 2175 2176 2177 2178 2179 2180 2181 2182 2183 fn burn_from(2184 &self,2185 sender: T::CrossAccountId,2186 from: T::CrossAccountId,2187 token: TokenId,2188 amount: u128,2189 budget: &dyn Budget,2190 ) -> DispatchResultWithPostInfo;21912192 2193 2194 2195 2196 2197 2198 fn check_nesting(2199 &self,2200 sender: &T::CrossAccountId,2201 from: (CollectionId, TokenId),2202 under: TokenId,2203 budget: &dyn Budget,2204 ) -> DispatchResult;22052206 2207 2208 2209 2210 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22112212 2213 2214 2215 2216 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22172218 2219 2220 2221 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22222223 2224 fn collection_tokens(&self) -> Vec<TokenId>;22252226 2227 2228 2229 fn token_exists(&self, token: TokenId) -> bool;22302231 2232 fn last_token_id(&self) -> TokenId;22332234 2235 2236 2237 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22382239 2240 2241 2242 2243 2244 fn check_token_indirect_owner(2245 &self,2246 token: TokenId,2247 maybe_owner: &T::CrossAccountId,2248 nesting_budget: &dyn Budget,2249 ) -> Result<bool, DispatchError>;22502251 2252 2253 2254 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22552256 2257 2258 2259 2260 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22612262 2263 2264 2265 2266 2267 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22682269 2270 fn total_supply(&self) -> u32;22712272 2273 2274 2275 fn account_balance(&self, account: T::CrossAccountId) -> u32;22762277 2278 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22792280 2281 fn total_pieces(&self, token: TokenId) -> Option<u128>;22822283 2284 2285 2286 2287 2288 fn allowance(2289 &self,2290 sender: T::CrossAccountId,2291 spender: T::CrossAccountId,2292 token: TokenId,2293 ) -> u128;22942295 2296 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22972298 2299 2300 2301 2302 fn set_allowance_for_all(2303 &self,2304 owner: T::CrossAccountId,2305 operator: T::CrossAccountId,2306 approve: bool,2307 ) -> DispatchResultWithPostInfo;23082309 2310 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23112312 2313 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2314}231523162317pub trait RefungibleExtensions<T>2318where2319 T: Config,2320{2321 2322 2323 2324 2325 2326 2327 2328 fn repartition(2329 &self,2330 sender: &T::CrossAccountId,2331 token: TokenId,2332 amount: u128,2333 ) -> DispatchResultWithPostInfo;2334}23352336233723382339pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2340 let post_info = PostDispatchInfo {2341 actual_weight: Some(weight),2342 pays_fee: Pays::Yes,2343 };2344 match res {2345 Ok(()) => Ok(post_info),2346 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2347 }2348}23492350impl<T: Config> From<PropertiesError> for Error<T> {2351 fn from(error: PropertiesError) -> Self {2352 match error {2353 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2354 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2355 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2356 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2357 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2358 }2359 }2360}23612362236323642365236623672368pub struct PropertyWriter<'a, WriterVariant, T, Handle> {2369 collection: &'a Handle,2370 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2371 _phantom: PhantomData<(T, WriterVariant)>,2372}23732374impl<'a, T, Handle, WriterVariant> PropertyWriter<'a, WriterVariant, T, Handle>2375where2376 T: Config,2377 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2378{2379 fn internal_write_token_properties(2380 &mut self,2381 token_id: TokenId,2382 mut token_lazy_info: PropertyWriterLazyTokenInfo,2383 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2384 log: evm_coder::ethereum::Log,2385 ) -> DispatchResult {2386 for (key, value) in properties_updates {2387 let permission = self2388 .collection_lazy_info2389 .property_permissions2390 .value()2391 .get(&key)2392 .cloned()2393 .unwrap_or_else(PropertyPermission::none);23942395 match permission {2396 PropertyPermission { mutable: false, .. }2397 if token_lazy_info2398 .stored_properties2399 .value()2400 .get(&key)2401 .is_some() =>2402 {2403 return Err(<Error<T>>::NoPermission.into());2404 }24052406 PropertyPermission {2407 collection_admin,2408 token_owner,2409 ..2410 } => check_token_permissions::<T>(2411 collection_admin,2412 token_owner,2413 &mut self.collection_lazy_info.is_collection_admin,2414 &mut token_lazy_info.is_token_owner,2415 &mut token_lazy_info.is_token_exist,2416 )?,2417 }24182419 match value {2420 Some(value) => {2421 token_lazy_info2422 .stored_properties2423 .value_mut()2424 .try_set(key.clone(), value)2425 .map_err(<Error<T>>::from)?;24262427 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2428 self.collection.id,2429 token_id,2430 key,2431 ));2432 }2433 None => {2434 token_lazy_info2435 .stored_properties2436 .value_mut()2437 .remove(&key)2438 .map_err(<Error<T>>::from)?;24392440 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2441 self.collection.id,2442 token_id,2443 key,2444 ));2445 }2446 }2447 }24482449 let properties_changed = token_lazy_info.stored_properties.has_value();2450 if properties_changed {2451 <PalletEvm<T>>::deposit_log(log);24522453 self.collection2454 .set_token_properties_raw(token_id, token_lazy_info.stored_properties.into_inner());2455 }24562457 Ok(())2458 }2459}24602461246224632464pub struct PropertyWriterLazyCollectionInfo<'a> {2465 is_collection_admin: LazyValue<'a, bool>,2466 property_permissions: LazyValue<'a, PropertiesPermissionMap>,2467}2468246924702471pub struct PropertyWriterLazyTokenInfo<'a> {2472 is_token_exist: LazyValue<'a, bool>,2473 is_token_owner: LazyValue<'a, Result<bool, DispatchError>>,2474 stored_properties: LazyValue<'a, TokenProperties>,2475}24762477impl<'a> PropertyWriterLazyTokenInfo<'a> {2478 2479 pub fn new(2480 check_token_exist: impl FnOnce() -> bool + 'a,2481 check_token_owner: impl FnOnce() -> Result<bool, DispatchError> + 'a,2482 get_token_properties: impl FnOnce() -> TokenProperties + 'a,2483 ) -> Self {2484 Self {2485 is_token_exist: LazyValue::new(check_token_exist),2486 is_token_owner: LazyValue::new(check_token_owner),2487 stored_properties: LazyValue::new(get_token_properties),2488 }2489 }2490}2491249224932494pub struct NewTokenPropertyWriter<T>(PhantomData<T>);2495impl<T: Config> NewTokenPropertyWriter<T> {2496 2497 pub fn new<'a, Handle>(2498 collection: &'a Handle,2499 sender: &'a T::CrossAccountId,2500 ) -> PropertyWriter<'a, Self, T, Handle>2501 where2502 T: Config,2503 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2504 {2505 PropertyWriter {2506 collection,2507 collection_lazy_info: PropertyWriterLazyCollectionInfo {2508 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2509 property_permissions: LazyValue::new(|| {2510 <Pallet<T>>::property_permissions(collection.id)2511 }),2512 },2513 _phantom: PhantomData,2514 }2515 }2516}25172518impl<'a, T, Handle> PropertyWriter<'a, NewTokenPropertyWriter<T>, T, Handle>2519where2520 T: Config,2521 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2522{2523 2524 pub fn write_token_properties(2525 &mut self,2526 mint_target_is_sender: bool,2527 token_id: TokenId,2528 properties_updates: impl Iterator<Item = Property>,2529 log: evm_coder::ethereum::Log,2530 ) -> DispatchResult {2531 let check_token_exist = || {2532 debug_assert!(self.collection.token_exists(token_id));2533 true2534 };25352536 let check_token_owner = || Ok(mint_target_is_sender);25372538 let get_token_properties = || {2539 debug_assert!(self.collection.get_token_properties_raw(token_id).is_none());2540 TokenProperties::new()2541 };25422543 self.internal_write_token_properties(2544 token_id,2545 PropertyWriterLazyTokenInfo::new(2546 check_token_exist,2547 check_token_owner,2548 get_token_properties,2549 ),2550 properties_updates.map(|p| (p.key, Some(p.value))),2551 log,2552 )2553 }2554}2555255625572558pub struct ExistingTokenPropertyWriter<T>(PhantomData<T>);2559impl<T: Config> ExistingTokenPropertyWriter<T> {2560 2561 pub fn new<'a, Handle>(2562 collection: &'a Handle,2563 sender: &'a T::CrossAccountId,2564 ) -> PropertyWriter<'a, Self, T, Handle>2565 where2566 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2567 {2568 PropertyWriter {2569 collection,2570 collection_lazy_info: PropertyWriterLazyCollectionInfo {2571 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2572 property_permissions: LazyValue::new(|| {2573 <Pallet<T>>::property_permissions(collection.id)2574 }),2575 },2576 _phantom: PhantomData,2577 }2578 }2579}25802581impl<'a, T, Handle> PropertyWriter<'a, ExistingTokenPropertyWriter<T>, T, Handle>2582where2583 T: Config,2584 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2585{2586 2587 pub fn write_token_properties(2588 &mut self,2589 sender: &T::CrossAccountId,2590 token_id: TokenId,2591 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2592 nesting_budget: &dyn Budget,2593 log: evm_coder::ethereum::Log,2594 ) -> DispatchResult {2595 let check_token_exist = || self.collection.token_exists(token_id);2596 let check_token_owner = || {2597 self.collection2598 .check_token_indirect_owner(token_id, sender, nesting_budget)2599 };2600 let get_token_properties = || {2601 self.collection2602 .get_token_properties_raw(token_id)2603 .unwrap_or_default()2604 };26052606 self.internal_write_token_properties(2607 token_id,2608 PropertyWriterLazyTokenInfo::new(2609 check_token_exist,2610 check_token_owner,2611 get_token_properties,2612 ),2613 properties_updates,2614 log,2615 )2616 }2617}2618261926202621#[cfg(feature = "runtime-benchmarks")]2622pub struct BenchmarkPropertyWriter<T>(PhantomData<T>);26232624#[cfg(feature = "runtime-benchmarks")]2625impl<T: Config> BenchmarkPropertyWriter<T> {2626 2627 pub fn new<'a, Handle>(2628 collection: &'a Handle,2629 collection_lazy_info: PropertyWriterLazyCollectionInfo<'a>,2630 ) -> PropertyWriter<'a, Self, T, Handle>2631 where2632 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2633 {2634 PropertyWriter {2635 collection,2636 collection_lazy_info,2637 _phantom: PhantomData,2638 }2639 }26402641 2642 pub fn load_collection_info<Handle>(2643 collection_handle: &Handle,2644 sender: &T::CrossAccountId,2645 ) -> PropertyWriterLazyCollectionInfo<'static>2646 where2647 Handle: Deref<Target = CollectionHandle<T>>,2648 {2649 let is_collection_admin = collection_handle.is_owner_or_admin(sender);2650 let property_permissions = <Pallet<T>>::property_permissions(collection_handle.id);26512652 PropertyWriterLazyCollectionInfo {2653 is_collection_admin: LazyValue::new(move || is_collection_admin),2654 property_permissions: LazyValue::new(move || property_permissions),2655 }2656 }26572658 2659 pub fn load_token_properties<Handle>(2660 collection: &Handle,2661 token_id: TokenId,2662 ) -> PropertyWriterLazyTokenInfo2663 where2664 Handle: CommonCollectionOperations<T>,2665 {2666 let stored_properties = collection2667 .get_token_properties_raw(token_id)2668 .unwrap_or_default();26692670 PropertyWriterLazyTokenInfo {2671 is_token_exist: LazyValue::new(|| true),2672 is_token_owner: LazyValue::new(|| Ok(true)),2673 stored_properties: LazyValue::new(move || stored_properties),2674 }2675 }2676}26772678#[cfg(feature = "runtime-benchmarks")]2679impl<'a, T, Handle> PropertyWriter<'a, BenchmarkPropertyWriter<T>, T, Handle>2680where2681 T: Config,2682 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2683{2684 2685 pub fn write_token_properties(2686 &mut self,2687 token_id: TokenId,2688 properties_updates: impl Iterator<Item = Property>,2689 log: evm_coder::ethereum::Log,2690 ) -> DispatchResult {2691 let check_token_exist = || true;2692 let check_token_owner = || Ok(true);2693 let get_token_properties = TokenProperties::new;26942695 self.internal_write_token_properties(2696 token_id,2697 PropertyWriterLazyTokenInfo::new(2698 check_token_exist,2699 check_token_owner,2700 get_token_properties,2701 ),2702 properties_updates.map(|p| (p.key, Some(p.value))),2703 log,2704 )2705 }2706}270727082709271027112712pub fn write_token_properties_total_weight<T: Config, I: Fn(u32) -> Weight>(2713 properties_nums: impl Iterator<Item = u32>,2714 per_token_weight: I,2715) -> Weight {2716 let mut weight = properties_nums2717 .filter_map(|properties_num| {2718 if properties_num > 0 {2719 Some(per_token_weight(properties_num))2720 } else {2721 None2722 }2723 })2724 .fold(Weight::zero(), |a, b| a.saturating_add(b));27252726 if !weight.is_zero() {2727 2728 2729 27302731 weight = weight.saturating_add(<SelfWeightOf<T>>::property_writer_load_collection_info());2732 }27332734 weight2735}27362737#[cfg(any(feature = "tests", test))]2738#[allow(missing_docs)]2739pub mod tests {2740 use crate::{Config, DispatchError, DispatchResult, LazyValue};27412742 const fn to_bool(u: u8) -> bool {2743 u != 02744 }27452746 #[derive(Debug)]2747 pub struct TestCase {2748 pub collection_admin: bool,2749 pub is_collection_admin: bool,2750 pub token_owner: bool,2751 pub is_token_owner: bool,2752 pub no_permission: bool,2753 }27542755 impl TestCase {2756 const fn new(2757 collection_admin: u8,2758 is_collection_admin: u8,2759 token_owner: u8,2760 is_token_owner: u8,2761 no_permission: u8,2762 ) -> Self {2763 Self {2764 collection_admin: to_bool(collection_admin),2765 is_collection_admin: to_bool(is_collection_admin),2766 token_owner: to_bool(token_owner),2767 is_token_owner: to_bool(is_token_owner),2768 no_permission: to_bool(no_permission),2769 }2770 }2771 }27722773 #[rustfmt::skip]2774 pub const TABLE: [TestCase; 16] = [2775 2776 2777 2778 2779 2780 TestCase::new(0, 0, 0, 0, 1),2781 TestCase::new(0, 0, 0, 1, 1),2782 TestCase::new(0, 0, 1, 0, 1),2783 TestCase::new(0, 0, 1, 1, 0),2784 TestCase::new(0, 1, 0, 0, 1),2785 TestCase::new(0, 1, 0, 1, 1),2786 TestCase::new(0, 1, 1, 0, 1),2787 TestCase::new(0, 1, 1, 1, 0),2788 TestCase::new(1, 0, 0, 0, 1),2789 TestCase::new(1, 0, 0, 1, 1),2790 TestCase::new(1, 0, 1, 0, 1),2791 TestCase::new(1, 0, 1, 1, 0),2792 TestCase::new(1, 1, 0, 0, 0),2793 TestCase::new(1, 1, 0, 1, 0),2794 TestCase::new(1, 1, 1, 0, 0),2795 TestCase::new(1, 1, 1, 1, 0),2796 ];27972798 pub fn check_token_permissions<T: Config>(2799 collection_admin_permitted: bool,2800 token_owner_permitted: bool,2801 is_collection_admin: &mut LazyValue<bool>,2802 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>>,2803 check_token_existence: &mut LazyValue<bool>,2804 ) -> DispatchResult {2805 crate::check_token_permissions::<T>(2806 collection_admin_permitted,2807 token_owner_permitted,2808 is_collection_admin,2809 check_token_ownership,2810 check_token_existence,2811 )2812 }2813}