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 weights;969798pub type SelfWeightOf<T> = <T as Config>::WeightInfo;99100101102103104105106#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]107pub struct CollectionHandle<T: Config> {108 109 pub id: CollectionId,110 collection: Collection<T::AccountId>,111 112 pub recorder: SubstrateRecorder<T>,113}114115impl<T: Config> WithRecorder<T> for CollectionHandle<T> {116 fn recorder(&self) -> &SubstrateRecorder<T> {117 &self.recorder118 }119 fn into_recorder(self) -> SubstrateRecorder<T> {120 self.recorder121 }122}123124impl<T: Config> CollectionHandle<T> {125 126 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {127 <CollectionById<T>>::get(id).map(|collection| Self {128 id,129 collection,130 recorder: SubstrateRecorder::new(gas_limit),131 })132 }133134 135 pub fn new_with_recorder(id: CollectionId, recorder: SubstrateRecorder<T>) -> Option<Self> {136 <CollectionById<T>>::get(id).map(|collection| Self {137 id,138 collection,139 recorder,140 })141 }142143 144 145 pub fn new(id: CollectionId) -> Option<Self> {146 Self::new_with_gas_limit(id, u64::MAX)147 }148149 150 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {151 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)152 }153154 155 pub fn consume_store_reads(156 &self,157 reads: u64,158 ) -> pallet_evm_coder_substrate::execution::Result<()> {159 self.recorder160 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(161 <T as frame_system::Config>::DbWeight::get()162 .read163 .saturating_mul(reads),164 )))165 }166167 168 pub fn consume_store_writes(169 &self,170 writes: u64,171 ) -> pallet_evm_coder_substrate::execution::Result<()> {172 self.recorder173 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(174 <T as frame_system::Config>::DbWeight::get()175 .write176 .saturating_mul(writes),177 )))178 }179180 181 pub fn consume_store_reads_and_writes(182 &self,183 reads: u64,184 writes: u64,185 ) -> pallet_evm_coder_substrate::execution::Result<()> {186 let weight = <T as frame_system::Config>::DbWeight::get();187 let reads = weight.read.saturating_mul(reads);188 let writes = weight.read.saturating_mul(writes);189 self.recorder190 .consume_gas(T::GasWeightMapping::weight_to_gas(Weight::from_ref_time(191 reads.saturating_add(writes),192 )))193 }194195 196 pub fn save(&self) -> DispatchResult {197 <CollectionById<T>>::insert(self.id, &self.collection);198 Ok(())199 }200201 202 203 204 205 206 pub fn set_sponsor(207 &mut self,208 sender: &T::CrossAccountId,209 sponsor: T::AccountId,210 ) -> DispatchResult {211 self.check_is_internal()?;212 self.check_is_owner_or_admin(sender)?;213214 self.collection.sponsorship = SponsorshipState::Unconfirmed(sponsor.clone());215216 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor));217 <PalletEvm<T>>::deposit_log(218 erc::CollectionHelpersEvents::CollectionChanged {219 collection_id: eth::collection_id_to_address(self.id),220 }221 .to_log(T::ContractAddress::get()),222 );223224 self.save()225 }226227 228 229 230 231 232 233 234 235 236 pub fn force_set_sponsor(&mut self, sponsor: T::AccountId) -> DispatchResult {237 self.check_is_internal()?;238239 self.collection.sponsorship = SponsorshipState::Confirmed(sponsor.clone());240241 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorSet(self.id, sponsor.clone()));242 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sponsor));243 <PalletEvm<T>>::deposit_log(244 erc::CollectionHelpersEvents::CollectionChanged {245 collection_id: eth::collection_id_to_address(self.id),246 }247 .to_log(T::ContractAddress::get()),248 );249250 self.save()251 }252253 254 255 256 257 pub fn confirm_sponsorship(&mut self, sender: &T::AccountId) -> DispatchResult {258 self.check_is_internal()?;259 ensure!(260 self.collection.sponsorship.pending_sponsor() == Some(sender),261 Error::<T>::ConfirmSponsorshipFail262 );263264 self.collection.sponsorship = SponsorshipState::Confirmed(sender.clone());265266 <Pallet<T>>::deposit_event(Event::<T>::SponsorshipConfirmed(self.id, sender.clone()));267 <PalletEvm<T>>::deposit_log(268 erc::CollectionHelpersEvents::CollectionChanged {269 collection_id: eth::collection_id_to_address(self.id),270 }271 .to_log(T::ContractAddress::get()),272 );273274 self.save()275 }276277 278 pub fn remove_sponsor(&mut self, sender: &T::CrossAccountId) -> DispatchResult {279 self.check_is_internal()?;280 self.check_is_owner_or_admin(sender)?;281282 self.collection.sponsorship = SponsorshipState::Disabled;283284 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));285 <PalletEvm<T>>::deposit_log(286 erc::CollectionHelpersEvents::CollectionChanged {287 collection_id: eth::collection_id_to_address(self.id),288 }289 .to_log(T::ContractAddress::get()),290 );291 self.save()292 }293294 295 296 297 298 pub fn force_remove_sponsor(&mut self) -> DispatchResult {299 self.check_is_internal()?;300301 self.collection.sponsorship = SponsorshipState::Disabled;302303 <Pallet<T>>::deposit_event(Event::<T>::CollectionSponsorRemoved(self.id));304 <PalletEvm<T>>::deposit_log(305 erc::CollectionHelpersEvents::CollectionChanged {306 collection_id: eth::collection_id_to_address(self.id),307 }308 .to_log(T::ContractAddress::get()),309 );310 self.save()311 }312313 314 315 pub fn check_is_internal(&self) -> DispatchResult {316 if self.flags.external {317 return Err(<Error<T>>::CollectionIsExternal)?;318 }319320 Ok(())321 }322323 324 325 pub fn check_is_external(&self) -> DispatchResult {326 if !self.flags.external {327 return Err(<Error<T>>::CollectionIsInternal)?;328 }329330 Ok(())331 }332}333334impl<T: Config> Deref for CollectionHandle<T> {335 type Target = Collection<T::AccountId>;336337 fn deref(&self) -> &Self::Target {338 &self.collection339 }340}341342impl<T: Config> DerefMut for CollectionHandle<T> {343 fn deref_mut(&mut self) -> &mut Self::Target {344 &mut self.collection345 }346}347348impl<T: Config> CollectionHandle<T> {349 350 pub fn check_is_owner(&self, user: &T::CrossAccountId) -> DispatchResult {351 ensure!(*user.as_sub() == self.owner, <Error<T>>::NoPermission);352 Ok(())353 }354355 356 pub fn is_owner_or_admin(&self, user: &T::CrossAccountId) -> bool {357 *user.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, user))358 }359360 361 pub fn check_is_owner_or_admin(&self, user: &T::CrossAccountId) -> DispatchResult {362 ensure!(self.is_owner_or_admin(user), <Error<T>>::NoPermission);363 Ok(())364 }365366 367 368 369 pub fn ignores_token_restrictions(&self, user: &T::CrossAccountId) -> bool {370 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)371 }372373 374 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {375 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)376 }377378 379 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {380 ensure!(381 <Allowlist<T>>::get((self.id, user)),382 <Error<T>>::AddressNotInAllowlist383 );384 Ok(())385 }386387 388 389 390 pub fn change_owner(391 &mut self,392 caller: T::CrossAccountId,393 new_owner: T::CrossAccountId,394 ) -> DispatchResult {395 self.check_is_internal()?;396 self.check_is_owner(&caller)?;397 self.collection.owner = new_owner.as_sub().clone();398399 <Pallet<T>>::deposit_event(Event::<T>::CollectionOwnerChanged(400 self.id,401 new_owner.as_sub().clone(),402 ));403 <PalletEvm<T>>::deposit_log(404 erc::CollectionHelpersEvents::CollectionChanged {405 collection_id: eth::collection_id_to_address(self.id),406 }407 .to_log(T::ContractAddress::get()),408 );409410 self.save()411 }412}413414#[frame_support::pallet]415pub mod pallet {416 use super::*;417 use dispatch::CollectionDispatch;418 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};419 use frame_system::pallet_prelude::*;420 use frame_support::traits::Currency;421 use up_data_structs::{TokenId, mapping::TokenAddressMapping};422 use scale_info::TypeInfo;423 use weights::WeightInfo;424425 #[pallet::config]426 pub trait Config:427 frame_system::Config + pallet_evm_coder_substrate::Config + pallet_evm::Config + TypeInfo428 {429 430 type WeightInfo: WeightInfo;431432 433 type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;434435 436 type Currency: Currency<Self::AccountId>;437438 439 #[pallet::constant]440 type CollectionCreationPrice: Get<441 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,442 >;443444 445 type CollectionDispatch: CollectionDispatch<Self>;446447 448 type TreasuryAccountId: Get<Self::AccountId>;449450 451 #[pallet::constant]452 type ContractAddress: Get<H160>;453454 455 type EvmTokenAddressMapping: TokenAddressMapping<H160>;456457 458 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;459 }460461 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);462463 #[pallet::pallet]464 #[pallet::storage_version(STORAGE_VERSION)]465 pub struct Pallet<T>(_);466467 #[pallet::extra_constants]468 impl<T: Config> Pallet<T> {469 470 pub fn collection_admins_limit() -> u32 {471 COLLECTION_ADMINS_LIMIT472 }473 }474475 impl<T: Config> Pallet<T> {476 477 pub fn deposit_event(event: Event<T>) {478 let event = <T as Config>::RuntimeEvent::from(event);479 let event = event.into();480 <frame_system::Pallet<T>>::deposit_event(event)481 }482 }483484 #[pallet::event]485 pub enum Event<T: Config> {486 487 CollectionCreated(488 489 CollectionId,490 491 u8,492 493 T::AccountId,494 ),495496 497 CollectionDestroyed(498 499 CollectionId,500 ),501502 503 ItemCreated(504 505 CollectionId,506 507 TokenId,508 509 T::CrossAccountId,510 511 u128,512 ),513514 515 ItemDestroyed(516 517 CollectionId,518 519 TokenId,520 521 T::CrossAccountId,522 523 u128,524 ),525526 527 Transfer(528 529 CollectionId,530 531 TokenId,532 533 T::CrossAccountId,534 535 T::CrossAccountId,536 537 u128,538 ),539540 541 Approved(542 543 CollectionId,544 545 TokenId,546 547 T::CrossAccountId,548 549 T::CrossAccountId,550 551 u128,552 ),553554 555 ApprovedForAll(556 557 CollectionId,558 559 T::CrossAccountId,560 561 T::CrossAccountId,562 563 bool,564 ),565566 567 CollectionPropertySet(568 569 CollectionId,570 571 PropertyKey,572 ),573574 575 CollectionPropertyDeleted(576 577 CollectionId,578 579 PropertyKey,580 ),581582 583 TokenPropertySet(584 585 CollectionId,586 587 TokenId,588 589 PropertyKey,590 ),591592 593 TokenPropertyDeleted(594 595 CollectionId,596 597 TokenId,598 599 PropertyKey,600 ),601602 603 PropertyPermissionSet(604 605 CollectionId,606 607 PropertyKey,608 ),609610 611 AllowListAddressAdded(612 613 CollectionId,614 615 T::CrossAccountId,616 ),617618 619 AllowListAddressRemoved(620 621 CollectionId,622 623 T::CrossAccountId,624 ),625626 627 CollectionAdminAdded(628 629 CollectionId,630 631 T::CrossAccountId,632 ),633634 635 CollectionAdminRemoved(636 637 CollectionId,638 639 T::CrossAccountId,640 ),641642 643 CollectionLimitSet(644 645 CollectionId,646 ),647648 649 CollectionOwnerChanged(650 651 CollectionId,652 653 T::AccountId,654 ),655656 657 CollectionPermissionSet(658 659 CollectionId,660 ),661662 663 CollectionSponsorSet(664 665 CollectionId,666 667 T::AccountId,668 ),669670 671 SponsorshipConfirmed(672 673 CollectionId,674 675 T::AccountId,676 ),677678 679 CollectionSponsorRemoved(680 681 CollectionId,682 ),683 }684685 #[pallet::error]686 pub enum Error<T> {687 688 CollectionNotFound,689 690 MustBeTokenOwner,691 692 NoPermission,693 694 CantDestroyNotEmptyCollection,695 696 PublicMintingNotAllowed,697 698 AddressNotInAllowlist,699700 701 CollectionNameLimitExceeded,702 703 CollectionDescriptionLimitExceeded,704 705 CollectionTokenPrefixLimitExceeded,706 707 TotalCollectionsLimitExceeded,708 709 CollectionAdminCountExceeded,710 711 CollectionLimitBoundsExceeded,712 713 OwnerPermissionsCantBeReverted,714 715 TransferNotAllowed,716 717 AccountTokenLimitExceeded,718 719 CollectionTokenLimitExceeded,720 721 MetadataFlagFrozen,722723 724 TokenNotFound,725 726 TokenValueTooLow,727 728 ApprovedValueTooLow,729 730 CantApproveMoreThanOwned,731 732 AddressIsNotEthMirror,733734 735 AddressIsZero,736737 738 UnsupportedOperation,739740 741 NotSufficientFounds,742743 744 UserIsNotAllowedToNest,745 746 SourceCollectionIsNotAllowedToNest,747748 749 CollectionFieldSizeExceeded,750751 752 NoSpaceForProperty,753754 755 PropertyLimitReached,756757 758 PropertyKeyIsTooLong,759760 761 InvalidCharacterInPropertyKey,762763 764 EmptyPropertyKey,765766 767 CollectionIsExternal,768769 770 CollectionIsInternal,771772 773 ConfirmSponsorshipFail,774775 776 UserIsNotCollectionAdmin,777 }778779 780 #[pallet::storage]781 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;782783 784 #[pallet::storage]785 pub type DestroyedCollectionCount<T> =786 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;787788 789 #[pallet::storage]790 pub type CollectionById<T> = StorageMap<791 Hasher = Blake2_128Concat,792 Key = CollectionId,793 Value = Collection<<T as frame_system::Config>::AccountId>,794 QueryKind = OptionQuery,795 >;796797 798 #[pallet::storage]799 #[pallet::getter(fn collection_properties)]800 pub type CollectionProperties<T> = StorageMap<801 Hasher = Blake2_128Concat,802 Key = CollectionId,803 Value = CollectionPropertiesT,804 QueryKind = ValueQuery,805 >;806807 808 #[pallet::storage]809 #[pallet::getter(fn property_permissions)]810 pub type CollectionPropertyPermissions<T> = StorageMap<811 Hasher = Blake2_128Concat,812 Key = CollectionId,813 Value = PropertiesPermissionMap,814 QueryKind = ValueQuery,815 >;816817 818 #[pallet::storage]819 pub type AdminAmount<T> = StorageMap<820 Hasher = Blake2_128Concat,821 Key = CollectionId,822 Value = u32,823 QueryKind = ValueQuery,824 >;825826 827 #[pallet::storage]828 pub type IsAdmin<T: Config> = StorageNMap<829 Key = (830 Key<Blake2_128Concat, CollectionId>,831 Key<Blake2_128Concat, T::CrossAccountId>,832 ),833 Value = bool,834 QueryKind = ValueQuery,835 >;836837 838 #[pallet::storage]839 pub type Allowlist<T: Config> = StorageNMap<840 Key = (841 Key<Blake2_128Concat, CollectionId>,842 Key<Blake2_128Concat, T::CrossAccountId>,843 ),844 Value = bool,845 QueryKind = ValueQuery,846 >;847848 849 #[pallet::storage]850 pub type DummyStorageValue<T: Config> = StorageValue<851 Value = (852 CollectionStats,853 CollectionId,854 TokenId,855 TokenChild,856 PhantomType<(857 TokenData<T::CrossAccountId>,858 RpcCollection<T::AccountId>,859 860 PovInfo,861 )>,862 ),863 QueryKind = OptionQuery,864 >;865866 #[pallet::hooks]867 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {868 fn on_runtime_upgrade() -> Weight {869 StorageVersion::new(1).put::<Pallet<T>>();870871 Weight::zero()872 }873 }874}875876impl<T: Config> Pallet<T> {877 878 879 880 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {881 ensure!(882 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,883 <Error<T>>::AddressIsZero884 );885 Ok(())886 }887888 889 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {890 <IsAdmin<T>>::iter_prefix((collection,))891 .map(|(a, _)| a)892 .collect()893 }894895 896 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {897 <Allowlist<T>>::iter_prefix((collection,))898 .map(|(a, _)| a)899 .collect()900 }901902 903 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {904 <Allowlist<T>>::get((collection, user))905 }906907 908 pub fn collection_stats() -> CollectionStats {909 let created = <CreatedCollectionCount<T>>::get();910 let destroyed = <DestroyedCollectionCount<T>>::get();911 CollectionStats {912 created: created.0,913 destroyed: destroyed.0,914 alive: created.0 - destroyed.0,915 }916 }917918 919 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {920 let collection = <CollectionById<T>>::get(collection)?;921 let limits = collection.limits;922 let effective_limits = CollectionLimits {923 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),924 sponsored_data_size: Some(limits.sponsored_data_size()),925 sponsored_data_rate_limit: Some(926 limits927 .sponsored_data_rate_limit928 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),929 ),930 token_limit: Some(limits.token_limit()),931 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(932 match collection.mode {933 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,934 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,935 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,936 },937 )),938 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),939 owner_can_transfer: Some(limits.owner_can_transfer()),940 owner_can_destroy: Some(limits.owner_can_destroy()),941 transfers_enabled: Some(limits.transfers_enabled()),942 };943944 Some(effective_limits)945 }946947 948 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {949 let Collection {950 name,951 description,952 owner,953 mode,954 token_prefix,955 sponsorship,956 limits,957 permissions,958 flags,959 } = <CollectionById<T>>::get(collection)?;960961 let token_property_permissions = <CollectionPropertyPermissions<T>>::get(collection)962 .into_iter()963 .map(|(key, permission)| PropertyKeyPermission { key, permission })964 .collect();965966 let properties = <CollectionProperties<T>>::get(collection)967 .into_iter()968 .map(|(key, value)| Property { key, value })969 .collect();970971 let permissions = CollectionPermissions {972 access: Some(permissions.access()),973 mint_mode: Some(permissions.mint_mode()),974 nesting: Some(permissions.nesting().clone()),975 };976977 Some(RpcCollection {978 name: name.into_inner(),979 description: description.into_inner(),980 owner,981 mode,982 token_prefix: token_prefix.into_inner(),983 sponsorship,984 limits,985 permissions,986 token_property_permissions,987 properties,988 read_only: flags.external,989990 flags: RpcCollectionFlags {991 foreign: flags.foreign,992 erc721metadata: flags.erc721metadata,993 },994 })995 }996}997998macro_rules! limit_default {999 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1000 $(1001 if let Some($new) = $new.$field {1002 let $old = $old.$field($($arg)?);1003 let _ = $new;1004 let _ = $old;1005 $check1006 } else {1007 $new.$field = $old.$field1008 }1009 )*1010 }};1011}1012macro_rules! limit_default_clone {1013 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{1014 $(1015 if let Some($new) = $new.$field.clone() {1016 let $old = $old.$field($($arg)?);1017 let _ = $new;1018 let _ = $old;1019 $check1020 } else {1021 $new.$field = $old.$field.clone()1022 }1023 )*1024 }};1025}10261027impl<T: Config> Pallet<T> {1028 1029 1030 1031 1032 1033 pub fn init_collection(1034 owner: T::CrossAccountId,1035 payer: T::CrossAccountId,1036 data: CreateCollectionData<T::AccountId>,1037 flags: CollectionFlags,1038 ) -> Result<CollectionId, DispatchError> {1039 {1040 ensure!(1041 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,1042 Error::<T>::CollectionTokenPrefixLimitExceeded1043 );1044 }10451046 let created_count = <CreatedCollectionCount<T>>::get()1047 .01048 .checked_add(1)1049 .ok_or(ArithmeticError::Overflow)?;1050 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;1051 let id = CollectionId(created_count);10521053 1054 ensure!(1055 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,1056 <Error<T>>::TotalCollectionsLimitExceeded1057 );10581059 10601061 let collection = Collection {1062 owner: owner.as_sub().clone(),1063 name: data.name,1064 mode: data.mode.clone(),1065 description: data.description,1066 token_prefix: data.token_prefix,1067 sponsorship: data1068 .pending_sponsor1069 .map(SponsorshipState::Unconfirmed)1070 .unwrap_or_default(),1071 limits: data1072 .limits1073 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))1074 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,1075 permissions: data1076 .permissions1077 .map(|permissions| {1078 Self::clamp_permissions(data.mode.clone(), &Default::default(), permissions)1079 })1080 .unwrap_or_else(|| Ok(CollectionPermissions::default()))?,1081 flags,1082 };10831084 let mut collection_properties = CollectionPropertiesT::new();1085 collection_properties1086 .try_set_from_iter(data.properties.into_iter())1087 .map_err(<Error<T>>::from)?;10881089 CollectionProperties::<T>::insert(id, collection_properties);10901091 let mut token_props_permissions = PropertiesPermissionMap::new();1092 token_props_permissions1093 .try_set_from_iter(data.token_property_permissions.into_iter())1094 .map_err(<Error<T>>::from)?;10951096 CollectionPropertyPermissions::<T>::insert(id, token_props_permissions);10971098 1099 {1100 let mut imbalance =1101 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();1102 imbalance.subsume(1103 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(1104 &T::TreasuryAccountId::get(),1105 T::CollectionCreationPrice::get(),1106 ),1107 );1108 <T as Config>::Currency::settle(1109 payer.as_sub(),1110 imbalance,1111 WithdrawReasons::TRANSFER,1112 ExistenceRequirement::KeepAlive,1113 )1114 .map_err(|_| Error::<T>::NotSufficientFounds)?;1115 }11161117 <CreatedCollectionCount<T>>::put(created_count);1118 <Pallet<T>>::deposit_event(Event::CollectionCreated(1119 id,1120 data.mode.id(),1121 owner.as_sub().clone(),1122 ));1123 <PalletEvm<T>>::deposit_log(1124 erc::CollectionHelpersEvents::CollectionCreated {1125 owner: *owner.as_eth(),1126 collection_id: eth::collection_id_to_address(id),1127 }1128 .to_log(T::ContractAddress::get()),1129 );1130 <CollectionById<T>>::insert(id, collection);1131 Ok(id)1132 }11331134 1135 1136 1137 1138 pub fn destroy_collection(1139 collection: CollectionHandle<T>,1140 sender: &T::CrossAccountId,1141 ) -> DispatchResult {1142 ensure!(1143 collection.limits.owner_can_destroy(),1144 <Error<T>>::NoPermission,1145 );1146 collection.check_is_owner(sender)?;11471148 let destroyed_collections = <DestroyedCollectionCount<T>>::get()1149 .01150 .checked_add(1)1151 .ok_or(ArithmeticError::Overflow)?;11521153 11541155 <DestroyedCollectionCount<T>>::put(destroyed_collections);1156 <CollectionById<T>>::remove(collection.id);1157 <AdminAmount<T>>::remove(collection.id);1158 let _ = <IsAdmin<T>>::clear_prefix((collection.id,), u32::MAX, None);1159 let _ = <Allowlist<T>>::clear_prefix((collection.id,), u32::MAX, None);1160 <CollectionProperties<T>>::remove(collection.id);11611162 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));11631164 <PalletEvm<T>>::deposit_log(1165 erc::CollectionHelpersEvents::CollectionDestroyed {1166 collection_id: eth::collection_id_to_address(collection.id),1167 }1168 .to_log(T::ContractAddress::get()),1169 );1170 Ok(())1171 }11721173 1174 1175 1176 1177 1178 1179 1180 1181 #[transactional]1182 fn modify_collection_properties(1183 collection: &CollectionHandle<T>,1184 sender: &T::CrossAccountId,1185 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1186 ) -> DispatchResult {1187 collection.check_is_owner_or_admin(sender)?;11881189 let mut stored_properties = <CollectionProperties<T>>::get(collection.id);11901191 for (key, value) in properties_updates {1192 match value {1193 Some(value) => {1194 stored_properties1195 .try_set(key.clone(), value)1196 .map_err(<Error<T>>::from)?;11971198 Self::deposit_event(Event::CollectionPropertySet(collection.id, key));1199 <PalletEvm<T>>::deposit_log(1200 erc::CollectionHelpersEvents::CollectionChanged {1201 collection_id: eth::collection_id_to_address(collection.id),1202 }1203 .to_log(T::ContractAddress::get()),1204 );1205 }1206 None => {1207 stored_properties.remove(&key).map_err(<Error<T>>::from)?;12081209 Self::deposit_event(Event::CollectionPropertyDeleted(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 }1218 }12191220 <CollectionProperties<T>>::set(collection.id, stored_properties);12211222 Ok(())1223 }12241225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 pub fn modify_token_properties(1243 collection: &CollectionHandle<T>,1244 sender: &T::CrossAccountId,1245 token_id: TokenId,1246 properties_updates: impl Iterator<Item = (PropertyKey, Option<PropertyValue>)>,1247 is_token_create: bool,1248 mut stored_properties: TokenProperties,1249 is_token_owner: impl Fn() -> Result<bool, DispatchError>,1250 set_token_properties: impl FnOnce(TokenProperties),1251 log: evm_coder::ethereum::Log,1252 ) -> DispatchResult {1253 let is_collection_admin = collection.is_owner_or_admin(sender);1254 let permissions = Self::property_permissions(collection.id);12551256 let mut token_owner_result = None;1257 let mut is_token_owner = || -> Result<bool, DispatchError> {1258 *token_owner_result.get_or_insert_with(&is_token_owner)1259 };12601261 for (key, value) in properties_updates {1262 let permission = permissions1263 .get(&key)1264 .cloned()1265 .unwrap_or_else(PropertyPermission::none);12661267 let is_property_exists = stored_properties.get(&key).is_some();12681269 match permission {1270 PropertyPermission { mutable: false, .. } if is_property_exists => {1271 return Err(<Error<T>>::NoPermission.into());1272 }12731274 PropertyPermission {1275 collection_admin,1276 token_owner,1277 ..1278 } => {1279 1280 let is_token_create =1281 is_token_create && (collection_admin || token_owner) && value.is_some();1282 if !(is_token_create1283 || (collection_admin && is_collection_admin)1284 || (token_owner && is_token_owner()?))1285 {1286 fail!(<Error<T>>::NoPermission);1287 }1288 }1289 }12901291 match value {1292 Some(value) => {1293 stored_properties1294 .try_set(key.clone(), value)1295 .map_err(<Error<T>>::from)?;12961297 Self::deposit_event(Event::TokenPropertySet(collection.id, token_id, key));1298 }1299 None => {1300 stored_properties.remove(&key).map_err(<Error<T>>::from)?;13011302 Self::deposit_event(Event::TokenPropertyDeleted(collection.id, token_id, key));1303 }1304 }13051306 <PalletEvm<T>>::deposit_log(log.clone());1307 }13081309 set_token_properties(stored_properties);13101311 Ok(())1312 }13131314 1315 1316 1317 1318 1319 1320 pub fn set_allowance_for_all(1321 collection: &CollectionHandle<T>,1322 owner: &T::CrossAccountId,1323 operator: &T::CrossAccountId,1324 approve: bool,1325 set_allowance: impl FnOnce(),1326 log: evm_coder::ethereum::Log,1327 ) -> DispatchResult {1328 if collection.permissions.access() == AccessMode::AllowList {1329 collection.check_allowlist(owner)?;1330 collection.check_allowlist(operator)?;1331 }13321333 Self::ensure_correct_receiver(operator)?;13341335 set_allowance();13361337 <PalletEvm<T>>::deposit_log(log);1338 Self::deposit_event(Event::ApprovedForAll(1339 collection.id,1340 owner.clone(),1341 operator.clone(),1342 approve,1343 ));1344 Ok(())1345 }13461347 1348 1349 1350 1351 1352 pub fn set_collection_property(1353 collection: &CollectionHandle<T>,1354 sender: &T::CrossAccountId,1355 property: Property,1356 ) -> DispatchResult {1357 Self::set_collection_properties(collection, sender, [property].into_iter())1358 }13591360 1361 1362 1363 1364 1365 1366 pub fn set_scoped_collection_property(1367 collection_id: CollectionId,1368 scope: PropertyScope,1369 property: Property,1370 ) -> DispatchResult {1371 CollectionProperties::<T>::try_mutate(collection_id, |properties| {1372 properties.try_scoped_set(scope, property.key, property.value)1373 })1374 .map_err(<Error<T>>::from)?;13751376 Ok(())1377 }13781379 1380 1381 1382 1383 1384 1385 pub fn set_scoped_collection_properties(1386 collection_id: CollectionId,1387 scope: PropertyScope,1388 properties: impl Iterator<Item = Property>,1389 ) -> DispatchResult {1390 CollectionProperties::<T>::try_mutate(collection_id, |stored_properties| {1391 stored_properties.try_scoped_set_from_iter(scope, properties)1392 })1393 .map_err(<Error<T>>::from)?;13941395 Ok(())1396 }13971398 1399 1400 1401 1402 1403 pub fn set_collection_properties(1404 collection: &CollectionHandle<T>,1405 sender: &T::CrossAccountId,1406 properties: impl Iterator<Item = Property>,1407 ) -> DispatchResult {1408 Self::modify_collection_properties(1409 collection,1410 sender,1411 properties.map(|property| (property.key, Some(property.value))),1412 )1413 }14141415 1416 1417 1418 1419 1420 pub fn delete_collection_property(1421 collection: &CollectionHandle<T>,1422 sender: &T::CrossAccountId,1423 property_key: PropertyKey,1424 ) -> DispatchResult {1425 Self::delete_collection_properties(collection, sender, [property_key].into_iter())1426 }14271428 1429 1430 1431 1432 1433 pub fn delete_collection_properties(1434 collection: &CollectionHandle<T>,1435 sender: &T::CrossAccountId,1436 property_keys: impl Iterator<Item = PropertyKey>,1437 ) -> DispatchResult {1438 Self::modify_collection_properties(collection, sender, property_keys.map(|key| (key, None)))1439 }14401441 1442 1443 1444 1445 1446 1447 pub fn set_property_permission_unchecked(1448 collection: CollectionId,1449 property_permission: PropertyKeyPermission,1450 ) -> DispatchResult {1451 <CollectionPropertyPermissions<T>>::try_mutate(collection, |permissions| {1452 permissions.try_set(property_permission.key, property_permission.permission)1453 })1454 .map_err(<Error<T>>::from)?;1455 Ok(())1456 }14571458 1459 1460 1461 1462 1463 pub fn set_property_permission(1464 collection: &CollectionHandle<T>,1465 sender: &T::CrossAccountId,1466 property_permission: PropertyKeyPermission,1467 ) -> DispatchResult {1468 Self::set_scoped_property_permission(1469 collection,1470 sender,1471 PropertyScope::None,1472 property_permission,1473 )1474 }14751476 1477 1478 1479 1480 1481 1482 pub fn set_scoped_property_permission(1483 collection: &CollectionHandle<T>,1484 sender: &T::CrossAccountId,1485 scope: PropertyScope,1486 property_permission: PropertyKeyPermission,1487 ) -> DispatchResult {1488 collection.check_is_owner_or_admin(sender)?;14891490 let all_permissions = CollectionPropertyPermissions::<T>::get(collection.id);1491 let current_permission = all_permissions.get(&property_permission.key);1492 if matches![1493 current_permission,1494 Some(PropertyPermission { mutable: false, .. })1495 ] {1496 return Err(<Error<T>>::NoPermission.into());1497 }14981499 CollectionPropertyPermissions::<T>::try_mutate(collection.id, |permissions| {1500 let property_permission = property_permission.clone();1501 permissions.try_scoped_set(1502 scope,1503 property_permission.key,1504 property_permission.permission,1505 )1506 })1507 .map_err(<Error<T>>::from)?;15081509 Self::deposit_event(Event::PropertyPermissionSet(1510 collection.id,1511 property_permission.key,1512 ));1513 <PalletEvm<T>>::deposit_log(1514 erc::CollectionHelpersEvents::CollectionChanged {1515 collection_id: eth::collection_id_to_address(collection.id),1516 }1517 .to_log(T::ContractAddress::get()),1518 );15191520 Ok(())1521 }15221523 1524 1525 1526 1527 1528 #[transactional]1529 pub fn set_token_property_permissions(1530 collection: &CollectionHandle<T>,1531 sender: &T::CrossAccountId,1532 property_permissions: Vec<PropertyKeyPermission>,1533 ) -> DispatchResult {1534 Self::set_scoped_token_property_permissions(1535 collection,1536 sender,1537 PropertyScope::None,1538 property_permissions,1539 )1540 }15411542 1543 1544 1545 1546 1547 1548 #[transactional]1549 pub fn set_scoped_token_property_permissions(1550 collection: &CollectionHandle<T>,1551 sender: &T::CrossAccountId,1552 scope: PropertyScope,1553 property_permissions: Vec<PropertyKeyPermission>,1554 ) -> DispatchResult {1555 for prop_pemission in property_permissions {1556 Self::set_scoped_property_permission(collection, sender, scope, prop_pemission)?;1557 }15581559 Ok(())1560 }15611562 1563 pub fn get_collection_property(1564 collection_id: CollectionId,1565 key: &PropertyKey,1566 ) -> Option<PropertyValue> {1567 Self::collection_properties(collection_id).get(key).cloned()1568 }15691570 1571 pub fn bytes_keys_to_property_keys(1572 keys: Vec<Vec<u8>>,1573 ) -> Result<Vec<PropertyKey>, DispatchError> {1574 keys.into_iter()1575 .map(|key| -> Result<PropertyKey, DispatchError> {1576 key.try_into()1577 .map_err(|_| <Error<T>>::PropertyKeyIsTooLong.into())1578 })1579 .collect::<Result<Vec<PropertyKey>, DispatchError>>()1580 }15811582 1583 pub fn filter_collection_properties(1584 collection_id: CollectionId,1585 keys: Option<Vec<PropertyKey>>,1586 ) -> Result<Vec<Property>, DispatchError> {1587 let properties = Self::collection_properties(collection_id);15881589 let properties = keys1590 .map(|keys| {1591 keys.into_iter()1592 .filter_map(|key| {1593 properties.get(&key).map(|value| Property {1594 key,1595 value: value.clone(),1596 })1597 })1598 .collect()1599 })1600 .unwrap_or_else(|| {1601 properties1602 .into_iter()1603 .map(|(key, value)| Property { key, value })1604 .collect()1605 });16061607 Ok(properties)1608 }16091610 1611 pub fn filter_property_permissions(1612 collection_id: CollectionId,1613 keys: Option<Vec<PropertyKey>>,1614 ) -> Result<Vec<PropertyKeyPermission>, DispatchError> {1615 let permissions = Self::property_permissions(collection_id);16161617 let key_permissions = keys1618 .map(|keys| {1619 keys.into_iter()1620 .filter_map(|key| {1621 permissions1622 .get(&key)1623 .map(|permission| PropertyKeyPermission {1624 key,1625 permission: permission.clone(),1626 })1627 })1628 .collect()1629 })1630 .unwrap_or_else(|| {1631 permissions1632 .into_iter()1633 .map(|(key, permission)| PropertyKeyPermission { key, permission })1634 .collect()1635 });16361637 Ok(key_permissions)1638 }16391640 1641 1642 1643 pub fn toggle_allowlist(1644 collection: &CollectionHandle<T>,1645 sender: &T::CrossAccountId,1646 user: &T::CrossAccountId,1647 allowed: bool,1648 ) -> DispatchResult {1649 collection.check_is_owner_or_admin(sender)?;16501651 16521653 if allowed {1654 <Allowlist<T>>::insert((collection.id, user), true);1655 Self::deposit_event(Event::<T>::AllowListAddressAdded(1656 collection.id,1657 user.clone(),1658 ));1659 } else {1660 <Allowlist<T>>::remove((collection.id, user));1661 Self::deposit_event(Event::<T>::AllowListAddressRemoved(1662 collection.id,1663 user.clone(),1664 ));1665 }16661667 <PalletEvm<T>>::deposit_log(1668 erc::CollectionHelpersEvents::CollectionChanged {1669 collection_id: eth::collection_id_to_address(collection.id),1670 }1671 .to_log(T::ContractAddress::get()),1672 );16731674 Ok(())1675 }16761677 1678 1679 1680 pub fn toggle_admin(1681 collection: &CollectionHandle<T>,1682 sender: &T::CrossAccountId,1683 user: &T::CrossAccountId,1684 admin: bool,1685 ) -> DispatchResult {1686 collection.check_is_internal()?;1687 collection.check_is_owner(sender)?;16881689 let is_admin = <IsAdmin<T>>::get((collection.id, user));1690 if is_admin == admin {1691 if admin {1692 return Ok(());1693 } else {1694 return Err(Error::<T>::UserIsNotCollectionAdmin.into());1695 }1696 }1697 let amount = <AdminAmount<T>>::get(collection.id);16981699 17001701 if admin {1702 let amount = amount1703 .checked_add(1)1704 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;1705 ensure!(1706 amount <= Self::collection_admins_limit(),1707 <Error<T>>::CollectionAdminCountExceeded,1708 );17091710 <AdminAmount<T>>::insert(collection.id, amount);1711 <IsAdmin<T>>::insert((collection.id, user), true);17121713 Self::deposit_event(Event::<T>::CollectionAdminAdded(1714 collection.id,1715 user.clone(),1716 ));1717 } else {1718 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));1719 <IsAdmin<T>>::remove((collection.id, user));17201721 Self::deposit_event(Event::<T>::CollectionAdminRemoved(1722 collection.id,1723 user.clone(),1724 ));1725 }17261727 <PalletEvm<T>>::deposit_log(1728 erc::CollectionHelpersEvents::CollectionChanged {1729 collection_id: eth::collection_id_to_address(collection.id),1730 }1731 .to_log(T::ContractAddress::get()),1732 );17331734 Ok(())1735 }17361737 1738 pub fn update_limits(1739 user: &T::CrossAccountId,1740 collection: &mut CollectionHandle<T>,1741 new_limit: CollectionLimits,1742 ) -> DispatchResult {1743 collection.check_is_internal()?;1744 collection.check_is_owner_or_admin(user)?;17451746 collection.limits =1747 Self::clamp_limits(collection.mode.clone(), &collection.limits, new_limit)?;17481749 Self::deposit_event(Event::<T>::CollectionLimitSet(collection.id));1750 <PalletEvm<T>>::deposit_log(1751 erc::CollectionHelpersEvents::CollectionChanged {1752 collection_id: eth::collection_id_to_address(collection.id),1753 }1754 .to_log(T::ContractAddress::get()),1755 );17561757 collection.save()1758 }17591760 1761 fn clamp_limits(1762 mode: CollectionMode,1763 old_limit: &CollectionLimits,1764 mut new_limit: CollectionLimits,1765 ) -> Result<CollectionLimits, DispatchError> {1766 let limits = old_limit;1767 limit_default!(old_limit, new_limit,1768 account_token_ownership_limit => ensure!(1769 new_limit <= MAX_TOKEN_OWNERSHIP,1770 <Error<T>>::CollectionLimitBoundsExceeded,1771 ),1772 sponsored_data_size => ensure!(1773 new_limit <= CUSTOM_DATA_LIMIT,1774 <Error<T>>::CollectionLimitBoundsExceeded,1775 ),17761777 sponsored_data_rate_limit => {},1778 token_limit => ensure!(1779 old_limit >= new_limit && new_limit > 0,1780 <Error<T>>::CollectionTokenLimitExceeded1781 ),17821783 sponsor_transfer_timeout(match mode {1784 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,1785 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1786 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,1787 }) => ensure!(1788 new_limit <= MAX_SPONSOR_TIMEOUT,1789 <Error<T>>::CollectionLimitBoundsExceeded,1790 ),1791 sponsor_approve_timeout => {},1792 owner_can_transfer => ensure!(1793 !limits.owner_can_transfer_instaled() ||1794 old_limit || !new_limit,1795 <Error<T>>::OwnerPermissionsCantBeReverted,1796 ),1797 owner_can_destroy => ensure!(1798 old_limit || !new_limit,1799 <Error<T>>::OwnerPermissionsCantBeReverted,1800 ),1801 transfers_enabled => {},1802 );1803 Ok(new_limit)1804 }18051806 1807 pub fn update_permissions(1808 user: &T::CrossAccountId,1809 collection: &mut CollectionHandle<T>,1810 new_permission: CollectionPermissions,1811 ) -> DispatchResult {1812 collection.check_is_internal()?;1813 collection.check_is_owner_or_admin(user)?;1814 collection.permissions = Self::clamp_permissions(1815 collection.mode.clone(),1816 &collection.permissions,1817 new_permission,1818 )?;18191820 Self::deposit_event(Event::<T>::CollectionPermissionSet(collection.id));1821 <PalletEvm<T>>::deposit_log(1822 erc::CollectionHelpersEvents::CollectionChanged {1823 collection_id: eth::collection_id_to_address(collection.id),1824 }1825 .to_log(T::ContractAddress::get()),1826 );18271828 collection.save()1829 }18301831 1832 fn clamp_permissions(1833 _mode: CollectionMode,1834 old_permission: &CollectionPermissions,1835 mut new_permission: CollectionPermissions,1836 ) -> Result<CollectionPermissions, DispatchError> {1837 limit_default_clone!(old_permission, new_permission,1838 access => {},1839 mint_mode => {},1840 nesting => { },1841 );1842 Ok(new_permission)1843 }18441845 1846 pub fn repair_collection(collection_id: CollectionId) -> DispatchResult {1847 CollectionProperties::<T>::mutate(collection_id, |properties| {1848 properties.recompute_consumed_space();1849 });18501851 Ok(())1852 }1853}185418551856#[macro_export]1857macro_rules! unsupported {1858 ($runtime:path) => {1859 Err($crate::Error::<$runtime>::UnsupportedOperation.into())1860 };1861}186218631864pub trait CommonWeightInfo<CrossAccountId> {1865 1866 fn create_item(data: &CreateItemData) -> Weight {1867 Self::create_multiple_items(from_ref(data))1868 }18691870 1871 fn create_multiple_items(amount: &[CreateItemData]) -> Weight;18721873 1874 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;18751876 1877 fn burn_item() -> Weight;18781879 1880 1881 1882 fn set_collection_properties(amount: u32) -> Weight;18831884 1885 1886 1887 fn delete_collection_properties(amount: u32) -> Weight;18881889 1890 1891 1892 fn set_token_properties(amount: u32) -> Weight;18931894 1895 1896 1897 fn delete_token_properties(amount: u32) -> Weight;18981899 1900 1901 1902 fn set_token_property_permissions(amount: u32) -> Weight;19031904 1905 fn transfer() -> Weight;19061907 1908 fn approve() -> Weight;19091910 1911 fn approve_from() -> Weight;19121913 1914 fn transfer_from() -> Weight;19151916 1917 fn burn_from() -> Weight;19181919 1920 1921 1922 1923 fn burn_recursively_self_raw() -> Weight;19241925 1926 1927 1928 fn burn_recursively_breadth_raw(amount: u32) -> Weight;19291930 1931 1932 1933 1934 fn burn_recursively(max_selfs: u32, max_breadth: u32) -> Weight {1935 Self::burn_recursively_self_raw()1936 .saturating_mul(max_selfs.max(1) as u64)1937 .saturating_add(Self::burn_recursively_breadth_raw(max_breadth))1938 }19391940 1941 fn token_owner() -> Weight;19421943 1944 fn set_allowance_for_all() -> Weight;19451946 1947 fn force_repair_item() -> Weight;1948}194919501951pub trait RefungibleExtensionsWeightInfo {1952 1953 fn repartition() -> Weight;1954}195519561957195819591960pub trait CommonCollectionOperations<T: Config> {1961 1962 1963 1964 1965 1966 1967 fn create_item(1968 &self,1969 sender: T::CrossAccountId,1970 to: T::CrossAccountId,1971 data: CreateItemData,1972 nesting_budget: &dyn Budget,1973 ) -> DispatchResultWithPostInfo;19741975 1976 1977 1978 1979 1980 1981 fn create_multiple_items(1982 &self,1983 sender: T::CrossAccountId,1984 to: T::CrossAccountId,1985 data: Vec<CreateItemData>,1986 nesting_budget: &dyn Budget,1987 ) -> DispatchResultWithPostInfo;19881989 1990 1991 1992 1993 1994 1995 fn create_multiple_items_ex(1996 &self,1997 sender: T::CrossAccountId,1998 data: CreateItemExData<T::CrossAccountId>,1999 nesting_budget: &dyn Budget,2000 ) -> DispatchResultWithPostInfo;20012002 2003 2004 2005 2006 2007 fn burn_item(2008 &self,2009 sender: T::CrossAccountId,2010 token: TokenId,2011 amount: u128,2012 ) -> DispatchResultWithPostInfo;20132014 2015 2016 2017 2018 2019 2020 fn burn_item_recursively(2021 &self,2022 sender: T::CrossAccountId,2023 token: TokenId,2024 self_budget: &dyn Budget,2025 breadth_budget: &dyn Budget,2026 ) -> DispatchResultWithPostInfo;20272028 2029 2030 2031 2032 fn set_collection_properties(2033 &self,2034 sender: T::CrossAccountId,2035 properties: Vec<Property>,2036 ) -> DispatchResultWithPostInfo;20372038 2039 2040 2041 2042 fn delete_collection_properties(2043 &self,2044 sender: &T::CrossAccountId,2045 property_keys: Vec<PropertyKey>,2046 ) -> DispatchResultWithPostInfo;20472048 2049 2050 2051 2052 2053 2054 2055 2056 2057 fn set_token_properties(2058 &self,2059 sender: T::CrossAccountId,2060 token_id: TokenId,2061 properties: Vec<Property>,2062 budget: &dyn Budget,2063 ) -> DispatchResultWithPostInfo;20642065 2066 2067 2068 2069 2070 2071 2072 2073 2074 fn delete_token_properties(2075 &self,2076 sender: T::CrossAccountId,2077 token_id: TokenId,2078 property_keys: Vec<PropertyKey>,2079 budget: &dyn Budget,2080 ) -> DispatchResultWithPostInfo;20812082 2083 2084 2085 2086 2087 2088 fn set_token_property_permissions(2089 &self,2090 sender: &T::CrossAccountId,2091 property_permissions: Vec<PropertyKeyPermission>,2092 ) -> DispatchResultWithPostInfo;20932094 2095 2096 2097 2098 2099 2100 2101 fn transfer(2102 &self,2103 sender: T::CrossAccountId,2104 to: T::CrossAccountId,2105 token: TokenId,2106 amount: u128,2107 budget: &dyn Budget,2108 ) -> DispatchResultWithPostInfo;21092110 2111 2112 2113 2114 2115 2116 fn approve(2117 &self,2118 sender: T::CrossAccountId,2119 spender: T::CrossAccountId,2120 token: TokenId,2121 amount: u128,2122 ) -> DispatchResultWithPostInfo;21232124 2125 2126 2127 2128 2129 2130 2131 fn approve_from(2132 &self,2133 sender: T::CrossAccountId,2134 from: T::CrossAccountId,2135 to: T::CrossAccountId,2136 token: TokenId,2137 amount: u128,2138 ) -> DispatchResultWithPostInfo;21392140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 fn transfer_from(2151 &self,2152 sender: T::CrossAccountId,2153 from: T::CrossAccountId,2154 to: T::CrossAccountId,2155 token: TokenId,2156 amount: u128,2157 budget: &dyn Budget,2158 ) -> DispatchResultWithPostInfo;21592160 2161 2162 2163 2164 2165 2166 2167 2168 2169 fn burn_from(2170 &self,2171 sender: T::CrossAccountId,2172 from: T::CrossAccountId,2173 token: TokenId,2174 amount: u128,2175 budget: &dyn Budget,2176 ) -> DispatchResultWithPostInfo;21772178 2179 2180 2181 2182 2183 2184 fn check_nesting(2185 &self,2186 sender: T::CrossAccountId,2187 from: (CollectionId, TokenId),2188 under: TokenId,2189 budget: &dyn Budget,2190 ) -> DispatchResult;21912192 2193 2194 2195 2196 fn nest(&self, under: TokenId, to_nest: (CollectionId, TokenId));21972198 2199 2200 2201 2202 fn unnest(&self, under: TokenId, to_nest: (CollectionId, TokenId));22032204 2205 2206 2207 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;22082209 2210 fn collection_tokens(&self) -> Vec<TokenId>;22112212 2213 2214 2215 fn token_exists(&self, token: TokenId) -> bool;22162217 2218 fn last_token_id(&self) -> TokenId;22192220 2221 2222 2223 fn token_owner(&self, token: TokenId) -> Result<T::CrossAccountId, TokenOwnerError>;22242225 2226 2227 2228 fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;22292230 2231 2232 2233 2234 fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue>;22352236 2237 2238 2239 2240 2241 fn token_properties(&self, token: TokenId, keys: Option<Vec<PropertyKey>>) -> Vec<Property>;22422243 2244 fn total_supply(&self) -> u32;22452246 2247 2248 2249 fn account_balance(&self, account: T::CrossAccountId) -> u32;22502251 2252 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;22532254 2255 fn total_pieces(&self, token: TokenId) -> Option<u128>;22562257 2258 2259 2260 2261 2262 fn allowance(2263 &self,2264 sender: T::CrossAccountId,2265 spender: T::CrossAccountId,2266 token: TokenId,2267 ) -> u128;22682269 2270 fn refungible_extensions(&self) -> Option<&dyn RefungibleExtensions<T>>;22712272 2273 2274 2275 2276 fn set_allowance_for_all(2277 &self,2278 owner: T::CrossAccountId,2279 operator: T::CrossAccountId,2280 approve: bool,2281 ) -> DispatchResultWithPostInfo;22822283 2284 fn allowance_for_all(&self, owner: T::CrossAccountId, operator: T::CrossAccountId) -> bool;22852286 2287 fn repair_item(&self, token: TokenId) -> DispatchResultWithPostInfo;2288}228922902291pub trait RefungibleExtensions<T>2292where2293 T: Config,2294{2295 2296 2297 2298 2299 2300 2301 2302 fn repartition(2303 &self,2304 sender: &T::CrossAccountId,2305 token: TokenId,2306 amount: u128,2307 ) -> DispatchResultWithPostInfo;2308}23092310231123122313pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {2314 let post_info = PostDispatchInfo {2315 actual_weight: Some(weight),2316 pays_fee: Pays::Yes,2317 };2318 match res {2319 Ok(()) => Ok(post_info),2320 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),2321 }2322}23232324impl<T: Config> From<PropertiesError> for Error<T> {2325 fn from(error: PropertiesError) -> Self {2326 match error {2327 PropertiesError::NoSpaceForProperty => Self::NoSpaceForProperty,2328 PropertiesError::PropertyLimitReached => Self::PropertyLimitReached,2329 PropertiesError::InvalidCharacterInPropertyKey => Self::InvalidCharacterInPropertyKey,2330 PropertiesError::PropertyKeyIsTooLong => Self::PropertyKeyIsTooLong,2331 PropertiesError::EmptyPropertyKey => Self::EmptyPropertyKey,2332 }2333 }2334}