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 marker::PhantomData,60};61use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};62use sp_std::vec::Vec;63use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};64use evm_coder::ToLog;65use frame_support::{66 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},67 ensure,68 traits::{69 Get,70 fungible::{Balanced, Debt, Inspect},71 tokens::{Imbalance, Precision, Preservation},72 },73 dispatch::Pays,74 transactional, fail,75};76use up_data_structs::{77 AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, RpcCollectionFlags,78 CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, TokenId,79 TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,80 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,81 CUSTOM_DATA_LIMIT, CollectionLimits, CreateCollectionData, SponsorshipState, CreateItemExData,82 SponsoringRateLimit, budget::Budget, PhantomType, Property,83 CollectionProperties as CollectionPropertiesT, TokenProperties, PropertiesPermissionMap,84 PropertyKey, PropertyValue, PropertyPermission, PropertiesError, TokenOwnerError,85 PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope, CollectionPermissions,86};87use up_pov_estimate_rpc::PovInfo;8889pub use pallet::*;90use sp_core::H160;91use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, traits::Zero};9293#[cfg(feature = "runtime-benchmarks")]94pub mod benchmarking;95pub mod dispatch;96pub mod erc;97pub mod eth;98pub mod helpers;99#[allow(missing_docs)]100pub mod weights;101102use weights::WeightInfo;103104105pub type SelfWeightOf<T> = <T as Config>::WeightInfo;106107108109110111112113#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]114pub struct CollectionHandle<T: Config> {115 116 pub id: CollectionId,117 collection: Collection<T::AccountId>,118 119 pub recorder: SubstrateRecorder<T>,120}121122impl<T: Config> WithRecorder<T> for CollectionHandle<T> {123 fn recorder(&self) -> &SubstrateRecorder<T> {124 &self.recorder125 }126 fn into_recorder(self) -> SubstrateRecorder<T> {127 self.recorder128 }129}130131impl<T: Config> CollectionHandle<T> {132 133 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {134 Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))135 }136137 138 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {139 <CollectionById<T>>::get(id).map(|collection| Self {140 id,141 collection,142 recorder,143 })144 }145146 147 148 pub fn new(id: CollectionId) -> Option<Self> {149 Self::new_with_gas_limit(id, u64::MAX)150 }151152 153 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {154 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)155 }156157 158 pub fn consume_store_reads(159 &self,160 reads: u64,161 ) -> pallet_evm_coder_substrate::execution::Result<()> {162 self.recorder().consume_store_reads(reads)163 }164165 166 pub fn consume_store_writes(167 &self,168 writes: u64,169 ) -> pallet_evm_coder_substrate::execution::Result<()> {170 self.recorder().consume_store_writes(writes)171 }172173 174 pub fn consume_store_reads_and_writes(175 &self,176 reads: u64,177 writes: u64,178 ) -> pallet_evm_coder_substrate::execution::Result<()> {179 self.recorder()180 .consume_store_reads_and_writes(reads, writes)181 }182183 184 pub fn save(&self) -> DispatchResult {185 <CollectionById<T>>::insert(self.id, &self.collection);186 Ok(())187 }188189 190 191 192 193 194 pub fn set_sponsor(195 &mut self,196 sender: &T::CrossAccountId,197 sponsor: T::AccountId,198 ) -> DispatchResult {199 self.check_is_internal()?;200 self.check_is_owner_or_admin(sender)?;201202 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());203204 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));205 <PalletEvm<T>>::deposit_log(206 erc::CollectionHelpersEvents::CollectionChanged {207 collection_id: eth::collection_id_to_address(self.id),208 }209 .to_log(T::ContractAddress::get()),210 );211212 self.save()213 }214215 216 217 218 219 220 221 222 223 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {224 self.check_is_internal()?;225226 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());227228 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));229 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));230 <PalletEvm<T>>::deposit_log(231 erc::CollectionHelpersEvents::CollectionChanged {232 collection_id: eth::collection_id_to_address(self.id),233 }234 .to_log(T::ContractAddress::get()),235 );236237 self.save()238 }239240 241 242 243 244 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {245 self.check_is_internal()?;246 ensure!(247 self.collection.sponsorship.pending_sponsor() == Some(sender),248 Error::<T>::ConfirmSponsorshipFail249 );250251 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());252253 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));254 <PalletEvm<T>>::deposit_log(255 erc::CollectionHelpersEvents::CollectionChanged {256 collection_id: eth::collection_id_to_address(self.id),257 }258 .to_log(T::ContractAddress::get()),259 );260261 self.save()262 }263264 265 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {266 self.check_is_internal()?;267 self.check_is_owner_or_admin(sender)?;268269 self.collection.sponsorship = SponsorshipState::Disabled;270271 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));272 <PalletEvm<T>>::deposit_log(273 erc::CollectionHelpersEvents::CollectionChanged {274 collection_id: eth::collection_id_to_address(self.id),275 }276 .to_log(T::ContractAddress::get()),277 );278 self.save()279 }280281 282 283 284 285 pub fn force_remove_sponsor(&mut self) -> DispatchResult {286 self.check_is_internal()?;287288 self.collection.sponsorship = SponsorshipState::Disabled;289290 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));291 <PalletEvm<T>>::deposit_log(292 erc::CollectionHelpersEvents::CollectionChanged {293 collection_id: eth::collection_id_to_address(self.id),294 }295 .to_log(T::ContractAddress::get()),296 );297 self.save()298 }299300 301 302 pub fn check_is_internal(&self) -> DispatchResult {303 if self.flags.external {304 return Err(<Error<T>>::CollectionIsExternal)?;305 }306307 Ok(())308 }309310 311 312 pub fn check_is_external(&self) -> DispatchResult {313 if !self.flags.external {314 return Err(<Error<T>>::CollectionIsInternal)?;315 }316317 Ok(())318 }319}320321impl<T: Config> Deref for CollectionHandle<T> {322 type Target = Collection<T::AccountId>;323324 fn deref(&self) -> &Self::Target {325 &self.collection326 }327}328329impl<T: Config> DerefMut for CollectionHandle<T> {330 fn deref_mut(&mut self) -> &mut Self::Target {331 &mut self.collection332 }333}334335impl<T: Config> CollectionHandle<T> {336 337 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {338 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);339 Ok(())340 }341342 343 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {344 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))345 }346347 348 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {349 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);350 Ok(())351 }352353 354 355 356 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {357 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)358 }359360 361 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {362 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)363 }364365 366 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {367 ensure!(368 <Allowlist<T>>::get((self.id, user)),369 <Error<T>>::AddressNotInAllowlist370 );371 Ok(())372 }373374 375 376 377 pub fn change_owner(378 &mut self,379 caller: T::CrossAccountId,380 new_owner: T::CrossAccountId,381 ) -> DispatchResult {382 self.check_is_internal()?;383 self.check_is_owner(&caller)?;384 self.collection.owner = new_owner.as_sub().clone();385386 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(387 self.id,388 new_owner.as_sub().clone(),389 ));390 <PalletEvm<T>>::deposit_log(391 erc::CollectionHelpersEvents::CollectionChanged {392 collection_id: eth::collection_id_to_address(self.id),393 }394 .to_log(T::ContractAddress::get()),395 );396397 self.save()398 }399}400401#[frame_support::pallet]402pub mod pallet {403404 use super::*;405 use dispatch::CollectionDispatch;406 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};407 use up_data_structs::{TokenId, mapping::TokenAddressMapping};408 use scale_info::TypeInfo;409 use weights::WeightInfo;410411 #[pallet::config]412 pub trait Config:413 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo414 {415 416 type WeightInfo: WeightInfo;417418 419 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;420421 422 type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;423424 425 #[pallet::constant]426 type CollectionCreationPrice: Get<427 <<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,428 >;429430 431 type CollectionDispatch: CollectionDispatch<Self>;432433 434 type TreasuryAccountId: Get<Self::AccountId>;435436 437 #[pallet::constant]438 type ContractAddress: Get<H160>;439440 441 type EvmTokenAddressMapping: TokenAddressMapping<H160>;442443 444 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;445 }446447 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);448 449 pub const NATIVE_FUNGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);450451 #[pallet::pallet]452 #[pallet::storage_version(STORAGE_VERSION)]453 pub struct Pallet<T>(_);454455 #[pallet::extra_constants]456 impl<T: Config> Pallet<T> {457 458 pub fn collection_admins_limit() -> u32 {459 COLLECTION_ADMINS_LIMIT460 }461 }462463 #[pallet::genesis_config]464 pub struct GenesisConfig<T>(PhantomData<T>);465466 impl<T: Config> Default for GenesisConfig<T> {467 fn default() -> Self {468 Self(Default::default())469 }470 }471472 #[pallet::genesis_build]473 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {474 fn build(&self) {475 StorageVersion::new(1).put::<Pallet<T>>();476 }477 }478479 impl<T: Config> Pallet<T> {480 481 pub fn deposit_event(event: Event<T>) {482 let event = <T as Config>::RuntimeEvent::from(event);483 let event = event.into();484 <frame_system::Pallet<T>>::deposit_event(event)485 }486 }487488 #[pallet::event]489 pub enum Event<T: Config> {490 491 CollectionCreated(492 493 CollectionId,494 495 u8,496 497 T::AccountId,498 ),499500 501 CollectionDestroyed(502 503 CollectionId,504 ),505506 507 ItemCreated(508 509 CollectionId,510 511 TokenId,512 513 T::CrossAccountId,514 515 u128,516 ),517518 519 ItemDestroyed(520 521 CollectionId,522 523 TokenId,524 525 T::CrossAccountId,526 527 u128,528 ),529530 531 Transfer(532 533 CollectionId,534 535 TokenId,536 537 T::CrossAccountId,538 539 T::CrossAccountId,540 541 u128,542 ),543544 545 Approved(546 547 CollectionId,548 549 TokenId,550 551 T::CrossAccountId,552 553 T::CrossAccountId,554 555 u128,556 ),557558 559 ApprovedForAll(560 561 CollectionId,562 563 T::CrossAccountId,564 565 T::CrossAccountId,566 567 bool,568 ),569570 571 CollectionPropertySet(572 573 CollectionId,574 575 PropertyKey,576 ),577578 579 CollectionPropertyDeleted(580 581 CollectionId,582 583 PropertyKey,584 ),585586 587 TokenPropertySet(588 589 CollectionId,590 591 TokenId,592 593 PropertyKey,594 ),595596 597 TokenPropertyDeleted(598 599 CollectionId,600 601 TokenId,602 603 PropertyKey,604 ),605606 607 PropertyPermissionSet(608 609 CollectionId,610 611 PropertyKey,612 ),613614 615 AllowListAddressAdded(616 617 CollectionId,618 619 T::CrossAccountId,620 ),621622 623 AllowListAddressRemoved(624 625 CollectionId,626 627 T::CrossAccountId,628 ),629630 631 CollectionAdminAdded(632 633 CollectionId,634 635 T::CrossAccountId,636 ),637638 639 CollectionAdminRemoved(640 641 CollectionId,642 643 T::CrossAccountId,644 ),645646 647 CollectionLimitSet(648 649 CollectionId,650 ),651652 653 CollectionOwnerChanged(654 655 CollectionId,656 657 T::AccountId,658 ),659660 661 CollectionPermissionSet(662 663 CollectionId,664 ),665666 667 CollectionSponsorSet(668 669 CollectionId,670 671 T::AccountId,672 ),673674 675 SponsorshipConfirmed(676 677 CollectionId,678 679 T::AccountId,680 ),681682 683 CollectionSponsorRemoved(684 685 CollectionId,686 ),687 }688689 #[pallet::error]690 pub enum Error<T> {691 692 CollectionNotFound,693 694 MustBeTokenOwner,695 696 NoPermission,697 698 CantDestroyNotEmptyCollection,699 700 PublicMintingNotAllowed,701 702 AddressNotInAllowlist,703704 705 CollectionNameLimitExceeded,706 707 CollectionDescriptionLimitExceeded,708 709 CollectionTokenPrefixLimitExceeded,710 711 TotalCollectionsLimitExceeded,712 713 CollectionAdminCountExceeded,714 715 CollectionLimitBoundsExceeded,716 717 OwnerPermissionsCantBeReverted,718 719 TransferNotAllowed,720 721 AccountTokenLimitExceeded,722 723 CollectionTokenLimitExceeded,724 725 MetadataFlagFrozen,726727 728 TokenNotFound,729 730 TokenValueTooLow,731 732 ApprovedValueTooLow,733 734 CantApproveMoreThanOwned,735 736 AddressIsNotEthMirror,737738 739 AddressIsZero,740741 742 UnsupportedOperation,743744 745 NotSufficientFounds,746747 748 UserIsNotAllowedToNest,749 750 SourceCollectionIsNotAllowedToNest,751752 753 CollectionFieldSizeExceeded,754755 756 NoSpaceForProperty,757758 759 PropertyLimitReached,760761 762 PropertyKeyIsTooLong,763764 765 InvalidCharacterInPropertyKey,766767 768 EmptyPropertyKey,769770 771 CollectionIsExternal,772773 774 CollectionIsInternal,775776 777 ConfirmSponsorshipFail,778779 780 UserIsNotCollectionAdmin,781 }782783 784 #[pallet::storage]785 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;786787 788 #[pallet::storage]789 pub type DestroyedCollectionCount<T> =790 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;791792 793 #[pallet::storage]794 pub type CollectionById<T> = StorageMap<795 Hasher = Blake2_128Concat,796 Key = CollectionId,797 Value = Collection<<T as frame_system::Config>::AccountId>,798 QueryKind = OptionQuery,799 >;800801 802 #[pallet::storage]803 #[pallet::getter(fn collection_properties)]804 pub type CollectionProperties<T> = StorageMap<805 Hasher = Blake2_128Concat,806 Key = CollectionId,807 Value = CollectionPropertiesT,808 QueryKind = ValueQuery,809 >;810811 812 #[pallet::storage]813 #[pallet::getter(fn property_permissions)]814 pub type CollectionPropertyPermissions<T> = StorageMap<815 Hasher = Blake2_128Concat,816 Key = CollectionId,817 Value = PropertiesPermissionMap,818 QueryKind = ValueQuery,819 >;820821 822 #[pallet::storage]823 pub type AdminAmount<T> = StorageMap<824 Hasher = Blake2_128Concat,825 Key = CollectionId,826 Value = u32,827 QueryKind = ValueQuery,828 >;829830 831 #[pallet::storage]832 pub type IsAdmin<T: Config> = StorageNMap<833 Key = (834 Key<Blake2_128Concat, CollectionId>,835 Key<Blake2_128Concat, T::CrossAccountId>,836 ),837 Value = bool,838 QueryKind = ValueQuery,839 >;840841 842 #[pallet::storage]843 pub type Allowlist<T: Config> = StorageNMap<844 Key = (845 Key<Blake2_128Concat, CollectionId>,846 Key<Blake2_128Concat, T::CrossAccountId>,847 ),848 Value = bool,849 QueryKind = ValueQuery,850 >;851852 853 #[pallet::storage]854 pub type DummyStorageValue<T: Config> = StorageValue<855 Value = (856 CollectionStats,857 CollectionId,858 TokenId,859 TokenChild,860 PhantomType<(861 TokenData<T::CrossAccountId>,862 RpcCollection<T::AccountId>,863 864 PovInfo,865 )>,866 ),867 QueryKind = OptionQuery,868 >;869}870871872pub struct LazyValue<T, F: FnOnce() -> T> {873 value: Option<T>,874 f: Option<F>,875}876877impl<T, F: FnOnce() -> T> LazyValue<T, F> {878 879 pub fn new(f: F) -> Self {880 Self {881 value: None,882 f: Some(f),883 }884 }885886 887 pub fn value(&mut self) -> &T {888 self.compute_value_if_not_already();889 self.value.as_ref().unwrap()890 }891892 893 pub fn value_mut(&mut self) -> &mut T {894 self.compute_value_if_not_already();895 self.value.as_mut().unwrap()896 }897898 fn into_inner(mut self) -> T {899 self.compute_value_if_not_already();900 self.value.unwrap()901 }902903 904 pub fn has_value(&self) -> bool {905 self.value.is_some()906 }907908 fn compute_value_if_not_already(&mut self) {909 if self.value.is_none() {910 self.value = Some(self.f.take().unwrap()())911 }912 }913}914915fn check_token_permissions<T, FCA, FTO, FTE>(916 collection_admin_permitted: bool,917 token_owner_permitted: bool,918 is_collection_admin: &mut LazyValue<bool, FCA>,919 is_token_owner: &mut LazyValue<Result<bool, DispatchError>, FTO>,920 is_token_exist: &mut LazyValue<bool, FTE>,921) -> DispatchResult922where923 T: Config,924 FCA: FnOnce() -> bool,925 FTO: FnOnce() -> Result<bool, DispatchError>,926 FTE: FnOnce() -> bool,927{928 if !(collection_admin_permitted && *is_collection_admin.value()929 || token_owner_permitted && (*is_token_owner.value())?)930 {931 fail!(<Error<T>>::NoPermission);932 }933934 let token_exist_due_to_owner_check_success =935 is_token_owner.has_value() && (*is_token_owner.value())?;936937 938 939 if !token_exist_due_to_owner_check_success {940 941 942 if !is_token_exist.value() {943 fail!(<Error<T>>::TokenNotFound);944 }945 }946947 Ok(())948}949950impl<T: Config> Pallet<T> {951 952 953 954 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {955 ensure!(956 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,957 <Error<T>>::AddressIsZero958 );959 Ok(())960 }961962 963 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {964 <IsAdmin<T>>::iter_prefix((collection,))965 .map(|(a, _)| a)966 .collect()967 }968969 970 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {971 <Allowlist<T>>::iter_prefix((collection,))972 .map(|(a, _)| a)973 .collect()974 }975976 977 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {978 <Allowlist<T>>::get((collection, user))979 }980981 982 pub fn collection_stats() -> CollectionStats {983 let created = <CreatedCollectionCount<T>>::get();984 let destroyed = <DestroyedCollectionCount<T>>::get();985 CollectionStats {986 created: created.0,987 destroyed: destroyed.0,988 alive: created.0 - destroyed.0,989 }990 }991992 993 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {994 let collection = <CollectionById<T>>::get(collection)?;995 let limits = collection.limits;996 let effective_limits = CollectionLimits {997 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),998 sponsored_data_size: Some(limits.sponsored_data_size()),999 sponsored_data_rate_limit: Some(1000 limits1001 .sponsored_data_rate_limit1002 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),1003 ),1004 token_limit: Some(limits.token_limit()),1005 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(1006 match collection.mode {1007 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1008 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1009 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1010 },1011 )),1012 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),1013 owner_can_transfer: Some(limits.owner_can_transfer()),1014 owner_can_destroy: Some(limits.owner_can_destroy()),1015 transfers_enabled: Some(limits.transfers_enabled()),1016 };10171018 Some(effective_limits)1019 }10201021 1022 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {1023 let Collection {1024 name,1025 description,1026 owner,1027 mode,1028 token_prefix,1029 sponsorship,1030 limits,1031 permissions,1032 flags,1033 } = <CollectionById<T>>::get(collection)?;10341035 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)1036 .into_iter()1037 .map(|(key, permission)| PropertyKeyPermission { key, permission })1038 .collect();10391040 let properties = <CollectionProperties<T>>::get(collection)1041 .into_iter()1042 .map(|(key, value)| Property { key, value })1043 .collect();10441045 let permissions = CollectionPermissions {1046 access: Some(permissions.access()),1047 mint_mode: Some(permissions.mint_mode()),1048 nesting: Some(permissions.nesting().clone()),1049 };10501051 Some(RpcCollection {1052 name: name.into_inner(),1053 description: description.into_inner(),1054 owner,1055 mode,1056 token_prefix: token_prefix.into_inner(),1057 sponsorship,1058 limits,1059 permissions,1060 token_property_permissions,1061 properties,1062 read_only: flags.external,10631064 flags: RpcCollectionFlags {1065 foreign: flags.foreign,1066 erc721metadata: flags.erc721metadata,1067 },1068 })1069 }1070}10711072macro_rules! limit_default {1073 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1074 $(1075 if let Some($new) = $new.$field {1076 let $old = $old.$field($($arg)?);1077 let _ = $new;1078 let _ = $old;1079 $check1080 } else {1081 $new.$field = $old.$field1082 }1083 )*1084 }};1085}1086macro_rules! limit_default_clone {1087 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1088 $(1089 if let Some($new) = $new.$field.clone() {1090 let $old = $old.$field($($arg)?);1091 let _ = $new;1092 let _ = $old;1093 $check1094 } else {1095 $new.$field = $old.$field.clone()1096 }1097 )*1098 }};1099}11001101impl<T: Config> Pallet<T> {1102 1103 1104 1105 1106 1107 pub fn init_collection(1108 owner: T::CrossAccountId,1109 payer: T::CrossAccountId,1110 data: CreateCollectionData<T::CrossAccountId>,1111 ) -> Result<CollectionId, DispatchError> {1112 ensure!(data.flags.is_allowed_for_user(), <Error<T>>::NoPermission);1113 Self::init_collection_internal(owner, payer, data)1114 }11151116 1117 pub fn init_foreign_collection(1118 owner: T::CrossAccountId,1119 payer: T::CrossAccountId,1120 mut data: CreateCollectionData<T::CrossAccountId>,1121 ) -> Result<CollectionId, DispatchError> {1122 data.flags.foreign = true;1123 let id = Self::init_collection_internal(owner, payer, data)?;1124 Ok(id)1125 }11261127 fn init_collection_internal(1128 owner: T::CrossAccountId,1129 payer: T::CrossAccountId,1130 data: CreateCollectionData<T::CrossAccountId>,1131 ) -> Result<CollectionId, DispatchError> {1132 {1133 ensure!(1134 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1135 Error::<T>::CollectionTokenPrefixLimitExceeded1136 );1137 }11381139 let created_count = <CreatedCollectionCount<T>>::get()1140 .01141 .checked_add(1)1142 .ok_or(ArithmeticError::Overflow)?;1143 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1144 let id = CollectionId(created_count);11451146 1147 ensure!(1148 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1149 <Error<T>>::TotalCollectionsLimitExceeded1150 );11511152 11531154 let collection = Collection {1155 owner: owner.as_sub().clone(),1156 name: data.name,1157 mode: data.mode.clone(),1158 description: data.description,1159 token_prefix: data.token_prefix,1160 sponsorship: data1161 .pending_sponsor1162 .map(|sponsor| SponsorshipState::Unconfirmed(sponsor.as_sub().clone()))1163 .unwrap_or_default(),1164 limits: data1165 .limits1166 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1167 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1168 permissions: data1169 .permissions1170 .map(|permissions| {1171 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1172 })1173 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1174 flags: data.flags,1175 };11761177 let mut collection_properties = CollectionPropertiesT::new();1178 collection_properties1179 .try_set_from_iter(data.properties.into_iter())1180 .map_err(<Error<T>>::from)?;11811182 CollectionProperties::<T>::insert(id, collection_properties);11831184 let mut token_props_permissions = PropertiesPermissionMap::new();1185 token_props_permissions1186 .try_set_from_iter(data.token_property_permissions.into_iter())1187 .map_err(<Error<T>>::from)?;11881189 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11901191 let mut admin_amount = 0u32;1192 for admin in data.admin_list.iter() {1193 if !<IsAdmin<T>>::get((id, admin)) {1194 <IsAdmin<T>>::insert((id, admin), true);1195 admin_amount = admin_amount1196 .checked_add(1)1197 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1198 }1199 }1200 ensure!(1201 admin_amount <= Self::collection_admins_limit(),1202 <Error<T>>::CollectionAdminCountExceeded,1203 );1204 <AdminAmount<T>>::insert(id, admin_amount);12051206 1207 {1208 let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();1209 imbalance.subsume(<T as Config>::Currency::deposit(1210 &T::TreasuryAccountId::get(),1211 T::CollectionCreationPrice::get(),1212 Precision::Exact,1213 )?);1214 let credit =1215 <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)1216 .map_err(|_| Error::<T>::NotSufficientFounds)?;12171218 debug_assert!(credit.peek().is_zero())1219 }12201221 <CreatedCollectionCount<T>>::put(created_count);1222 <Pallet<T>>::deposit_event(Event::CollectionCreated(1223 id,1224 data.mode.id(),1225 owner.as_sub().clone(),1226 ));1227 <PalletEvm<T>>::deposit_log(1228 erc::CollectionHelpersEvents::CollectionCreated {1229 owner: *owner.as_eth(),1230 collection_id: eth::collection_id_to_address(id),1231 }1232 .to_log(T::ContractAddress::get()),1233 );1234 <CollectionById<T>>::insert(id, collection);1235 Ok(id)1236 }12371238 1239 1240 1241 1242 pub fn destroy_collection(1243 collection: CollectionHandle<T>,1244 sender: &T::CrossAccountId,1245 ) -> DispatchResult {1246 ensure!(1247 collection.limits.owner_can_destroy(),1248 <Error<T>>::NoPermission,1249 );1250 collection.check_is_owner(sender)?;12511252 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1253 .01254 .checked_add(1)1255 .ok_or(ArithmeticError::Overflow)?;12561257 12581259 <DestroyedCollectionCount<T>>::put(destroyed_collections);1260 <CollectionById<T>>::remove(collection.id);1261 <AdminAmount<T>>::remove(collection.id);1262 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1263 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1264 <CollectionProperties<T>>::remove(collection.id);12651266 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));12671268 <PalletEvm<T>>::deposit_log(1269 erc::CollectionHelpersEvents::CollectionDestroyed {1270 collection_id: eth::collection_id_to_address(collection.id),1271 }1272 .to_log(T::ContractAddress::get()),1273 );1274 Ok(())1275 }12761277 1278 1279 1280 1281 1282 1283 1284 1285 #[transactional]1286 fn modify_collection_properties(1287 collection: &CollectionHandle<T>,1288 sender: &T::CrossAccountId,1289 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1290 ) -> DispatchResult {1291 collection.check_is_owner_or_admin(sender)?;12921293 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12941295 for (key, value) in properties_updates {1296 match value {1297 Some(value) => {1298 stored_properties1299 .try_set(key.clone(), value)1300 .map_err(<Error<T>>::from)?;13011302 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1303 <PalletEvm<T>>::deposit_log(1304 erc::CollectionHelpersEvents::CollectionChanged {1305 collection_id: eth::collection_id_to_address(collection.id),1306 }1307 .to_log(T::ContractAddress::get()),1308 );1309 }1310 None => {1311 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13121313 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1314 <PalletEvm<T>>::deposit_log(1315 erc::CollectionHelpersEvents::CollectionChanged {1316 collection_id: eth::collection_id_to_address(collection.id),1317 }1318 .to_log(T::ContractAddress::get()),1319 );1320 }1321 }1322 }13231324 <CollectionProperties<T>>::set(collection.id, stored_properties);13251326 Ok(())1327 }13281329 1330 1331 1332 1333 1334 1335 pub fn set_allowance_for_all(1336 collection: &CollectionHandle<T>,1337 owner: &T::CrossAccountId,1338 operator: &T::CrossAccountId,1339 approve: bool,1340 set_allowance: impl FnOnce(),1341 log: evm_coder::ethereum::Log,1342 ) -> DispatchResult {1343 if collection.permissions.access() == AccessMode::AllowList {1344 collection.check_allowlist(owner)?;1345 collection.check_allowlist(operator)?;1346 }13471348 Self::ensure_correct_receiver(operator)?;13491350 set_allowance();13511352 <PalletEvm<T>>::deposit_log(log);1353 Self::deposit_event(Event::ApprovedForAll(1354 collection.id,1355 owner.clone(),1356 operator.clone(),1357 approve,1358 ));1359 Ok(())1360 }13611362 1363 1364 1365 1366 1367 pub fn set_collection_property(1368 collection: &CollectionHandle<T>,1369 sender: &T::CrossAccountId,1370 property: Property,1371 ) -> DispatchResult {1372 Self::set_collection_properties(collection, sender, [property].into_iter())1373 }13741375 1376 1377 1378 1379 1380 1381 pub fn set_scoped_collection_property(1382 collection_id: CollectionId,1383 scope: PropertyScope,1384 property: Property,1385 ) -> DispatchResult {1386 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1387 properties.try_scoped_set(scope, property.key, property.value)1388 })1389 .map_err(<Error<T>>::from)?;13901391 Ok(())1392 }13931394 1395 1396 1397 1398 1399 1400 pub fn set_scoped_collection_properties(1401 collection_id: CollectionId,1402 scope: PropertyScope,1403 properties: impl Iterator<Item = Property>,1404 ) -> DispatchResult {1405 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1406 stored_properties.try_scoped_set_from_iter(scope, properties)1407 })1408 .map_err(<Error<T>>::from)?;14091410 Ok(())1411 }14121413 1414 1415 1416 1417 1418 pub fn set_collection_properties(1419 collection: &CollectionHandle<T>,1420 sender: &T::CrossAccountId,1421 properties: impl Iterator<Item = Property>,1422 ) -> DispatchResult {1423 Self::modify_collection_properties(1424 collection,1425 sender,1426 properties.map(|property| (property.key, Some(property.value))),1427 )1428 }14291430 1431 1432 1433 1434 1435 pub fn delete_collection_property(1436 collection: &CollectionHandle<T>,1437 sender: &T::CrossAccountId,1438 property_key: PropertyKey,1439 ) -> DispatchResult {1440 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1441 }14421443 1444 1445 1446 1447 1448 pub fn delete_collection_properties(1449 collection: &CollectionHandle<T>,1450 sender: &T::CrossAccountId,1451 property_keys: impl Iterator<Item = PropertyKey>,1452 ) -> DispatchResult {1453 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1454 }14551456 1457 1458 1459 1460 1461 1462 pub fn set_property_permission_unchecked(1463 collection: CollectionId,1464 property_permission: PropertyKeyPermission,1465 ) -> DispatchResult {1466 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1467 permissions.try_set(property_permission.key, property_permission.permission)1468 })1469 .map_err(<Error<T>>::from)?;1470 Ok(())1471 }14721473 1474 1475 1476 1477 1478 pub fn set_property_permission(1479 collection: &CollectionHandle<T>,1480 sender: &T::CrossAccountId,1481 property_permission: PropertyKeyPermission,1482 ) -> DispatchResult {1483 Self::set_scoped_property_permission(1484 collection,1485 sender,1486 PropertyScope::None,1487 property_permission,1488 )1489 }14901491 1492 1493 1494 1495 1496 1497 pub fn set_scoped_property_permission(1498 collection: &CollectionHandle<T>,1499 sender: &T::CrossAccountId,1500 scope: PropertyScope,1501 property_permission: PropertyKeyPermission,1502 ) -> DispatchResult {1503 collection.check_is_owner_or_admin(sender)?;15041505 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1506 let current_permission = all_permissions.get(&property_permission.key);1507 if matches![1508 current_permission,1509 Some(PropertyPermission { mutable: false, .. })1510 ] {1511 return Err(<Error<T>>::NoPermission.into());1512 }15131514 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1515 let property_permission = property_permission.clone();1516 permissions.try_scoped_set(1517 scope,1518 property_permission.key,1519 property_permission.permission,1520 )1521 })1522 .map_err(<Error<T>>::from)?;15231524 Self::deposit_event(Event::PropertyPermissionSet(1525 collection.id,1526 property_permission.key,1527 ));1528 <PalletEvm<T>>::deposit_log(1529 erc::CollectionHelpersEvents::CollectionChanged {1530 collection_id: eth::collection_id_to_address(collection.id),1531 }1532 .to_log(T::ContractAddress::get()),1533 );15341535 Ok(())1536 }15371538 1539 1540 1541 1542 1543 #[transactional]1544 pub fn set_token_property_permissions(1545 collection: &CollectionHandle<T>,1546 sender: &T::CrossAccountId,1547 property_permissions: Vec<PropertyKeyPermission>,1548 ) -> DispatchResult {1549 Self::set_scoped_token_property_permissions(1550 collection,1551 sender,1552 PropertyScope::None,1553 property_permissions,1554 )1555 }15561557 1558 1559 1560 1561 1562 1563 #[transactional]1564 pub fn set_scoped_token_property_permissions(1565 collection: &CollectionHandle<T>,1566 sender: &T::CrossAccountId,1567 scope: PropertyScope,1568 property_permissions: Vec<PropertyKeyPermission>,1569 ) -> DispatchResult {1570 for prop_pemission in property_permissions {1571 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1572 }15731574 Ok(())1575 }15761577 1578 pub fn get_collection_property(1579 collection_id: CollectionId,1580 key: &PropertyKey,1581 ) -> Option<PropertyValue> {1582 Self::collection_properties(collection_id).get(key).cloned()1583 }15841585 1586 pub fn bytes_keys_to_property_keys(1587 keys: Vec<Vec<u8>>,1588 ) -> Result<Vec<PropertyKey>, DispatchError> {1589 keys.into_iter()1590 .map(|key| -> Result<PropertyKey, DispatchError> {1591 key.try_into()1592 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1593 })1594 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1595 }15961597 1598 pub fn filter_collection_properties(1599 collection_id: CollectionId,1600 keys: Option<Vec<PropertyKey>>,1601 ) -> Result<Vec<Property>, DispatchError> {1602 let properties = Self::collection_properties(collection_id);16031604 let properties = keys1605 .map(|keys| {1606 keys.into_iter()1607 .filter_map(|key| {1608 properties.get(&key).map(|value| Property {1609 key,1610 value: value.clone(),1611 })1612 })1613 .collect()1614 })1615 .unwrap_or_else(|| {1616 properties1617 .into_iter()1618 .map(|(key, value)| Property { key, value })1619 .collect()1620 });16211622 Ok(properties)1623 }16241625 1626 pub fn filter_property_permissions(1627 collection_id: CollectionId,1628 keys: Option<Vec<PropertyKey>>,1629 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1630 let permissions = Self::property_permissions(collection_id);16311632 let key_permissions = keys1633 .map(|keys| {1634 keys.into_iter()1635 .filter_map(|key| {1636 permissions1637 .get(&key)1638 .map(|permission| PropertyKeyPermission {1639 key,1640 permission: permission.clone(),1641 })1642 })1643 .collect()1644 })1645 .unwrap_or_else(|| {1646 permissions1647 .into_iter()1648 .map(|(key, permission)| PropertyKeyPermission { key, permission })1649 .collect()1650 });16511652 Ok(key_permissions)1653 }16541655 1656 1657 1658 pub fn toggle_allowlist(1659 collection: &CollectionHandle<T>,1660 sender: &T::CrossAccountId,1661 user: &T::CrossAccountId,1662 allowed: bool,1663 ) -> DispatchResult {1664 collection.check_is_owner_or_admin(sender)?;16651666 16671668 if allowed {1669 <Allowlist<T>>::insert((collection.id, user), true);1670 Self::deposit_event(Event::<T>::AllowListAddressAdded(1671 collection.id,1672 user.clone(),1673 ));1674 } else {1675 <Allowlist<T>>::remove((collection.id, user));1676 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1677 collection.id,1678 user.clone(),1679 ));1680 }16811682 <PalletEvm<T>>::deposit_log(1683 erc::CollectionHelpersEvents::CollectionChanged {1684 collection_id: eth::collection_id_to_address(collection.id),1685 }1686 .to_log(T::ContractAddress::get()),1687 );16881689 Ok(())1690 }16911692 1693 1694 1695 pub fn toggle_admin(1696 collection: &CollectionHandle<T>,1697 sender: &T::CrossAccountId,1698 user: &T::CrossAccountId,1699 admin: bool,1700 ) -> DispatchResult {1701 collection.check_is_internal()?;1702 collection.check_is_owner(sender)?;17031704 let is_admin = <IsAdmin<T>>::get((collection.id, user));1705 if is_admin == admin {1706 if admin {1707 return Ok(());1708 } else {1709 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1710 }1711 }1712 let amount = <AdminAmount<T>>::get(collection.id);17131714 17151716 if admin {1717 let amount = amount1718 .checked_add(1)1719 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1720 ensure!(1721 amount <= Self::collection_admins_limit(),1722 <Error<T>>::CollectionAdminCountExceeded,1723 );17241725 <AdminAmount<T>>::insert(collection.id, amount);1726 <IsAdmin<T>>::insert((collection.id, user), true);17271728 Self::deposit_event(Event::<T>::CollectionAdminAdded(1729 collection.id,1730 user.clone(),1731 ));1732 } else {1733 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1734 <IsAdmin<T>>::remove((collection.id, user));17351736 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1737 collection.id,1738 user.clone(),1739 ));1740 }17411742 <PalletEvm<T>>::deposit_log(1743 erc::CollectionHelpersEvents::CollectionChanged {1744 collection_id: eth::collection_id_to_address(collection.id),1745 }1746 .to_log(T::ContractAddress::get()),1747 );17481749 Ok(())1750 }17511752 1753 pub fn update_limits(1754 user: &T::CrossAccountId,1755 collection: &mut CollectionHandle<T>,1756 new_limit: CollectionLimits,1757 ) -> DispatchResult {1758 collection.check_is_internal()?;1759 collection.check_is_owner_or_admin(user)?;17601761 collection.limits =1762 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17631764 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1765 <PalletEvm<T>>::deposit_log(1766 erc::CollectionHelpersEvents::CollectionChanged {1767 collection_id: eth::collection_id_to_address(collection.id),1768 }1769 .to_log(T::ContractAddress::get()),1770 );17711772 collection.save()1773 }17741775 1776 fn clamp_limits(1777 mode: CollectionMode,1778 old_limit: &CollectionLimits,1779 mut new_limit: CollectionLimits,1780 ) -> Result<CollectionLimits, DispatchError> {1781 let limits = old_limit;1782 limit_default!(old_limit, new_limit,1783 account_token_ownership_limit => ensure!(1784 new_limit <= MAX_TOKEN_OWNERSHIP,1785 <Error<T>>::CollectionLimitBoundsExceeded,1786 ),1787 sponsored_data_size => ensure!(1788 new_limit <= CUSTOM_DATA_LIMIT,1789 <Error<T>>::CollectionLimitBoundsExceeded,1790 ),17911792 sponsored_data_rate_limit => {},1793 token_limit => ensure!(1794 old_limit >= new_limit && new_limit > 0,1795 <Error<T>>::CollectionTokenLimitExceeded1796 ),17971798 sponsor_transfer_timeout(match mode {1799 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1800 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1801 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1802 }) => ensure!(1803 new_limit <= MAX_SPONSOR_TIMEOUT,1804 <Error<T>>::CollectionLimitBoundsExceeded,1805 ),1806 sponsor_approve_timeout => {},1807 owner_can_transfer => ensure!(1808 !limits.owner_can_transfer_instaled() ||1809 old_limit || !new_limit,1810 <Error<T>>::OwnerPermissionsCantBeReverted,1811 ),1812 owner_can_destroy => ensure!(1813 old_limit || !new_limit,1814 <Error<T>>::OwnerPermissionsCantBeReverted,1815 ),1816 transfers_enabled => {},1817 );1818 Ok(new_limit)1819 }18201821 1822 pub fn update_permissions(1823 user: &T::CrossAccountId,1824 collection: &mut CollectionHandle<T>,1825 new_permission: CollectionPermissions,1826 ) -> DispatchResult {1827 collection.check_is_internal()?;1828 collection.check_is_owner_or_admin(user)?;1829 collection.permissions = Self::clamp_permissions(1830 collection.mode.clone(),1831 &collection.permissions,1832 new_permission,1833 )?;18341835 Self::deposit_event(Event::<T>::CollectionPermissionSet(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_permissions(1848 _mode: CollectionMode,1849 old_permission: &CollectionPermissions,1850 mut new_permission: CollectionPermissions,1851 ) -> Result<CollectionPermissions, DispatchError> {1852 limit_default_clone!(old_permission, new_permission,1853 access => {},1854 mint_mode => {},1855 nesting => { },1856 );1857 Ok(new_permission)1858 }18591860 1861 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1862 CollectionProperties::<T>::mutate(collection_id, |properties| {1863 properties.recompute_consumed_space();1864 });18651866 Ok(())1867 }1868}186918701871#[macro_export]1872macro_rules! unsupported {1873 ($runtime:path) => {1874 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1875 };1876}187718781879pub trait CommonWeightInfo<CrossAccountId> {1880 1881 fn create_item(data: &CreateItemData) -> Weight {1882 Self::create_multiple_items(from_ref(data))1883 }18841885 1886 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18871888 1889 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18901891 1892 fn burn_item() -> Weight;18931894 1895 1896 1897 fn set_collection_properties(amount: u32) -> Weight;18981899 1900 1901 1902 fn delete_collection_properties(amount: u32) -> Weight;19031904 1905 1906 1907 fn set_token_properties(amount: u32) -> Weight;19081909 1910 1911 1912 fn delete_token_properties(amount: u32) -> Weight;19131914 1915 1916 1917 fn set_token_property_permissions(amount: u32) -> Weight;19181919 1920 fn transfer() -> Weight;19211922 1923 fn approve() -> Weight;19241925 1926 fn approve_from() -> Weight;19271928 1929 fn transfer_from() -> Weight;19301931 1932 fn burn_from() -> Weight;19331934 1935 1936 1937 1938 fn burn_recursively_self_raw() -> Weight;19391940 1941 1942 1943 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19441945 1946 1947 1948 1949 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1950 Self::burn_recursively_self_raw()1951 .saturating_mul(max_selfs.max(1) as u64)1952 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1953 }19541955 1956 fn token_owner() -> Weight;19571958 1959 fn set_allowance_for_all() -> Weight;19601961 1962 fn force_repair_item() -> Weight;1963}196419651966pub trait RefungibleExtensionsWeightInfo {1967 1968 fn repartition() -> Weight;1969}197019711972197319741975pub trait CommonCollectionOperations<T: Config> {1976 1977 1978 1979 1980 1981 1982 fn create_item(1983 &self,1984 sender: T::CrossAccountId,1985 to: T::CrossAccountId,1986 data: CreateItemData,1987 nesting_budget: &dyn Budget,1988 ) -> DispatchResultWithPostInfo;19891990 1991 1992 1993 1994 1995 1996 fn create_multiple_items(1997 &self,1998 sender: T::CrossAccountId,1999 to: T::CrossAccountId,2000 data: Vec<CreateItemData>,2001 nesting_budget: &dyn Budget,2002 ) -> DispatchResultWithPostInfo;20032004 2005 2006 2007 2008 2009 2010 fn create_multiple_items_ex(2011 &self,2012 sender: T::CrossAccountId,2013 data: CreateItemExData<T::CrossAccountId>,2014 nesting_budget: &dyn Budget,2015 ) -> DispatchResultWithPostInfo;20162017 2018 2019 2020 2021 2022 fn burn_item(2023 &self,2024 sender: T::CrossAccountId,2025 token: TokenId,2026 amount: u128,2027 ) -> DispatchResultWithPostInfo;20282029 2030 2031 2032 2033 2034 2035 fn burn_item_recursively(2036 &self,2037 sender: T::CrossAccountId,2038 token: TokenId,2039 self_budget: &dyn Budget,2040 breadth_budget: &dyn Budget,2041 ) -> DispatchResultWithPostInfo;20422043 2044 2045 2046 2047 fn set_collection_properties(2048 &self,2049 sender: T::CrossAccountId,2050 properties: Vec<Property>,2051 ) -> DispatchResultWithPostInfo;20522053 2054 2055 2056 2057 fn delete_collection_properties(2058 &self,2059 sender: &T::CrossAccountId,2060 property_keys: Vec<PropertyKey>,2061 ) -> DispatchResultWithPostInfo;20622063 2064 2065 2066 2067 2068 2069 2070 2071 2072 fn set_token_properties(2073 &self,2074 sender: T::CrossAccountId,2075 token_id: TokenId,2076 properties: Vec<Property>,2077 budget: &dyn Budget,2078 ) -> DispatchResultWithPostInfo;20792080 2081 2082 2083 2084 2085 2086 2087 2088 2089 fn delete_token_properties(2090 &self,2091 sender: T::CrossAccountId,2092 token_id: TokenId,2093 property_keys: Vec<PropertyKey>,2094 budget: &dyn Budget,2095 ) -> DispatchResultWithPostInfo;20962097 2098 2099 2100 fn get_token_properties_raw(&self, token_id: TokenId) -> Option<TokenProperties>;21012102 2103 2104 2105 2106 fn set_token_properties_raw(&self, token_id: TokenId, map: TokenProperties);21072108 2109 2110 2111 2112 2113 2114 fn set_token_property_permissions(2115 &self,2116 sender: &T::CrossAccountId,2117 property_permissions: Vec<PropertyKeyPermission>,2118 ) -> DispatchResultWithPostInfo;21192120 2121 2122 2123 2124 2125 2126 2127 fn transfer(2128 &self,2129 sender: T::CrossAccountId,2130 to: T::CrossAccountId,2131 token: TokenId,2132 amount: u128,2133 budget: &dyn Budget,2134 ) -> DispatchResultWithPostInfo;21352136 2137 2138 2139 2140 2141 2142 fn approve(2143 &self,2144 sender: T::CrossAccountId,2145 spender: T::CrossAccountId,2146 token: TokenId,2147 amount: u128,2148 ) -> DispatchResultWithPostInfo;21492150 2151 2152 2153 2154 2155 2156 2157 fn approve_from(2158 &self,2159 sender: T::CrossAccountId,2160 from: T::CrossAccountId,2161 to: T::CrossAccountId,2162 token: TokenId,2163 amount: u128,2164 ) -> DispatchResultWithPostInfo;21652166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 fn transfer_from(2177 &self,2178 sender: T::CrossAccountId,2179 from: T::CrossAccountId,2180 to: T::CrossAccountId,2181 token: TokenId,2182 amount: u128,2183 budget: &dyn Budget,2184 ) -> DispatchResultWithPostInfo;21852186 2187 2188 2189 2190 2191 2192 2193 2194 2195 fn burn_from(2196 &self,2197 sender: T::CrossAccountId,2198 from: T::CrossAccountId,2199 token: TokenId,2200 amount: u128,2201 budget: &dyn Budget,2202 ) -> DispatchResultWithPostInfo;22032204 2205 2206 2207 2208 2209 2210 fn check_nesting(2211 &self,2212 sender: T::CrossAccountId,2213 from: (CollectionId, TokenId),2214 under: TokenId,2215 budget: &dyn Budget,2216 ) -> DispatchResult;22172218 2219 2220 2221 2222 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22232224 2225 2226 2227 2228 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22292230 2231 2232 2233 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22342235 2236 fn collection_tokens(&self) -> Vec<TokenId>;22372238 2239 2240 2241 fn token_exists(&self, token: TokenId) -> bool;22422243 2244 fn last_token_id(&self) -> TokenId;22452246 2247 2248 2249 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22502251 2252 2253 2254 2255 2256 fn check_token_indirect_owner(2257 &self,2258 token: TokenId,2259 maybe_owner: &T::CrossAccountId,2260 nesting_budget: &dyn Budget,2261 ) -> Result<bool, DispatchError>;22622263 2264 2265 2266 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22672268 2269 2270 2271 2272 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22732274 2275 2276 2277 2278 2279 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22802281 2282 fn total_supply(&self) -> u32;22832284 2285 2286 2287 fn account_balance(&self, account: T::CrossAccountId) -> u32;22882289 2290 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22912292 2293 fn total_pieces(&self, token: TokenId) -> Option<u128>;22942295 2296 2297 2298 2299 2300 fn allowance(2301 &self,2302 sender: T::CrossAccountId,2303 spender: T::CrossAccountId,2304 token: TokenId,2305 ) -> u128;23062307 2308 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;23092310 2311 2312 2313 2314 fn set_allowance_for_all(2315 &self,2316 owner: T::CrossAccountId,2317 operator: T::CrossAccountId,2318 approve: bool,2319 ) -> DispatchResultWithPostInfo;23202321 2322 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;23232324 2325 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2326}232723282329pub trait RefungibleExtensions<T>2330where2331 T: Config,2332{2333 2334 2335 2336 2337 2338 2339 2340 fn repartition(2341 &self,2342 sender: &T::CrossAccountId,2343 token: TokenId,2344 amount: u128,2345 ) -> DispatchResultWithPostInfo;2346}23472348234923502351pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2352 let post_info = PostDispatchInfo {2353 actual_weight: Some(weight),2354 pays_fee: Pays::Yes,2355 };2356 match res {2357 Ok(()) => Ok(post_info),2358 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2359 }2360}23612362impl<T: Config> From<PropertiesError> for Error<T> {2363 fn from(error: PropertiesError) -> Self {2364 match error {2365 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2366 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2367 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2368 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2369 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2370 }2371 }2372}2373237423752376pub struct NewTokenPropertyWriter;2377237823792380pub struct ExistingTokenPropertyWriter;23812382238323842385238623872388pub struct PropertyWriter<2389 'a,2390 T,2391 Handle,2392 WriterVariant,2393 FIsAdmin,2394 FPropertyPermissions,2395 FCheckTokenExist,2396 FGetProperties,2397> where2398 T: Config,2399 FIsAdmin: FnOnce() -> bool,2400 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2401{2402 collection: &'a Handle,2403 is_collection_admin: LazyValue<bool, FIsAdmin>,2404 property_permissions: LazyValue<PropertiesPermissionMap, FPropertyPermissions>,2405 check_token_exist: FCheckTokenExist,2406 get_properties: FGetProperties,2407 _phantom: PhantomData<(T, WriterVariant)>,2408}24092410impl<'a, T, Handle, FIsAdmin, FPropertyPermissions, FCheckTokenExist, FGetProperties>2411 PropertyWriter<2412 'a,2413 T,2414 Handle,2415 NewTokenPropertyWriter,2416 FIsAdmin,2417 FPropertyPermissions,2418 FCheckTokenExist,2419 FGetProperties,2420 > where2421 T: Config,2422 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2423 FIsAdmin: FnOnce() -> bool,2424 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2425 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,2426 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,2427{2428 2429 pub fn write_token_properties(2430 &mut self,2431 mint_target_is_sender: bool,2432 token_id: TokenId,2433 properties_updates: impl Iterator<Item = Property>,2434 log: evm_coder::ethereum::Log,2435 ) -> DispatchResult {2436 self.internal_write_token_properties(2437 token_id,2438 properties_updates.map(|p| (p.key, Some(p.value))),2439 |_| Ok(mint_target_is_sender),2440 log,2441 )2442 }2443}24442445impl<'a, T, Handle, FIsAdmin, FPropertyPermissions, FCheckTokenExist, FGetProperties>2446 PropertyWriter<2447 'a,2448 T,2449 Handle,2450 ExistingTokenPropertyWriter,2451 FIsAdmin,2452 FPropertyPermissions,2453 FCheckTokenExist,2454 FGetProperties,2455 > where2456 T: Config,2457 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2458 FIsAdmin: FnOnce() -> bool,2459 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2460 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,2461 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,2462{2463 2464 pub fn write_token_properties(2465 &mut self,2466 sender: &T::CrossAccountId,2467 token_id: TokenId,2468 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2469 nesting_budget: &dyn Budget,2470 log: evm_coder::ethereum::Log,2471 ) -> DispatchResult {2472 self.internal_write_token_properties(2473 token_id,2474 properties_updates,2475 |collection| collection.check_token_indirect_owner(token_id, sender, nesting_budget),2476 log,2477 )2478 }2479}24802481impl<2482 'a,2483 T,2484 Handle,2485 WriterVariant,2486 FIsAdmin,2487 FPropertyPermissions,2488 FCheckTokenExist,2489 FGetProperties,2490 >2491 PropertyWriter<2492 'a,2493 T,2494 Handle,2495 WriterVariant,2496 FIsAdmin,2497 FPropertyPermissions,2498 FCheckTokenExist,2499 FGetProperties,2500 > where2501 T: Config,2502 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2503 FIsAdmin: FnOnce() -> bool,2504 FPropertyPermissions: FnOnce() -> PropertiesPermissionMap,2505 FCheckTokenExist: Copy + FnOnce(TokenId) -> bool,2506 FGetProperties: Copy + FnOnce(TokenId) -> TokenProperties,2507{2508 fn internal_write_token_properties<FCheckTokenOwner>(2509 &mut self,2510 token_id: TokenId,2511 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,2512 check_token_owner: FCheckTokenOwner,2513 log: evm_coder::ethereum::Log,2514 ) -> DispatchResult2515 where2516 FCheckTokenOwner: FnOnce(&Handle) -> Result<bool, DispatchError>,2517 {2518 let get_properties = self.get_properties;2519 let mut stored_properties = LazyValue::new(move || get_properties(token_id));25202521 let mut is_token_owner = LazyValue::new(|| check_token_owner(self.collection));25222523 let check_token_exist = self.check_token_exist;2524 let mut is_token_exist = LazyValue::new(move || check_token_exist(token_id));25252526 for (key, value) in properties_updates {2527 let permission = self2528 .property_permissions2529 .value()2530 .get(&key)2531 .cloned()2532 .unwrap_or_else(PropertyPermission::none);25332534 match permission {2535 PropertyPermission { mutable: false, .. }2536 if stored_properties.value().get(&key).is_some() =>2537 {2538 return Err(<Error<T>>::NoPermission.into());2539 }25402541 PropertyPermission {2542 collection_admin,2543 token_owner,2544 ..2545 } => check_token_permissions::<T, _, _, _>(2546 collection_admin,2547 token_owner,2548 &mut self.is_collection_admin,2549 &mut is_token_owner,2550 &mut is_token_exist,2551 )?,2552 }25532554 match value {2555 Some(value) => {2556 stored_properties2557 .value_mut()2558 .try_set(key.clone(), value)2559 .map_err(<Error<T>>::from)?;25602561 <Pallet<T>>::deposit_event(Event::TokenPropertySet(2562 self.collection.id,2563 token_id,2564 key,2565 ));2566 }2567 None => {2568 stored_properties2569 .value_mut()2570 .remove(&key)2571 .map_err(<Error<T>>::from)?;25722573 <Pallet<T>>::deposit_event(Event::TokenPropertyDeleted(2574 self.collection.id,2575 token_id,2576 key,2577 ));2578 }2579 }2580 }25812582 let properties_changed = stored_properties.has_value();2583 if properties_changed {2584 <PalletEvm<T>>::deposit_log(log);25852586 self.collection2587 .set_token_properties_raw(token_id, stored_properties.into_inner());2588 }25892590 Ok(())2591 }2592}259325942595pub fn property_writer_for_new_token<'a, T, Handle>(2596 collection: &'a Handle,2597 sender: &'a T::CrossAccountId,2598) -> PropertyWriter<2599 'a,2600 T,2601 Handle,2602 NewTokenPropertyWriter,2603 impl FnOnce() -> bool + 'a,2604 impl FnOnce() -> PropertiesPermissionMap + 'a,2605 impl Copy + FnOnce(TokenId) -> bool + 'a,2606 impl Copy + FnOnce(TokenId) -> TokenProperties + 'a,2607>2608where2609 T: Config,2610 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2611{2612 PropertyWriter {2613 collection,2614 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2615 property_permissions: LazyValue::new(|| <Pallet<T>>::property_permissions(collection.id)),2616 check_token_exist: |token_id| {2617 debug_assert!(collection.token_exists(token_id));2618 true2619 },2620 get_properties: |token_id| {2621 debug_assert!(collection.get_token_properties_raw(token_id).is_none());2622 TokenProperties::new()2623 },2624 _phantom: PhantomData,2625 }2626}26272628#[cfg(feature = "runtime-benchmarks")]26292630263126322633pub fn collection_info_loaded_property_writer<T, Handle>(2634 collection: &Handle,2635 is_collection_admin: bool,2636 property_permissions: PropertiesPermissionMap,2637) -> PropertyWriter<2638 T,2639 Handle,2640 NewTokenPropertyWriter,2641 impl FnOnce() -> bool,2642 impl FnOnce() -> PropertiesPermissionMap,2643 impl Copy + FnOnce(TokenId) -> bool,2644 impl Copy + FnOnce(TokenId) -> TokenProperties,2645>2646where2647 T: Config,2648 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2649{2650 PropertyWriter {2651 collection,2652 is_collection_admin: LazyValue::new(move || is_collection_admin),2653 property_permissions: LazyValue::new(move || property_permissions),2654 check_token_exist: |_token_id| true,2655 get_properties: |_token_id| TokenProperties::new(),2656 _phantom: PhantomData,2657 }2658}265926602661pub fn property_writer_for_existing_token<'a, T, Handle>(2662 collection: &'a Handle,2663 sender: &'a T::CrossAccountId,2664) -> PropertyWriter<2665 'a,2666 T,2667 Handle,2668 ExistingTokenPropertyWriter,2669 impl FnOnce() -> bool + 'a,2670 impl FnOnce() -> PropertiesPermissionMap + 'a,2671 impl Copy + FnOnce(TokenId) -> bool + 'a,2672 impl Copy + FnOnce(TokenId) -> TokenProperties + 'a,2673>2674where2675 T: Config,2676 Handle: CommonCollectionOperations<T> + Deref<Target = CollectionHandle<T>>,2677{2678 PropertyWriter {2679 collection,2680 is_collection_admin: LazyValue::new(|| collection.is_owner_or_admin(sender)),2681 property_permissions: LazyValue::new(|| <Pallet<T>>::property_permissions(collection.id)),2682 check_token_exist: |token_id| collection.token_exists(token_id),2683 get_properties: |token_id| {2684 collection2685 .get_token_properties_raw(token_id)2686 .unwrap_or_default()2687 },2688 _phantom: PhantomData,2689 }2690}26912692269326942695pub fn init_token_properties_delta<T: Config, I: Fn(u32) -> Weight>(2696 properties_nums: impl Iterator<Item = u32>,2697 init_token_properties: I,2698) -> Weight {2699 let mut delta = properties_nums2700 .filter_map(|properties_num| {2701 if properties_num > 0 {2702 Some(init_token_properties(properties_num))2703 } else {2704 None2705 }2706 })2707 .fold(Weight::zero(), |a, b| a.saturating_add(b));27082709 2710 2711 2712 2713 if !delta.is_zero() {2714 delta = delta.saturating_add(<SelfWeightOf<T>>::init_token_properties_common())2715 }27162717 delta2718}27192720#[cfg(any(feature = "tests", test))]2721#[allow(missing_docs)]2722pub mod tests {2723 use crate::{DispatchResult, DispatchError, LazyValue, Config};27242725 const fn to_bool(u: u8) -> bool {2726 u != 02727 }27282729 #[derive(Debug)]2730 pub struct TestCase {2731 pub collection_admin: bool,2732 pub is_collection_admin: bool,2733 pub token_owner: bool,2734 pub is_token_owner: bool,2735 pub no_permission: bool,2736 }27372738 impl TestCase {2739 const fn new(2740 collection_admin: u8,2741 is_collection_admin: u8,2742 token_owner: u8,2743 is_token_owner: u8,2744 no_permission: u8,2745 ) -> Self {2746 Self {2747 collection_admin: to_bool(collection_admin),2748 is_collection_admin: to_bool(is_collection_admin),2749 token_owner: to_bool(token_owner),2750 is_token_owner: to_bool(is_token_owner),2751 no_permission: to_bool(no_permission),2752 }2753 }2754 }27552756 #[rustfmt::skip]2757 pub const TABLE: [TestCase; 16] = [2758 2759 2760 2761 2762 2763 TestCase::new(0, 0, 0, 0, 1),2764 TestCase::new(0, 0, 0, 1, 1),2765 TestCase::new(0, 0, 1, 0, 1),2766 TestCase::new(0, 0, 1, 1, 0),2767 TestCase::new(0, 1, 0, 0, 1),2768 TestCase::new(0, 1, 0, 1, 1),2769 TestCase::new(0, 1, 1, 0, 1),2770 TestCase::new(0, 1, 1, 1, 0),2771 TestCase::new(1, 0, 0, 0, 1),2772 TestCase::new(1, 0, 0, 1, 1),2773 TestCase::new(1, 0, 1, 0, 1),2774 TestCase::new(1, 0, 1, 1, 0),2775 TestCase::new(1, 1, 0, 0, 0),2776 TestCase::new(1, 1, 0, 1, 0),2777 TestCase::new(1, 1, 1, 0, 0),2778 TestCase::new(1, 1, 1, 1, 0),2779 ];27802781 pub fn check_token_permissions<T, FCA, FTO, FTE>(2782 collection_admin_permitted: bool,2783 token_owner_permitted: bool,2784 is_collection_admin: &mut LazyValue<bool, FCA>,2785 check_token_ownership: &mut LazyValue<Result<bool, DispatchError>, FTO>,2786 check_token_existence: &mut LazyValue<bool, FTE>,2787 ) -> DispatchResult2788 where2789 T: Config,2790 FCA: FnOnce() -> bool,2791 FTO: FnOnce() -> Result<bool, DispatchError>,2792 FTE: FnOnce() -> bool,2793 {2794 crate::check_token_permissions::<T, FCA, FTO, FTE>(2795 collection_admin_permitted,2796 token_owner_permitted,2797 is_collection_admin,2798 check_token_ownership,2799 check_token_existence,2800 )2801 }2802}