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::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},68 dispatch::Pays,69 transactional, fail,70};71use pallet_evm::GasWeightMapping;72use up_data_structs::{73 AccessMode, COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionFlags,74 RpcCollectionFlags, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,75 COLLECTION_ADMINS_LIMIT, TokenId, TokenChild, CollectionStats, MAX_TOKEN_OWNERSHIP,76 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,77 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,78 CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,79 PhantomType, Property, CollectionProperties as CollectionPropertiesT, TokenProperties,80 PropertiesPermissionMap, PropertyKey, PropertyValue, PropertyPermission, PropertiesError,81 TokenOwnerError, PropertyKeyPermission, TokenData, TrySetProperty, PropertyScope,82 CollectionPermissions,83};84use up_pov_estimate_rpc::PovInfo;8586pub use pallet::*;87use sp_core::H160;88use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};8990#[cfg(feature = "runtime-benchmarks")]91pub mod benchmarking;92pub mod dispatch;93pub mod erc;94pub mod eth;95pub mod helpers;96#[allow(missing_docs)]97pub mod weights;9899pub type SelfWeightOf<T> = <T as Config>::WeightInfo;100101102103104105106107#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]108pub struct CollectionHandle<T: Config> {109 110 pub id: CollectionId,111 collection: Collection<T::AccountId>,112 113 pub recorder: SubstrateRecorder<T>,114}115116impl<T: Config> WithRecorder<T> for CollectionHandle<T> {117 fn recorder(&self) -> &SubstrateRecorder<T> {118 &self.recorder119 }120 fn into_recorder(self) -> SubstrateRecorder<T> {121 self.recorder122 }123}124125impl<T: Config> CollectionHandle<T> {126 127 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {128 <CollectionById<T>>::get(id).map(|collection| Self {129 id,130 collection,131 recorder: SubstrateRecorder::new(gas_limit),132 })133 }134135 136 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {137 <CollectionById<T>>::get(id).map(|collection| Self {138 id,139 collection,140 recorder,141 })142 }143144 145 146 pub fn new(id: CollectionId) -> Option<Self> {147 Self::new_with_gas_limit(id, u64::MAX)148 }149150 151 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {152 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)153 }154155 156 pub fn consume_store_reads(157 &self,158 reads: u64,159 ) -> pallet_evm_coder_substrate::execution::Result<()> {160 self.recorder161 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(162 <T as frame_system::Config>::DbWeight::get()163 .read164 .saturating_mul(reads),165 166 0,167 )))168 }169170 171 pub fn consume_store_writes(172 &self,173 writes: u64,174 ) -> pallet_evm_coder_substrate::execution::Result<()> {175 self.recorder176 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(177 <T as frame_system::Config>::DbWeight::get()178 .write179 .saturating_mul(writes),180 181 0,182 )))183 }184185 186 pub fn consume_store_reads_and_writes(187 &self,188 reads: u64,189 writes: u64,190 ) -> pallet_evm_coder_substrate::execution::Result<()> {191 let weight = <T as frame_system::Config>::DbWeight::get();192 let reads = weight.read.saturating_mul(reads);193 let writes = weight.read.saturating_mul(writes);194 self.recorder195 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(196 reads.saturating_add(writes),197 198 0,199 )))200 }201202 203 pub fn save(&self) -> DispatchResult {204 <CollectionById<T>>::insert(self.id, &self.collection);205 Ok(())206 }207208 209 210 211 212 213 pub fn set_sponsor(214 &mut self,215 sender: &T::CrossAccountId,216 sponsor: T::AccountId,217 ) -> DispatchResult {218 self.check_is_internal()?;219 self.check_is_owner_or_admin(sender)?;220221 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());222223 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));224 <PalletEvm<T>>::deposit_log(225 erc::CollectionHelpersEvents::CollectionChanged {226 collection_id: eth::collection_id_to_address(self.id),227 }228 .to_log(T::ContractAddress::get()),229 );230231 self.save()232 }233234 235 236 237 238 239 240 241 242 243 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {244 self.check_is_internal()?;245246 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());247248 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));249 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));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 262 263 264 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {265 self.check_is_internal()?;266 ensure!(267 self.collection.sponsorship.pending_sponsor() == Some(sender),268 Error::<T>::ConfirmSponsorshipFail269 );270271 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());272273 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));274 <PalletEvm<T>>::deposit_log(275 erc::CollectionHelpersEvents::CollectionChanged {276 collection_id: eth::collection_id_to_address(self.id),277 }278 .to_log(T::ContractAddress::get()),279 );280281 self.save()282 }283284 285 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {286 self.check_is_internal()?;287 self.check_is_owner_or_admin(sender)?;288289 self.collection.sponsorship = SponsorshipState::Disabled;290291 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));292 <PalletEvm<T>>::deposit_log(293 erc::CollectionHelpersEvents::CollectionChanged {294 collection_id: eth::collection_id_to_address(self.id),295 }296 .to_log(T::ContractAddress::get()),297 );298 self.save()299 }300301 302 303 304 305 pub fn force_remove_sponsor(&mut self) -> DispatchResult {306 self.check_is_internal()?;307308 self.collection.sponsorship = SponsorshipState::Disabled;309310 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));311 <PalletEvm<T>>::deposit_log(312 erc::CollectionHelpersEvents::CollectionChanged {313 collection_id: eth::collection_id_to_address(self.id),314 }315 .to_log(T::ContractAddress::get()),316 );317 self.save()318 }319320 321 322 pub fn check_is_internal(&self) -> DispatchResult {323 if self.flags.external {324 return Err(<Error<T>>::CollectionIsExternal)?;325 }326327 Ok(())328 }329330 331 332 pub fn check_is_external(&self) -> DispatchResult {333 if !self.flags.external {334 return Err(<Error<T>>::CollectionIsInternal)?;335 }336337 Ok(())338 }339}340341impl<T: Config> Deref for CollectionHandle<T> {342 type Target = Collection<T::AccountId>;343344 fn deref(&self) -> &Self::Target {345 &self.collection346 }347}348349impl<T: Config> DerefMut for CollectionHandle<T> {350 fn deref_mut(&mut self) -> &mut Self::Target {351 &mut self.collection352 }353}354355impl<T: Config> CollectionHandle<T> {356 357 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {358 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);359 Ok(())360 }361362 363 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {364 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))365 }366367 368 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {369 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);370 Ok(())371 }372373 374 375 376 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {377 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)378 }379380 381 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {382 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)383 }384385 386 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {387 ensure!(388 <Allowlist<T>>::get((self.id, user)),389 <Error<T>>::AddressNotInAllowlist390 );391 Ok(())392 }393394 395 396 397 pub fn change_owner(398 &mut self,399 caller: T::CrossAccountId,400 new_owner: T::CrossAccountId,401 ) -> DispatchResult {402 self.check_is_internal()?;403 self.check_is_owner(&caller)?;404 self.collection.owner = new_owner.as_sub().clone();405406 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(407 self.id,408 new_owner.as_sub().clone(),409 ));410 <PalletEvm<T>>::deposit_log(411 erc::CollectionHelpersEvents::CollectionChanged {412 collection_id: eth::collection_id_to_address(self.id),413 }414 .to_log(T::ContractAddress::get()),415 );416417 self.save()418 }419}420421#[frame_support::pallet]422pub mod pallet {423 use super::*;424 use dispatch::CollectionDispatch;425 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};426 use frame_system::pallet_prelude::*;427 use frame_support::traits::Currency;428 use up_data_structs::{TokenId, mapping::TokenAddressMapping};429 use scale_info::TypeInfo;430 use weights::WeightInfo;431432 #[pallet::config]433 pub trait Config:434 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo435 {436 437 type WeightInfo: WeightInfo;438439 440 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;441442 443 type Currency: Currency<Self::AccountId>;444445 446 #[pallet::constant]447 type CollectionCreationPrice: Get<448 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,449 >;450451 452 type CollectionDispatch: CollectionDispatch<Self>;453454 455 type TreasuryAccountId: Get<Self::AccountId>;456457 458 #[pallet::constant]459 type ContractAddress: Get<H160>;460461 462 type EvmTokenAddressMapping: TokenAddressMapping<H160>;463464 465 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;466 }467468 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);469470 #[pallet::pallet]471 #[pallet::storage_version(STORAGE_VERSION)]472 pub struct Pallet<T>(_);473474 #[pallet::extra_constants]475 impl<T: Config> Pallet<T> {476 477 pub fn collection_admins_limit() -> u32 {478 COLLECTION_ADMINS_LIMIT479 }480 }481482 impl<T: Config> Pallet<T> {483 484 pub fn deposit_event(event: Event<T>) {485 let event = <T as Config>::RuntimeEvent::from(event);486 let event = event.into();487 <frame_system::Pallet<T>>::deposit_event(event)488 }489 }490491 #[pallet::event]492 pub enum Event<T: Config> {493 494 CollectionCreated(495 496 CollectionId,497 498 u8,499 500 T::AccountId,501 ),502503 504 CollectionDestroyed(505 506 CollectionId,507 ),508509 510 ItemCreated(511 512 CollectionId,513 514 TokenId,515 516 T::CrossAccountId,517 518 u128,519 ),520521 522 ItemDestroyed(523 524 CollectionId,525 526 TokenId,527 528 T::CrossAccountId,529 530 u128,531 ),532533 534 Transfer(535 536 CollectionId,537 538 TokenId,539 540 T::CrossAccountId,541 542 T::CrossAccountId,543 544 u128,545 ),546547 548 Approved(549 550 CollectionId,551 552 TokenId,553 554 T::CrossAccountId,555 556 T::CrossAccountId,557 558 u128,559 ),560561 562 ApprovedForAll(563 564 CollectionId,565 566 T::CrossAccountId,567 568 T::CrossAccountId,569 570 bool,571 ),572573 574 CollectionPropertySet(575 576 CollectionId,577 578 PropertyKey,579 ),580581 582 CollectionPropertyDeleted(583 584 CollectionId,585 586 PropertyKey,587 ),588589 590 TokenPropertySet(591 592 CollectionId,593 594 TokenId,595 596 PropertyKey,597 ),598599 600 TokenPropertyDeleted(601 602 CollectionId,603 604 TokenId,605 606 PropertyKey,607 ),608609 610 PropertyPermissionSet(611 612 CollectionId,613 614 PropertyKey,615 ),616617 618 AllowListAddressAdded(619 620 CollectionId,621 622 T::CrossAccountId,623 ),624625 626 AllowListAddressRemoved(627 628 CollectionId,629 630 T::CrossAccountId,631 ),632633 634 CollectionAdminAdded(635 636 CollectionId,637 638 T::CrossAccountId,639 ),640641 642 CollectionAdminRemoved(643 644 CollectionId,645 646 T::CrossAccountId,647 ),648649 650 CollectionLimitSet(651 652 CollectionId,653 ),654655 656 CollectionOwnerChanged(657 658 CollectionId,659 660 T::AccountId,661 ),662663 664 CollectionPermissionSet(665 666 CollectionId,667 ),668669 670 CollectionSponsorSet(671 672 CollectionId,673 674 T::AccountId,675 ),676677 678 SponsorshipConfirmed(679 680 CollectionId,681 682 T::AccountId,683 ),684685 686 CollectionSponsorRemoved(687 688 CollectionId,689 ),690 }691692 #[pallet::error]693 pub enum Error<T> {694 695 CollectionNotFound,696 697 MustBeTokenOwner,698 699 NoPermission,700 701 CantDestroyNotEmptyCollection,702 703 PublicMintingNotAllowed,704 705 AddressNotInAllowlist,706707 708 CollectionNameLimitExceeded,709 710 CollectionDescriptionLimitExceeded,711 712 CollectionTokenPrefixLimitExceeded,713 714 TotalCollectionsLimitExceeded,715 716 CollectionAdminCountExceeded,717 718 CollectionLimitBoundsExceeded,719 720 OwnerPermissionsCantBeReverted,721 722 TransferNotAllowed,723 724 AccountTokenLimitExceeded,725 726 CollectionTokenLimitExceeded,727 728 MetadataFlagFrozen,729730 731 TokenNotFound,732 733 TokenValueTooLow,734 735 ApprovedValueTooLow,736 737 CantApproveMoreThanOwned,738 739 AddressIsNotEthMirror,740741 742 AddressIsZero,743744 745 UnsupportedOperation,746747 748 NotSufficientFounds,749750 751 UserIsNotAllowedToNest,752 753 SourceCollectionIsNotAllowedToNest,754755 756 CollectionFieldSizeExceeded,757758 759 NoSpaceForProperty,760761 762 PropertyLimitReached,763764 765 PropertyKeyIsTooLong,766767 768 InvalidCharacterInPropertyKey,769770 771 EmptyPropertyKey,772773 774 CollectionIsExternal,775776 777 CollectionIsInternal,778779 780 ConfirmSponsorshipFail,781782 783 UserIsNotCollectionAdmin,784 }785786 787 #[pallet::storage]788 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;789790 791 #[pallet::storage]792 pub type DestroyedCollectionCount<T> =793 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;794795 796 #[pallet::storage]797 pub type CollectionById<T> = StorageMap<798 Hasher = Blake2_128Concat,799 Key = CollectionId,800 Value = Collection<<T as frame_system::Config>::AccountId>,801 QueryKind = OptionQuery,802 >;803804 805 #[pallet::storage]806 #[pallet::getter(fn collection_properties)]807 pub type CollectionProperties<T> = StorageMap<808 Hasher = Blake2_128Concat,809 Key = CollectionId,810 Value = CollectionPropertiesT,811 QueryKind = ValueQuery,812 >;813814 815 #[pallet::storage]816 #[pallet::getter(fn property_permissions)]817 pub type CollectionPropertyPermissions<T> = StorageMap<818 Hasher = Blake2_128Concat,819 Key = CollectionId,820 Value = PropertiesPermissionMap,821 QueryKind = ValueQuery,822 >;823824 825 #[pallet::storage]826 pub type AdminAmount<T> = StorageMap<827 Hasher = Blake2_128Concat,828 Key = CollectionId,829 Value = u32,830 QueryKind = ValueQuery,831 >;832833 834 #[pallet::storage]835 pub type IsAdmin<T: Config> = StorageNMap<836 Key = (837 Key<Blake2_128Concat, CollectionId>,838 Key<Blake2_128Concat, T::CrossAccountId>,839 ),840 Value = bool,841 QueryKind = ValueQuery,842 >;843844 845 #[pallet::storage]846 pub type Allowlist<T: Config> = StorageNMap<847 Key = (848 Key<Blake2_128Concat, CollectionId>,849 Key<Blake2_128Concat, T::CrossAccountId>,850 ),851 Value = bool,852 QueryKind = ValueQuery,853 >;854855 856 #[pallet::storage]857 pub type DummyStorageValue<T: Config> = StorageValue<858 Value = (859 CollectionStats,860 CollectionId,861 TokenId,862 TokenChild,863 PhantomType<(864 TokenData<T::CrossAccountId>,865 RpcCollection<T::AccountId>,866 867 PovInfo,868 )>,869 ),870 QueryKind = OptionQuery,871 >;872873 #[pallet::hooks]874 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {875 fn on_runtime_upgrade() -> Weight {876 StorageVersion::new(1).put::<Pallet<T>>();877878 Weight::zero()879 }880 }881}882883impl<T: Config> Pallet<T> {884 885 886 887 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {888 ensure!(889 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,890 <Error<T>>::AddressIsZero891 );892 Ok(())893 }894895 896 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {897 <IsAdmin<T>>::iter_prefix((collection,))898 .map(|(a, _)| a)899 .collect()900 }901902 903 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {904 <Allowlist<T>>::iter_prefix((collection,))905 .map(|(a, _)| a)906 .collect()907 }908909 910 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {911 <Allowlist<T>>::get((collection, user))912 }913914 915 pub fn collection_stats() -> CollectionStats {916 let created = <CreatedCollectionCount<T>>::get();917 let destroyed = <DestroyedCollectionCount<T>>::get();918 CollectionStats {919 created: created.0,920 destroyed: destroyed.0,921 alive: created.0 - destroyed.0,922 }923 }924925 926 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {927 let collection = <CollectionById<T>>::get(collection)?;928 let limits = collection.limits;929 let effective_limits = CollectionLimits {930 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),931 sponsored_data_size: Some(limits.sponsored_data_size()),932 sponsored_data_rate_limit: Some(933 limits934 .sponsored_data_rate_limit935 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),936 ),937 token_limit: Some(limits.token_limit()),938 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(939 match collection.mode {940 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,941 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,942 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,943 },944 )),945 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),946 owner_can_transfer: Some(limits.owner_can_transfer()),947 owner_can_destroy: Some(limits.owner_can_destroy()),948 transfers_enabled: Some(limits.transfers_enabled()),949 };950951 Some(effective_limits)952 }953954 955 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {956 let Collection {957 name,958 description,959 owner,960 mode,961 token_prefix,962 sponsorship,963 limits,964 permissions,965 flags,966 } = <CollectionById<T>>::get(collection)?;967968 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)969 .into_iter()970 .map(|(key, permission)| PropertyKeyPermission { key, permission })971 .collect();972973 let properties = <CollectionProperties<T>>::get(collection)974 .into_iter()975 .map(|(key, value)| Property { key, value })976 .collect();977978 let permissions = CollectionPermissions {979 access: Some(permissions.access()),980 mint_mode: Some(permissions.mint_mode()),981 nesting: Some(permissions.nesting().clone()),982 };983984 Some(RpcCollection {985 name: name.into_inner(),986 description: description.into_inner(),987 owner,988 mode,989 token_prefix: token_prefix.into_inner(),990 sponsorship,991 limits,992 permissions,993 token_property_permissions,994 properties,995 read_only: flags.external,996997 flags: RpcCollectionFlags {998 foreign: flags.foreign,999 erc721metadata: flags.erc721metadata,1000 },1001 })1002 }1003}10041005macro_rules! limit_default {1006 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1007 $(1008 if let Some($new) = $new.$field {1009 let $old = $old.$field($($arg)?);1010 let _ = $new;1011 let _ = $old;1012 $check1013 } else {1014 $new.$field = $old.$field1015 }1016 )*1017 }};1018}1019macro_rules! limit_default_clone {1020 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1021 $(1022 if let Some($new) = $new.$field.clone() {1023 let $old = $old.$field($($arg)?);1024 let _ = $new;1025 let _ = $old;1026 $check1027 } else {1028 $new.$field = $old.$field.clone()1029 }1030 )*1031 }};1032}10331034impl<T: Config> Pallet<T> {1035 1036 1037 1038 1039 1040 pub fn init_collection(1041 owner: T::CrossAccountId,1042 payer: T::CrossAccountId,1043 data: CreateCollectionData<T::AccountId>,1044 flags: CollectionFlags,1045 ) -> Result<CollectionId, DispatchError> {1046 {1047 ensure!(1048 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1049 Error::<T>::CollectionTokenPrefixLimitExceeded1050 );1051 }10521053 let created_count = <CreatedCollectionCount<T>>::get()1054 .01055 .checked_add(1)1056 .ok_or(ArithmeticError::Overflow)?;1057 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1058 let id = CollectionId(created_count);10591060 1061 ensure!(1062 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1063 <Error<T>>::TotalCollectionsLimitExceeded1064 );10651066 10671068 let collection = Collection {1069 owner: owner.as_sub().clone(),1070 name: data.name,1071 mode: data.mode.clone(),1072 description: data.description,1073 token_prefix: data.token_prefix,1074 sponsorship: data1075 .pending_sponsor1076 .map(SponsorshipState::Unconfirmed)1077 .unwrap_or_default(),1078 limits: data1079 .limits1080 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1081 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1082 permissions: data1083 .permissions1084 .map(|permissions| {1085 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1086 })1087 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1088 flags,1089 };10901091 let mut collection_properties = CollectionPropertiesT::new();1092 collection_properties1093 .try_set_from_iter(data.properties.into_iter())1094 .map_err(<Error<T>>::from)?;10951096 CollectionProperties::<T>::insert(id, collection_properties);10971098 let mut token_props_permissions = PropertiesPermissionMap::new();1099 token_props_permissions1100 .try_set_from_iter(data.token_property_permissions.into_iter())1101 .map_err(<Error<T>>::from)?;11021103 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11041105 1106 {1107 let mut imbalance =1108 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1109 imbalance.subsume(1110 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1111 &T::TreasuryAccountId::get(),1112 T::CollectionCreationPrice::get(),1113 ),1114 );1115 <T as Config>::Currency::settle(1116 payer.as_sub(),1117 imbalance,1118 WithdrawReasons::TRANSFER,1119 ExistenceRequirement::KeepAlive,1120 )1121 .map_err(|_| Error::<T>::NotSufficientFounds)?;1122 }11231124 <CreatedCollectionCount<T>>::put(created_count);1125 <Pallet<T>>::deposit_event(Event::CollectionCreated(1126 id,1127 data.mode.id(),1128 owner.as_sub().clone(),1129 ));1130 <PalletEvm<T>>::deposit_log(1131 erc::CollectionHelpersEvents::CollectionCreated {1132 owner: *owner.as_eth(),1133 collection_id: eth::collection_id_to_address(id),1134 }1135 .to_log(T::ContractAddress::get()),1136 );1137 <CollectionById<T>>::insert(id, collection);1138 Ok(id)1139 }11401141 1142 1143 1144 1145 pub fn destroy_collection(1146 collection: CollectionHandle<T>,1147 sender: &T::CrossAccountId,1148 ) -> DispatchResult {1149 ensure!(1150 collection.limits.owner_can_destroy(),1151 <Error<T>>::NoPermission,1152 );1153 collection.check_is_owner(sender)?;11541155 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1156 .01157 .checked_add(1)1158 .ok_or(ArithmeticError::Overflow)?;11591160 11611162 <DestroyedCollectionCount<T>>::put(destroyed_collections);1163 <CollectionById<T>>::remove(collection.id);1164 <AdminAmount<T>>::remove(collection.id);1165 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1166 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1167 <CollectionProperties<T>>::remove(collection.id);11681169 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11701171 <PalletEvm<T>>::deposit_log(1172 erc::CollectionHelpersEvents::CollectionDestroyed {1173 collection_id: eth::collection_id_to_address(collection.id),1174 }1175 .to_log(T::ContractAddress::get()),1176 );1177 Ok(())1178 }11791180 1181 1182 1183 1184 1185 1186 1187 1188 #[transactional]1189 fn modify_collection_properties(1190 collection: &CollectionHandle<T>,1191 sender: &T::CrossAccountId,1192 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1193 ) -> DispatchResult {1194 collection.check_is_owner_or_admin(sender)?;11951196 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);11971198 for (key, value) in properties_updates {1199 match value {1200 Some(value) => {1201 stored_properties1202 .try_set(key.clone(), value)1203 .map_err(<Error<T>>::from)?;12041205 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1206 <PalletEvm<T>>::deposit_log(1207 erc::CollectionHelpersEvents::CollectionChanged {1208 collection_id: eth::collection_id_to_address(collection.id),1209 }1210 .to_log(T::ContractAddress::get()),1211 );1212 }1213 None => {1214 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12151216 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1217 <PalletEvm<T>>::deposit_log(1218 erc::CollectionHelpersEvents::CollectionChanged {1219 collection_id: eth::collection_id_to_address(collection.id),1220 }1221 .to_log(T::ContractAddress::get()),1222 );1223 }1224 }1225 }12261227 <CollectionProperties<T>>::set(collection.id, stored_properties);12281229 Ok(())1230 }12311232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 pub fn modify_token_properties(1250 collection: &CollectionHandle<T>,1251 sender: &T::CrossAccountId,1252 token_id: TokenId,1253 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1254 is_token_create: bool,1255 mut stored_properties: TokenProperties,1256 is_token_owner: impl Fn() -> Result<bool, DispatchError>,1257 set_token_properties: impl FnOnce(TokenProperties),1258 log: evm_coder::ethereum::Log,1259 ) -> DispatchResult {1260 let is_collection_admin = collection.is_owner_or_admin(sender);1261 let permissions = Self::property_permissions(collection.id);12621263 let mut token_owner_result = None;1264 let mut is_token_owner = || -> Result<bool, DispatchError> {1265 *token_owner_result.get_or_insert_with(&is_token_owner)1266 };12671268 for (key, value) in properties_updates {1269 let permission = permissions1270 .get(&key)1271 .cloned()1272 .unwrap_or_else(PropertyPermission::none);12731274 let is_property_exists = stored_properties.get(&key).is_some();12751276 match permission {1277 PropertyPermission { mutable: false, .. } if is_property_exists => {1278 return Err(<Error<T>>::NoPermission.into());1279 }12801281 PropertyPermission {1282 collection_admin,1283 token_owner,1284 ..1285 } => {1286 1287 let is_token_create =1288 is_token_create && (collection_admin || token_owner) && value.is_some();1289 if !(is_token_create1290 || (collection_admin && is_collection_admin)1291 || (token_owner && is_token_owner()?))1292 {1293 fail!(<Error<T>>::NoPermission);1294 }1295 }1296 }12971298 match value {1299 Some(value) => {1300 stored_properties1301 .try_set(key.clone(), value)1302 .map_err(<Error<T>>::from)?;13031304 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1305 }1306 None => {1307 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13081309 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1310 }1311 }13121313 <PalletEvm<T>>::deposit_log(log.clone());1314 }13151316 set_token_properties(stored_properties);13171318 Ok(())1319 }13201321 1322 1323 1324 1325 1326 1327 pub fn set_allowance_for_all(1328 collection: &CollectionHandle<T>,1329 owner: &T::CrossAccountId,1330 operator: &T::CrossAccountId,1331 approve: bool,1332 set_allowance: impl FnOnce(),1333 log: evm_coder::ethereum::Log,1334 ) -> DispatchResult {1335 if collection.permissions.access() == AccessMode::AllowList {1336 collection.check_allowlist(owner)?;1337 collection.check_allowlist(operator)?;1338 }13391340 Self::ensure_correct_receiver(operator)?;13411342 set_allowance();13431344 <PalletEvm<T>>::deposit_log(log);1345 Self::deposit_event(Event::ApprovedForAll(1346 collection.id,1347 owner.clone(),1348 operator.clone(),1349 approve,1350 ));1351 Ok(())1352 }13531354 1355 1356 1357 1358 1359 pub fn set_collection_property(1360 collection: &CollectionHandle<T>,1361 sender: &T::CrossAccountId,1362 property: Property,1363 ) -> DispatchResult {1364 Self::set_collection_properties(collection, sender, [property].into_iter())1365 }13661367 1368 1369 1370 1371 1372 1373 pub fn set_scoped_collection_property(1374 collection_id: CollectionId,1375 scope: PropertyScope,1376 property: Property,1377 ) -> DispatchResult {1378 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1379 properties.try_scoped_set(scope, property.key, property.value)1380 })1381 .map_err(<Error<T>>::from)?;13821383 Ok(())1384 }13851386 1387 1388 1389 1390 1391 1392 pub fn set_scoped_collection_properties(1393 collection_id: CollectionId,1394 scope: PropertyScope,1395 properties: impl Iterator<Item = Property>,1396 ) -> DispatchResult {1397 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1398 stored_properties.try_scoped_set_from_iter(scope, properties)1399 })1400 .map_err(<Error<T>>::from)?;14011402 Ok(())1403 }14041405 1406 1407 1408 1409 1410 pub fn set_collection_properties(1411 collection: &CollectionHandle<T>,1412 sender: &T::CrossAccountId,1413 properties: impl Iterator<Item = Property>,1414 ) -> DispatchResult {1415 Self::modify_collection_properties(1416 collection,1417 sender,1418 properties.map(|property| (property.key, Some(property.value))),1419 )1420 }14211422 1423 1424 1425 1426 1427 pub fn delete_collection_property(1428 collection: &CollectionHandle<T>,1429 sender: &T::CrossAccountId,1430 property_key: PropertyKey,1431 ) -> DispatchResult {1432 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1433 }14341435 1436 1437 1438 1439 1440 pub fn delete_collection_properties(1441 collection: &CollectionHandle<T>,1442 sender: &T::CrossAccountId,1443 property_keys: impl Iterator<Item = PropertyKey>,1444 ) -> DispatchResult {1445 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1446 }14471448 1449 1450 1451 1452 1453 1454 pub fn set_property_permission_unchecked(1455 collection: CollectionId,1456 property_permission: PropertyKeyPermission,1457 ) -> DispatchResult {1458 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1459 permissions.try_set(property_permission.key, property_permission.permission)1460 })1461 .map_err(<Error<T>>::from)?;1462 Ok(())1463 }14641465 1466 1467 1468 1469 1470 pub fn set_property_permission(1471 collection: &CollectionHandle<T>,1472 sender: &T::CrossAccountId,1473 property_permission: PropertyKeyPermission,1474 ) -> DispatchResult {1475 Self::set_scoped_property_permission(1476 collection,1477 sender,1478 PropertyScope::None,1479 property_permission,1480 )1481 }14821483 1484 1485 1486 1487 1488 1489 pub fn set_scoped_property_permission(1490 collection: &CollectionHandle<T>,1491 sender: &T::CrossAccountId,1492 scope: PropertyScope,1493 property_permission: PropertyKeyPermission,1494 ) -> DispatchResult {1495 collection.check_is_owner_or_admin(sender)?;14961497 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1498 let current_permission = all_permissions.get(&property_permission.key);1499 if matches![1500 current_permission,1501 Some(PropertyPermission { mutable: false, .. })1502 ] {1503 return Err(<Error<T>>::NoPermission.into());1504 }15051506 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1507 let property_permission = property_permission.clone();1508 permissions.try_scoped_set(1509 scope,1510 property_permission.key,1511 property_permission.permission,1512 )1513 })1514 .map_err(<Error<T>>::from)?;15151516 Self::deposit_event(Event::PropertyPermissionSet(1517 collection.id,1518 property_permission.key,1519 ));1520 <PalletEvm<T>>::deposit_log(1521 erc::CollectionHelpersEvents::CollectionChanged {1522 collection_id: eth::collection_id_to_address(collection.id),1523 }1524 .to_log(T::ContractAddress::get()),1525 );15261527 Ok(())1528 }15291530 1531 1532 1533 1534 1535 #[transactional]1536 pub fn set_token_property_permissions(1537 collection: &CollectionHandle<T>,1538 sender: &T::CrossAccountId,1539 property_permissions: Vec<PropertyKeyPermission>,1540 ) -> DispatchResult {1541 Self::set_scoped_token_property_permissions(1542 collection,1543 sender,1544 PropertyScope::None,1545 property_permissions,1546 )1547 }15481549 1550 1551 1552 1553 1554 1555 #[transactional]1556 pub fn set_scoped_token_property_permissions(1557 collection: &CollectionHandle<T>,1558 sender: &T::CrossAccountId,1559 scope: PropertyScope,1560 property_permissions: Vec<PropertyKeyPermission>,1561 ) -> DispatchResult {1562 for prop_pemission in property_permissions {1563 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1564 }15651566 Ok(())1567 }15681569 1570 pub fn get_collection_property(1571 collection_id: CollectionId,1572 key: &PropertyKey,1573 ) -> Option<PropertyValue> {1574 Self::collection_properties(collection_id).get(key).cloned()1575 }15761577 1578 pub fn bytes_keys_to_property_keys(1579 keys: Vec<Vec<u8>>,1580 ) -> Result<Vec<PropertyKey>, DispatchError> {1581 keys.into_iter()1582 .map(|key| -> Result<PropertyKey, DispatchError> {1583 key.try_into()1584 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1585 })1586 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1587 }15881589 1590 pub fn filter_collection_properties(1591 collection_id: CollectionId,1592 keys: Option<Vec<PropertyKey>>,1593 ) -> Result<Vec<Property>, DispatchError> {1594 let properties = Self::collection_properties(collection_id);15951596 let properties = keys1597 .map(|keys| {1598 keys.into_iter()1599 .filter_map(|key| {1600 properties.get(&key).map(|value| Property {1601 key,1602 value: value.clone(),1603 })1604 })1605 .collect()1606 })1607 .unwrap_or_else(|| {1608 properties1609 .into_iter()1610 .map(|(key, value)| Property { key, value })1611 .collect()1612 });16131614 Ok(properties)1615 }16161617 1618 pub fn filter_property_permissions(1619 collection_id: CollectionId,1620 keys: Option<Vec<PropertyKey>>,1621 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1622 let permissions = Self::property_permissions(collection_id);16231624 let key_permissions = keys1625 .map(|keys| {1626 keys.into_iter()1627 .filter_map(|key| {1628 permissions1629 .get(&key)1630 .map(|permission| PropertyKeyPermission {1631 key,1632 permission: permission.clone(),1633 })1634 })1635 .collect()1636 })1637 .unwrap_or_else(|| {1638 permissions1639 .into_iter()1640 .map(|(key, permission)| PropertyKeyPermission { key, permission })1641 .collect()1642 });16431644 Ok(key_permissions)1645 }16461647 1648 1649 1650 pub fn toggle_allowlist(1651 collection: &CollectionHandle<T>,1652 sender: &T::CrossAccountId,1653 user: &T::CrossAccountId,1654 allowed: bool,1655 ) -> DispatchResult {1656 collection.check_is_owner_or_admin(sender)?;16571658 16591660 if allowed {1661 <Allowlist<T>>::insert((collection.id, user), true);1662 Self::deposit_event(Event::<T>::AllowListAddressAdded(1663 collection.id,1664 user.clone(),1665 ));1666 } else {1667 <Allowlist<T>>::remove((collection.id, user));1668 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1669 collection.id,1670 user.clone(),1671 ));1672 }16731674 <PalletEvm<T>>::deposit_log(1675 erc::CollectionHelpersEvents::CollectionChanged {1676 collection_id: eth::collection_id_to_address(collection.id),1677 }1678 .to_log(T::ContractAddress::get()),1679 );16801681 Ok(())1682 }16831684 1685 1686 1687 pub fn toggle_admin(1688 collection: &CollectionHandle<T>,1689 sender: &T::CrossAccountId,1690 user: &T::CrossAccountId,1691 admin: bool,1692 ) -> DispatchResult {1693 collection.check_is_internal()?;1694 collection.check_is_owner(sender)?;16951696 let is_admin = <IsAdmin<T>>::get((collection.id, user));1697 if is_admin == admin {1698 if admin {1699 return Ok(());1700 } else {1701 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1702 }1703 }1704 let amount = <AdminAmount<T>>::get(collection.id);17051706 17071708 if admin {1709 let amount = amount1710 .checked_add(1)1711 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1712 ensure!(1713 amount <= Self::collection_admins_limit(),1714 <Error<T>>::CollectionAdminCountExceeded,1715 );17161717 <AdminAmount<T>>::insert(collection.id, amount);1718 <IsAdmin<T>>::insert((collection.id, user), true);17191720 Self::deposit_event(Event::<T>::CollectionAdminAdded(1721 collection.id,1722 user.clone(),1723 ));1724 } else {1725 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1726 <IsAdmin<T>>::remove((collection.id, user));17271728 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1729 collection.id,1730 user.clone(),1731 ));1732 }17331734 <PalletEvm<T>>::deposit_log(1735 erc::CollectionHelpersEvents::CollectionChanged {1736 collection_id: eth::collection_id_to_address(collection.id),1737 }1738 .to_log(T::ContractAddress::get()),1739 );17401741 Ok(())1742 }17431744 1745 pub fn update_limits(1746 user: &T::CrossAccountId,1747 collection: &mut CollectionHandle<T>,1748 new_limit: CollectionLimits,1749 ) -> DispatchResult {1750 collection.check_is_internal()?;1751 collection.check_is_owner_or_admin(user)?;17521753 collection.limits =1754 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17551756 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1757 <PalletEvm<T>>::deposit_log(1758 erc::CollectionHelpersEvents::CollectionChanged {1759 collection_id: eth::collection_id_to_address(collection.id),1760 }1761 .to_log(T::ContractAddress::get()),1762 );17631764 collection.save()1765 }17661767 1768 fn clamp_limits(1769 mode: CollectionMode,1770 old_limit: &CollectionLimits,1771 mut new_limit: CollectionLimits,1772 ) -> Result<CollectionLimits, DispatchError> {1773 let limits = old_limit;1774 limit_default!(old_limit, new_limit,1775 account_token_ownership_limit => ensure!(1776 new_limit <= MAX_TOKEN_OWNERSHIP,1777 <Error<T>>::CollectionLimitBoundsExceeded,1778 ),1779 sponsored_data_size => ensure!(1780 new_limit <= CUSTOM_DATA_LIMIT,1781 <Error<T>>::CollectionLimitBoundsExceeded,1782 ),17831784 sponsored_data_rate_limit => {},1785 token_limit => ensure!(1786 old_limit >= new_limit && new_limit > 0,1787 <Error<T>>::CollectionTokenLimitExceeded1788 ),17891790 sponsor_transfer_timeout(match mode {1791 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1792 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1793 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1794 }) => ensure!(1795 new_limit <= MAX_SPONSOR_TIMEOUT,1796 <Error<T>>::CollectionLimitBoundsExceeded,1797 ),1798 sponsor_approve_timeout => {},1799 owner_can_transfer => ensure!(1800 !limits.owner_can_transfer_instaled() ||1801 old_limit || !new_limit,1802 <Error<T>>::OwnerPermissionsCantBeReverted,1803 ),1804 owner_can_destroy => ensure!(1805 old_limit || !new_limit,1806 <Error<T>>::OwnerPermissionsCantBeReverted,1807 ),1808 transfers_enabled => {},1809 );1810 Ok(new_limit)1811 }18121813 1814 pub fn update_permissions(1815 user: &T::CrossAccountId,1816 collection: &mut CollectionHandle<T>,1817 new_permission: CollectionPermissions,1818 ) -> DispatchResult {1819 collection.check_is_internal()?;1820 collection.check_is_owner_or_admin(user)?;1821 collection.permissions = Self::clamp_permissions(1822 collection.mode.clone(),1823 &collection.permissions,1824 new_permission,1825 )?;18261827 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1828 <PalletEvm<T>>::deposit_log(1829 erc::CollectionHelpersEvents::CollectionChanged {1830 collection_id: eth::collection_id_to_address(collection.id),1831 }1832 .to_log(T::ContractAddress::get()),1833 );18341835 collection.save()1836 }18371838 1839 fn clamp_permissions(1840 _mode: CollectionMode,1841 old_permission: &CollectionPermissions,1842 mut new_permission: CollectionPermissions,1843 ) -> Result<CollectionPermissions, DispatchError> {1844 limit_default_clone!(old_permission, new_permission,1845 access => {},1846 mint_mode => {},1847 nesting => { },1848 );1849 Ok(new_permission)1850 }18511852 1853 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1854 CollectionProperties::<T>::mutate(collection_id, |properties| {1855 properties.recompute_consumed_space();1856 });18571858 Ok(())1859 }1860}186118621863#[macro_export]1864macro_rules! unsupported {1865 ($runtime:path) => {1866 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1867 };1868}186918701871pub trait CommonWeightInfo<CrossAccountId> {1872 1873 fn create_item(data: &CreateItemData) -> Weight {1874 Self::create_multiple_items(from_ref(data))1875 }18761877 1878 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18791880 1881 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18821883 1884 fn burn_item() -> Weight;18851886 1887 1888 1889 fn set_collection_properties(amount: u32) -> Weight;18901891 1892 1893 1894 fn delete_collection_properties(amount: u32) -> Weight;18951896 1897 1898 1899 fn set_token_properties(amount: u32) -> Weight;19001901 1902 1903 1904 fn delete_token_properties(amount: u32) -> Weight;19051906 1907 1908 1909 fn set_token_property_permissions(amount: u32) -> Weight;19101911 1912 fn transfer() -> Weight;19131914 1915 fn approve() -> Weight;19161917 1918 fn approve_from() -> Weight;19191920 1921 fn transfer_from() -> Weight;19221923 1924 fn burn_from() -> Weight;19251926 1927 1928 1929 1930 fn burn_recursively_self_raw() -> Weight;19311932 1933 1934 1935 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19361937 1938 1939 1940 1941 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1942 Self::burn_recursively_self_raw()1943 .saturating_mul(max_selfs.max(1) as u64)1944 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1945 }19461947 1948 fn token_owner() -> Weight;19491950 1951 fn set_allowance_for_all() -> Weight;19521953 1954 fn force_repair_item() -> Weight;1955}195619571958pub trait RefungibleExtensionsWeightInfo {1959 1960 fn repartition() -> Weight;1961}196219631964196519661967pub trait CommonCollectionOperations<T: Config> {1968 1969 1970 1971 1972 1973 1974 fn create_item(1975 &self,1976 sender: T::CrossAccountId,1977 to: T::CrossAccountId,1978 data: CreateItemData,1979 nesting_budget: &dyn Budget,1980 ) -> DispatchResultWithPostInfo;19811982 1983 1984 1985 1986 1987 1988 fn create_multiple_items(1989 &self,1990 sender: T::CrossAccountId,1991 to: T::CrossAccountId,1992 data: Vec<CreateItemData>,1993 nesting_budget: &dyn Budget,1994 ) -> DispatchResultWithPostInfo;19951996 1997 1998 1999 2000 2001 2002 fn create_multiple_items_ex(2003 &self,2004 sender: T::CrossAccountId,2005 data: CreateItemExData<T::CrossAccountId>,2006 nesting_budget: &dyn Budget,2007 ) -> DispatchResultWithPostInfo;20082009 2010 2011 2012 2013 2014 fn burn_item(2015 &self,2016 sender: T::CrossAccountId,2017 token: TokenId,2018 amount: u128,2019 ) -> DispatchResultWithPostInfo;20202021 2022 2023 2024 2025 2026 2027 fn burn_item_recursively(2028 &self,2029 sender: T::CrossAccountId,2030 token: TokenId,2031 self_budget: &dyn Budget,2032 breadth_budget: &dyn Budget,2033 ) -> DispatchResultWithPostInfo;20342035 2036 2037 2038 2039 fn set_collection_properties(2040 &self,2041 sender: T::CrossAccountId,2042 properties: Vec<Property>,2043 ) -> DispatchResultWithPostInfo;20442045 2046 2047 2048 2049 fn delete_collection_properties(2050 &self,2051 sender: &T::CrossAccountId,2052 property_keys: Vec<PropertyKey>,2053 ) -> DispatchResultWithPostInfo;20542055 2056 2057 2058 2059 2060 2061 2062 2063 2064 fn set_token_properties(2065 &self,2066 sender: T::CrossAccountId,2067 token_id: TokenId,2068 properties: Vec<Property>,2069 budget: &dyn Budget,2070 ) -> DispatchResultWithPostInfo;20712072 2073 2074 2075 2076 2077 2078 2079 2080 2081 fn delete_token_properties(2082 &self,2083 sender: T::CrossAccountId,2084 token_id: TokenId,2085 property_keys: Vec<PropertyKey>,2086 budget: &dyn Budget,2087 ) -> DispatchResultWithPostInfo;20882089 2090 2091 2092 2093 2094 2095 fn set_token_property_permissions(2096 &self,2097 sender: &T::CrossAccountId,2098 property_permissions: Vec<PropertyKeyPermission>,2099 ) -> DispatchResultWithPostInfo;21002101 2102 2103 2104 2105 2106 2107 2108 fn transfer(2109 &self,2110 sender: T::CrossAccountId,2111 to: T::CrossAccountId,2112 token: TokenId,2113 amount: u128,2114 budget: &dyn Budget,2115 ) -> DispatchResultWithPostInfo;21162117 2118 2119 2120 2121 2122 2123 fn approve(2124 &self,2125 sender: T::CrossAccountId,2126 spender: T::CrossAccountId,2127 token: TokenId,2128 amount: u128,2129 ) -> DispatchResultWithPostInfo;21302131 2132 2133 2134 2135 2136 2137 2138 fn approve_from(2139 &self,2140 sender: T::CrossAccountId,2141 from: T::CrossAccountId,2142 to: T::CrossAccountId,2143 token: TokenId,2144 amount: u128,2145 ) -> DispatchResultWithPostInfo;21462147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 fn transfer_from(2158 &self,2159 sender: T::CrossAccountId,2160 from: T::CrossAccountId,2161 to: T::CrossAccountId,2162 token: TokenId,2163 amount: u128,2164 budget: &dyn Budget,2165 ) -> DispatchResultWithPostInfo;21662167 2168 2169 2170 2171 2172 2173 2174 2175 2176 fn burn_from(2177 &self,2178 sender: T::CrossAccountId,2179 from: T::CrossAccountId,2180 token: TokenId,2181 amount: u128,2182 budget: &dyn Budget,2183 ) -> DispatchResultWithPostInfo;21842185 2186 2187 2188 2189 2190 2191 fn check_nesting(2192 &self,2193 sender: T::CrossAccountId,2194 from: (CollectionId, TokenId),2195 under: TokenId,2196 budget: &dyn Budget,2197 ) -> DispatchResult;21982199 2200 2201 2202 2203 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22042205 2206 2207 2208 2209 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22102211 2212 2213 2214 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22152216 2217 fn collection_tokens(&self) -> Vec<TokenId>;22182219 2220 2221 2222 fn token_exists(&self, token: TokenId) -> bool;22232224 2225 fn last_token_id(&self) -> TokenId;22262227 2228 2229 2230 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22312232 2233 2234 2235 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22362237 2238 2239 2240 2241 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22422243 2244 2245 2246 2247 2248 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22492250 2251 fn total_supply(&self) -> u32;22522253 2254 2255 2256 fn account_balance(&self, account: T::CrossAccountId) -> u32;22572258 2259 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22602261 2262 fn total_pieces(&self, token: TokenId) -> Option<u128>;22632264 2265 2266 2267 2268 2269 fn allowance(2270 &self,2271 sender: T::CrossAccountId,2272 spender: T::CrossAccountId,2273 token: TokenId,2274 ) -> u128;22752276 2277 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22782279 2280 2281 2282 2283 fn set_allowance_for_all(2284 &self,2285 owner: T::CrossAccountId,2286 operator: T::CrossAccountId,2287 approve: bool,2288 ) -> DispatchResultWithPostInfo;22892290 2291 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;22922293 2294 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2295}229622972298pub trait RefungibleExtensions<T>2299where2300 T: Config,2301{2302 2303 2304 2305 2306 2307 2308 2309 fn repartition(2310 &self,2311 sender: &T::CrossAccountId,2312 token: TokenId,2313 amount: u128,2314 ) -> DispatchResultWithPostInfo;2315}23162317231823192320pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2321 let post_info = PostDispatchInfo {2322 actual_weight: Some(weight),2323 pays_fee: Pays::Yes,2324 };2325 match res {2326 Ok(()) => Ok(post_info),2327 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2328 }2329}23302331impl<T: Config> From<PropertiesError> for Error<T> {2332 fn from(error: PropertiesError) -> Self {2333 match error {2334 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2335 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2336 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2337 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2338 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2339 }2340 }2341}