1234567891011121314151617#![cfg_attr(not(feature = "std"), no_std)]1819use core::ops::{Deref, DerefMut};20use pallet_evm_coder_substrate::{SubstrateRecorder, WithRecorder};21use sp_std::vec::Vec;22use pallet_evm::account::CrossAccountId;23use frame_support::{24 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},25 ensure, fail,26 traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},27 BoundedVec,28 weights::Pays,29};30use pallet_evm::GasWeightMapping;31use up_data_structs::{32 COLLECTION_NUMBER_LIMIT, Collection, RpcCollection, CollectionId, CreateItemData,33 MAX_TOKEN_PREFIX_LENGTH, COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId,34 CollectionStats, MAX_TOKEN_OWNERSHIP, CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT,35 FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT,36 CUSTOM_DATA_LIMIT, CollectionLimits, CustomDataLimit, CreateCollectionData, SponsorshipState,37 CreateItemExData, SponsoringRateLimit, budget::Budget, COLLECTION_FIELD_LIMIT, CollectionField,38 PhantomType,39};40pub use pallet::*;41use sp_core::H160;42use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};43#[cfg(feature = "runtime-benchmarks")]44pub mod benchmarking;45pub mod dispatch;46pub mod erc;47pub mod eth;4849#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]50pub struct CollectionHandle<T: Config> {51 pub id: CollectionId,52 collection: Collection<T::AccountId>,53 pub recorder: SubstrateRecorder<T>,54}55impl<T: Config> WithRecorder<T> for CollectionHandle<T> {56 fn recorder(&self) -> &SubstrateRecorder<T> {57 &self.recorder58 }59 fn into_recorder(self) -> SubstrateRecorder<T> {60 self.recorder61 }62}63impl<T: Config> CollectionHandle<T> {64 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {65 <CollectionById<T>>::get(id).map(|collection| Self {66 id,67 collection,68 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),69 })70 }71 pub fn new(id: CollectionId) -> Option<Self> {72 Self::new_with_gas_limit(id, u64::MAX)73 }74 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {75 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)76 }77 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {78 self.recorder.log_mirrored(log)79 }80 pub fn log_direct(&self, log: impl evm_coder::ToLog) {81 self.recorder.log_direct(log)82 }83 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {84 self.recorder85 .consume_gas(T::GasWeightMapping::weight_to_gas(86 <T as frame_system::Config>::DbWeight::get()87 .read88 .saturating_mul(reads),89 ))90 }91 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {92 self.recorder93 .consume_gas(T::GasWeightMapping::weight_to_gas(94 <T as frame_system::Config>::DbWeight::get()95 .write96 .saturating_mul(writes),97 ))98 }99 pub fn submit_logs(self) {100 self.recorder.submit_logs()101 }102 pub fn save(self) -> DispatchResult {103 self.recorder.submit_logs();104 <CollectionById<T>>::insert(self.id, self.collection);105 Ok(())106 }107}108impl<T: Config> Deref for CollectionHandle<T> {109 type Target = Collection<T::AccountId>;110111 fn deref(&self) -> &Self::Target {112 &self.collection113 }114}115116impl<T: Config> DerefMut for CollectionHandle<T> {117 fn deref_mut(&mut self) -> &mut Self::Target {118 &mut self.collection119 }120}121122impl<T: Config> CollectionHandle<T> {123 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {124 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);125 Ok(())126 }127 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {128 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))129 }130 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {131 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);132 Ok(())133 }134 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {135 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)136 }137 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {138 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)139 }140 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {141 ensure!(142 <Allowlist<T>>::get((self.id, user)),143 <Error<T>>::AddressNotInAllowlist144 );145 Ok(())146 }147148 pub fn check_can_update_meta(149 &self,150 subject: &T::CrossAccountId,151 item_owner: &T::CrossAccountId,152 ) -> DispatchResult {153 match self.meta_update_permission {154 MetaUpdatePermission::ItemOwner => {155 ensure!(subject == item_owner, <Error<T>>::NoPermission);156 Ok(())157 }158 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),159 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),160 }161 }162}163164#[frame_support::pallet]165pub mod pallet {166 use super::*;167 use pallet_evm::account;168 use dispatch::CollectionDispatch;169 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};170 use frame_system::pallet_prelude::*;171 use frame_support::traits::Currency;172 use up_data_structs::{TokenId, mapping::TokenAddressMapping};173 use scale_info::TypeInfo;174175 #[pallet::config]176 pub trait Config:177 frame_system::Config + pallet_evm_coder_substrate::Config + TypeInfo + account::Config178 {179 type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;180181 type Currency: Currency<Self::AccountId>;182183 #[pallet::constant]184 type CollectionCreationPrice: Get<185 <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,186 >;187 type CollectionDispatch: CollectionDispatch<Self>;188189 type TreasuryAccountId: Get<Self::AccountId>;190191 type EvmTokenAddressMapping: TokenAddressMapping<H160>;192 type CrossTokenAddressMapping: TokenAddressMapping<Self::CrossAccountId>;193 }194195 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);196197 #[pallet::pallet]198 #[pallet::storage_version(STORAGE_VERSION)]199 #[pallet::generate_store(pub(super) trait Store)]200 pub struct Pallet<T>(_);201202 #[pallet::extra_constants]203 impl<T: Config> Pallet<T> {204 pub fn collection_admins_limit() -> u32 {205 COLLECTION_ADMINS_LIMIT206 }207 }208209 #[pallet::event]210 #[pallet::generate_deposit(pub fn deposit_event)]211 pub enum Event<T: Config> {212 213 214 215 216 217 218 219 220 221 CollectionCreated(CollectionId, u8, T::AccountId),222223 224 225 226 227 228 CollectionDestroyed(CollectionId),229230 231 232 233 234 235 236 237 238 239 240 241 ItemCreated(CollectionId, TokenId, T::CrossAccountId, u128),242243 244 245 246 247 248 249 250 251 252 253 254 ItemDestroyed(CollectionId, TokenId, T::CrossAccountId, u128),255256 257 258 259 260 261 262 263 264 265 266 267 Transfer(268 CollectionId,269 TokenId,270 T::CrossAccountId,271 T::CrossAccountId,272 u128,273 ),274275 276 277 278 279 280 281 282 283 284 Approved(285 CollectionId,286 TokenId,287 T::CrossAccountId,288 T::CrossAccountId,289 u128,290 ),291 }292293 #[pallet::error]294 pub enum Error<T> {295 296 CollectionNotFound,297 298 MustBeTokenOwner,299 300 NoPermission,301 302 PublicMintingNotAllowed,303 304 AddressNotInAllowlist,305306 307 CollectionNameLimitExceeded,308 309 CollectionDescriptionLimitExceeded,310 311 CollectionTokenPrefixLimitExceeded,312 313 TotalCollectionsLimitExceeded,314 315 TokenVariableDataLimitExceeded,316 317 CollectionAdminCountExceeded,318 319 CollectionLimitBoundsExceeded,320 321 OwnerPermissionsCantBeReverted,322323 324 TransferNotAllowed,325 326 AccountTokenLimitExceeded,327 328 CollectionTokenLimitExceeded,329 330 MetadataFlagFrozen,331332 333 TokenNotFound,334 335 TokenValueTooLow,336 337 ApprovedValueTooLow,338 339 CantApproveMoreThanOwned,340341 342 AddressIsZero,343 344 UnsupportedOperation,345346 347 NotSufficientFounds,348349 350 NestingIsDisabled,351 352 OnlyOwnerAllowedToNest,353 354 SourceCollectionIsNotAllowedToNest,355356 357 CollectionFieldSizeExceeded,358 }359360 #[pallet::storage]361 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;362 #[pallet::storage]363 pub type DestroyedCollectionCount<T> =364 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;365366 367 #[pallet::storage]368 pub type CollectionById<T> = StorageMap<369 Hasher = Blake2_128Concat,370 Key = CollectionId,371 Value = Collection<<T as frame_system::Config>::AccountId>,372 QueryKind = OptionQuery,373 >;374375 376 #[pallet::storage]377 pub type CollectionData<T> = StorageNMap<378 Key = (379 Key<Twox64Concat, CollectionId>,380 Key<Twox64Concat, CollectionField>,381 ),382 Value = BoundedVec<u8, ConstU32<COLLECTION_FIELD_LIMIT>>,383 QueryKind = ValueQuery,384 >;385386 #[pallet::storage]387 pub type AdminAmount<T> = StorageMap<388 Hasher = Blake2_128Concat,389 Key = CollectionId,390 Value = u32,391 QueryKind = ValueQuery,392 >;393394 395 #[pallet::storage]396 pub type IsAdmin<T: Config> = StorageNMap<397 Key = (398 Key<Blake2_128Concat, CollectionId>,399 Key<Blake2_128Concat, T::CrossAccountId>,400 ),401 Value = bool,402 QueryKind = ValueQuery,403 >;404405 406 #[pallet::storage]407 pub type Allowlist<T: Config> = StorageNMap<408 Key = (409 Key<Blake2_128Concat, CollectionId>,410 Key<Blake2_128Concat, T::CrossAccountId>,411 ),412 Value = bool,413 QueryKind = ValueQuery,414 >;415416 417 #[pallet::storage]418 pub type DummyStorageValue<T: Config> = StorageValue<419 Value = (420 CollectionStats,421 CollectionId,422 TokenId,423 PhantomType<RpcCollection<T::AccountId>>,424 ),425 QueryKind = OptionQuery,426 >;427428 #[pallet::hooks]429 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {430 fn on_runtime_upgrade() -> Weight {431 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {432 use up_data_structs::{CollectionVersion1, CollectionVersion2};433 <CollectionById<T>>::translate::<CollectionVersion1<T::AccountId>, _>(|id, v| {434 Self::set_field_raw(435 id,436 CollectionField::OffchainSchema,437 v.offchain_schema.clone().into_inner(),438 )439 .expect("data has lower bounds than field");440 Self::set_field_raw(441 id,442 CollectionField::VariableOnChainSchema,443 v.variable_on_chain_schema.clone().into_inner(),444 )445 .expect("data has lower bounds than field");446 Self::set_field_raw(447 id,448 CollectionField::ConstOnChainSchema,449 v.const_on_chain_schema.clone().into_inner(),450 )451 .expect("data has lower bounds than field");452453 Some(CollectionVersion2::from(v))454 });455 }456457 0458 }459 }460}461462impl<T: Config> Pallet<T> {463 464 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {465 ensure!(466 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,467 <Error<T>>::AddressIsZero468 );469 Ok(())470 }471 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {472 <IsAdmin<T>>::iter_prefix((collection,))473 .map(|(a, _)| a)474 .collect()475 }476 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {477 <Allowlist<T>>::iter_prefix((collection,))478 .map(|(a, _)| a)479 .collect()480 }481 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {482 <Allowlist<T>>::get((collection, user))483 }484 pub fn collection_stats() -> CollectionStats {485 let created = <CreatedCollectionCount<T>>::get();486 let destroyed = <DestroyedCollectionCount<T>>::get();487 CollectionStats {488 created: created.0,489 destroyed: destroyed.0,490 alive: created.0 - destroyed.0,491 }492 }493494 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {495 let collection = <CollectionById<T>>::get(collection);496 if collection.is_none() {497 return None;498 }499500 let collection = collection.unwrap();501 let limits = collection.limits;502 let effective_limits = CollectionLimits {503 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),504 sponsored_data_size: Some(limits.sponsored_data_size()),505 sponsored_data_rate_limit: Some(506 limits507 .sponsored_data_rate_limit508 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),509 ),510 token_limit: Some(limits.token_limit()),511 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(512 match collection.mode {513 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,514 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,515 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,516 },517 )),518 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),519 owner_can_transfer: Some(limits.owner_can_transfer()),520 owner_can_destroy: Some(limits.owner_can_destroy()),521 transfers_enabled: Some(limits.transfers_enabled()),522 nesting_rule: Some(limits.nesting_rule().clone()),523 };524525 Some(effective_limits)526 }527528 pub fn rpc_collection(collection: CollectionId) -> Option<RpcCollection<T::AccountId>> {529 let Collection {530 name,531 description,532 owner,533 mode,534 access,535 token_prefix,536 mint_mode,537 schema_version,538 sponsorship,539 limits,540 meta_update_permission,541 } = <CollectionById<T>>::get(collection)?;542 Some(RpcCollection {543 name: name.into_inner(),544 description: description.into_inner(),545 owner,546 mode,547 access,548 token_prefix: token_prefix.into_inner(),549 mint_mode,550 schema_version,551 sponsorship,552 limits,553 meta_update_permission,554 offchain_schema: <CollectionData<T>>::get((555 collection,556 CollectionField::OffchainSchema,557 ))558 .into_inner(),559 const_on_chain_schema: <CollectionData<T>>::get((560 collection,561 CollectionField::ConstOnChainSchema,562 ))563 .into_inner(),564 variable_on_chain_schema: <CollectionData<T>>::get((565 collection,566 CollectionField::VariableOnChainSchema,567 ))568 .into_inner(),569 })570 }571}572573impl<T: Config> Pallet<T> {574 pub fn init_collection(575 owner: T::AccountId,576 data: CreateCollectionData<T::AccountId>,577 ) -> Result<CollectionId, DispatchError> {578 {579 ensure!(580 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,581 Error::<T>::CollectionTokenPrefixLimitExceeded582 );583 }584585 let created_count = <CreatedCollectionCount<T>>::get()586 .0587 .checked_add(1)588 .ok_or(ArithmeticError::Overflow)?;589 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;590 let id = CollectionId(created_count);591592 593 ensure!(594 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,595 <Error<T>>::TotalCollectionsLimitExceeded596 );597598 599600 let collection = Collection {601 owner: owner.clone(),602 name: data.name,603 mode: data.mode.clone(),604 mint_mode: false,605 access: data.access.unwrap_or_default(),606 description: data.description,607 token_prefix: data.token_prefix,608 schema_version: data.schema_version.unwrap_or_default(),609 sponsorship: data610 .pending_sponsor611 .map(SponsorshipState::Unconfirmed)612 .unwrap_or_default(),613 limits: data614 .limits615 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))616 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,617 meta_update_permission: data.meta_update_permission.unwrap_or_default(),618 };619620 621 {622 let mut imbalance =623 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();624 imbalance.subsume(625 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(626 &T::TreasuryAccountId::get(),627 T::CollectionCreationPrice::get(),628 ),629 );630 <T as Config>::Currency::settle(631 &owner,632 imbalance,633 WithdrawReasons::TRANSFER,634 ExistenceRequirement::KeepAlive,635 )636 .map_err(|_| Error::<T>::NotSufficientFounds)?;637 }638639 <CreatedCollectionCount<T>>::put(created_count);640 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));641 <CollectionById<T>>::insert(id, collection);642 Self::set_field_raw(643 id,644 CollectionField::OffchainSchema,645 data.offchain_schema.into_inner(),646 )647 .expect("data has lower bounds than field");648 Self::set_field_raw(649 id,650 CollectionField::VariableOnChainSchema,651 data.variable_on_chain_schema.into_inner(),652 )653 .expect("data has lower bounds than field");654 Self::set_field_raw(655 id,656 CollectionField::ConstOnChainSchema,657 data.const_on_chain_schema.into_inner(),658 )659 .expect("data has lower bounds than field");660 Ok(id)661 }662663 pub fn destroy_collection(664 collection: CollectionHandle<T>,665 sender: &T::CrossAccountId,666 ) -> DispatchResult {667 ensure!(668 collection.limits.owner_can_destroy(),669 <Error<T>>::NoPermission,670 );671 collection.check_is_owner(sender)?;672673 let destroyed_collections = <DestroyedCollectionCount<T>>::get()674 .0675 .checked_add(1)676 .ok_or(ArithmeticError::Overflow)?;677678 679680 <DestroyedCollectionCount<T>>::put(destroyed_collections);681 <CollectionById<T>>::remove(collection.id);682 <CollectionData<T>>::remove_prefix((collection.id,), None);683 <AdminAmount<T>>::remove(collection.id);684 <IsAdmin<T>>::remove_prefix((collection.id,), None);685 <Allowlist<T>>::remove_prefix((collection.id,), None);686687 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));688 Ok(())689 }690691 fn set_field_raw(692 collection_id: CollectionId,693 field: CollectionField,694 value: Vec<u8>,695 ) -> DispatchResult {696 if !value.is_empty() {697 <CollectionData<T>>::insert(698 (collection_id, field),699 BoundedVec::try_from(value).map_err(|_| <Error<T>>::CollectionFieldSizeExceeded)?,700 )701 } else {702 <CollectionData<T>>::remove((collection_id, field));703 }704 Ok(())705 }706707 pub fn set_field(708 collection: &CollectionHandle<T>,709 sender: &T::CrossAccountId,710 field: CollectionField,711 value: Vec<u8>,712 ) -> DispatchResult {713 collection.check_is_owner_or_admin(sender)?;714715 716717 Self::set_field_raw(collection.id, field, value)718 }719720 pub fn toggle_allowlist(721 collection: &CollectionHandle<T>,722 sender: &T::CrossAccountId,723 user: &T::CrossAccountId,724 allowed: bool,725 ) -> DispatchResult {726 collection.check_is_owner_or_admin(sender)?;727728 729730 if allowed {731 <Allowlist<T>>::insert((collection.id, user), true);732 } else {733 <Allowlist<T>>::remove((collection.id, user));734 }735736 Ok(())737 }738739 pub fn toggle_admin(740 collection: &CollectionHandle<T>,741 sender: &T::CrossAccountId,742 user: &T::CrossAccountId,743 admin: bool,744 ) -> DispatchResult {745 collection.check_is_owner_or_admin(sender)?;746747 let was_admin = <IsAdmin<T>>::get((collection.id, user));748 if was_admin == admin {749 return Ok(());750 }751 let amount = <AdminAmount<T>>::get(collection.id);752753 if admin {754 let amount = amount755 .checked_add(1)756 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;757 ensure!(758 amount <= Self::collection_admins_limit(),759 <Error<T>>::CollectionAdminCountExceeded,760 );761762 763764 <AdminAmount<T>>::insert(collection.id, amount);765 <IsAdmin<T>>::insert((collection.id, user), true);766 } else {767 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));768 <IsAdmin<T>>::remove((collection.id, user));769 }770771 Ok(())772 }773774 pub fn clamp_limits(775 mode: CollectionMode,776 old_limit: &CollectionLimits,777 mut new_limit: CollectionLimits,778 ) -> Result<CollectionLimits, DispatchError> {779 macro_rules! limit_default {780 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{781 $(782 if let Some($new) = $new.$field {783 let $old = $old.$field($($arg)?);784 let _ = $new;785 let _ = $old;786 $check787 } else {788 $new.$field = $old.$field789 }790 )*791 }};792 }793794 limit_default!(old_limit, new_limit,795 account_token_ownership_limit => ensure!(796 new_limit <= MAX_TOKEN_OWNERSHIP,797 <Error<T>>::CollectionLimitBoundsExceeded,798 ),799 sponsor_transfer_timeout(match mode {800 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,801 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,802 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,803 }) => ensure!(804 new_limit <= MAX_SPONSOR_TIMEOUT,805 <Error<T>>::CollectionLimitBoundsExceeded,806 ),807 sponsored_data_size => ensure!(808 new_limit <= CUSTOM_DATA_LIMIT,809 <Error<T>>::CollectionLimitBoundsExceeded,810 ),811 token_limit => ensure!(812 old_limit >= new_limit && new_limit > 0,813 <Error<T>>::CollectionTokenLimitExceeded814 ),815 owner_can_transfer => ensure!(816 old_limit || !new_limit,817 <Error<T>>::OwnerPermissionsCantBeReverted,818 ),819 owner_can_destroy => ensure!(820 old_limit || !new_limit,821 <Error<T>>::OwnerPermissionsCantBeReverted,822 ),823 sponsored_data_rate_limit => {},824 transfers_enabled => {},825 );826 Ok(new_limit)827 }828}829830#[macro_export]831macro_rules! unsupported {832 () => {833 Err(<Error<T>>::UnsupportedOperation.into())834 };835}836837838pub trait CommonWeightInfo<CrossAccountId> {839 fn create_item() -> Weight;840 fn create_multiple_items(amount: u32) -> Weight;841 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;842 fn burn_item() -> Weight;843 fn transfer() -> Weight;844 fn approve() -> Weight;845 fn transfer_from() -> Weight;846 fn burn_from() -> Weight;847 fn set_variable_metadata(bytes: u32) -> Weight;848}849850pub trait CommonCollectionOperations<T: Config> {851 fn create_item(852 &self,853 sender: T::CrossAccountId,854 to: T::CrossAccountId,855 data: CreateItemData,856 nesting_budget: &dyn Budget,857 ) -> DispatchResultWithPostInfo;858 fn create_multiple_items(859 &self,860 sender: T::CrossAccountId,861 to: T::CrossAccountId,862 data: Vec<CreateItemData>,863 nesting_budget: &dyn Budget,864 ) -> DispatchResultWithPostInfo;865 fn create_multiple_items_ex(866 &self,867 sender: T::CrossAccountId,868 data: CreateItemExData<T::CrossAccountId>,869 nesting_budget: &dyn Budget,870 ) -> DispatchResultWithPostInfo;871 fn burn_item(872 &self,873 sender: T::CrossAccountId,874 token: TokenId,875 amount: u128,876 ) -> DispatchResultWithPostInfo;877878 fn transfer(879 &self,880 sender: T::CrossAccountId,881 to: T::CrossAccountId,882 token: TokenId,883 amount: u128,884 nesting_budget: &dyn Budget,885 ) -> DispatchResultWithPostInfo;886 fn approve(887 &self,888 sender: T::CrossAccountId,889 spender: T::CrossAccountId,890 token: TokenId,891 amount: u128,892 ) -> DispatchResultWithPostInfo;893 fn transfer_from(894 &self,895 sender: T::CrossAccountId,896 from: T::CrossAccountId,897 to: T::CrossAccountId,898 token: TokenId,899 amount: u128,900 nesting_budget: &dyn Budget,901 ) -> DispatchResultWithPostInfo;902 fn burn_from(903 &self,904 sender: T::CrossAccountId,905 from: T::CrossAccountId,906 token: TokenId,907 amount: u128,908 nesting_budget: &dyn Budget,909 ) -> DispatchResultWithPostInfo;910911 fn set_variable_metadata(912 &self,913 sender: T::CrossAccountId,914 token: TokenId,915 data: BoundedVec<u8, CustomDataLimit>,916 ) -> DispatchResultWithPostInfo;917918 fn check_nesting(919 &self,920 sender: T::CrossAccountId,921 from: CollectionId,922 under: TokenId,923 budget: &dyn Budget,924 ) -> DispatchResult;925926 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;927 fn collection_tokens(&self) -> Vec<TokenId>;928 fn token_exists(&self, token: TokenId) -> bool;929 fn last_token_id(&self) -> TokenId;930931 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;932 fn const_metadata(&self, token: TokenId) -> Vec<u8>;933 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;934935 936 fn account_balance(&self, account: T::CrossAccountId) -> u32;937 938 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;939 fn allowance(940 &self,941 sender: T::CrossAccountId,942 spender: T::CrossAccountId,943 token: TokenId,944 ) -> u128;945}946947948pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {949 let post_info = PostDispatchInfo {950 actual_weight: Some(weight),951 pays_fee: Pays::Yes,952 };953 match res {954 Ok(()) => Ok(post_info),955 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),956 }957}