12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#![warn(missing_docs)]53#![cfg_attr(not(feature = "std"), no_std)]54extern crate alloc;5556use core::{57 ops::{Deref, DerefMut},58 slice::from_ref,59};60use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};61use sp_std::vec::Vec;62use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};63use evm_coder::ToLog;64use frame_support::{65 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},66 ensure,67 traits::{68 Get,69 fungible::{Balanced, Debt, Inspect},70 tokens::{Imbalance, Precision, Preservation},71 },72 dispatch::Pays,73 transactional, fail,74};75use up_data_structs::{76 AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, RpcCollectionFlags,77 CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, TokenId,78 TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,79 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,80 CUSTOM_DATA_LIMIT, CollectionLimits, CreateCollectionData, SponsorshipState, CreateItemExData,81 SponsoringRateLimit, budget::Budget, PhantomType, Property,82 CollectionProperties as CollectionPropertiesT, TokenProperties, PropertiesPermissionMap,83 PropertyKey, PropertyValue, PropertyPermission, PropertiesError, TokenOwnerError,84 PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope, CollectionPermissions,85};86use up_pov_estimate_rpc::PovInfo;8788pub use pallet::*;89use sp_core::H160;90use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, traits::Zero};9192#[cfg(feature = "runtime-benchmarks")]93pub mod benchmarking;94pub mod dispatch;95pub mod erc;96pub mod eth;97pub mod helpers;98#[allow(missing_docs)]99pub mod weights;100101pub type SelfWeightOf<T> = <T as Config>::WeightInfo;102103104105106107108109#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]110pub struct CollectionHandle<T: Config> {111 112 pub id: CollectionId,113 collection: Collection<T::AccountId>,114 115 pub recorder: SubstrateRecorder<T>,116}117118impl<T: Config> WithRecorder<T> for CollectionHandle<T> {119 fn recorder(&self) -> &SubstrateRecorder<T> {120 &self.recorder121 }122 fn into_recorder(self) -> SubstrateRecorder<T> {123 self.recorder124 }125}126127impl<T: Config> CollectionHandle<T> {128 129 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {130 Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))131 }132133 134 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {135 <CollectionById<T>>::get(id).map(|collection| Self {136 id,137 collection,138 recorder,139 })140 }141142 143 144 pub fn new(id: CollectionId) -> Option<Self> {145 Self::new_with_gas_limit(id, u64::MAX)146 }147148 149 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {150 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)151 }152153 154 pub fn consume_store_reads(155 &self,156 reads: u64,157 ) -> pallet_evm_coder_substrate::execution::Result<()> {158 self.recorder().consume_store_reads(reads)159 }160161 162 pub fn consume_store_writes(163 &self,164 writes: u64,165 ) -> pallet_evm_coder_substrate::execution::Result<()> {166 self.recorder().consume_store_writes(writes)167 }168169 170 pub fn consume_store_reads_and_writes(171 &self,172 reads: u64,173 writes: u64,174 ) -> pallet_evm_coder_substrate::execution::Result<()> {175 self.recorder()176 .consume_store_reads_and_writes(reads, writes)177 }178179 180 pub fn save(&self) -> DispatchResult {181 <CollectionById<T>>::insert(self.id, &self.collection);182 Ok(())183 }184185 186 187 188 189 190 pub fn set_sponsor(191 &mut self,192 sender: &T::CrossAccountId,193 sponsor: T::AccountId,194 ) -> DispatchResult {195 self.check_is_internal()?;196 self.check_is_owner_or_admin(sender)?;197198 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());199200 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));201 <PalletEvm<T>>::deposit_log(202 erc::CollectionHelpersEvents::CollectionChanged {203 collection_id: eth::collection_id_to_address(self.id),204 }205 .to_log(T::ContractAddress::get()),206 );207208 self.save()209 }210211 212 213 214 215 216 217 218 219 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {220 self.check_is_internal()?;221222 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());223224 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));225 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));226 <PalletEvm<T>>::deposit_log(227 erc::CollectionHelpersEvents::CollectionChanged {228 collection_id: eth::collection_id_to_address(self.id),229 }230 .to_log(T::ContractAddress::get()),231 );232233 self.save()234 }235236 237 238 239 240 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {241 self.check_is_internal()?;242 ensure!(243 self.collection.sponsorship.pending_sponsor() == Some(sender),244 Error::<T>::ConfirmSponsorshipFail245 );246247 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());248249 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));250 <PalletEvm<T>>::deposit_log(251 erc::CollectionHelpersEvents::CollectionChanged {252 collection_id: eth::collection_id_to_address(self.id),253 }254 .to_log(T::ContractAddress::get()),255 );256257 self.save()258 }259260 261 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {262 self.check_is_internal()?;263 self.check_is_owner_or_admin(sender)?;264265 self.collection.sponsorship = SponsorshipState::Disabled;266267 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));268 <PalletEvm<T>>::deposit_log(269 erc::CollectionHelpersEvents::CollectionChanged {270 collection_id: eth::collection_id_to_address(self.id),271 }272 .to_log(T::ContractAddress::get()),273 );274 self.save()275 }276277 278 279 280 281 pub fn force_remove_sponsor(&mut self) -> DispatchResult {282 self.check_is_internal()?;283284 self.collection.sponsorship = SponsorshipState::Disabled;285286 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));287 <PalletEvm<T>>::deposit_log(288 erc::CollectionHelpersEvents::CollectionChanged {289 collection_id: eth::collection_id_to_address(self.id),290 }291 .to_log(T::ContractAddress::get()),292 );293 self.save()294 }295296 297 298 pub fn check_is_internal(&self) -> DispatchResult {299 if self.flags.external {300 return Err(<Error<T>>::CollectionIsExternal)?;301 }302303 Ok(())304 }305306 307 308 pub fn check_is_external(&self) -> DispatchResult {309 if !self.flags.external {310 return Err(<Error<T>>::CollectionIsInternal)?;311 }312313 Ok(())314 }315}316317impl<T: Config> Deref for CollectionHandle<T> {318 type Target = Collection<T::AccountId>;319320 fn deref(&self) -> &Self::Target {321 &self.collection322 }323}324325impl<T: Config> DerefMut for CollectionHandle<T> {326 fn deref_mut(&mut self) -> &mut Self::Target {327 &mut self.collection328 }329}330331impl<T: Config> CollectionHandle<T> {332 333 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {334 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);335 Ok(())336 }337338 339 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {340 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))341 }342343 344 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {345 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);346 Ok(())347 }348349 350 351 352 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {353 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)354 }355356 357 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {358 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)359 }360361 362 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {363 ensure!(364 <Allowlist<T>>::get((self.id, user)),365 <Error<T>>::AddressNotInAllowlist366 );367 Ok(())368 }369370 371 372 373 pub fn change_owner(374 &mut self,375 caller: T::CrossAccountId,376 new_owner: T::CrossAccountId,377 ) -> DispatchResult {378 self.check_is_internal()?;379 self.check_is_owner(&caller)?;380 self.collection.owner = new_owner.as_sub().clone();381382 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(383 self.id,384 new_owner.as_sub().clone(),385 ));386 <PalletEvm<T>>::deposit_log(387 erc::CollectionHelpersEvents::CollectionChanged {388 collection_id: eth::collection_id_to_address(self.id),389 }390 .to_log(T::ContractAddress::get()),391 );392393 self.save()394 }395}396397#[frame_support::pallet]398pub mod pallet {399400 use super::*;401 use dispatch::CollectionDispatch;402 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};403 use up_data_structs::{TokenId, mapping::TokenAddressMapping};404 use scale_info::TypeInfo;405 use weights::WeightInfo;406407 #[pallet::config]408 pub trait Config:409 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo410 {411 412 type WeightInfo: WeightInfo;413414 415 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;416417 418 type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;419420 421 #[pallet::constant]422 type CollectionCreationPrice: Get<423 <<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,424 >;425426 427 type CollectionDispatch: CollectionDispatch<Self>;428429 430 type TreasuryAccountId: Get<Self::AccountId>;431432 433 #[pallet::constant]434 type ContractAddress: Get<H160>;435436 437 type EvmTokenAddressMapping: TokenAddressMapping<H160>;438439 440 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;441 }442443 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);444 445 pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);446447 #[pallet::pallet]448 #[pallet::storage_version(STORAGE_VERSION)]449 pub struct Pallet<T>(_);450451 #[pallet::extra_constants]452 impl<T: Config> Pallet<T> {453 454 pub fn collection_admins_limit() -> u32 {455 COLLECTION_ADMINS_LIMIT456 }457 }458459 #[pallet::genesis_config]460 pub struct GenesisConfig<T>(PhantomData<T>);461462 #[cfg(feature = "std")]463 impl<T: Config> Default for GenesisConfig<T> {464 fn default() -> Self {465 Self(Default::default())466 }467 }468469 #[pallet::genesis_build]470 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {471 fn build(&self) {472 StorageVersion::new(1).put::<Pallet<T>>();473 }474 }475476 impl<T: Config> Pallet<T> {477 478 pub fn deposit_event(event: Event<T>) {479 let event = <T as Config>::RuntimeEvent::from(event);480 let event = event.into();481 <frame_system::Pallet<T>>::deposit_event(event)482 }483 }484485 #[pallet::event]486 pub enum Event<T: Config> {487 488 CollectionCreated(489 490 CollectionId,491 492 u8,493 494 T::AccountId,495 ),496497 498 CollectionDestroyed(499 500 CollectionId,501 ),502503 504 ItemCreated(505 506 CollectionId,507 508 TokenId,509 510 T::CrossAccountId,511 512 u128,513 ),514515 516 ItemDestroyed(517 518 CollectionId,519 520 TokenId,521 522 T::CrossAccountId,523 524 u128,525 ),526527 528 Transfer(529 530 CollectionId,531 532 TokenId,533 534 T::CrossAccountId,535 536 T::CrossAccountId,537 538 u128,539 ),540541 542 Approved(543 544 CollectionId,545 546 TokenId,547 548 T::CrossAccountId,549 550 T::CrossAccountId,551 552 u128,553 ),554555 556 ApprovedForAll(557 558 CollectionId,559 560 T::CrossAccountId,561 562 T::CrossAccountId,563 564 bool,565 ),566567 568 CollectionPropertySet(569 570 CollectionId,571 572 PropertyKey,573 ),574575 576 CollectionPropertyDeleted(577 578 CollectionId,579 580 PropertyKey,581 ),582583 584 TokenPropertySet(585 586 CollectionId,587 588 TokenId,589 590 PropertyKey,591 ),592593 594 TokenPropertyDeleted(595 596 CollectionId,597 598 TokenId,599 600 PropertyKey,601 ),602603 604 PropertyPermissionSet(605 606 CollectionId,607 608 PropertyKey,609 ),610611 612 AllowListAddressAdded(613 614 CollectionId,615 616 T::CrossAccountId,617 ),618619 620 AllowListAddressRemoved(621 622 CollectionId,623 624 T::CrossAccountId,625 ),626627 628 CollectionAdminAdded(629 630 CollectionId,631 632 T::CrossAccountId,633 ),634635 636 CollectionAdminRemoved(637 638 CollectionId,639 640 T::CrossAccountId,641 ),642643 644 CollectionLimitSet(645 646 CollectionId,647 ),648649 650 CollectionOwnerChanged(651 652 CollectionId,653 654 T::AccountId,655 ),656657 658 CollectionPermissionSet(659 660 CollectionId,661 ),662663 664 CollectionSponsorSet(665 666 CollectionId,667 668 T::AccountId,669 ),670671 672 SponsorshipConfirmed(673 674 CollectionId,675 676 T::AccountId,677 ),678679 680 CollectionSponsorRemoved(681 682 CollectionId,683 ),684 }685686 #[pallet::error]687 pub enum Error<T> {688 689 CollectionNotFound,690 691 MustBeTokenOwner,692 693 NoPermission,694 695 CantDestroyNotEmptyCollection,696 697 PublicMintingNotAllowed,698 699 AddressNotInAllowlist,700701 702 CollectionNameLimitExceeded,703 704 CollectionDescriptionLimitExceeded,705 706 CollectionTokenPrefixLimitExceeded,707 708 TotalCollectionsLimitExceeded,709 710 CollectionAdminCountExceeded,711 712 CollectionLimitBoundsExceeded,713 714 OwnerPermissionsCantBeReverted,715 716 TransferNotAllowed,717 718 AccountTokenLimitExceeded,719 720 CollectionTokenLimitExceeded,721 722 MetadataFlagFrozen,723724 725 TokenNotFound,726 727 TokenValueTooLow,728 729 ApprovedValueTooLow,730 731 CantApproveMoreThanOwned,732 733 AddressIsNotEthMirror,734735 736 AddressIsZero,737738 739 UnsupportedOperation,740741 742 NotSufficientFounds,743744 745 UserIsNotAllowedToNest,746 747 SourceCollectionIsNotAllowedToNest,748749 750 CollectionFieldSizeExceeded,751752 753 NoSpaceForProperty,754755 756 PropertyLimitReached,757758 759 PropertyKeyIsTooLong,760761 762 InvalidCharacterInPropertyKey,763764 765 EmptyPropertyKey,766767 768 CollectionIsExternal,769770 771 CollectionIsInternal,772773 774 ConfirmSponsorshipFail,775776 777 UserIsNotCollectionAdmin,778 }779780 781 #[pallet::storage]782 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;783784 785 #[pallet::storage]786 pub type DestroyedCollectionCount<T> =787 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;788789 790 #[pallet::storage]791 pub type CollectionById<T> = StorageMap<792 Hasher = Blake2_128Concat,793 Key = CollectionId,794 Value = Collection<<T as frame_system::Config>::AccountId>,795 QueryKind = OptionQuery,796 >;797798 799 #[pallet::storage]800 #[pallet::getter(fn collection_properties)]801 pub type CollectionProperties<T> = StorageMap<802 Hasher = Blake2_128Concat,803 Key = CollectionId,804 Value = CollectionPropertiesT,805 QueryKind = ValueQuery,806 >;807808 809 #[pallet::storage]810 #[pallet::getter(fn property_permissions)]811 pub type CollectionPropertyPermissions<T> = StorageMap<812 Hasher = Blake2_128Concat,813 Key = CollectionId,814 Value = PropertiesPermissionMap,815 QueryKind = ValueQuery,816 >;817818 819 #[pallet::storage]820 pub type AdminAmount<T> = StorageMap<821 Hasher = Blake2_128Concat,822 Key = CollectionId,823 Value = u32,824 QueryKind = ValueQuery,825 >;826827 828 #[pallet::storage]829 pub type IsAdmin<T: Config> = StorageNMap<830 Key = (831 Key<Blake2_128Concat, CollectionId>,832 Key<Blake2_128Concat, T::CrossAccountId>,833 ),834 Value = bool,835 QueryKind = ValueQuery,836 >;837838 839 #[pallet::storage]840 pub type Allowlist<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 DummyStorageValue<T: Config> = StorageValue<852 Value = (853 CollectionStats,854 CollectionId,855 TokenId,856 TokenChild,857 PhantomType<(858 TokenData<T::CrossAccountId>,859 RpcCollection<T::AccountId>,860 861 PovInfo,862 )>,863 ),864 QueryKind = OptionQuery,865 >;866}867868869pub enum SetPropertyMode {870 871 ExistingToken,872873 874 NewToken {875 876 mint_target_is_sender: bool,877 },878}879880881pub struct LazyValue<T, F: FnOnce() -> T> {882 value: Option<T>,883 f: Option<F>,884}885886impl<T, F: FnOnce() -> T> LazyValue<T, F> {887 888 pub fn new(f: F) -> Self {889 Self {890 value: None,891 f: Some(f),892 }893 }894895 896 pub fn value(&mut self) -> &T {897 if self.value.is_none() {898 self.value = Some(self.f.take().unwrap()())899 }900901 self.value.as_ref().unwrap()902 }903904 905 pub fn has_value(&self) -> bool {906 self.value.is_some()907 }908}909910fn check_token_permissions<T, FCA, FTO, FTE>(911 collection_admin_permitted: bool,912 token_owner_permitted: bool,913 is_collection_admin: &mut LazyValue<bool, FCA>,914 is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,915 is_token_exist: &mut LazyValue<bool, FTE>,916) -> DispatchResult917where918 T: Config,919 FCA: FnOnce() -> bool,920 FTO: FnOnce() -> Result<bool, DispatchError>,921 FTE: FnOnce() -> bool,922{923 if !(collection_admin_permitted && *is_collection_admin.value()924 || token_owner_permitted && (*is_token_owner.value())?)925 {926 fail!(<Error<T>>::NoPermission);927 }928929 let token_certainly_exist = is_token_owner.has_value() && (*is_token_owner.value())?;930 if !token_certainly_exist && !is_token_exist.value() {931 fail!(<Error<T>>::TokenNotFound);932 }933 Ok(())934}935936impl<T: Config> Pallet<T> {937 938 939 940 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {941 ensure!(942 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,943 <Error<T>>::AddressIsZero944 );945 Ok(())946 }947948 949 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {950 <IsAdmin<T>>::iter_prefix((collection,))951 .map(|(a, _)| a)952 .collect()953 }954955 956 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {957 <Allowlist<T>>::iter_prefix((collection,))958 .map(|(a, _)| a)959 .collect()960 }961962 963 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {964 <Allowlist<T>>::get((collection, user))965 }966967 968 pub fn collection_stats() -> CollectionStats {969 let created = <CreatedCollectionCount<T>>::get();970 let destroyed = <DestroyedCollectionCount<T>>::get();971 CollectionStats {972 created: created.0,973 destroyed: destroyed.0,974 alive: created.0 - destroyed.0,975 }976 }977978 979 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {980 let collection = <CollectionById<T>>::get(collection)?;981 let limits = collection.limits;982 let effective_limits = CollectionLimits {983 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),984 sponsored_data_size: Some(limits.sponsored_data_size()),985 sponsored_data_rate_limit: Some(986 limits987 .sponsored_data_rate_limit988 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),989 ),990 token_limit: Some(limits.token_limit()),991 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(992 match collection.mode {993 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,994 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,995 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,996 },997 )),998 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),999 owner_can_transfer: Some(limits.owner_can_transfer()),1000 owner_can_destroy: Some(limits.owner_can_destroy()),1001 transfers_enabled: Some(limits.transfers_enabled()),1002 };10031004 Some(effective_limits)1005 }10061007 1008 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1009 let Collection {1010 name,1011 description,1012 owner,1013 mode,1014 token_prefix,1015 sponsorship,1016 limits,1017 permissions,1018 flags,1019 } = <CollectionById<T>>::get(collection)?;10201021 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1022 .into_iter()1023 .map(|(key, permission)| PropertyKeyPermission { key, permission })1024 .collect();10251026 let properties = <CollectionProperties<T>>::get(collection)1027 .into_iter()1028 .map(|(key, value)| Property { key, value })1029 .collect();10301031 let permissions = CollectionPermissions {1032 access: Some(permissions.access()),1033 mint_mode: Some(permissions.mint_mode()),1034 nesting: Some(permissions.nesting().clone()),1035 };10361037 Some(RpcCollection {1038 name: name.into_inner(),1039 description: description.into_inner(),1040 owner,1041 mode,1042 token_prefix: token_prefix.into_inner(),1043 sponsorship,1044 limits,1045 permissions,1046 token_property_permissions,1047 properties,1048 read_only: flags.external,10491050 flags: RpcCollectionFlags {1051 foreign: flags.foreign,1052 erc721metadata: flags.erc721metadata,1053 },1054 })1055 }1056}10571058macro_rules! limit_default {1059 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1060 $(1061 if let Some($new) = $new.$field {1062 let $old = $old.$field($($arg)?);1063 let _ = $new;1064 let _ = $old;1065 $check1066 } else {1067 $new.$field = $old.$field1068 }1069 )*1070 }};1071}1072macro_rules! limit_default_clone {1073 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1074 $(1075 if let Some($new) = $new.$field.clone() {1076 let $old = $old.$field($($arg)?);1077 let _ = $new;1078 let _ = $old;1079 $check1080 } else {1081 $new.$field = $old.$field.clone()1082 }1083 )*1084 }};1085}10861087impl<T: Config> Pallet<T> {1088 1089 1090 1091 1092 1093 pub fn init_collection(1094 owner: T::CrossAccountId,1095 payer: T::CrossAccountId,1096 data: CreateCollectionData<T::CrossAccountId>,1097 ) -> Result<CollectionId, DispatchError> {1098 ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);1099 Self::init_collection_internal(owner, payer, data)1100 }11011102 1103 pub fn init_foreign_collection(1104 owner: T::CrossAccountId,1105 payer: T::CrossAccountId,1106 mut data: CreateCollectionData<T::CrossAccountId>,1107 ) -> Result<CollectionId, DispatchError> {1108 data.flags.foreign = true;1109 let id = Self::init_collection_internal(owner, payer, data)?;1110 Ok(id)1111 }11121113 fn init_collection_internal(1114 owner: T::CrossAccountId,1115 payer: T::CrossAccountId,1116 data: CreateCollectionData<T::CrossAccountId>,1117 ) -> Result<CollectionId, DispatchError> {1118 {1119 ensure!(1120 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1121 Error::<T>::CollectionTokenPrefixLimitExceeded1122 );1123 }11241125 let created_count = <CreatedCollectionCount<T>>::get()1126 .01127 .checked_add(1)1128 .ok_or(ArithmeticError::Overflow)?;1129 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1130 let id = CollectionId(created_count);11311132 1133 ensure!(1134 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1135 <Error<T>>::TotalCollectionsLimitExceeded1136 );11371138 11391140 let collection = Collection {1141 owner: owner.as_sub().clone(),1142 name: data.name,1143 mode: data.mode.clone(),1144 description: data.description,1145 token_prefix: data.token_prefix,1146 sponsorship: data1147 .pending_sponsor1148 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1149 .unwrap_or_default(),1150 limits: data1151 .limits1152 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1153 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1154 permissions: data1155 .permissions1156 .map(|permissions| {1157 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1158 })1159 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1160 flags: data.flags,1161 };11621163 let mut collection_properties = CollectionPropertiesT::new();1164 collection_properties1165 .try_set_from_iter(data.properties.into_iter())1166 .map_err(<Error<T>>::from)?;11671168 CollectionProperties::<T>::insert(id, collection_properties);11691170 let mut token_props_permissions = PropertiesPermissionMap::new();1171 token_props_permissions1172 .try_set_from_iter(data.token_property_permissions.into_iter())1173 .map_err(<Error<T>>::from)?;11741175 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11761177 let mut admin_amount = 0u32;1178 for admin in data.admin_list.iter() {1179 if !<IsAdmin<T>>::get((id, admin)) {1180 <IsAdmin<T>>::insert((id, admin), true);1181 admin_amount = admin_amount1182 .checked_add(1)1183 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1184 }1185 }1186 ensure!(1187 admin_amount <= Self::collection_admins_limit(),1188 <Error<T>>::CollectionAdminCountExceeded,1189 );1190 <AdminAmount<T>>::insert(id, admin_amount);11911192 1193 {1194 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1195 imbalance.subsume(<T as Config>::Currency::deposit(1196 &T::TreasuryAccountId::get(),1197 T::CollectionCreationPrice::get(),1198 Precision::Exact,1199 )?);1200 let credit =1201 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1202 .map_err(|_| Error::<T>::NotSufficientFounds)?;12031204 debug_assert!(credit.peek().is_zero())1205 }12061207 <CreatedCollectionCount<T>>::put(created_count);1208 <Pallet<T>>::deposit_event(Event::CollectionCreated(1209 id,1210 data.mode.id(),1211 owner.as_sub().clone(),1212 ));1213 <PalletEvm<T>>::deposit_log(1214 erc::CollectionHelpersEvents::CollectionCreated {1215 owner: *owner.as_eth(),1216 collection_id: eth::collection_id_to_address(id),1217 }1218 .to_log(T::ContractAddress::get()),1219 );1220 <CollectionById<T>>::insert(id, collection);1221 Ok(id)1222 }12231224 1225 1226 1227 1228 pub fn destroy_collection(1229 collection: CollectionHandle<T>,1230 sender: &T::CrossAccountId,1231 ) -> DispatchResult {1232 ensure!(1233 collection.limits.owner_can_destroy(),1234 <Error<T>>::NoPermission,1235 );1236 collection.check_is_owner(sender)?;12371238 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1239 .01240 .checked_add(1)1241 .ok_or(ArithmeticError::Overflow)?;12421243 12441245 <DestroyedCollectionCount<T>>::put(destroyed_collections);1246 <CollectionById<T>>::remove(collection.id);1247 <AdminAmount<T>>::remove(collection.id);1248 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1249 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1250 <CollectionProperties<T>>::remove(collection.id);12511252 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12531254 <PalletEvm<T>>::deposit_log(1255 erc::CollectionHelpersEvents::CollectionDestroyed {1256 collection_id: eth::collection_id_to_address(collection.id),1257 }1258 .to_log(T::ContractAddress::get()),1259 );1260 Ok(())1261 }12621263 1264 1265 1266 1267 1268 1269 1270 1271 #[transactional]1272 fn modify_collection_properties(1273 collection: &CollectionHandle<T>,1274 sender: &T::CrossAccountId,1275 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1276 ) -> DispatchResult {1277 collection.check_is_owner_or_admin(sender)?;12781279 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12801281 for (key, value) in properties_updates {1282 match value {1283 Some(value) => {1284 stored_properties1285 .try_set(key.clone(), value)1286 .map_err(<Error<T>>::from)?;12871288 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1289 <PalletEvm<T>>::deposit_log(1290 erc::CollectionHelpersEvents::CollectionChanged {1291 collection_id: eth::collection_id_to_address(collection.id),1292 }1293 .to_log(T::ContractAddress::get()),1294 );1295 }1296 None => {1297 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12981299 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1300 <PalletEvm<T>>::deposit_log(1301 erc::CollectionHelpersEvents::CollectionChanged {1302 collection_id: eth::collection_id_to_address(collection.id),1303 }1304 .to_log(T::ContractAddress::get()),1305 );1306 }1307 }1308 }13091310 <CollectionProperties<T>>::set(collection.id, stored_properties);13111312 Ok(())1313 }13141315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 #[allow(clippy::too_many_arguments)]1329 pub fn modify_token_properties<FTO, FTE>(1330 collection: &CollectionHandle<T>,1331 sender: &T::CrossAccountId,1332 token_id: TokenId,1333 is_token_exist: &mut LazyValue<bool, FTE>,1334 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1335 mut stored_properties: TokenProperties,1336 is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,1337 set_token_properties: impl FnOnce(TokenProperties),1338 log: evm_coder::ethereum::Log,1339 ) -> DispatchResult1340 where1341 FTO: FnOnce() -> Result<bool, DispatchError>,1342 FTE: FnOnce() -> bool,1343 {1344 let mut is_collection_admin = LazyValue::new(|| collection.is_owner_or_admin(sender));1345 let permissions = Self::property_permissions(collection.id);13461347 let mut changed = false;1348 for (key, value) in properties_updates {1349 let permission = permissions1350 .get(&key)1351 .cloned()1352 .unwrap_or_else(PropertyPermission::none);13531354 let property_exists = stored_properties.get(&key).is_some();13551356 match permission {1357 PropertyPermission { mutable: false, .. } if property_exists => {1358 return Err(<Error<T>>::NoPermission.into());1359 }13601361 PropertyPermission {1362 collection_admin,1363 token_owner,1364 ..1365 } => check_token_permissions::<T, _, FTO, FTE>(1366 collection_admin,1367 token_owner,1368 &mut is_collection_admin,1369 is_token_owner,1370 is_token_exist,1371 )?,1372 }13731374 match value {1375 Some(value) => {1376 stored_properties1377 .try_set(key.clone(), value)1378 .map_err(<Error<T>>::from)?;13791380 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1381 }1382 None => {1383 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13841385 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1386 }1387 }13881389 changed = true;1390 }13911392 if changed {1393 <PalletEvm<T>>::deposit_log(log);1394 set_token_properties(stored_properties);1395 }13961397 Ok(())1398 }13991400 1401 1402 1403 1404 1405 1406 pub fn set_allowance_for_all(1407 collection: &CollectionHandle<T>,1408 owner: &T::CrossAccountId,1409 operator: &T::CrossAccountId,1410 approve: bool,1411 set_allowance: impl FnOnce(),1412 log: evm_coder::ethereum::Log,1413 ) -> DispatchResult {1414 if collection.permissions.access() == AccessMode::AllowList {1415 collection.check_allowlist(owner)?;1416 collection.check_allowlist(operator)?;1417 }14181419 Self::ensure_correct_receiver(operator)?;14201421 set_allowance();14221423 <PalletEvm<T>>::deposit_log(log);1424 Self::deposit_event(Event::ApprovedForAll(1425 collection.id,1426 owner.clone(),1427 operator.clone(),1428 approve,1429 ));1430 Ok(())1431 }14321433 1434 1435 1436 1437 1438 pub fn set_collection_property(1439 collection: &CollectionHandle<T>,1440 sender: &T::CrossAccountId,1441 property: Property,1442 ) -> DispatchResult {1443 Self::set_collection_properties(collection, sender, [property].into_iter())1444 }14451446 1447 1448 1449 1450 1451 1452 pub fn set_scoped_collection_property(1453 collection_id: CollectionId,1454 scope: PropertyScope,1455 property: Property,1456 ) -> DispatchResult {1457 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1458 properties.try_scoped_set(scope, property.key, property.value)1459 })1460 .map_err(<Error<T>>::from)?;14611462 Ok(())1463 }14641465 1466 1467 1468 1469 1470 1471 pub fn set_scoped_collection_properties(1472 collection_id: CollectionId,1473 scope: PropertyScope,1474 properties: impl Iterator<Item = Property>,1475 ) -> DispatchResult {1476 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1477 stored_properties.try_scoped_set_from_iter(scope, properties)1478 })1479 .map_err(<Error<T>>::from)?;14801481 Ok(())1482 }14831484 1485 1486 1487 1488 1489 pub fn set_collection_properties(1490 collection: &CollectionHandle<T>,1491 sender: &T::CrossAccountId,1492 properties: impl Iterator<Item = Property>,1493 ) -> DispatchResult {1494 Self::modify_collection_properties(1495 collection,1496 sender,1497 properties.map(|property| (property.key, Some(property.value))),1498 )1499 }15001501 1502 1503 1504 1505 1506 pub fn delete_collection_property(1507 collection: &CollectionHandle<T>,1508 sender: &T::CrossAccountId,1509 property_key: PropertyKey,1510 ) -> DispatchResult {1511 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1512 }15131514 1515 1516 1517 1518 1519 pub fn delete_collection_properties(1520 collection: &CollectionHandle<T>,1521 sender: &T::CrossAccountId,1522 property_keys: impl Iterator<Item = PropertyKey>,1523 ) -> DispatchResult {1524 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1525 }15261527 1528 1529 1530 1531 1532 1533 pub fn set_property_permission_unchecked(1534 collection: CollectionId,1535 property_permission: PropertyKeyPermission,1536 ) -> DispatchResult {1537 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1538 permissions.try_set(property_permission.key, property_permission.permission)1539 })1540 .map_err(<Error<T>>::from)?;1541 Ok(())1542 }15431544 1545 1546 1547 1548 1549 pub fn set_property_permission(1550 collection: &CollectionHandle<T>,1551 sender: &T::CrossAccountId,1552 property_permission: PropertyKeyPermission,1553 ) -> DispatchResult {1554 Self::set_scoped_property_permission(1555 collection,1556 sender,1557 PropertyScope::None,1558 property_permission,1559 )1560 }15611562 1563 1564 1565 1566 1567 1568 pub fn set_scoped_property_permission(1569 collection: &CollectionHandle<T>,1570 sender: &T::CrossAccountId,1571 scope: PropertyScope,1572 property_permission: PropertyKeyPermission,1573 ) -> DispatchResult {1574 collection.check_is_owner_or_admin(sender)?;15751576 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1577 let current_permission = all_permissions.get(&property_permission.key);1578 if matches![1579 current_permission,1580 Some(PropertyPermission { mutable: false, .. })1581 ] {1582 return Err(<Error<T>>::NoPermission.into());1583 }15841585 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1586 let property_permission = property_permission.clone();1587 permissions.try_scoped_set(1588 scope,1589 property_permission.key,1590 property_permission.permission,1591 )1592 })1593 .map_err(<Error<T>>::from)?;15941595 Self::deposit_event(Event::PropertyPermissionSet(1596 collection.id,1597 property_permission.key,1598 ));1599 <PalletEvm<T>>::deposit_log(1600 erc::CollectionHelpersEvents::CollectionChanged {1601 collection_id: eth::collection_id_to_address(collection.id),1602 }1603 .to_log(T::ContractAddress::get()),1604 );16051606 Ok(())1607 }16081609 1610 1611 1612 1613 1614 #[transactional]1615 pub fn set_token_property_permissions(1616 collection: &CollectionHandle<T>,1617 sender: &T::CrossAccountId,1618 property_permissions: Vec<PropertyKeyPermission>,1619 ) -> DispatchResult {1620 Self::set_scoped_token_property_permissions(1621 collection,1622 sender,1623 PropertyScope::None,1624 property_permissions,1625 )1626 }16271628 1629 1630 1631 1632 1633 1634 #[transactional]1635 pub fn set_scoped_token_property_permissions(1636 collection: &CollectionHandle<T>,1637 sender: &T::CrossAccountId,1638 scope: PropertyScope,1639 property_permissions: Vec<PropertyKeyPermission>,1640 ) -> DispatchResult {1641 for prop_pemission in property_permissions {1642 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1643 }16441645 Ok(())1646 }16471648 1649 pub fn get_collection_property(1650 collection_id: CollectionId,1651 key: &PropertyKey,1652 ) -> Option<PropertyValue> {1653 Self::collection_properties(collection_id).get(key).cloned()1654 }16551656 1657 pub fn bytes_keys_to_property_keys(1658 keys: Vec<Vec<u8>>,1659 ) -> Result<Vec<PropertyKey>, DispatchError> {1660 keys.into_iter()1661 .map(|key| -> Result<PropertyKey, DispatchError> {1662 key.try_into()1663 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1664 })1665 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1666 }16671668 1669 pub fn filter_collection_properties(1670 collection_id: CollectionId,1671 keys: Option<Vec<PropertyKey>>,1672 ) -> Result<Vec<Property>, DispatchError> {1673 let properties = Self::collection_properties(collection_id);16741675 let properties = keys1676 .map(|keys| {1677 keys.into_iter()1678 .filter_map(|key| {1679 properties.get(&key).map(|value| Property {1680 key,1681 value: value.clone(),1682 })1683 })1684 .collect()1685 })1686 .unwrap_or_else(|| {1687 properties1688 .into_iter()1689 .map(|(key, value)| Property { key, value })1690 .collect()1691 });16921693 Ok(properties)1694 }16951696 1697 pub fn filter_property_permissions(1698 collection_id: CollectionId,1699 keys: Option<Vec<PropertyKey>>,1700 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1701 let permissions = Self::property_permissions(collection_id);17021703 let key_permissions = keys1704 .map(|keys| {1705 keys.into_iter()1706 .filter_map(|key| {1707 permissions1708 .get(&key)1709 .map(|permission| PropertyKeyPermission {1710 key,1711 permission: permission.clone(),1712 })1713 })1714 .collect()1715 })1716 .unwrap_or_else(|| {1717 permissions1718 .into_iter()1719 .map(|(key, permission)| PropertyKeyPermission { key, permission })1720 .collect()1721 });17221723 Ok(key_permissions)1724 }17251726 1727 1728 1729 pub fn toggle_allowlist(1730 collection: &CollectionHandle<T>,1731 sender: &T::CrossAccountId,1732 user: &T::CrossAccountId,1733 allowed: bool,1734 ) -> DispatchResult {1735 collection.check_is_owner_or_admin(sender)?;17361737 17381739 if allowed {1740 <Allowlist<T>>::insert((collection.id, user), true);1741 Self::deposit_event(Event::<T>::AllowListAddressAdded(1742 collection.id,1743 user.clone(),1744 ));1745 } else {1746 <Allowlist<T>>::remove((collection.id, user));1747 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1748 collection.id,1749 user.clone(),1750 ));1751 }17521753 <PalletEvm<T>>::deposit_log(1754 erc::CollectionHelpersEvents::CollectionChanged {1755 collection_id: eth::collection_id_to_address(collection.id),1756 }1757 .to_log(T::ContractAddress::get()),1758 );17591760 Ok(())1761 }17621763 1764 1765 1766 pub fn toggle_admin(1767 collection: &CollectionHandle<T>,1768 sender: &T::CrossAccountId,1769 user: &T::CrossAccountId,1770 admin: bool,1771 ) -> DispatchResult {1772 collection.check_is_internal()?;1773 collection.check_is_owner(sender)?;17741775 let is_admin = <IsAdmin<T>>::get((collection.id, user));1776 if is_admin == admin {1777 if admin {1778 return Ok(());1779 } else {1780 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1781 }1782 }1783 let amount = <AdminAmount<T>>::get(collection.id);17841785 17861787 if admin {1788 let amount = amount1789 .checked_add(1)1790 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1791 ensure!(1792 amount <= Self::collection_admins_limit(),1793 <Error<T>>::CollectionAdminCountExceeded,1794 );17951796 <AdminAmount<T>>::insert(collection.id, amount);1797 <IsAdmin<T>>::insert((collection.id, user), true);17981799 Self::deposit_event(Event::<T>::CollectionAdminAdded(1800 collection.id,1801 user.clone(),1802 ));1803 } else {1804 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1805 <IsAdmin<T>>::remove((collection.id, user));18061807 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1808 collection.id,1809 user.clone(),1810 ));1811 }18121813 <PalletEvm<T>>::deposit_log(1814 erc::CollectionHelpersEvents::CollectionChanged {1815 collection_id: eth::collection_id_to_address(collection.id),1816 }1817 .to_log(T::ContractAddress::get()),1818 );18191820 Ok(())1821 }18221823 1824 pub fn update_limits(1825 user: &T::CrossAccountId,1826 collection: &mut CollectionHandle<T>,1827 new_limit: CollectionLimits,1828 ) -> DispatchResult {1829 collection.check_is_internal()?;1830 collection.check_is_owner_or_admin(user)?;18311832 collection.limits =1833 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;18341835 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1836 <PalletEvm<T>>::deposit_log(1837 erc::CollectionHelpersEvents::CollectionChanged {1838 collection_id: eth::collection_id_to_address(collection.id),1839 }1840 .to_log(T::ContractAddress::get()),1841 );18421843 collection.save()1844 }18451846 1847 fn clamp_limits(1848 mode: CollectionMode,1849 old_limit: &CollectionLimits,1850 mut new_limit: CollectionLimits,1851 ) -> Result<CollectionLimits, DispatchError> {1852 let limits = old_limit;1853 limit_default!(old_limit, new_limit,1854 account_token_ownership_limit => ensure!(1855 new_limit <= MAX_TOKEN_OWNERSHIP,1856 <Error<T>>::CollectionLimitBoundsExceeded,1857 ),1858 sponsored_data_size => ensure!(1859 new_limit <= CUSTOM_DATA_LIMIT,1860 <Error<T>>::CollectionLimitBoundsExceeded,1861 ),18621863 sponsored_data_rate_limit => {},1864 token_limit => ensure!(1865 old_limit >= new_limit && new_limit > 0,1866 <Error<T>>::CollectionTokenLimitExceeded1867 ),18681869 sponsor_transfer_timeout(match mode {1870 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1871 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1872 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1873 }) => ensure!(1874 new_limit <= MAX_SPONSOR_TIMEOUT,1875 <Error<T>>::CollectionLimitBoundsExceeded,1876 ),1877 sponsor_approve_timeout => {},1878 owner_can_transfer => ensure!(1879 !limits.owner_can_transfer_instaled() ||1880 old_limit || !new_limit,1881 <Error<T>>::OwnerPermissionsCantBeReverted,1882 ),1883 owner_can_destroy => ensure!(1884 old_limit || !new_limit,1885 <Error<T>>::OwnerPermissionsCantBeReverted,1886 ),1887 transfers_enabled => {},1888 );1889 Ok(new_limit)1890 }18911892 1893 pub fn update_permissions(1894 user: &T::CrossAccountId,1895 collection: &mut CollectionHandle<T>,1896 new_permission: CollectionPermissions,1897 ) -> DispatchResult {1898 collection.check_is_internal()?;1899 collection.check_is_owner_or_admin(user)?;1900 collection.permissions = Self::clamp_permissions(1901 collection.mode.clone(),1902 &collection.permissions,1903 new_permission,1904 )?;19051906 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1907 <PalletEvm<T>>::deposit_log(1908 erc::CollectionHelpersEvents::CollectionChanged {1909 collection_id: eth::collection_id_to_address(collection.id),1910 }1911 .to_log(T::ContractAddress::get()),1912 );19131914 collection.save()1915 }19161917 1918 fn clamp_permissions(1919 _mode: CollectionMode,1920 old_permission: &CollectionPermissions,1921 mut new_permission: CollectionPermissions,1922 ) -> Result<CollectionPermissions, DispatchError> {1923 limit_default_clone!(old_permission, new_permission,1924 access => {},1925 mint_mode => {},1926 nesting => { },1927 );1928 Ok(new_permission)1929 }19301931 1932 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1933 CollectionProperties::<T>::mutate(collection_id, |properties| {1934 properties.recompute_consumed_space();1935 });19361937 Ok(())1938 }1939}194019411942#[macro_export]1943macro_rules! unsupported {1944 ($runtime:path) => {1945 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1946 };1947}194819491950pub trait CommonWeightInfo<CrossAccountId> {1951 1952 fn create_item(data: &CreateItemData) -> Weight {1953 Self::create_multiple_items(from_ref(data))1954 }19551956 1957 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19581959 1960 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19611962 1963 fn burn_item() -> Weight;19641965 1966 1967 1968 fn set_collection_properties(amount: u32) -> Weight;19691970 1971 1972 1973 fn delete_collection_properties(amount: u32) -> Weight;19741975 1976 1977 1978 fn set_token_properties(amount: u32) -> Weight;19791980 1981 1982 1983 fn delete_token_properties(amount: u32) -> Weight;19841985 1986 1987 1988 fn set_token_property_permissions(amount: u32) -> Weight;19891990 1991 fn transfer() -> Weight;19921993 1994 fn approve() -> Weight;19951996 1997 fn approve_from() -> Weight;19981999 2000 fn transfer_from() -> Weight;20012002 2003 fn burn_from() -> Weight;20042005 2006 2007 2008 2009 fn burn_recursively_self_raw() -> Weight;20102011 2012 2013 2014 fn burn_recursively_breadth_raw(amount: u32) -> Weight;20152016 2017 2018 2019 2020 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {2021 Self::burn_recursively_self_raw()2022 .saturating_mul(max_selfs.max(1) as u64)2023 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))2024 }20252026 2027 fn token_owner() -> Weight;20282029 2030 fn set_allowance_for_all() -> Weight;20312032 2033 fn force_repair_item() -> Weight;2034}203520362037pub trait RefungibleExtensionsWeightInfo {2038 2039 fn repartition() -> Weight;2040}204120422043204420452046pub trait CommonCollectionOperations<T: Config> {2047 2048 2049 2050 2051 2052 2053 fn create_item(2054 &self,2055 sender: T::CrossAccountId,2056 to: T::CrossAccountId,2057 data: CreateItemData,2058 nesting_budget: &dyn Budget,2059 ) -> DispatchResultWithPostInfo;20602061 2062 2063 2064 2065 2066 2067 fn create_multiple_items(2068 &self,2069 sender: T::CrossAccountId,2070 to: T::CrossAccountId,2071 data: Vec<CreateItemData>,2072 nesting_budget: &dyn Budget,2073 ) -> DispatchResultWithPostInfo;20742075 2076 2077 2078 2079 2080 2081 fn create_multiple_items_ex(2082 &self,2083 sender: T::CrossAccountId,2084 data: CreateItemExData<T::CrossAccountId>,2085 nesting_budget: &dyn Budget,2086 ) -> DispatchResultWithPostInfo;20872088 2089 2090 2091 2092 2093 fn burn_item(2094 &self,2095 sender: T::CrossAccountId,2096 token: TokenId,2097 amount: u128,2098 ) -> DispatchResultWithPostInfo;20992100 2101 2102 2103 2104 2105 2106 fn burn_item_recursively(2107 &self,2108 sender: T::CrossAccountId,2109 token: TokenId,2110 self_budget: &dyn Budget,2111 breadth_budget: &dyn Budget,2112 ) -> DispatchResultWithPostInfo;21132114 2115 2116 2117 2118 fn set_collection_properties(2119 &self,2120 sender: T::CrossAccountId,2121 properties: Vec<Property>,2122 ) -> DispatchResultWithPostInfo;21232124 2125 2126 2127 2128 fn delete_collection_properties(2129 &self,2130 sender: &T::CrossAccountId,2131 property_keys: Vec<PropertyKey>,2132 ) -> DispatchResultWithPostInfo;21332134 2135 2136 2137 2138 2139 2140 2141 2142 2143 fn set_token_properties(2144 &self,2145 sender: T::CrossAccountId,2146 token_id: TokenId,2147 properties: Vec<Property>,2148 budget: &dyn Budget,2149 ) -> DispatchResultWithPostInfo;21502151 2152 2153 2154 2155 2156 2157 2158 2159 2160 fn delete_token_properties(2161 &self,2162 sender: T::CrossAccountId,2163 token_id: TokenId,2164 property_keys: Vec<PropertyKey>,2165 budget: &dyn Budget,2166 ) -> DispatchResultWithPostInfo;21672168 2169 2170 2171 2172 2173 2174 fn set_token_property_permissions(2175 &self,2176 sender: &T::CrossAccountId,2177 property_permissions: Vec<PropertyKeyPermission>,2178 ) -> DispatchResultWithPostInfo;21792180 2181 2182 2183 2184 2185 2186 2187 fn transfer(2188 &self,2189 sender: T::CrossAccountId,2190 to: T::CrossAccountId,2191 token: TokenId,2192 amount: u128,2193 budget: &dyn Budget,2194 ) -> DispatchResultWithPostInfo;21952196 2197 2198 2199 2200 2201 2202 fn approve(2203 &self,2204 sender: T::CrossAccountId,2205 spender: T::CrossAccountId,2206 token: TokenId,2207 amount: u128,2208 ) -> DispatchResultWithPostInfo;22092210 2211 2212 2213 2214 2215 2216 2217 fn approve_from(2218 &self,2219 sender: T::CrossAccountId,2220 from: T::CrossAccountId,2221 to: T::CrossAccountId,2222 token: TokenId,2223 amount: u128,2224 ) -> DispatchResultWithPostInfo;22252226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 fn transfer_from(2237 &self,2238 sender: T::CrossAccountId,2239 from: T::CrossAccountId,2240 to: T::CrossAccountId,2241 token: TokenId,2242 amount: u128,2243 budget: &dyn Budget,2244 ) -> DispatchResultWithPostInfo;22452246 2247 2248 2249 2250 2251 2252 2253 2254 2255 fn burn_from(2256 &self,2257 sender: T::CrossAccountId,2258 from: T::CrossAccountId,2259 token: TokenId,2260 amount: u128,2261 budget: &dyn Budget,2262 ) -> DispatchResultWithPostInfo;22632264 2265 2266 2267 2268 2269 2270 fn check_nesting(2271 &self,2272 sender: T::CrossAccountId,2273 from: (CollectionId, TokenId),2274 under: TokenId,2275 budget: &dyn Budget,2276 ) -> DispatchResult;22772278 2279 2280 2281 2282 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22832284 2285 2286 2287 2288 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22892290 2291 2292 2293 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22942295 2296 fn collection_tokens(&self) -> Vec<TokenId>;22972298 2299 2300 2301 fn token_exists(&self, token: TokenId) -> bool;23022303 2304 fn last_token_id(&self) -> TokenId;23052306 2307 2308 2309 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;23102311 2312 2313 2314 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;23152316 2317 2318 2319 2320 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;23212322 2323 2324 2325 2326 2327 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;23282329 2330 fn total_supply(&self) -> u32;23312332 2333 2334 2335 fn account_balance(&self, account: T::CrossAccountId) -> u32;23362337 2338 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;23392340 2341 fn total_pieces(&self, token: TokenId) -> Option<u128>;23422343 2344 2345 2346 2347 2348 fn allowance(2349 &self,2350 sender: T::CrossAccountId,2351 spender: T::CrossAccountId,2352 token: TokenId,2353 ) -> u128;23542355 2356 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;23572358 2359 2360 2361 2362 fn set_allowance_for_all(2363 &self,2364 owner: T::CrossAccountId,2365 operator: T::CrossAccountId,2366 approve: bool,2367 ) -> DispatchResultWithPostInfo;23682369 2370 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23712372 2373 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2374}237523762377pub trait RefungibleExtensions<T>2378where2379 T: Config,2380{2381 2382 2383 2384 2385 2386 2387 2388 fn repartition(2389 &self,2390 sender: &T::CrossAccountId,2391 token: TokenId,2392 amount: u128,2393 ) -> DispatchResultWithPostInfo;2394}23952396239723982399pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2400 let post_info = PostDispatchInfo {2401 actual_weight: Some(weight),2402 pays_fee: Pays::Yes,2403 };2404 match res {2405 Ok(()) => Ok(post_info),2406 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2407 }2408}24092410impl<T: Config> From<PropertiesError> for Error<T> {2411 fn from(error: PropertiesError) -> Self {2412 match error {2413 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2414 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2415 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2416 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2417 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2418 }2419 }2420}24212422#[cfg(any(feature = "tests", test))]2423#[allow(missing_docs)]2424pub mod tests {2425 use crate::{DispatchResult, DispatchError, LazyValue, Config};24262427 const fn to_bool(u: u8) -> bool {2428 u != 02429 }24302431 #[derive(Debug)]2432 pub struct TestCase {2433 pub collection_admin: bool,2434 pub is_collection_admin: bool,2435 pub token_owner: bool,2436 pub is_token_owner: bool,2437 pub no_permission: bool,2438 }24392440 impl TestCase {2441 const fn new(2442 collection_admin: u8,2443 is_collection_admin: u8,2444 token_owner: u8,2445 is_token_owner: u8,2446 no_permission: u8,2447 ) -> Self {2448 Self {2449 collection_admin: to_bool(collection_admin),2450 is_collection_admin: to_bool(is_collection_admin),2451 token_owner: to_bool(token_owner),2452 is_token_owner: to_bool(is_token_owner),2453 no_permission: to_bool(no_permission),2454 }2455 }2456 }24572458 #[rustfmt::skip]2459 pub const TABLE: [TestCase; 16] = [2460 2461 2462 2463 2464 2465 TestCase::new(0, 0, 0, 0, 1),2466 TestCase::new(0, 0, 0, 1, 1),2467 TestCase::new(0, 0, 1, 0, 1),2468 TestCase::new(0, 0, 1, 1, 0),2469 TestCase::new(0, 1, 0, 0, 1),2470 TestCase::new(0, 1, 0, 1, 1),2471 TestCase::new(0, 1, 1, 0, 1),2472 TestCase::new(0, 1, 1, 1, 0),2473 TestCase::new(1, 0, 0, 0, 1),2474 TestCase::new(1, 0, 0, 1, 1),2475 TestCase::new(1, 0, 1, 0, 1),2476 TestCase::new(1, 0, 1, 1, 0),2477 TestCase::new(1, 1, 0, 0, 0),2478 TestCase::new(1, 1, 0, 1, 0),2479 TestCase::new(1, 1, 1, 0, 0),2480 TestCase::new(1, 1, 1, 1, 0),2481 ];24822483 pub fn check_token_permissions<T, FCA, FTO, FTE>(2484 collection_admin_permitted: bool,2485 token_owner_permitted: bool,2486 is_collection_admin: &mut LazyValue<bool, FCA>,2487 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>, FTO>,2488 check_token_existence: &mut LazyValue<bool, FTE>,2489 ) -> DispatchResult2490 where2491 T: Config,2492 FCA: FnOnce() -> bool,2493 FTO: FnOnce() -> Result<bool, DispatchError>,2494 FTE: FnOnce() -> bool,2495 {2496 crate::check_token_permissions::<T, FCA, FTO, FTE>(2497 collection_admin_permitted,2498 token_owner_permitted,2499 is_collection_admin,2500 check_token_ownership,2501 check_token_existence,2502 )2503 }2504}