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 }13951396 set_token_properties(stored_properties);13971398 Ok(())1399 }14001401 1402 1403 1404 1405 1406 1407 pub fn set_allowance_for_all(1408 collection: &CollectionHandle<T>,1409 owner: &T::CrossAccountId,1410 operator: &T::CrossAccountId,1411 approve: bool,1412 set_allowance: impl FnOnce(),1413 log: evm_coder::ethereum::Log,1414 ) -> DispatchResult {1415 if collection.permissions.access() == AccessMode::AllowList {1416 collection.check_allowlist(owner)?;1417 collection.check_allowlist(operator)?;1418 }14191420 Self::ensure_correct_receiver(operator)?;14211422 set_allowance();14231424 <PalletEvm<T>>::deposit_log(log);1425 Self::deposit_event(Event::ApprovedForAll(1426 collection.id,1427 owner.clone(),1428 operator.clone(),1429 approve,1430 ));1431 Ok(())1432 }14331434 1435 1436 1437 1438 1439 pub fn set_collection_property(1440 collection: &CollectionHandle<T>,1441 sender: &T::CrossAccountId,1442 property: Property,1443 ) -> DispatchResult {1444 Self::set_collection_properties(collection, sender, [property].into_iter())1445 }14461447 1448 1449 1450 1451 1452 1453 pub fn set_scoped_collection_property(1454 collection_id: CollectionId,1455 scope: PropertyScope,1456 property: Property,1457 ) -> DispatchResult {1458 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1459 properties.try_scoped_set(scope, property.key, property.value)1460 })1461 .map_err(<Error<T>>::from)?;14621463 Ok(())1464 }14651466 1467 1468 1469 1470 1471 1472 pub fn set_scoped_collection_properties(1473 collection_id: CollectionId,1474 scope: PropertyScope,1475 properties: impl Iterator<Item = Property>,1476 ) -> DispatchResult {1477 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1478 stored_properties.try_scoped_set_from_iter(scope, properties)1479 })1480 .map_err(<Error<T>>::from)?;14811482 Ok(())1483 }14841485 1486 1487 1488 1489 1490 pub fn set_collection_properties(1491 collection: &CollectionHandle<T>,1492 sender: &T::CrossAccountId,1493 properties: impl Iterator<Item = Property>,1494 ) -> DispatchResult {1495 Self::modify_collection_properties(1496 collection,1497 sender,1498 properties.map(|property| (property.key, Some(property.value))),1499 )1500 }15011502 1503 1504 1505 1506 1507 pub fn delete_collection_property(1508 collection: &CollectionHandle<T>,1509 sender: &T::CrossAccountId,1510 property_key: PropertyKey,1511 ) -> DispatchResult {1512 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1513 }15141515 1516 1517 1518 1519 1520 pub fn delete_collection_properties(1521 collection: &CollectionHandle<T>,1522 sender: &T::CrossAccountId,1523 property_keys: impl Iterator<Item = PropertyKey>,1524 ) -> DispatchResult {1525 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1526 }15271528 1529 1530 1531 1532 1533 1534 pub fn set_property_permission_unchecked(1535 collection: CollectionId,1536 property_permission: PropertyKeyPermission,1537 ) -> DispatchResult {1538 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1539 permissions.try_set(property_permission.key, property_permission.permission)1540 })1541 .map_err(<Error<T>>::from)?;1542 Ok(())1543 }15441545 1546 1547 1548 1549 1550 pub fn set_property_permission(1551 collection: &CollectionHandle<T>,1552 sender: &T::CrossAccountId,1553 property_permission: PropertyKeyPermission,1554 ) -> DispatchResult {1555 Self::set_scoped_property_permission(1556 collection,1557 sender,1558 PropertyScope::None,1559 property_permission,1560 )1561 }15621563 1564 1565 1566 1567 1568 1569 pub fn set_scoped_property_permission(1570 collection: &CollectionHandle<T>,1571 sender: &T::CrossAccountId,1572 scope: PropertyScope,1573 property_permission: PropertyKeyPermission,1574 ) -> DispatchResult {1575 collection.check_is_owner_or_admin(sender)?;15761577 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1578 let current_permission = all_permissions.get(&property_permission.key);1579 if matches![1580 current_permission,1581 Some(PropertyPermission { mutable: false, .. })1582 ] {1583 return Err(<Error<T>>::NoPermission.into());1584 }15851586 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1587 let property_permission = property_permission.clone();1588 permissions.try_scoped_set(1589 scope,1590 property_permission.key,1591 property_permission.permission,1592 )1593 })1594 .map_err(<Error<T>>::from)?;15951596 Self::deposit_event(Event::PropertyPermissionSet(1597 collection.id,1598 property_permission.key,1599 ));1600 <PalletEvm<T>>::deposit_log(1601 erc::CollectionHelpersEvents::CollectionChanged {1602 collection_id: eth::collection_id_to_address(collection.id),1603 }1604 .to_log(T::ContractAddress::get()),1605 );16061607 Ok(())1608 }16091610 1611 1612 1613 1614 1615 #[transactional]1616 pub fn set_token_property_permissions(1617 collection: &CollectionHandle<T>,1618 sender: &T::CrossAccountId,1619 property_permissions: Vec<PropertyKeyPermission>,1620 ) -> DispatchResult {1621 Self::set_scoped_token_property_permissions(1622 collection,1623 sender,1624 PropertyScope::None,1625 property_permissions,1626 )1627 }16281629 1630 1631 1632 1633 1634 1635 #[transactional]1636 pub fn set_scoped_token_property_permissions(1637 collection: &CollectionHandle<T>,1638 sender: &T::CrossAccountId,1639 scope: PropertyScope,1640 property_permissions: Vec<PropertyKeyPermission>,1641 ) -> DispatchResult {1642 for prop_pemission in property_permissions {1643 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1644 }16451646 Ok(())1647 }16481649 1650 pub fn get_collection_property(1651 collection_id: CollectionId,1652 key: &PropertyKey,1653 ) -> Option<PropertyValue> {1654 Self::collection_properties(collection_id).get(key).cloned()1655 }16561657 1658 pub fn bytes_keys_to_property_keys(1659 keys: Vec<Vec<u8>>,1660 ) -> Result<Vec<PropertyKey>, DispatchError> {1661 keys.into_iter()1662 .map(|key| -> Result<PropertyKey, DispatchError> {1663 key.try_into()1664 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1665 })1666 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1667 }16681669 1670 pub fn filter_collection_properties(1671 collection_id: CollectionId,1672 keys: Option<Vec<PropertyKey>>,1673 ) -> Result<Vec<Property>, DispatchError> {1674 let properties = Self::collection_properties(collection_id);16751676 let properties = keys1677 .map(|keys| {1678 keys.into_iter()1679 .filter_map(|key| {1680 properties.get(&key).map(|value| Property {1681 key,1682 value: value.clone(),1683 })1684 })1685 .collect()1686 })1687 .unwrap_or_else(|| {1688 properties1689 .into_iter()1690 .map(|(key, value)| Property { key, value })1691 .collect()1692 });16931694 Ok(properties)1695 }16961697 1698 pub fn filter_property_permissions(1699 collection_id: CollectionId,1700 keys: Option<Vec<PropertyKey>>,1701 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1702 let permissions = Self::property_permissions(collection_id);17031704 let key_permissions = keys1705 .map(|keys| {1706 keys.into_iter()1707 .filter_map(|key| {1708 permissions1709 .get(&key)1710 .map(|permission| PropertyKeyPermission {1711 key,1712 permission: permission.clone(),1713 })1714 })1715 .collect()1716 })1717 .unwrap_or_else(|| {1718 permissions1719 .into_iter()1720 .map(|(key, permission)| PropertyKeyPermission { key, permission })1721 .collect()1722 });17231724 Ok(key_permissions)1725 }17261727 1728 1729 1730 pub fn toggle_allowlist(1731 collection: &CollectionHandle<T>,1732 sender: &T::CrossAccountId,1733 user: &T::CrossAccountId,1734 allowed: bool,1735 ) -> DispatchResult {1736 collection.check_is_owner_or_admin(sender)?;17371738 17391740 if allowed {1741 <Allowlist<T>>::insert((collection.id, user), true);1742 Self::deposit_event(Event::<T>::AllowListAddressAdded(1743 collection.id,1744 user.clone(),1745 ));1746 } else {1747 <Allowlist<T>>::remove((collection.id, user));1748 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1749 collection.id,1750 user.clone(),1751 ));1752 }17531754 <PalletEvm<T>>::deposit_log(1755 erc::CollectionHelpersEvents::CollectionChanged {1756 collection_id: eth::collection_id_to_address(collection.id),1757 }1758 .to_log(T::ContractAddress::get()),1759 );17601761 Ok(())1762 }17631764 1765 1766 1767 pub fn toggle_admin(1768 collection: &CollectionHandle<T>,1769 sender: &T::CrossAccountId,1770 user: &T::CrossAccountId,1771 admin: bool,1772 ) -> DispatchResult {1773 collection.check_is_internal()?;1774 collection.check_is_owner(sender)?;17751776 let is_admin = <IsAdmin<T>>::get((collection.id, user));1777 if is_admin == admin {1778 if admin {1779 return Ok(());1780 } else {1781 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1782 }1783 }1784 let amount = <AdminAmount<T>>::get(collection.id);17851786 17871788 if admin {1789 let amount = amount1790 .checked_add(1)1791 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1792 ensure!(1793 amount <= Self::collection_admins_limit(),1794 <Error<T>>::CollectionAdminCountExceeded,1795 );17961797 <AdminAmount<T>>::insert(collection.id, amount);1798 <IsAdmin<T>>::insert((collection.id, user), true);17991800 Self::deposit_event(Event::<T>::CollectionAdminAdded(1801 collection.id,1802 user.clone(),1803 ));1804 } else {1805 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1806 <IsAdmin<T>>::remove((collection.id, user));18071808 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1809 collection.id,1810 user.clone(),1811 ));1812 }18131814 <PalletEvm<T>>::deposit_log(1815 erc::CollectionHelpersEvents::CollectionChanged {1816 collection_id: eth::collection_id_to_address(collection.id),1817 }1818 .to_log(T::ContractAddress::get()),1819 );18201821 Ok(())1822 }18231824 1825 pub fn update_limits(1826 user: &T::CrossAccountId,1827 collection: &mut CollectionHandle<T>,1828 new_limit: CollectionLimits,1829 ) -> DispatchResult {1830 collection.check_is_internal()?;1831 collection.check_is_owner_or_admin(user)?;18321833 collection.limits =1834 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;18351836 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1837 <PalletEvm<T>>::deposit_log(1838 erc::CollectionHelpersEvents::CollectionChanged {1839 collection_id: eth::collection_id_to_address(collection.id),1840 }1841 .to_log(T::ContractAddress::get()),1842 );18431844 collection.save()1845 }18461847 1848 fn clamp_limits(1849 mode: CollectionMode,1850 old_limit: &CollectionLimits,1851 mut new_limit: CollectionLimits,1852 ) -> Result<CollectionLimits, DispatchError> {1853 let limits = old_limit;1854 limit_default!(old_limit, new_limit,1855 account_token_ownership_limit => ensure!(1856 new_limit <= MAX_TOKEN_OWNERSHIP,1857 <Error<T>>::CollectionLimitBoundsExceeded,1858 ),1859 sponsored_data_size => ensure!(1860 new_limit <= CUSTOM_DATA_LIMIT,1861 <Error<T>>::CollectionLimitBoundsExceeded,1862 ),18631864 sponsored_data_rate_limit => {},1865 token_limit => ensure!(1866 old_limit >= new_limit && new_limit > 0,1867 <Error<T>>::CollectionTokenLimitExceeded1868 ),18691870 sponsor_transfer_timeout(match mode {1871 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1872 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1873 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1874 }) => ensure!(1875 new_limit <= MAX_SPONSOR_TIMEOUT,1876 <Error<T>>::CollectionLimitBoundsExceeded,1877 ),1878 sponsor_approve_timeout => {},1879 owner_can_transfer => ensure!(1880 !limits.owner_can_transfer_instaled() ||1881 old_limit || !new_limit,1882 <Error<T>>::OwnerPermissionsCantBeReverted,1883 ),1884 owner_can_destroy => ensure!(1885 old_limit || !new_limit,1886 <Error<T>>::OwnerPermissionsCantBeReverted,1887 ),1888 transfers_enabled => {},1889 );1890 Ok(new_limit)1891 }18921893 1894 pub fn update_permissions(1895 user: &T::CrossAccountId,1896 collection: &mut CollectionHandle<T>,1897 new_permission: CollectionPermissions,1898 ) -> DispatchResult {1899 collection.check_is_internal()?;1900 collection.check_is_owner_or_admin(user)?;1901 collection.permissions = Self::clamp_permissions(1902 collection.mode.clone(),1903 &collection.permissions,1904 new_permission,1905 )?;19061907 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1908 <PalletEvm<T>>::deposit_log(1909 erc::CollectionHelpersEvents::CollectionChanged {1910 collection_id: eth::collection_id_to_address(collection.id),1911 }1912 .to_log(T::ContractAddress::get()),1913 );19141915 collection.save()1916 }19171918 1919 fn clamp_permissions(1920 _mode: CollectionMode,1921 old_permission: &CollectionPermissions,1922 mut new_permission: CollectionPermissions,1923 ) -> Result<CollectionPermissions, DispatchError> {1924 limit_default_clone!(old_permission, new_permission,1925 access => {},1926 mint_mode => {},1927 nesting => { },1928 );1929 Ok(new_permission)1930 }19311932 1933 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1934 CollectionProperties::<T>::mutate(collection_id, |properties| {1935 properties.recompute_consumed_space();1936 });19371938 Ok(())1939 }1940}194119421943#[macro_export]1944macro_rules! unsupported {1945 ($runtime:path) => {1946 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1947 };1948}194919501951pub trait CommonWeightInfo<CrossAccountId> {1952 1953 fn create_item(data: &CreateItemData) -> Weight {1954 Self::create_multiple_items(from_ref(data))1955 }19561957 1958 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;19591960 1961 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;19621963 1964 fn burn_item() -> Weight;19651966 1967 1968 1969 fn set_collection_properties(amount: u32) -> Weight;19701971 1972 1973 1974 fn delete_collection_properties(amount: u32) -> Weight;19751976 1977 1978 1979 fn set_token_properties(amount: u32) -> Weight;19801981 1982 1983 1984 fn delete_token_properties(amount: u32) -> Weight;19851986 1987 1988 1989 fn set_token_property_permissions(amount: u32) -> Weight;19901991 1992 fn transfer() -> Weight;19931994 1995 fn approve() -> Weight;19961997 1998 fn approve_from() -> Weight;19992000 2001 fn transfer_from() -> Weight;20022003 2004 fn burn_from() -> Weight;20052006 2007 2008 2009 2010 fn burn_recursively_self_raw() -> Weight;20112012 2013 2014 2015 fn burn_recursively_breadth_raw(amount: u32) -> Weight;20162017 2018 2019 2020 2021 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {2022 Self::burn_recursively_self_raw()2023 .saturating_mul(max_selfs.max(1) as u64)2024 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))2025 }20262027 2028 fn token_owner() -> Weight;20292030 2031 fn set_allowance_for_all() -> Weight;20322033 2034 fn force_repair_item() -> Weight;2035}203620372038pub trait RefungibleExtensionsWeightInfo {2039 2040 fn repartition() -> Weight;2041}204220432044204520462047pub trait CommonCollectionOperations<T: Config> {2048 2049 2050 2051 2052 2053 2054 fn create_item(2055 &self,2056 sender: T::CrossAccountId,2057 to: T::CrossAccountId,2058 data: CreateItemData,2059 nesting_budget: &dyn Budget,2060 ) -> DispatchResultWithPostInfo;20612062 2063 2064 2065 2066 2067 2068 fn create_multiple_items(2069 &self,2070 sender: T::CrossAccountId,2071 to: T::CrossAccountId,2072 data: Vec<CreateItemData>,2073 nesting_budget: &dyn Budget,2074 ) -> DispatchResultWithPostInfo;20752076 2077 2078 2079 2080 2081 2082 fn create_multiple_items_ex(2083 &self,2084 sender: T::CrossAccountId,2085 data: CreateItemExData<T::CrossAccountId>,2086 nesting_budget: &dyn Budget,2087 ) -> DispatchResultWithPostInfo;20882089 2090 2091 2092 2093 2094 fn burn_item(2095 &self,2096 sender: T::CrossAccountId,2097 token: TokenId,2098 amount: u128,2099 ) -> DispatchResultWithPostInfo;21002101 2102 2103 2104 2105 2106 2107 fn burn_item_recursively(2108 &self,2109 sender: T::CrossAccountId,2110 token: TokenId,2111 self_budget: &dyn Budget,2112 breadth_budget: &dyn Budget,2113 ) -> DispatchResultWithPostInfo;21142115 2116 2117 2118 2119 fn set_collection_properties(2120 &self,2121 sender: T::CrossAccountId,2122 properties: Vec<Property>,2123 ) -> DispatchResultWithPostInfo;21242125 2126 2127 2128 2129 fn delete_collection_properties(2130 &self,2131 sender: &T::CrossAccountId,2132 property_keys: Vec<PropertyKey>,2133 ) -> DispatchResultWithPostInfo;21342135 2136 2137 2138 2139 2140 2141 2142 2143 2144 fn set_token_properties(2145 &self,2146 sender: T::CrossAccountId,2147 token_id: TokenId,2148 properties: Vec<Property>,2149 budget: &dyn Budget,2150 ) -> DispatchResultWithPostInfo;21512152 2153 2154 2155 2156 2157 2158 2159 2160 2161 fn delete_token_properties(2162 &self,2163 sender: T::CrossAccountId,2164 token_id: TokenId,2165 property_keys: Vec<PropertyKey>,2166 budget: &dyn Budget,2167 ) -> DispatchResultWithPostInfo;21682169 2170 2171 2172 2173 2174 2175 fn set_token_property_permissions(2176 &self,2177 sender: &T::CrossAccountId,2178 property_permissions: Vec<PropertyKeyPermission>,2179 ) -> DispatchResultWithPostInfo;21802181 2182 2183 2184 2185 2186 2187 2188 fn transfer(2189 &self,2190 sender: T::CrossAccountId,2191 to: T::CrossAccountId,2192 token: TokenId,2193 amount: u128,2194 budget: &dyn Budget,2195 ) -> DispatchResultWithPostInfo;21962197 2198 2199 2200 2201 2202 2203 fn approve(2204 &self,2205 sender: T::CrossAccountId,2206 spender: T::CrossAccountId,2207 token: TokenId,2208 amount: u128,2209 ) -> DispatchResultWithPostInfo;22102211 2212 2213 2214 2215 2216 2217 2218 fn approve_from(2219 &self,2220 sender: T::CrossAccountId,2221 from: T::CrossAccountId,2222 to: T::CrossAccountId,2223 token: TokenId,2224 amount: u128,2225 ) -> DispatchResultWithPostInfo;22262227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 fn transfer_from(2238 &self,2239 sender: T::CrossAccountId,2240 from: T::CrossAccountId,2241 to: T::CrossAccountId,2242 token: TokenId,2243 amount: u128,2244 budget: &dyn Budget,2245 ) -> DispatchResultWithPostInfo;22462247 2248 2249 2250 2251 2252 2253 2254 2255 2256 fn burn_from(2257 &self,2258 sender: T::CrossAccountId,2259 from: T::CrossAccountId,2260 token: TokenId,2261 amount: u128,2262 budget: &dyn Budget,2263 ) -> DispatchResultWithPostInfo;22642265 2266 2267 2268 2269 2270 2271 fn check_nesting(2272 &self,2273 sender: T::CrossAccountId,2274 from: (CollectionId, TokenId),2275 under: TokenId,2276 budget: &dyn Budget,2277 ) -> DispatchResult;22782279 2280 2281 2282 2283 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22842285 2286 2287 2288 2289 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22902291 2292 2293 2294 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22952296 2297 fn collection_tokens(&self) -> Vec<TokenId>;22982299 2300 2301 2302 fn token_exists(&self, token: TokenId) -> bool;23032304 2305 fn last_token_id(&self) -> TokenId;23062307 2308 2309 2310 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;23112312 2313 2314 2315 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;23162317 2318 2319 2320 2321 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;23222323 2324 2325 2326 2327 2328 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;23292330 2331 fn total_supply(&self) -> u32;23322333 2334 2335 2336 fn account_balance(&self, account: T::CrossAccountId) -> u32;23372338 2339 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;23402341 2342 fn total_pieces(&self, token: TokenId) -> Option<u128>;23432344 2345 2346 2347 2348 2349 fn allowance(2350 &self,2351 sender: T::CrossAccountId,2352 spender: T::CrossAccountId,2353 token: TokenId,2354 ) -> u128;23552356 2357 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;23582359 2360 2361 2362 2363 fn set_allowance_for_all(2364 &self,2365 owner: T::CrossAccountId,2366 operator: T::CrossAccountId,2367 approve: bool,2368 ) -> DispatchResultWithPostInfo;23692370 2371 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23722373 2374 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2375}237623772378pub trait RefungibleExtensions<T>2379where2380 T: Config,2381{2382 2383 2384 2385 2386 2387 2388 2389 fn repartition(2390 &self,2391 sender: &T::CrossAccountId,2392 token: TokenId,2393 amount: u128,2394 ) -> DispatchResultWithPostInfo;2395}23962397239823992400pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2401 let post_info = PostDispatchInfo {2402 actual_weight: Some(weight),2403 pays_fee: Pays::Yes,2404 };2405 match res {2406 Ok(()) => Ok(post_info),2407 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2408 }2409}24102411impl<T: Config> From<PropertiesError> for Error<T> {2412 fn from(error: PropertiesError) -> Self {2413 match error {2414 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2415 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2416 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2417 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2418 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2419 }2420 }2421}24222423#[cfg(any(feature = "tests", test))]2424#[allow(missing_docs)]2425pub mod tests {2426 use crate::{DispatchResult, DispatchError, LazyValue, Config};24272428 const fn to_bool(u: u8) -> bool {2429 u != 02430 }24312432 #[derive(Debug)]2433 pub struct TestCase {2434 pub collection_admin: bool,2435 pub is_collection_admin: bool,2436 pub token_owner: bool,2437 pub is_token_owner: bool,2438 pub no_permission: bool,2439 }24402441 impl TestCase {2442 const fn new(2443 collection_admin: u8,2444 is_collection_admin: u8,2445 token_owner: u8,2446 is_token_owner: u8,2447 no_permission: u8,2448 ) -> Self {2449 Self {2450 collection_admin: to_bool(collection_admin),2451 is_collection_admin: to_bool(is_collection_admin),2452 token_owner: to_bool(token_owner),2453 is_token_owner: to_bool(is_token_owner),2454 no_permission: to_bool(no_permission),2455 }2456 }2457 }24582459 #[rustfmt::skip]2460 pub const TABLE: [TestCase; 16] = [2461 2462 2463 2464 2465 2466 TestCase::new(0, 0, 0, 0, 1),2467 TestCase::new(0, 0, 0, 1, 1),2468 TestCase::new(0, 0, 1, 0, 1),2469 TestCase::new(0, 0, 1, 1, 0),2470 TestCase::new(0, 1, 0, 0, 1),2471 TestCase::new(0, 1, 0, 1, 1),2472 TestCase::new(0, 1, 1, 0, 1),2473 TestCase::new(0, 1, 1, 1, 0),2474 TestCase::new(1, 0, 0, 0, 1),2475 TestCase::new(1, 0, 0, 1, 1),2476 TestCase::new(1, 0, 1, 0, 1),2477 TestCase::new(1, 0, 1, 1, 0),2478 TestCase::new(1, 1, 0, 0, 0),2479 TestCase::new(1, 1, 0, 1, 0),2480 TestCase::new(1, 1, 1, 0, 0),2481 TestCase::new(1, 1, 1, 1, 0),2482 ];24832484 pub fn check_token_permissions<T, FCA, FTO, FTE>(2485 collection_admin_permitted: bool,2486 token_owner_permitted: bool,2487 is_collection_admin: &mut LazyValue<bool, FCA>,2488 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>, FTO>,2489 check_token_existence: &mut LazyValue<bool, FTE>,2490 ) -> DispatchResult2491 where2492 T: Config,2493 FCA: FnOnce() -> bool,2494 FTO: FnOnce() -> Result<bool, DispatchError>,2495 FTE: FnOnce() -> bool,2496 {2497 crate::check_token_permissions::<T, FCA, FTO, FTE>(2498 collection_admin_permitted,2499 token_owner_permitted,2500 is_collection_admin,2501 check_token_ownership,2502 check_token_existence,2503 )2504 }2505}