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 Self::new_with_recorder(id, SubstrateRecorder::new(gas_limit))129 }130131 132 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {133 <CollectionById<T>>::get(id).map(|collection| Self {134 id,135 collection,136 recorder,137 })138 }139140 141 142 pub fn new(id: CollectionId) -> Option<Self> {143 Self::new_with_gas_limit(id, u64::MAX)144 }145146 147 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {148 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)149 }150151 152 pub fn consume_store_reads(153 &self,154 reads: u64,155 ) -> pallet_evm_coder_substrate::execution::Result<()> {156 self.recorder157 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(158 <T as frame_system::Config>::DbWeight::get()159 .read160 .saturating_mul(reads),161 162 0,163 )))164 }165166 167 pub fn consume_store_writes(168 &self,169 writes: u64,170 ) -> pallet_evm_coder_substrate::execution::Result<()> {171 self.recorder172 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(173 <T as frame_system::Config>::DbWeight::get()174 .write175 .saturating_mul(writes),176 177 0,178 )))179 }180181 182 pub fn consume_store_reads_and_writes(183 &self,184 reads: u64,185 writes: u64,186 ) -> pallet_evm_coder_substrate::execution::Result<()> {187 let weight = <T as frame_system::Config>::DbWeight::get();188 let reads = weight.read.saturating_mul(reads);189 let writes = weight.read.saturating_mul(writes);190 self.recorder191 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_parts(192 reads.saturating_add(writes),193 194 0,195 )))196 }197198 199 pub fn save(&self) -> DispatchResult {200 <CollectionById<T>>::insert(self.id, &self.collection);201 Ok(())202 }203204 205 206 207 208 209 pub fn set_sponsor(210 &mut self,211 sender: &T::CrossAccountId,212 sponsor: T::AccountId,213 ) -> DispatchResult {214 self.check_is_internal()?;215 self.check_is_owner_or_admin(sender)?;216217 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());218219 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));220 <PalletEvm<T>>::deposit_log(221 erc::CollectionHelpersEvents::CollectionChanged {222 collection_id: eth::collection_id_to_address(self.id),223 }224 .to_log(T::ContractAddress::get()),225 );226227 self.save()228 }229230 231 232 233 234 235 236 237 238 239 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {240 self.check_is_internal()?;241242 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());243244 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));245 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));246 <PalletEvm<T>>::deposit_log(247 erc::CollectionHelpersEvents::CollectionChanged {248 collection_id: eth::collection_id_to_address(self.id),249 }250 .to_log(T::ContractAddress::get()),251 );252253 self.save()254 }255256 257 258 259 260 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {261 self.check_is_internal()?;262 ensure!(263 self.collection.sponsorship.pending_sponsor() == Some(sender),264 Error::<T>::ConfirmSponsorshipFail265 );266267 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());268269 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));270 <PalletEvm<T>>::deposit_log(271 erc::CollectionHelpersEvents::CollectionChanged {272 collection_id: eth::collection_id_to_address(self.id),273 }274 .to_log(T::ContractAddress::get()),275 );276277 self.save()278 }279280 281 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {282 self.check_is_internal()?;283 self.check_is_owner_or_admin(sender)?;284285 self.collection.sponsorship = SponsorshipState::Disabled;286287 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));288 <PalletEvm<T>>::deposit_log(289 erc::CollectionHelpersEvents::CollectionChanged {290 collection_id: eth::collection_id_to_address(self.id),291 }292 .to_log(T::ContractAddress::get()),293 );294 self.save()295 }296297 298 299 300 301 pub fn force_remove_sponsor(&mut self) -> DispatchResult {302 self.check_is_internal()?;303304 self.collection.sponsorship = SponsorshipState::Disabled;305306 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));307 <PalletEvm<T>>::deposit_log(308 erc::CollectionHelpersEvents::CollectionChanged {309 collection_id: eth::collection_id_to_address(self.id),310 }311 .to_log(T::ContractAddress::get()),312 );313 self.save()314 }315316 317 318 pub fn check_is_internal(&self) -> DispatchResult {319 if self.flags.external {320 return Err(<Error<T>>::CollectionIsExternal)?;321 }322323 Ok(())324 }325326 327 328 pub fn check_is_external(&self) -> DispatchResult {329 if !self.flags.external {330 return Err(<Error<T>>::CollectionIsInternal)?;331 }332333 Ok(())334 }335}336337impl<T: Config> Deref for CollectionHandle<T> {338 type Target = Collection<T::AccountId>;339340 fn deref(&self) -> &Self::Target {341 &self.collection342 }343}344345impl<T: Config> DerefMut for CollectionHandle<T> {346 fn deref_mut(&mut self) -> &mut Self::Target {347 &mut self.collection348 }349}350351impl<T: Config> CollectionHandle<T> {352 353 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {354 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);355 Ok(())356 }357358 359 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {360 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))361 }362363 364 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {365 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);366 Ok(())367 }368369 370 371 372 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {373 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)374 }375376 377 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {378 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)379 }380381 382 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {383 ensure!(384 <Allowlist<T>>::get((self.id, user)),385 <Error<T>>::AddressNotInAllowlist386 );387 Ok(())388 }389390 391 392 393 pub fn change_owner(394 &mut self,395 caller: T::CrossAccountId,396 new_owner: T::CrossAccountId,397 ) -> DispatchResult {398 self.check_is_internal()?;399 self.check_is_owner(&caller)?;400 self.collection.owner = new_owner.as_sub().clone();401402 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(403 self.id,404 new_owner.as_sub().clone(),405 ));406 <PalletEvm<T>>::deposit_log(407 erc::CollectionHelpersEvents::CollectionChanged {408 collection_id: eth::collection_id_to_address(self.id),409 }410 .to_log(T::ContractAddress::get()),411 );412413 self.save()414 }415}416417#[frame_support::pallet]418pub mod pallet {419420 use super::*;421 use dispatch::CollectionDispatch;422 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};423 use frame_support::traits::Currency;424 use up_data_structs::{TokenId, mapping::TokenAddressMapping};425 use scale_info::TypeInfo;426 use weights::WeightInfo;427428 #[pallet::config]429 pub trait Config:430 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo431 {432 433 type WeightInfo: WeightInfo;434435 436 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;437438 439 type Currency: Currency<Self::AccountId>;440441 442 #[pallet::constant]443 type CollectionCreationPrice: Get<444 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,445 >;446447 448 type CollectionDispatch: CollectionDispatch<Self>;449450 451 type TreasuryAccountId: Get<Self::AccountId>;452453 454 #[pallet::constant]455 type ContractAddress: Get<H160>;456457 458 type EvmTokenAddressMapping: TokenAddressMapping<H160>;459460 461 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;462 }463464 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);465 466 pub const NATIVE_FINGIBLE_COLLECTION_ID: CollectionId = CollectionId(0);467468 #[pallet::pallet]469 #[pallet::storage_version(STORAGE_VERSION)]470 pub struct Pallet<T>(_);471472 #[pallet::extra_constants]473 impl<T: Config> Pallet<T> {474 475 pub fn collection_admins_limit() -> u32 {476 COLLECTION_ADMINS_LIMIT477 }478 }479480 #[pallet::genesis_config]481 pub struct GenesisConfig<T>(PhantomData<T>);482483 #[cfg(feature = "std")]484 impl<T: Config> Default for GenesisConfig<T> {485 fn default() -> Self {486 Self(Default::default())487 }488 }489490 #[pallet::genesis_build]491 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {492 fn build(&self) {493 StorageVersion::new(1).put::<Pallet<T>>();494 }495 }496497 impl<T: Config> Pallet<T> {498 499 pub fn deposit_event(event: Event<T>) {500 let event = <T as Config>::RuntimeEvent::from(event);501 let event = event.into();502 <frame_system::Pallet<T>>::deposit_event(event)503 }504 }505506 #[pallet::event]507 pub enum Event<T: Config> {508 509 CollectionCreated(510 511 CollectionId,512 513 u8,514 515 T::AccountId,516 ),517518 519 CollectionDestroyed(520 521 CollectionId,522 ),523524 525 ItemCreated(526 527 CollectionId,528 529 TokenId,530 531 T::CrossAccountId,532 533 u128,534 ),535536 537 ItemDestroyed(538 539 CollectionId,540 541 TokenId,542 543 T::CrossAccountId,544 545 u128,546 ),547548 549 Transfer(550 551 CollectionId,552 553 TokenId,554 555 T::CrossAccountId,556 557 T::CrossAccountId,558 559 u128,560 ),561562 563 Approved(564 565 CollectionId,566 567 TokenId,568 569 T::CrossAccountId,570 571 T::CrossAccountId,572 573 u128,574 ),575576 577 ApprovedForAll(578 579 CollectionId,580 581 T::CrossAccountId,582 583 T::CrossAccountId,584 585 bool,586 ),587588 589 CollectionPropertySet(590 591 CollectionId,592 593 PropertyKey,594 ),595596 597 CollectionPropertyDeleted(598 599 CollectionId,600 601 PropertyKey,602 ),603604 605 TokenPropertySet(606 607 CollectionId,608 609 TokenId,610 611 PropertyKey,612 ),613614 615 TokenPropertyDeleted(616 617 CollectionId,618 619 TokenId,620 621 PropertyKey,622 ),623624 625 PropertyPermissionSet(626 627 CollectionId,628 629 PropertyKey,630 ),631632 633 AllowListAddressAdded(634 635 CollectionId,636 637 T::CrossAccountId,638 ),639640 641 AllowListAddressRemoved(642 643 CollectionId,644 645 T::CrossAccountId,646 ),647648 649 CollectionAdminAdded(650 651 CollectionId,652 653 T::CrossAccountId,654 ),655656 657 CollectionAdminRemoved(658 659 CollectionId,660 661 T::CrossAccountId,662 ),663664 665 CollectionLimitSet(666 667 CollectionId,668 ),669670 671 CollectionOwnerChanged(672 673 CollectionId,674 675 T::AccountId,676 ),677678 679 CollectionPermissionSet(680 681 CollectionId,682 ),683684 685 CollectionSponsorSet(686 687 CollectionId,688 689 T::AccountId,690 ),691692 693 SponsorshipConfirmed(694 695 CollectionId,696 697 T::AccountId,698 ),699700 701 CollectionSponsorRemoved(702 703 CollectionId,704 ),705 }706707 #[pallet::error]708 pub enum Error<T> {709 710 CollectionNotFound,711 712 MustBeTokenOwner,713 714 NoPermission,715 716 CantDestroyNotEmptyCollection,717 718 PublicMintingNotAllowed,719 720 AddressNotInAllowlist,721722 723 CollectionNameLimitExceeded,724 725 CollectionDescriptionLimitExceeded,726 727 CollectionTokenPrefixLimitExceeded,728 729 TotalCollectionsLimitExceeded,730 731 CollectionAdminCountExceeded,732 733 CollectionLimitBoundsExceeded,734 735 OwnerPermissionsCantBeReverted,736 737 TransferNotAllowed,738 739 AccountTokenLimitExceeded,740 741 CollectionTokenLimitExceeded,742 743 MetadataFlagFrozen,744745 746 TokenNotFound,747 748 TokenValueTooLow,749 750 ApprovedValueTooLow,751 752 CantApproveMoreThanOwned,753 754 AddressIsNotEthMirror,755756 757 AddressIsZero,758759 760 UnsupportedOperation,761762 763 NotSufficientFounds,764765 766 UserIsNotAllowedToNest,767 768 SourceCollectionIsNotAllowedToNest,769770 771 CollectionFieldSizeExceeded,772773 774 NoSpaceForProperty,775776 777 PropertyLimitReached,778779 780 PropertyKeyIsTooLong,781782 783 InvalidCharacterInPropertyKey,784785 786 EmptyPropertyKey,787788 789 CollectionIsExternal,790791 792 CollectionIsInternal,793794 795 ConfirmSponsorshipFail,796797 798 UserIsNotCollectionAdmin,799 }800801 802 #[pallet::storage]803 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;804805 806 #[pallet::storage]807 pub type DestroyedCollectionCount<T> =808 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;809810 811 #[pallet::storage]812 pub type CollectionById<T> = StorageMap<813 Hasher = Blake2_128Concat,814 Key = CollectionId,815 Value = Collection<<T as frame_system::Config>::AccountId>,816 QueryKind = OptionQuery,817 >;818819 820 #[pallet::storage]821 #[pallet::getter(fn collection_properties)]822 pub type CollectionProperties<T> = StorageMap<823 Hasher = Blake2_128Concat,824 Key = CollectionId,825 Value = CollectionPropertiesT,826 QueryKind = ValueQuery,827 >;828829 830 #[pallet::storage]831 #[pallet::getter(fn property_permissions)]832 pub type CollectionPropertyPermissions<T> = StorageMap<833 Hasher = Blake2_128Concat,834 Key = CollectionId,835 Value = PropertiesPermissionMap,836 QueryKind = ValueQuery,837 >;838839 840 #[pallet::storage]841 pub type AdminAmount<T> = StorageMap<842 Hasher = Blake2_128Concat,843 Key = CollectionId,844 Value = u32,845 QueryKind = ValueQuery,846 >;847848 849 #[pallet::storage]850 pub type IsAdmin<T: Config> = StorageNMap<851 Key = (852 Key<Blake2_128Concat, CollectionId>,853 Key<Blake2_128Concat, T::CrossAccountId>,854 ),855 Value = bool,856 QueryKind = ValueQuery,857 >;858859 860 #[pallet::storage]861 pub type Allowlist<T: Config> = StorageNMap<862 Key = (863 Key<Blake2_128Concat, CollectionId>,864 Key<Blake2_128Concat, T::CrossAccountId>,865 ),866 Value = bool,867 QueryKind = ValueQuery,868 >;869870 871 #[pallet::storage]872 pub type DummyStorageValue<T: Config> = StorageValue<873 Value = (874 CollectionStats,875 CollectionId,876 TokenId,877 TokenChild,878 PhantomType<(879 TokenData<T::CrossAccountId>,880 RpcCollection<T::AccountId>,881 882 PovInfo,883 )>,884 ),885 QueryKind = OptionQuery,886 >;887}888889impl<T: Config> Pallet<T> {890 891 892 893 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {894 ensure!(895 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,896 <Error<T>>::AddressIsZero897 );898 Ok(())899 }900901 902 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {903 <IsAdmin<T>>::iter_prefix((collection,))904 .map(|(a, _)| a)905 .collect()906 }907908 909 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {910 <Allowlist<T>>::iter_prefix((collection,))911 .map(|(a, _)| a)912 .collect()913 }914915 916 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {917 <Allowlist<T>>::get((collection, user))918 }919920 921 pub fn collection_stats() -> CollectionStats {922 let created = <CreatedCollectionCount<T>>::get();923 let destroyed = <DestroyedCollectionCount<T>>::get();924 CollectionStats {925 created: created.0,926 destroyed: destroyed.0,927 alive: created.0 - destroyed.0,928 }929 }930931 932 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {933 let collection = <CollectionById<T>>::get(collection)?;934 let limits = collection.limits;935 let effective_limits = CollectionLimits {936 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),937 sponsored_data_size: Some(limits.sponsored_data_size()),938 sponsored_data_rate_limit: Some(939 limits940 .sponsored_data_rate_limit941 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),942 ),943 token_limit: Some(limits.token_limit()),944 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(945 match collection.mode {946 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,947 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,948 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,949 },950 )),951 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),952 owner_can_transfer: Some(limits.owner_can_transfer()),953 owner_can_destroy: Some(limits.owner_can_destroy()),954 transfers_enabled: Some(limits.transfers_enabled()),955 };956957 Some(effective_limits)958 }959960 961 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {962 let Collection {963 name,964 description,965 owner,966 mode,967 token_prefix,968 sponsorship,969 limits,970 permissions,971 flags,972 } = <CollectionById<T>>::get(collection)?;973974 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)975 .into_iter()976 .map(|(key, permission)| PropertyKeyPermission { key, permission })977 .collect();978979 let properties = <CollectionProperties<T>>::get(collection)980 .into_iter()981 .map(|(key, value)| Property { key, value })982 .collect();983984 let permissions = CollectionPermissions {985 access: Some(permissions.access()),986 mint_mode: Some(permissions.mint_mode()),987 nesting: Some(permissions.nesting().clone()),988 };989990 Some(RpcCollection {991 name: name.into_inner(),992 description: description.into_inner(),993 owner,994 mode,995 token_prefix: token_prefix.into_inner(),996 sponsorship,997 limits,998 permissions,999 token_property_permissions,1000 properties,1001 read_only: flags.external,10021003 flags: RpcCollectionFlags {1004 foreign: flags.foreign,1005 erc721metadata: flags.erc721metadata,1006 },1007 })1008 }1009}10101011macro_rules! limit_default {1012 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1013 $(1014 if let Some($new) = $new.$field {1015 let $old = $old.$field($($arg)?);1016 let _ = $new;1017 let _ = $old;1018 $check1019 } else {1020 $new.$field = $old.$field1021 }1022 )*1023 }};1024}1025macro_rules! limit_default_clone {1026 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1027 $(1028 if let Some($new) = $new.$field.clone() {1029 let $old = $old.$field($($arg)?);1030 let _ = $new;1031 let _ = $old;1032 $check1033 } else {1034 $new.$field = $old.$field.clone()1035 }1036 )*1037 }};1038}10391040impl<T: Config> Pallet<T> {1041 1042 1043 1044 1045 1046 pub fn init_collection(1047 owner: T::CrossAccountId,1048 payer: T::CrossAccountId,1049 data: CreateCollectionData<T::AccountId>,1050 flags: CollectionFlags,1051 ) -> Result<CollectionId, DispatchError> {1052 {1053 ensure!(1054 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1055 Error::<T>::CollectionTokenPrefixLimitExceeded1056 );1057 }10581059 let created_count = <CreatedCollectionCount<T>>::get()1060 .01061 .checked_add(1)1062 .ok_or(ArithmeticError::Overflow)?;1063 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1064 let id = CollectionId(created_count);10651066 1067 ensure!(1068 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1069 <Error<T>>::TotalCollectionsLimitExceeded1070 );10711072 10731074 let collection = Collection {1075 owner: owner.as_sub().clone(),1076 name: data.name,1077 mode: data.mode.clone(),1078 description: data.description,1079 token_prefix: data.token_prefix,1080 sponsorship: data1081 .pending_sponsor1082 .map(SponsorshipState::Unconfirmed)1083 .unwrap_or_default(),1084 limits: data1085 .limits1086 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1087 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1088 permissions: data1089 .permissions1090 .map(|permissions| {1091 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1092 })1093 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1094 flags,1095 };10961097 let mut collection_properties = CollectionPropertiesT::new();1098 collection_properties1099 .try_set_from_iter(data.properties.into_iter())1100 .map_err(<Error<T>>::from)?;11011102 CollectionProperties::<T>::insert(id, collection_properties);11031104 let mut token_props_permissions = PropertiesPermissionMap::new();1105 token_props_permissions1106 .try_set_from_iter(data.token_property_permissions.into_iter())1107 .map_err(<Error<T>>::from)?;11081109 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11101111 1112 {1113 let mut imbalance =1114 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1115 imbalance.subsume(1116 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1117 &T::TreasuryAccountId::get(),1118 T::CollectionCreationPrice::get(),1119 ),1120 );1121 <T as Config>::Currency::settle(1122 payer.as_sub(),1123 imbalance,1124 WithdrawReasons::TRANSFER,1125 ExistenceRequirement::KeepAlive,1126 )1127 .map_err(|_| Error::<T>::NotSufficientFounds)?;1128 }11291130 <CreatedCollectionCount<T>>::put(created_count);1131 <Pallet<T>>::deposit_event(Event::CollectionCreated(1132 id,1133 data.mode.id(),1134 owner.as_sub().clone(),1135 ));1136 <PalletEvm<T>>::deposit_log(1137 erc::CollectionHelpersEvents::CollectionCreated {1138 owner: *owner.as_eth(),1139 collection_id: eth::collection_id_to_address(id),1140 }1141 .to_log(T::ContractAddress::get()),1142 );1143 <CollectionById<T>>::insert(id, collection);1144 Ok(id)1145 }11461147 1148 1149 1150 1151 pub fn destroy_collection(1152 collection: CollectionHandle<T>,1153 sender: &T::CrossAccountId,1154 ) -> DispatchResult {1155 ensure!(1156 collection.limits.owner_can_destroy(),1157 <Error<T>>::NoPermission,1158 );1159 collection.check_is_owner(sender)?;11601161 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1162 .01163 .checked_add(1)1164 .ok_or(ArithmeticError::Overflow)?;11651166 11671168 <DestroyedCollectionCount<T>>::put(destroyed_collections);1169 <CollectionById<T>>::remove(collection.id);1170 <AdminAmount<T>>::remove(collection.id);1171 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1172 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1173 <CollectionProperties<T>>::remove(collection.id);11741175 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11761177 <PalletEvm<T>>::deposit_log(1178 erc::CollectionHelpersEvents::CollectionDestroyed {1179 collection_id: eth::collection_id_to_address(collection.id),1180 }1181 .to_log(T::ContractAddress::get()),1182 );1183 Ok(())1184 }11851186 1187 1188 1189 1190 1191 1192 1193 1194 #[transactional]1195 fn modify_collection_properties(1196 collection: &CollectionHandle<T>,1197 sender: &T::CrossAccountId,1198 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1199 ) -> DispatchResult {1200 collection.check_is_owner_or_admin(sender)?;12011202 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12031204 for (key, value) in properties_updates {1205 match value {1206 Some(value) => {1207 stored_properties1208 .try_set(key.clone(), value)1209 .map_err(<Error<T>>::from)?;12101211 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1212 <PalletEvm<T>>::deposit_log(1213 erc::CollectionHelpersEvents::CollectionChanged {1214 collection_id: eth::collection_id_to_address(collection.id),1215 }1216 .to_log(T::ContractAddress::get()),1217 );1218 }1219 None => {1220 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12211222 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1223 <PalletEvm<T>>::deposit_log(1224 erc::CollectionHelpersEvents::CollectionChanged {1225 collection_id: eth::collection_id_to_address(collection.id),1226 }1227 .to_log(T::ContractAddress::get()),1228 );1229 }1230 }1231 }12321233 <CollectionProperties<T>>::set(collection.id, stored_properties);12341235 Ok(())1236 }12371238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 pub fn modify_token_properties(1256 collection: &CollectionHandle<T>,1257 sender: &T::CrossAccountId,1258 token_id: TokenId,1259 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1260 is_token_create: bool,1261 mut stored_properties: TokenProperties,1262 is_token_owner: impl Fn() -> Result<bool, DispatchError>,1263 set_token_properties: impl FnOnce(TokenProperties),1264 log: evm_coder::ethereum::Log,1265 ) -> DispatchResult {1266 let is_collection_admin = collection.is_owner_or_admin(sender);1267 let permissions = Self::property_permissions(collection.id);12681269 let mut token_owner_result = None;1270 let mut is_token_owner = || -> Result<bool, DispatchError> {1271 *token_owner_result.get_or_insert_with(&is_token_owner)1272 };12731274 for (key, value) in properties_updates {1275 let permission = permissions1276 .get(&key)1277 .cloned()1278 .unwrap_or_else(PropertyPermission::none);12791280 let is_property_exists = stored_properties.get(&key).is_some();12811282 match permission {1283 PropertyPermission { mutable: false, .. } if is_property_exists => {1284 return Err(<Error<T>>::NoPermission.into());1285 }12861287 PropertyPermission {1288 collection_admin,1289 token_owner,1290 ..1291 } => {1292 1293 let is_token_create =1294 is_token_create && (collection_admin || token_owner) && value.is_some();1295 if !(is_token_create1296 || (collection_admin && is_collection_admin)1297 || (token_owner && is_token_owner()?))1298 {1299 fail!(<Error<T>>::NoPermission);1300 }1301 }1302 }13031304 match value {1305 Some(value) => {1306 stored_properties1307 .try_set(key.clone(), value)1308 .map_err(<Error<T>>::from)?;13091310 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1311 }1312 None => {1313 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13141315 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1316 }1317 }13181319 <PalletEvm<T>>::deposit_log(log.clone());1320 }13211322 set_token_properties(stored_properties);13231324 Ok(())1325 }13261327 1328 1329 1330 1331 1332 1333 pub fn set_allowance_for_all(1334 collection: &CollectionHandle<T>,1335 owner: &T::CrossAccountId,1336 operator: &T::CrossAccountId,1337 approve: bool,1338 set_allowance: impl FnOnce(),1339 log: evm_coder::ethereum::Log,1340 ) -> DispatchResult {1341 if collection.permissions.access() == AccessMode::AllowList {1342 collection.check_allowlist(owner)?;1343 collection.check_allowlist(operator)?;1344 }13451346 Self::ensure_correct_receiver(operator)?;13471348 set_allowance();13491350 <PalletEvm<T>>::deposit_log(log);1351 Self::deposit_event(Event::ApprovedForAll(1352 collection.id,1353 owner.clone(),1354 operator.clone(),1355 approve,1356 ));1357 Ok(())1358 }13591360 1361 1362 1363 1364 1365 pub fn set_collection_property(1366 collection: &CollectionHandle<T>,1367 sender: &T::CrossAccountId,1368 property: Property,1369 ) -> DispatchResult {1370 Self::set_collection_properties(collection, sender, [property].into_iter())1371 }13721373 1374 1375 1376 1377 1378 1379 pub fn set_scoped_collection_property(1380 collection_id: CollectionId,1381 scope: PropertyScope,1382 property: Property,1383 ) -> DispatchResult {1384 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1385 properties.try_scoped_set(scope, property.key, property.value)1386 })1387 .map_err(<Error<T>>::from)?;13881389 Ok(())1390 }13911392 1393 1394 1395 1396 1397 1398 pub fn set_scoped_collection_properties(1399 collection_id: CollectionId,1400 scope: PropertyScope,1401 properties: impl Iterator<Item = Property>,1402 ) -> DispatchResult {1403 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1404 stored_properties.try_scoped_set_from_iter(scope, properties)1405 })1406 .map_err(<Error<T>>::from)?;14071408 Ok(())1409 }14101411 1412 1413 1414 1415 1416 pub fn set_collection_properties(1417 collection: &CollectionHandle<T>,1418 sender: &T::CrossAccountId,1419 properties: impl Iterator<Item = Property>,1420 ) -> DispatchResult {1421 Self::modify_collection_properties(1422 collection,1423 sender,1424 properties.map(|property| (property.key, Some(property.value))),1425 )1426 }14271428 1429 1430 1431 1432 1433 pub fn delete_collection_property(1434 collection: &CollectionHandle<T>,1435 sender: &T::CrossAccountId,1436 property_key: PropertyKey,1437 ) -> DispatchResult {1438 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1439 }14401441 1442 1443 1444 1445 1446 pub fn delete_collection_properties(1447 collection: &CollectionHandle<T>,1448 sender: &T::CrossAccountId,1449 property_keys: impl Iterator<Item = PropertyKey>,1450 ) -> DispatchResult {1451 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1452 }14531454 1455 1456 1457 1458 1459 1460 pub fn set_property_permission_unchecked(1461 collection: CollectionId,1462 property_permission: PropertyKeyPermission,1463 ) -> DispatchResult {1464 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1465 permissions.try_set(property_permission.key, property_permission.permission)1466 })1467 .map_err(<Error<T>>::from)?;1468 Ok(())1469 }14701471 1472 1473 1474 1475 1476 pub fn set_property_permission(1477 collection: &CollectionHandle<T>,1478 sender: &T::CrossAccountId,1479 property_permission: PropertyKeyPermission,1480 ) -> DispatchResult {1481 Self::set_scoped_property_permission(1482 collection,1483 sender,1484 PropertyScope::None,1485 property_permission,1486 )1487 }14881489 1490 1491 1492 1493 1494 1495 pub fn set_scoped_property_permission(1496 collection: &CollectionHandle<T>,1497 sender: &T::CrossAccountId,1498 scope: PropertyScope,1499 property_permission: PropertyKeyPermission,1500 ) -> DispatchResult {1501 collection.check_is_owner_or_admin(sender)?;15021503 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1504 let current_permission = all_permissions.get(&property_permission.key);1505 if matches![1506 current_permission,1507 Some(PropertyPermission { mutable: false, .. })1508 ] {1509 return Err(<Error<T>>::NoPermission.into());1510 }15111512 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1513 let property_permission = property_permission.clone();1514 permissions.try_scoped_set(1515 scope,1516 property_permission.key,1517 property_permission.permission,1518 )1519 })1520 .map_err(<Error<T>>::from)?;15211522 Self::deposit_event(Event::PropertyPermissionSet(1523 collection.id,1524 property_permission.key,1525 ));1526 <PalletEvm<T>>::deposit_log(1527 erc::CollectionHelpersEvents::CollectionChanged {1528 collection_id: eth::collection_id_to_address(collection.id),1529 }1530 .to_log(T::ContractAddress::get()),1531 );15321533 Ok(())1534 }15351536 1537 1538 1539 1540 1541 #[transactional]1542 pub fn set_token_property_permissions(1543 collection: &CollectionHandle<T>,1544 sender: &T::CrossAccountId,1545 property_permissions: Vec<PropertyKeyPermission>,1546 ) -> DispatchResult {1547 Self::set_scoped_token_property_permissions(1548 collection,1549 sender,1550 PropertyScope::None,1551 property_permissions,1552 )1553 }15541555 1556 1557 1558 1559 1560 1561 #[transactional]1562 pub fn set_scoped_token_property_permissions(1563 collection: &CollectionHandle<T>,1564 sender: &T::CrossAccountId,1565 scope: PropertyScope,1566 property_permissions: Vec<PropertyKeyPermission>,1567 ) -> DispatchResult {1568 for prop_pemission in property_permissions {1569 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1570 }15711572 Ok(())1573 }15741575 1576 pub fn get_collection_property(1577 collection_id: CollectionId,1578 key: &PropertyKey,1579 ) -> Option<PropertyValue> {1580 Self::collection_properties(collection_id).get(key).cloned()1581 }15821583 1584 pub fn bytes_keys_to_property_keys(1585 keys: Vec<Vec<u8>>,1586 ) -> Result<Vec<PropertyKey>, DispatchError> {1587 keys.into_iter()1588 .map(|key| -> Result<PropertyKey, DispatchError> {1589 key.try_into()1590 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1591 })1592 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1593 }15941595 1596 pub fn filter_collection_properties(1597 collection_id: CollectionId,1598 keys: Option<Vec<PropertyKey>>,1599 ) -> Result<Vec<Property>, DispatchError> {1600 let properties = Self::collection_properties(collection_id);16011602 let properties = keys1603 .map(|keys| {1604 keys.into_iter()1605 .filter_map(|key| {1606 properties.get(&key).map(|value| Property {1607 key,1608 value: value.clone(),1609 })1610 })1611 .collect()1612 })1613 .unwrap_or_else(|| {1614 properties1615 .into_iter()1616 .map(|(key, value)| Property { key, value })1617 .collect()1618 });16191620 Ok(properties)1621 }16221623 1624 pub fn filter_property_permissions(1625 collection_id: CollectionId,1626 keys: Option<Vec<PropertyKey>>,1627 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1628 let permissions = Self::property_permissions(collection_id);16291630 let key_permissions = keys1631 .map(|keys| {1632 keys.into_iter()1633 .filter_map(|key| {1634 permissions1635 .get(&key)1636 .map(|permission| PropertyKeyPermission {1637 key,1638 permission: permission.clone(),1639 })1640 })1641 .collect()1642 })1643 .unwrap_or_else(|| {1644 permissions1645 .into_iter()1646 .map(|(key, permission)| PropertyKeyPermission { key, permission })1647 .collect()1648 });16491650 Ok(key_permissions)1651 }16521653 1654 1655 1656 pub fn toggle_allowlist(1657 collection: &CollectionHandle<T>,1658 sender: &T::CrossAccountId,1659 user: &T::CrossAccountId,1660 allowed: bool,1661 ) -> DispatchResult {1662 collection.check_is_owner_or_admin(sender)?;16631664 16651666 if allowed {1667 <Allowlist<T>>::insert((collection.id, user), true);1668 Self::deposit_event(Event::<T>::AllowListAddressAdded(1669 collection.id,1670 user.clone(),1671 ));1672 } else {1673 <Allowlist<T>>::remove((collection.id, user));1674 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1675 collection.id,1676 user.clone(),1677 ));1678 }16791680 <PalletEvm<T>>::deposit_log(1681 erc::CollectionHelpersEvents::CollectionChanged {1682 collection_id: eth::collection_id_to_address(collection.id),1683 }1684 .to_log(T::ContractAddress::get()),1685 );16861687 Ok(())1688 }16891690 1691 1692 1693 pub fn toggle_admin(1694 collection: &CollectionHandle<T>,1695 sender: &T::CrossAccountId,1696 user: &T::CrossAccountId,1697 admin: bool,1698 ) -> DispatchResult {1699 collection.check_is_internal()?;1700 collection.check_is_owner(sender)?;17011702 let is_admin = <IsAdmin<T>>::get((collection.id, user));1703 if is_admin == admin {1704 if admin {1705 return Ok(());1706 } else {1707 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1708 }1709 }1710 let amount = <AdminAmount<T>>::get(collection.id);17111712 17131714 if admin {1715 let amount = amount1716 .checked_add(1)1717 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1718 ensure!(1719 amount <= Self::collection_admins_limit(),1720 <Error<T>>::CollectionAdminCountExceeded,1721 );17221723 <AdminAmount<T>>::insert(collection.id, amount);1724 <IsAdmin<T>>::insert((collection.id, user), true);17251726 Self::deposit_event(Event::<T>::CollectionAdminAdded(1727 collection.id,1728 user.clone(),1729 ));1730 } else {1731 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1732 <IsAdmin<T>>::remove((collection.id, user));17331734 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1735 collection.id,1736 user.clone(),1737 ));1738 }17391740 <PalletEvm<T>>::deposit_log(1741 erc::CollectionHelpersEvents::CollectionChanged {1742 collection_id: eth::collection_id_to_address(collection.id),1743 }1744 .to_log(T::ContractAddress::get()),1745 );17461747 Ok(())1748 }17491750 1751 pub fn update_limits(1752 user: &T::CrossAccountId,1753 collection: &mut CollectionHandle<T>,1754 new_limit: CollectionLimits,1755 ) -> DispatchResult {1756 collection.check_is_internal()?;1757 collection.check_is_owner_or_admin(user)?;17581759 collection.limits =1760 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17611762 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1763 <PalletEvm<T>>::deposit_log(1764 erc::CollectionHelpersEvents::CollectionChanged {1765 collection_id: eth::collection_id_to_address(collection.id),1766 }1767 .to_log(T::ContractAddress::get()),1768 );17691770 collection.save()1771 }17721773 1774 fn clamp_limits(1775 mode: CollectionMode,1776 old_limit: &CollectionLimits,1777 mut new_limit: CollectionLimits,1778 ) -> Result<CollectionLimits, DispatchError> {1779 let limits = old_limit;1780 limit_default!(old_limit, new_limit,1781 account_token_ownership_limit => ensure!(1782 new_limit <= MAX_TOKEN_OWNERSHIP,1783 <Error<T>>::CollectionLimitBoundsExceeded,1784 ),1785 sponsored_data_size => ensure!(1786 new_limit <= CUSTOM_DATA_LIMIT,1787 <Error<T>>::CollectionLimitBoundsExceeded,1788 ),17891790 sponsored_data_rate_limit => {},1791 token_limit => ensure!(1792 old_limit >= new_limit && new_limit > 0,1793 <Error<T>>::CollectionTokenLimitExceeded1794 ),17951796 sponsor_transfer_timeout(match mode {1797 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1798 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1799 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1800 }) => ensure!(1801 new_limit <= MAX_SPONSOR_TIMEOUT,1802 <Error<T>>::CollectionLimitBoundsExceeded,1803 ),1804 sponsor_approve_timeout => {},1805 owner_can_transfer => ensure!(1806 !limits.owner_can_transfer_instaled() ||1807 old_limit || !new_limit,1808 <Error<T>>::OwnerPermissionsCantBeReverted,1809 ),1810 owner_can_destroy => ensure!(1811 old_limit || !new_limit,1812 <Error<T>>::OwnerPermissionsCantBeReverted,1813 ),1814 transfers_enabled => {},1815 );1816 Ok(new_limit)1817 }18181819 1820 pub fn update_permissions(1821 user: &T::CrossAccountId,1822 collection: &mut CollectionHandle<T>,1823 new_permission: CollectionPermissions,1824 ) -> DispatchResult {1825 collection.check_is_internal()?;1826 collection.check_is_owner_or_admin(user)?;1827 collection.permissions = Self::clamp_permissions(1828 collection.mode.clone(),1829 &collection.permissions,1830 new_permission,1831 )?;18321833 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1834 <PalletEvm<T>>::deposit_log(1835 erc::CollectionHelpersEvents::CollectionChanged {1836 collection_id: eth::collection_id_to_address(collection.id),1837 }1838 .to_log(T::ContractAddress::get()),1839 );18401841 collection.save()1842 }18431844 1845 fn clamp_permissions(1846 _mode: CollectionMode,1847 old_permission: &CollectionPermissions,1848 mut new_permission: CollectionPermissions,1849 ) -> Result<CollectionPermissions, DispatchError> {1850 limit_default_clone!(old_permission, new_permission,1851 access => {},1852 mint_mode => {},1853 nesting => { },1854 );1855 Ok(new_permission)1856 }18571858 1859 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1860 CollectionProperties::<T>::mutate(collection_id, |properties| {1861 properties.recompute_consumed_space();1862 });18631864 Ok(())1865 }1866}186718681869#[macro_export]1870macro_rules! unsupported {1871 ($runtime:path) => {1872 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1873 };1874}187518761877pub trait CommonWeightInfo<CrossAccountId> {1878 1879 fn create_item(data: &CreateItemData) -> Weight {1880 Self::create_multiple_items(from_ref(data))1881 }18821883 1884 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18851886 1887 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18881889 1890 fn burn_item() -> Weight;18911892 1893 1894 1895 fn set_collection_properties(amount: u32) -> Weight;18961897 1898 1899 1900 fn delete_collection_properties(amount: u32) -> Weight;19011902 1903 1904 1905 fn set_token_properties(amount: u32) -> Weight;19061907 1908 1909 1910 fn delete_token_properties(amount: u32) -> Weight;19111912 1913 1914 1915 fn set_token_property_permissions(amount: u32) -> Weight;19161917 1918 fn transfer() -> Weight;19191920 1921 fn approve() -> Weight;19221923 1924 fn approve_from() -> Weight;19251926 1927 fn transfer_from() -> Weight;19281929 1930 fn burn_from() -> Weight;19311932 1933 1934 1935 1936 fn burn_recursively_self_raw() -> Weight;19371938 1939 1940 1941 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19421943 1944 1945 1946 1947 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1948 Self::burn_recursively_self_raw()1949 .saturating_mul(max_selfs.max(1) as u64)1950 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1951 }19521953 1954 fn token_owner() -> Weight;19551956 1957 fn set_allowance_for_all() -> Weight;19581959 1960 fn force_repair_item() -> Weight;1961}196219631964pub trait RefungibleExtensionsWeightInfo {1965 1966 fn repartition() -> Weight;1967}196819691970197119721973pub trait CommonCollectionOperations<T: Config> {1974 1975 1976 1977 1978 1979 1980 fn create_item(1981 &self,1982 sender: T::CrossAccountId,1983 to: T::CrossAccountId,1984 data: CreateItemData,1985 nesting_budget: &dyn Budget,1986 ) -> DispatchResultWithPostInfo;19871988 1989 1990 1991 1992 1993 1994 fn create_multiple_items(1995 &self,1996 sender: T::CrossAccountId,1997 to: T::CrossAccountId,1998 data: Vec<CreateItemData>,1999 nesting_budget: &dyn Budget,2000 ) -> DispatchResultWithPostInfo;20012002 2003 2004 2005 2006 2007 2008 fn create_multiple_items_ex(2009 &self,2010 sender: T::CrossAccountId,2011 data: CreateItemExData<T::CrossAccountId>,2012 nesting_budget: &dyn Budget,2013 ) -> DispatchResultWithPostInfo;20142015 2016 2017 2018 2019 2020 fn burn_item(2021 &self,2022 sender: T::CrossAccountId,2023 token: TokenId,2024 amount: u128,2025 ) -> DispatchResultWithPostInfo;20262027 2028 2029 2030 2031 2032 2033 fn burn_item_recursively(2034 &self,2035 sender: T::CrossAccountId,2036 token: TokenId,2037 self_budget: &dyn Budget,2038 breadth_budget: &dyn Budget,2039 ) -> DispatchResultWithPostInfo;20402041 2042 2043 2044 2045 fn set_collection_properties(2046 &self,2047 sender: T::CrossAccountId,2048 properties: Vec<Property>,2049 ) -> DispatchResultWithPostInfo;20502051 2052 2053 2054 2055 fn delete_collection_properties(2056 &self,2057 sender: &T::CrossAccountId,2058 property_keys: Vec<PropertyKey>,2059 ) -> DispatchResultWithPostInfo;20602061 2062 2063 2064 2065 2066 2067 2068 2069 2070 fn set_token_properties(2071 &self,2072 sender: T::CrossAccountId,2073 token_id: TokenId,2074 properties: Vec<Property>,2075 budget: &dyn Budget,2076 ) -> DispatchResultWithPostInfo;20772078 2079 2080 2081 2082 2083 2084 2085 2086 2087 fn delete_token_properties(2088 &self,2089 sender: T::CrossAccountId,2090 token_id: TokenId,2091 property_keys: Vec<PropertyKey>,2092 budget: &dyn Budget,2093 ) -> DispatchResultWithPostInfo;20942095 2096 2097 2098 2099 2100 2101 fn set_token_property_permissions(2102 &self,2103 sender: &T::CrossAccountId,2104 property_permissions: Vec<PropertyKeyPermission>,2105 ) -> DispatchResultWithPostInfo;21062107 2108 2109 2110 2111 2112 2113 2114 fn transfer(2115 &self,2116 sender: T::CrossAccountId,2117 to: T::CrossAccountId,2118 token: TokenId,2119 amount: u128,2120 budget: &dyn Budget,2121 ) -> DispatchResultWithPostInfo;21222123 2124 2125 2126 2127 2128 2129 fn approve(2130 &self,2131 sender: T::CrossAccountId,2132 spender: T::CrossAccountId,2133 token: TokenId,2134 amount: u128,2135 ) -> DispatchResultWithPostInfo;21362137 2138 2139 2140 2141 2142 2143 2144 fn approve_from(2145 &self,2146 sender: T::CrossAccountId,2147 from: T::CrossAccountId,2148 to: T::CrossAccountId,2149 token: TokenId,2150 amount: u128,2151 ) -> DispatchResultWithPostInfo;21522153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 fn transfer_from(2164 &self,2165 sender: T::CrossAccountId,2166 from: T::CrossAccountId,2167 to: T::CrossAccountId,2168 token: TokenId,2169 amount: u128,2170 budget: &dyn Budget,2171 ) -> DispatchResultWithPostInfo;21722173 2174 2175 2176 2177 2178 2179 2180 2181 2182 fn burn_from(2183 &self,2184 sender: T::CrossAccountId,2185 from: T::CrossAccountId,2186 token: TokenId,2187 amount: u128,2188 budget: &dyn Budget,2189 ) -> DispatchResultWithPostInfo;21902191 2192 2193 2194 2195 2196 2197 fn check_nesting(2198 &self,2199 sender: T::CrossAccountId,2200 from: (CollectionId, TokenId),2201 under: TokenId,2202 budget: &dyn Budget,2203 ) -> DispatchResult;22042205 2206 2207 2208 2209 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22102211 2212 2213 2214 2215 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22162217 2218 2219 2220 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22212222 2223 fn collection_tokens(&self) -> Vec<TokenId>;22242225 2226 2227 2228 fn token_exists(&self, token: TokenId) -> bool;22292230 2231 fn last_token_id(&self) -> TokenId;22322233 2234 2235 2236 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22372238 2239 2240 2241 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22422243 2244 2245 2246 2247 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22482249 2250 2251 2252 2253 2254 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22552256 2257 fn total_supply(&self) -> u32;22582259 2260 2261 2262 fn account_balance(&self, account: T::CrossAccountId) -> u32;22632264 2265 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22662267 2268 fn total_pieces(&self, token: TokenId) -> Option<u128>;22692270 2271 2272 2273 2274 2275 fn allowance(2276 &self,2277 sender: T::CrossAccountId,2278 spender: T::CrossAccountId,2279 token: TokenId,2280 ) -> u128;22812282 2283 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22842285 2286 2287 2288 2289 fn set_allowance_for_all(2290 &self,2291 owner: T::CrossAccountId,2292 operator: T::CrossAccountId,2293 approve: bool,2294 ) -> DispatchResultWithPostInfo;22952296 2297 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;22982299 2300 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2301}230223032304pub trait RefungibleExtensions<T>2305where2306 T: Config,2307{2308 2309 2310 2311 2312 2313 2314 2315 fn repartition(2316 &self,2317 sender: &T::CrossAccountId,2318 token: TokenId,2319 amount: u128,2320 ) -> DispatchResultWithPostInfo;2321}23222323232423252326pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2327 let post_info = PostDispatchInfo {2328 actual_weight: Some(weight),2329 pays_fee: Pays::Yes,2330 };2331 match res {2332 Ok(()) => Ok(post_info),2333 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2334 }2335}23362337impl<T: Config> From<PropertiesError> for Error<T> {2338 fn from(error: PropertiesError) -> Self {2339 match error {2340 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2341 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2342 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2343 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2344 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2345 }2346 }2347}