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);465466 #[pallet::pallet]467 #[pallet::storage_version(STORAGE_VERSION)]468 pub struct Pallet<T>(_);469470 #[pallet::extra_constants]471 impl<T: Config> Pallet<T> {472 473 pub fn collection_admins_limit() -> u32 {474 COLLECTION_ADMINS_LIMIT475 }476 }477478 #[pallet::genesis_config]479 pub struct GenesisConfig<T>(PhantomData<T>);480481 #[cfg(feature = "std")]482 impl<T: Config> Default for GenesisConfig<T> {483 fn default() -> Self {484 Self(Default::default())485 }486 }487488 #[pallet::genesis_build]489 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {490 fn build(&self) {491 StorageVersion::new(1).put::<Pallet<T>>();492 }493 }494495 impl<T: Config> Pallet<T> {496 497 pub fn deposit_event(event: Event<T>) {498 let event = <T as Config>::RuntimeEvent::from(event);499 let event = event.into();500 <frame_system::Pallet<T>>::deposit_event(event)501 }502 }503504 #[pallet::event]505 pub enum Event<T: Config> {506 507 CollectionCreated(508 509 CollectionId,510 511 u8,512 513 T::AccountId,514 ),515516 517 CollectionDestroyed(518 519 CollectionId,520 ),521522 523 ItemCreated(524 525 CollectionId,526 527 TokenId,528 529 T::CrossAccountId,530 531 u128,532 ),533534 535 ItemDestroyed(536 537 CollectionId,538 539 TokenId,540 541 T::CrossAccountId,542 543 u128,544 ),545546 547 Transfer(548 549 CollectionId,550 551 TokenId,552 553 T::CrossAccountId,554 555 T::CrossAccountId,556 557 u128,558 ),559560 561 Approved(562 563 CollectionId,564 565 TokenId,566 567 T::CrossAccountId,568 569 T::CrossAccountId,570 571 u128,572 ),573574 575 ApprovedForAll(576 577 CollectionId,578 579 T::CrossAccountId,580 581 T::CrossAccountId,582 583 bool,584 ),585586 587 CollectionPropertySet(588 589 CollectionId,590 591 PropertyKey,592 ),593594 595 CollectionPropertyDeleted(596 597 CollectionId,598 599 PropertyKey,600 ),601602 603 TokenPropertySet(604 605 CollectionId,606 607 TokenId,608 609 PropertyKey,610 ),611612 613 TokenPropertyDeleted(614 615 CollectionId,616 617 TokenId,618 619 PropertyKey,620 ),621622 623 PropertyPermissionSet(624 625 CollectionId,626 627 PropertyKey,628 ),629630 631 AllowListAddressAdded(632 633 CollectionId,634 635 T::CrossAccountId,636 ),637638 639 AllowListAddressRemoved(640 641 CollectionId,642 643 T::CrossAccountId,644 ),645646 647 CollectionAdminAdded(648 649 CollectionId,650 651 T::CrossAccountId,652 ),653654 655 CollectionAdminRemoved(656 657 CollectionId,658 659 T::CrossAccountId,660 ),661662 663 CollectionLimitSet(664 665 CollectionId,666 ),667668 669 CollectionOwnerChanged(670 671 CollectionId,672 673 T::AccountId,674 ),675676 677 CollectionPermissionSet(678 679 CollectionId,680 ),681682 683 CollectionSponsorSet(684 685 CollectionId,686 687 T::AccountId,688 ),689690 691 SponsorshipConfirmed(692 693 CollectionId,694 695 T::AccountId,696 ),697698 699 CollectionSponsorRemoved(700 701 CollectionId,702 ),703 }704705 #[pallet::error]706 pub enum Error<T> {707 708 CollectionNotFound,709 710 MustBeTokenOwner,711 712 NoPermission,713 714 CantDestroyNotEmptyCollection,715 716 PublicMintingNotAllowed,717 718 AddressNotInAllowlist,719720 721 CollectionNameLimitExceeded,722 723 CollectionDescriptionLimitExceeded,724 725 CollectionTokenPrefixLimitExceeded,726 727 TotalCollectionsLimitExceeded,728 729 CollectionAdminCountExceeded,730 731 CollectionLimitBoundsExceeded,732 733 OwnerPermissionsCantBeReverted,734 735 TransferNotAllowed,736 737 AccountTokenLimitExceeded,738 739 CollectionTokenLimitExceeded,740 741 MetadataFlagFrozen,742743 744 TokenNotFound,745 746 TokenValueTooLow,747 748 ApprovedValueTooLow,749 750 CantApproveMoreThanOwned,751 752 AddressIsNotEthMirror,753754 755 AddressIsZero,756757 758 UnsupportedOperation,759760 761 NotSufficientFounds,762763 764 UserIsNotAllowedToNest,765 766 SourceCollectionIsNotAllowedToNest,767768 769 CollectionFieldSizeExceeded,770771 772 NoSpaceForProperty,773774 775 PropertyLimitReached,776777 778 PropertyKeyIsTooLong,779780 781 InvalidCharacterInPropertyKey,782783 784 EmptyPropertyKey,785786 787 CollectionIsExternal,788789 790 CollectionIsInternal,791792 793 ConfirmSponsorshipFail,794795 796 UserIsNotCollectionAdmin,797 }798799 800 #[pallet::storage]801 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;802803 804 #[pallet::storage]805 pub type DestroyedCollectionCount<T> =806 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;807808 809 #[pallet::storage]810 pub type CollectionById<T> = StorageMap<811 Hasher = Blake2_128Concat,812 Key = CollectionId,813 Value = Collection<<T as frame_system::Config>::AccountId>,814 QueryKind = OptionQuery,815 >;816817 818 #[pallet::storage]819 #[pallet::getter(fn collection_properties)]820 pub type CollectionProperties<T> = StorageMap<821 Hasher = Blake2_128Concat,822 Key = CollectionId,823 Value = CollectionPropertiesT,824 QueryKind = ValueQuery,825 >;826827 828 #[pallet::storage]829 #[pallet::getter(fn property_permissions)]830 pub type CollectionPropertyPermissions<T> = StorageMap<831 Hasher = Blake2_128Concat,832 Key = CollectionId,833 Value = PropertiesPermissionMap,834 QueryKind = ValueQuery,835 >;836837 838 #[pallet::storage]839 pub type AdminAmount<T> = StorageMap<840 Hasher = Blake2_128Concat,841 Key = CollectionId,842 Value = u32,843 QueryKind = ValueQuery,844 >;845846 847 #[pallet::storage]848 pub type IsAdmin<T: Config> = StorageNMap<849 Key = (850 Key<Blake2_128Concat, CollectionId>,851 Key<Blake2_128Concat, T::CrossAccountId>,852 ),853 Value = bool,854 QueryKind = ValueQuery,855 >;856857 858 #[pallet::storage]859 pub type Allowlist<T: Config> = StorageNMap<860 Key = (861 Key<Blake2_128Concat, CollectionId>,862 Key<Blake2_128Concat, T::CrossAccountId>,863 ),864 Value = bool,865 QueryKind = ValueQuery,866 >;867868 869 #[pallet::storage]870 pub type DummyStorageValue<T: Config> = StorageValue<871 Value = (872 CollectionStats,873 CollectionId,874 TokenId,875 TokenChild,876 PhantomType<(877 TokenData<T::CrossAccountId>,878 RpcCollection<T::AccountId>,879 880 PovInfo,881 )>,882 ),883 QueryKind = OptionQuery,884 >;885}886887impl<T: Config> Pallet<T> {888 889 890 891 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {892 ensure!(893 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,894 <Error<T>>::AddressIsZero895 );896 Ok(())897 }898899 900 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {901 <IsAdmin<T>>::iter_prefix((collection,))902 .map(|(a, _)| a)903 .collect()904 }905906 907 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {908 <Allowlist<T>>::iter_prefix((collection,))909 .map(|(a, _)| a)910 .collect()911 }912913 914 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {915 <Allowlist<T>>::get((collection, user))916 }917918 919 pub fn collection_stats() -> CollectionStats {920 let created = <CreatedCollectionCount<T>>::get();921 let destroyed = <DestroyedCollectionCount<T>>::get();922 CollectionStats {923 created: created.0,924 destroyed: destroyed.0,925 alive: created.0 - destroyed.0,926 }927 }928929 930 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {931 let collection = <CollectionById<T>>::get(collection)?;932 let limits = collection.limits;933 let effective_limits = CollectionLimits {934 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),935 sponsored_data_size: Some(limits.sponsored_data_size()),936 sponsored_data_rate_limit: Some(937 limits938 .sponsored_data_rate_limit939 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),940 ),941 token_limit: Some(limits.token_limit()),942 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(943 match collection.mode {944 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,945 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,946 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,947 },948 )),949 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),950 owner_can_transfer: Some(limits.owner_can_transfer()),951 owner_can_destroy: Some(limits.owner_can_destroy()),952 transfers_enabled: Some(limits.transfers_enabled()),953 };954955 Some(effective_limits)956 }957958 959 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {960 let Collection {961 name,962 description,963 owner,964 mode,965 token_prefix,966 sponsorship,967 limits,968 permissions,969 flags,970 } = <CollectionById<T>>::get(collection)?;971972 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)973 .into_iter()974 .map(|(key, permission)| PropertyKeyPermission { key, permission })975 .collect();976977 let properties = <CollectionProperties<T>>::get(collection)978 .into_iter()979 .map(|(key, value)| Property { key, value })980 .collect();981982 let permissions = CollectionPermissions {983 access: Some(permissions.access()),984 mint_mode: Some(permissions.mint_mode()),985 nesting: Some(permissions.nesting().clone()),986 };987988 Some(RpcCollection {989 name: name.into_inner(),990 description: description.into_inner(),991 owner,992 mode,993 token_prefix: token_prefix.into_inner(),994 sponsorship,995 limits,996 permissions,997 token_property_permissions,998 properties,999 read_only: flags.external,10001001 flags: RpcCollectionFlags {1002 foreign: flags.foreign,1003 erc721metadata: flags.erc721metadata,1004 },1005 })1006 }1007}10081009macro_rules! limit_default {1010 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1011 $(1012 if let Some($new) = $new.$field {1013 let $old = $old.$field($($arg)?);1014 let _ = $new;1015 let _ = $old;1016 $check1017 } else {1018 $new.$field = $old.$field1019 }1020 )*1021 }};1022}1023macro_rules! limit_default_clone {1024 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1025 $(1026 if let Some($new) = $new.$field.clone() {1027 let $old = $old.$field($($arg)?);1028 let _ = $new;1029 let _ = $old;1030 $check1031 } else {1032 $new.$field = $old.$field.clone()1033 }1034 )*1035 }};1036}10371038impl<T: Config> Pallet<T> {1039 1040 1041 1042 1043 1044 pub fn init_collection(1045 owner: T::CrossAccountId,1046 payer: T::CrossAccountId,1047 data: CreateCollectionData<T::AccountId>,1048 flags: CollectionFlags,1049 ) -> Result<CollectionId, DispatchError> {1050 {1051 ensure!(1052 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1053 Error::<T>::CollectionTokenPrefixLimitExceeded1054 );1055 }10561057 let created_count = <CreatedCollectionCount<T>>::get()1058 .01059 .checked_add(1)1060 .ok_or(ArithmeticError::Overflow)?;1061 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1062 let id = CollectionId(created_count);10631064 1065 ensure!(1066 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1067 <Error<T>>::TotalCollectionsLimitExceeded1068 );10691070 10711072 let collection = Collection {1073 owner: owner.as_sub().clone(),1074 name: data.name,1075 mode: data.mode.clone(),1076 description: data.description,1077 token_prefix: data.token_prefix,1078 sponsorship: data1079 .pending_sponsor1080 .map(SponsorshipState::Unconfirmed)1081 .unwrap_or_default(),1082 limits: data1083 .limits1084 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1085 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1086 permissions: data1087 .permissions1088 .map(|permissions| {1089 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1090 })1091 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1092 flags,1093 };10941095 let mut collection_properties = CollectionPropertiesT::new();1096 collection_properties1097 .try_set_from_iter(data.properties.into_iter())1098 .map_err(<Error<T>>::from)?;10991100 CollectionProperties::<T>::insert(id, collection_properties);11011102 let mut token_props_permissions = PropertiesPermissionMap::new();1103 token_props_permissions1104 .try_set_from_iter(data.token_property_permissions.into_iter())1105 .map_err(<Error<T>>::from)?;11061107 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);11081109 1110 {1111 let mut imbalance =1112 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1113 imbalance.subsume(1114 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1115 &T::TreasuryAccountId::get(),1116 T::CollectionCreationPrice::get(),1117 ),1118 );1119 <T as Config>::Currency::settle(1120 payer.as_sub(),1121 imbalance,1122 WithdrawReasons::TRANSFER,1123 ExistenceRequirement::KeepAlive,1124 )1125 .map_err(|_| Error::<T>::NotSufficientFounds)?;1126 }11271128 <CreatedCollectionCount<T>>::put(created_count);1129 <Pallet<T>>::deposit_event(Event::CollectionCreated(1130 id,1131 data.mode.id(),1132 owner.as_sub().clone(),1133 ));1134 <PalletEvm<T>>::deposit_log(1135 erc::CollectionHelpersEvents::CollectionCreated {1136 owner: *owner.as_eth(),1137 collection_id: eth::collection_id_to_address(id),1138 }1139 .to_log(T::ContractAddress::get()),1140 );1141 <CollectionById<T>>::insert(id, collection);1142 Ok(id)1143 }11441145 1146 1147 1148 1149 pub fn destroy_collection(1150 collection: CollectionHandle<T>,1151 sender: &T::CrossAccountId,1152 ) -> DispatchResult {1153 ensure!(1154 collection.limits.owner_can_destroy(),1155 <Error<T>>::NoPermission,1156 );1157 collection.check_is_owner(sender)?;11581159 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1160 .01161 .checked_add(1)1162 .ok_or(ArithmeticError::Overflow)?;11631164 11651166 <DestroyedCollectionCount<T>>::put(destroyed_collections);1167 <CollectionById<T>>::remove(collection.id);1168 <AdminAmount<T>>::remove(collection.id);1169 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1170 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1171 <CollectionProperties<T>>::remove(collection.id);11721173 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11741175 <PalletEvm<T>>::deposit_log(1176 erc::CollectionHelpersEvents::CollectionDestroyed {1177 collection_id: eth::collection_id_to_address(collection.id),1178 }1179 .to_log(T::ContractAddress::get()),1180 );1181 Ok(())1182 }11831184 1185 1186 1187 1188 1189 1190 1191 1192 #[transactional]1193 fn modify_collection_properties(1194 collection: &CollectionHandle<T>,1195 sender: &T::CrossAccountId,1196 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1197 ) -> DispatchResult {1198 collection.check_is_owner_or_admin(sender)?;11991200 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);12011202 for (key, value) in properties_updates {1203 match value {1204 Some(value) => {1205 stored_properties1206 .try_set(key.clone(), value)1207 .map_err(<Error<T>>::from)?;12081209 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1210 <PalletEvm<T>>::deposit_log(1211 erc::CollectionHelpersEvents::CollectionChanged {1212 collection_id: eth::collection_id_to_address(collection.id),1213 }1214 .to_log(T::ContractAddress::get()),1215 );1216 }1217 None => {1218 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12191220 Self::deposit_event(Event::CollectionPropertyDeleted(collection.id, key));1221 <PalletEvm<T>>::deposit_log(1222 erc::CollectionHelpersEvents::CollectionChanged {1223 collection_id: eth::collection_id_to_address(collection.id),1224 }1225 .to_log(T::ContractAddress::get()),1226 );1227 }1228 }1229 }12301231 <CollectionProperties<T>>::set(collection.id, stored_properties);12321233 Ok(())1234 }12351236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 pub fn modify_token_properties(1254 collection: &CollectionHandle<T>,1255 sender: &T::CrossAccountId,1256 token_id: TokenId,1257 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1258 is_token_create: bool,1259 mut stored_properties: TokenProperties,1260 is_token_owner: impl Fn() -> Result<bool, DispatchError>,1261 set_token_properties: impl FnOnce(TokenProperties),1262 log: evm_coder::ethereum::Log,1263 ) -> DispatchResult {1264 let is_collection_admin = collection.is_owner_or_admin(sender);1265 let permissions = Self::property_permissions(collection.id);12661267 let mut token_owner_result = None;1268 let mut is_token_owner = || -> Result<bool, DispatchError> {1269 *token_owner_result.get_or_insert_with(&is_token_owner)1270 };12711272 for (key, value) in properties_updates {1273 let permission = permissions1274 .get(&key)1275 .cloned()1276 .unwrap_or_else(PropertyPermission::none);12771278 let is_property_exists = stored_properties.get(&key).is_some();12791280 match permission {1281 PropertyPermission { mutable: false, .. } if is_property_exists => {1282 return Err(<Error<T>>::NoPermission.into());1283 }12841285 PropertyPermission {1286 collection_admin,1287 token_owner,1288 ..1289 } => {1290 1291 let is_token_create =1292 is_token_create && (collection_admin || token_owner) && value.is_some();1293 if !(is_token_create1294 || (collection_admin && is_collection_admin)1295 || (token_owner && is_token_owner()?))1296 {1297 fail!(<Error<T>>::NoPermission);1298 }1299 }1300 }13011302 match value {1303 Some(value) => {1304 stored_properties1305 .try_set(key.clone(), value)1306 .map_err(<Error<T>>::from)?;13071308 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1309 }1310 None => {1311 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13121313 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1314 }1315 }13161317 <PalletEvm<T>>::deposit_log(log.clone());1318 }13191320 set_token_properties(stored_properties);13211322 Ok(())1323 }13241325 1326 1327 1328 1329 1330 1331 pub fn set_allowance_for_all(1332 collection: &CollectionHandle<T>,1333 owner: &T::CrossAccountId,1334 operator: &T::CrossAccountId,1335 approve: bool,1336 set_allowance: impl FnOnce(),1337 log: evm_coder::ethereum::Log,1338 ) -> DispatchResult {1339 if collection.permissions.access() == AccessMode::AllowList {1340 collection.check_allowlist(owner)?;1341 collection.check_allowlist(operator)?;1342 }13431344 Self::ensure_correct_receiver(operator)?;13451346 set_allowance();13471348 <PalletEvm<T>>::deposit_log(log);1349 Self::deposit_event(Event::ApprovedForAll(1350 collection.id,1351 owner.clone(),1352 operator.clone(),1353 approve,1354 ));1355 Ok(())1356 }13571358 1359 1360 1361 1362 1363 pub fn set_collection_property(1364 collection: &CollectionHandle<T>,1365 sender: &T::CrossAccountId,1366 property: Property,1367 ) -> DispatchResult {1368 Self::set_collection_properties(collection, sender, [property].into_iter())1369 }13701371 1372 1373 1374 1375 1376 1377 pub fn set_scoped_collection_property(1378 collection_id: CollectionId,1379 scope: PropertyScope,1380 property: Property,1381 ) -> DispatchResult {1382 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1383 properties.try_scoped_set(scope, property.key, property.value)1384 })1385 .map_err(<Error<T>>::from)?;13861387 Ok(())1388 }13891390 1391 1392 1393 1394 1395 1396 pub fn set_scoped_collection_properties(1397 collection_id: CollectionId,1398 scope: PropertyScope,1399 properties: impl Iterator<Item = Property>,1400 ) -> DispatchResult {1401 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1402 stored_properties.try_scoped_set_from_iter(scope, properties)1403 })1404 .map_err(<Error<T>>::from)?;14051406 Ok(())1407 }14081409 1410 1411 1412 1413 1414 pub fn set_collection_properties(1415 collection: &CollectionHandle<T>,1416 sender: &T::CrossAccountId,1417 properties: impl Iterator<Item = Property>,1418 ) -> DispatchResult {1419 Self::modify_collection_properties(1420 collection,1421 sender,1422 properties.map(|property| (property.key, Some(property.value))),1423 )1424 }14251426 1427 1428 1429 1430 1431 pub fn delete_collection_property(1432 collection: &CollectionHandle<T>,1433 sender: &T::CrossAccountId,1434 property_key: PropertyKey,1435 ) -> DispatchResult {1436 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1437 }14381439 1440 1441 1442 1443 1444 pub fn delete_collection_properties(1445 collection: &CollectionHandle<T>,1446 sender: &T::CrossAccountId,1447 property_keys: impl Iterator<Item = PropertyKey>,1448 ) -> DispatchResult {1449 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1450 }14511452 1453 1454 1455 1456 1457 1458 pub fn set_property_permission_unchecked(1459 collection: CollectionId,1460 property_permission: PropertyKeyPermission,1461 ) -> DispatchResult {1462 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1463 permissions.try_set(property_permission.key, property_permission.permission)1464 })1465 .map_err(<Error<T>>::from)?;1466 Ok(())1467 }14681469 1470 1471 1472 1473 1474 pub fn set_property_permission(1475 collection: &CollectionHandle<T>,1476 sender: &T::CrossAccountId,1477 property_permission: PropertyKeyPermission,1478 ) -> DispatchResult {1479 Self::set_scoped_property_permission(1480 collection,1481 sender,1482 PropertyScope::None,1483 property_permission,1484 )1485 }14861487 1488 1489 1490 1491 1492 1493 pub fn set_scoped_property_permission(1494 collection: &CollectionHandle<T>,1495 sender: &T::CrossAccountId,1496 scope: PropertyScope,1497 property_permission: PropertyKeyPermission,1498 ) -> DispatchResult {1499 collection.check_is_owner_or_admin(sender)?;15001501 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1502 let current_permission = all_permissions.get(&property_permission.key);1503 if matches![1504 current_permission,1505 Some(PropertyPermission { mutable: false, .. })1506 ] {1507 return Err(<Error<T>>::NoPermission.into());1508 }15091510 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1511 let property_permission = property_permission.clone();1512 permissions.try_scoped_set(1513 scope,1514 property_permission.key,1515 property_permission.permission,1516 )1517 })1518 .map_err(<Error<T>>::from)?;15191520 Self::deposit_event(Event::PropertyPermissionSet(1521 collection.id,1522 property_permission.key,1523 ));1524 <PalletEvm<T>>::deposit_log(1525 erc::CollectionHelpersEvents::CollectionChanged {1526 collection_id: eth::collection_id_to_address(collection.id),1527 }1528 .to_log(T::ContractAddress::get()),1529 );15301531 Ok(())1532 }15331534 1535 1536 1537 1538 1539 #[transactional]1540 pub fn set_token_property_permissions(1541 collection: &CollectionHandle<T>,1542 sender: &T::CrossAccountId,1543 property_permissions: Vec<PropertyKeyPermission>,1544 ) -> DispatchResult {1545 Self::set_scoped_token_property_permissions(1546 collection,1547 sender,1548 PropertyScope::None,1549 property_permissions,1550 )1551 }15521553 1554 1555 1556 1557 1558 1559 #[transactional]1560 pub fn set_scoped_token_property_permissions(1561 collection: &CollectionHandle<T>,1562 sender: &T::CrossAccountId,1563 scope: PropertyScope,1564 property_permissions: Vec<PropertyKeyPermission>,1565 ) -> DispatchResult {1566 for prop_pemission in property_permissions {1567 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1568 }15691570 Ok(())1571 }15721573 1574 pub fn get_collection_property(1575 collection_id: CollectionId,1576 key: &PropertyKey,1577 ) -> Option<PropertyValue> {1578 Self::collection_properties(collection_id).get(key).cloned()1579 }15801581 1582 pub fn bytes_keys_to_property_keys(1583 keys: Vec<Vec<u8>>,1584 ) -> Result<Vec<PropertyKey>, DispatchError> {1585 keys.into_iter()1586 .map(|key| -> Result<PropertyKey, DispatchError> {1587 key.try_into()1588 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1589 })1590 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1591 }15921593 1594 pub fn filter_collection_properties(1595 collection_id: CollectionId,1596 keys: Option<Vec<PropertyKey>>,1597 ) -> Result<Vec<Property>, DispatchError> {1598 let properties = Self::collection_properties(collection_id);15991600 let properties = keys1601 .map(|keys| {1602 keys.into_iter()1603 .filter_map(|key| {1604 properties.get(&key).map(|value| Property {1605 key,1606 value: value.clone(),1607 })1608 })1609 .collect()1610 })1611 .unwrap_or_else(|| {1612 properties1613 .into_iter()1614 .map(|(key, value)| Property { key, value })1615 .collect()1616 });16171618 Ok(properties)1619 }16201621 1622 pub fn filter_property_permissions(1623 collection_id: CollectionId,1624 keys: Option<Vec<PropertyKey>>,1625 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1626 let permissions = Self::property_permissions(collection_id);16271628 let key_permissions = keys1629 .map(|keys| {1630 keys.into_iter()1631 .filter_map(|key| {1632 permissions1633 .get(&key)1634 .map(|permission| PropertyKeyPermission {1635 key,1636 permission: permission.clone(),1637 })1638 })1639 .collect()1640 })1641 .unwrap_or_else(|| {1642 permissions1643 .into_iter()1644 .map(|(key, permission)| PropertyKeyPermission { key, permission })1645 .collect()1646 });16471648 Ok(key_permissions)1649 }16501651 1652 1653 1654 pub fn toggle_allowlist(1655 collection: &CollectionHandle<T>,1656 sender: &T::CrossAccountId,1657 user: &T::CrossAccountId,1658 allowed: bool,1659 ) -> DispatchResult {1660 collection.check_is_owner_or_admin(sender)?;16611662 16631664 if allowed {1665 <Allowlist<T>>::insert((collection.id, user), true);1666 Self::deposit_event(Event::<T>::AllowListAddressAdded(1667 collection.id,1668 user.clone(),1669 ));1670 } else {1671 <Allowlist<T>>::remove((collection.id, user));1672 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1673 collection.id,1674 user.clone(),1675 ));1676 }16771678 <PalletEvm<T>>::deposit_log(1679 erc::CollectionHelpersEvents::CollectionChanged {1680 collection_id: eth::collection_id_to_address(collection.id),1681 }1682 .to_log(T::ContractAddress::get()),1683 );16841685 Ok(())1686 }16871688 1689 1690 1691 pub fn toggle_admin(1692 collection: &CollectionHandle<T>,1693 sender: &T::CrossAccountId,1694 user: &T::CrossAccountId,1695 admin: bool,1696 ) -> DispatchResult {1697 collection.check_is_internal()?;1698 collection.check_is_owner(sender)?;16991700 let is_admin = <IsAdmin<T>>::get((collection.id, user));1701 if is_admin == admin {1702 if admin {1703 return Ok(());1704 } else {1705 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1706 }1707 }1708 let amount = <AdminAmount<T>>::get(collection.id);17091710 17111712 if admin {1713 let amount = amount1714 .checked_add(1)1715 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1716 ensure!(1717 amount <= Self::collection_admins_limit(),1718 <Error<T>>::CollectionAdminCountExceeded,1719 );17201721 <AdminAmount<T>>::insert(collection.id, amount);1722 <IsAdmin<T>>::insert((collection.id, user), true);17231724 Self::deposit_event(Event::<T>::CollectionAdminAdded(1725 collection.id,1726 user.clone(),1727 ));1728 } else {1729 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1730 <IsAdmin<T>>::remove((collection.id, user));17311732 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1733 collection.id,1734 user.clone(),1735 ));1736 }17371738 <PalletEvm<T>>::deposit_log(1739 erc::CollectionHelpersEvents::CollectionChanged {1740 collection_id: eth::collection_id_to_address(collection.id),1741 }1742 .to_log(T::ContractAddress::get()),1743 );17441745 Ok(())1746 }17471748 1749 pub fn update_limits(1750 user: &T::CrossAccountId,1751 collection: &mut CollectionHandle<T>,1752 new_limit: CollectionLimits,1753 ) -> DispatchResult {1754 collection.check_is_internal()?;1755 collection.check_is_owner_or_admin(user)?;17561757 collection.limits =1758 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17591760 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1761 <PalletEvm<T>>::deposit_log(1762 erc::CollectionHelpersEvents::CollectionChanged {1763 collection_id: eth::collection_id_to_address(collection.id),1764 }1765 .to_log(T::ContractAddress::get()),1766 );17671768 collection.save()1769 }17701771 1772 fn clamp_limits(1773 mode: CollectionMode,1774 old_limit: &CollectionLimits,1775 mut new_limit: CollectionLimits,1776 ) -> Result<CollectionLimits, DispatchError> {1777 let limits = old_limit;1778 limit_default!(old_limit, new_limit,1779 account_token_ownership_limit => ensure!(1780 new_limit <= MAX_TOKEN_OWNERSHIP,1781 <Error<T>>::CollectionLimitBoundsExceeded,1782 ),1783 sponsored_data_size => ensure!(1784 new_limit <= CUSTOM_DATA_LIMIT,1785 <Error<T>>::CollectionLimitBoundsExceeded,1786 ),17871788 sponsored_data_rate_limit => {},1789 token_limit => ensure!(1790 old_limit >= new_limit && new_limit > 0,1791 <Error<T>>::CollectionTokenLimitExceeded1792 ),17931794 sponsor_transfer_timeout(match mode {1795 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1796 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1797 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1798 }) => ensure!(1799 new_limit <= MAX_SPONSOR_TIMEOUT,1800 <Error<T>>::CollectionLimitBoundsExceeded,1801 ),1802 sponsor_approve_timeout => {},1803 owner_can_transfer => ensure!(1804 !limits.owner_can_transfer_instaled() ||1805 old_limit || !new_limit,1806 <Error<T>>::OwnerPermissionsCantBeReverted,1807 ),1808 owner_can_destroy => ensure!(1809 old_limit || !new_limit,1810 <Error<T>>::OwnerPermissionsCantBeReverted,1811 ),1812 transfers_enabled => {},1813 );1814 Ok(new_limit)1815 }18161817 1818 pub fn update_permissions(1819 user: &T::CrossAccountId,1820 collection: &mut CollectionHandle<T>,1821 new_permission: CollectionPermissions,1822 ) -> DispatchResult {1823 collection.check_is_internal()?;1824 collection.check_is_owner_or_admin(user)?;1825 collection.permissions = Self::clamp_permissions(1826 collection.mode.clone(),1827 &collection.permissions,1828 new_permission,1829 )?;18301831 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1832 <PalletEvm<T>>::deposit_log(1833 erc::CollectionHelpersEvents::CollectionChanged {1834 collection_id: eth::collection_id_to_address(collection.id),1835 }1836 .to_log(T::ContractAddress::get()),1837 );18381839 collection.save()1840 }18411842 1843 fn clamp_permissions(1844 _mode: CollectionMode,1845 old_permission: &CollectionPermissions,1846 mut new_permission: CollectionPermissions,1847 ) -> Result<CollectionPermissions, DispatchError> {1848 limit_default_clone!(old_permission, new_permission,1849 access => {},1850 mint_mode => {},1851 nesting => { },1852 );1853 Ok(new_permission)1854 }18551856 1857 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1858 CollectionProperties::<T>::mutate(collection_id, |properties| {1859 properties.recompute_consumed_space();1860 });18611862 Ok(())1863 }1864}186518661867#[macro_export]1868macro_rules! unsupported {1869 ($runtime:path) => {1870 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1871 };1872}187318741875pub trait CommonWeightInfo<CrossAccountId> {1876 1877 fn create_item(data: &CreateItemData) -> Weight {1878 Self::create_multiple_items(from_ref(data))1879 }18801881 1882 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18831884 1885 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18861887 1888 fn burn_item() -> Weight;18891890 1891 1892 1893 fn set_collection_properties(amount: u32) -> Weight;18941895 1896 1897 1898 fn delete_collection_properties(amount: u32) -> Weight;18991900 1901 1902 1903 fn set_token_properties(amount: u32) -> Weight;19041905 1906 1907 1908 fn delete_token_properties(amount: u32) -> Weight;19091910 1911 1912 1913 fn set_token_property_permissions(amount: u32) -> Weight;19141915 1916 fn transfer() -> Weight;19171918 1919 fn approve() -> Weight;19201921 1922 fn approve_from() -> Weight;19231924 1925 fn transfer_from() -> Weight;19261927 1928 fn burn_from() -> Weight;19291930 1931 1932 1933 1934 fn burn_recursively_self_raw() -> Weight;19351936 1937 1938 1939 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19401941 1942 1943 1944 1945 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1946 Self::burn_recursively_self_raw()1947 .saturating_mul(max_selfs.max(1) as u64)1948 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1949 }19501951 1952 fn token_owner() -> Weight;19531954 1955 fn set_allowance_for_all() -> Weight;19561957 1958 fn force_repair_item() -> Weight;1959}196019611962pub trait RefungibleExtensionsWeightInfo {1963 1964 fn repartition() -> Weight;1965}196619671968196919701971pub trait CommonCollectionOperations<T: Config> {1972 1973 1974 1975 1976 1977 1978 fn create_item(1979 &self,1980 sender: T::CrossAccountId,1981 to: T::CrossAccountId,1982 data: CreateItemData,1983 nesting_budget: &dyn Budget,1984 ) -> DispatchResultWithPostInfo;19851986 1987 1988 1989 1990 1991 1992 fn create_multiple_items(1993 &self,1994 sender: T::CrossAccountId,1995 to: T::CrossAccountId,1996 data: Vec<CreateItemData>,1997 nesting_budget: &dyn Budget,1998 ) -> DispatchResultWithPostInfo;19992000 2001 2002 2003 2004 2005 2006 fn create_multiple_items_ex(2007 &self,2008 sender: T::CrossAccountId,2009 data: CreateItemExData<T::CrossAccountId>,2010 nesting_budget: &dyn Budget,2011 ) -> DispatchResultWithPostInfo;20122013 2014 2015 2016 2017 2018 fn burn_item(2019 &self,2020 sender: T::CrossAccountId,2021 token: TokenId,2022 amount: u128,2023 ) -> DispatchResultWithPostInfo;20242025 2026 2027 2028 2029 2030 2031 fn burn_item_recursively(2032 &self,2033 sender: T::CrossAccountId,2034 token: TokenId,2035 self_budget: &dyn Budget,2036 breadth_budget: &dyn Budget,2037 ) -> DispatchResultWithPostInfo;20382039 2040 2041 2042 2043 fn set_collection_properties(2044 &self,2045 sender: T::CrossAccountId,2046 properties: Vec<Property>,2047 ) -> DispatchResultWithPostInfo;20482049 2050 2051 2052 2053 fn delete_collection_properties(2054 &self,2055 sender: &T::CrossAccountId,2056 property_keys: Vec<PropertyKey>,2057 ) -> DispatchResultWithPostInfo;20582059 2060 2061 2062 2063 2064 2065 2066 2067 2068 fn set_token_properties(2069 &self,2070 sender: T::CrossAccountId,2071 token_id: TokenId,2072 properties: Vec<Property>,2073 budget: &dyn Budget,2074 ) -> DispatchResultWithPostInfo;20752076 2077 2078 2079 2080 2081 2082 2083 2084 2085 fn delete_token_properties(2086 &self,2087 sender: T::CrossAccountId,2088 token_id: TokenId,2089 property_keys: Vec<PropertyKey>,2090 budget: &dyn Budget,2091 ) -> DispatchResultWithPostInfo;20922093 2094 2095 2096 2097 2098 2099 fn set_token_property_permissions(2100 &self,2101 sender: &T::CrossAccountId,2102 property_permissions: Vec<PropertyKeyPermission>,2103 ) -> DispatchResultWithPostInfo;21042105 2106 2107 2108 2109 2110 2111 2112 fn transfer(2113 &self,2114 sender: T::CrossAccountId,2115 to: T::CrossAccountId,2116 token: TokenId,2117 amount: u128,2118 budget: &dyn Budget,2119 ) -> DispatchResultWithPostInfo;21202121 2122 2123 2124 2125 2126 2127 fn approve(2128 &self,2129 sender: T::CrossAccountId,2130 spender: T::CrossAccountId,2131 token: TokenId,2132 amount: u128,2133 ) -> DispatchResultWithPostInfo;21342135 2136 2137 2138 2139 2140 2141 2142 fn approve_from(2143 &self,2144 sender: T::CrossAccountId,2145 from: T::CrossAccountId,2146 to: T::CrossAccountId,2147 token: TokenId,2148 amount: u128,2149 ) -> DispatchResultWithPostInfo;21502151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 fn transfer_from(2162 &self,2163 sender: T::CrossAccountId,2164 from: T::CrossAccountId,2165 to: T::CrossAccountId,2166 token: TokenId,2167 amount: u128,2168 budget: &dyn Budget,2169 ) -> DispatchResultWithPostInfo;21702171 2172 2173 2174 2175 2176 2177 2178 2179 2180 fn burn_from(2181 &self,2182 sender: T::CrossAccountId,2183 from: T::CrossAccountId,2184 token: TokenId,2185 amount: u128,2186 budget: &dyn Budget,2187 ) -> DispatchResultWithPostInfo;21882189 2190 2191 2192 2193 2194 2195 fn check_nesting(2196 &self,2197 sender: T::CrossAccountId,2198 from: (CollectionId, TokenId),2199 under: TokenId,2200 budget: &dyn Budget,2201 ) -> DispatchResult;22022203 2204 2205 2206 2207 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22082209 2210 2211 2212 2213 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22142215 2216 2217 2218 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22192220 2221 fn collection_tokens(&self) -> Vec<TokenId>;22222223 2224 2225 2226 fn token_exists(&self, token: TokenId) -> bool;22272228 2229 fn last_token_id(&self) -> TokenId;22302231 2232 2233 2234 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22352236 2237 2238 2239 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22402241 2242 2243 2244 2245 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22462247 2248 2249 2250 2251 2252 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22532254 2255 fn total_supply(&self) -> u32;22562257 2258 2259 2260 fn account_balance(&self, account: T::CrossAccountId) -> u32;22612262 2263 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22642265 2266 fn total_pieces(&self, token: TokenId) -> Option<u128>;22672268 2269 2270 2271 2272 2273 fn allowance(2274 &self,2275 sender: T::CrossAccountId,2276 spender: T::CrossAccountId,2277 token: TokenId,2278 ) -> u128;22792280 2281 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22822283 2284 2285 2286 2287 fn set_allowance_for_all(2288 &self,2289 owner: T::CrossAccountId,2290 operator: T::CrossAccountId,2291 approve: bool,2292 ) -> DispatchResultWithPostInfo;22932294 2295 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;22962297 2298 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2299}230023012302pub trait RefungibleExtensions<T>2303where2304 T: Config,2305{2306 2307 2308 2309 2310 2311 2312 2313 fn repartition(2314 &self,2315 sender: &T::CrossAccountId,2316 token: TokenId,2317 amount: u128,2318 ) -> DispatchResultWithPostInfo;2319}23202321232223232324pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2325 let post_info = PostDispatchInfo {2326 actual_weight: Some(weight),2327 pays_fee: Pays::Yes,2328 };2329 match res {2330 Ok(()) => Ok(post_info),2331 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2332 }2333}23342335impl<T: Config> From<PropertiesError> for Error<T> {2336 fn from(error: PropertiesError) -> Self {2337 match error {2338 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2339 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2340 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2341 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2342 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2343 }2344 }2345}