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, CollectionId, CreateItemData, MAX_TOKEN_PREFIX_LENGTH,33 COLLECTION_ADMINS_LIMIT, MetaUpdatePermission, TokenId, CollectionStats, MAX_TOKEN_OWNERSHIP,34 CollectionMode, NFT_SPONSOR_TRANSFER_TIMEOUT, FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,35 REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT, MAX_SPONSOR_TIMEOUT, CUSTOM_DATA_LIMIT, CollectionLimits,36 CustomDataLimit, CreateCollectionData, SponsorshipState, CreateItemExData, SponsoringRateLimit, budget::Budget,37};38pub use pallet::*;39use sp_core::H160;40use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};41#[cfg(feature = "runtime-benchmarks")]42pub mod benchmarking;43pub mod dispatch;44pub mod erc;45pub mod eth;4647#[must_use = "Should call submit_logs or save, otherwise some data will be lost for evm side"]48pub struct CollectionHandle<T: Config> {49 pub id: CollectionId,50 collection: Collection<T::AccountId>,51 pub recorder: SubstrateRecorder<T>,52}53impl<T: Config> WithRecorder<T> for CollectionHandle<T> {54 fn recorder(&self) -> &SubstrateRecorder<T> {55 &self.recorder56 }57 fn into_recorder(self) -> SubstrateRecorder<T> {58 self.recorder59 }60}61impl<T: Config> CollectionHandle<T> {62 pub fn new_with_gas_limit(id: CollectionId, gas_limit: u64) -> Option<Self> {63 <CollectionById<T>>::get(id).map(|collection| Self {64 id,65 collection,66 recorder: SubstrateRecorder::new(eth::collection_id_to_address(id), gas_limit),67 })68 }69 pub fn new(id: CollectionId) -> Option<Self> {70 Self::new_with_gas_limit(id, u64::MAX)71 }72 pub fn try_get(id: CollectionId) -> Result<Self, DispatchError> {73 Ok(Self::new(id).ok_or(<Error<T>>::CollectionNotFound)?)74 }75 pub fn log_mirrored(&self, log: impl evm_coder::ToLog) {76 self.recorder.log_mirrored(log)77 }78 pub fn log_direct(&self, log: impl evm_coder::ToLog) {79 self.recorder.log_direct(log)80 }81 pub fn consume_store_reads(&self, reads: u64) -> evm_coder::execution::Result<()> {82 self.recorder83 .consume_gas(T::GasWeightMapping::weight_to_gas(84 <T as frame_system::Config>::DbWeight::get()85 .read86 .saturating_mul(reads),87 ))88 }89 pub fn consume_store_writes(&self, writes: u64) -> evm_coder::execution::Result<()> {90 self.recorder91 .consume_gas(T::GasWeightMapping::weight_to_gas(92 <T as frame_system::Config>::DbWeight::get()93 .write94 .saturating_mul(writes),95 ))96 }97 pub fn submit_logs(self) {98 self.recorder.submit_logs()99 }100 pub fn save(self) -> DispatchResult {101 self.recorder.submit_logs();102 <CollectionById<T>>::insert(self.id, self.collection);103 Ok(())104 }105}106impl<T: Config> Deref for CollectionHandle<T> {107 type Target = Collection<T::AccountId>;108109 fn deref(&self) -> &Self::Target {110 &self.collection111 }112}113114impl<T: Config> DerefMut for CollectionHandle<T> {115 fn deref_mut(&mut self) -> &mut Self::Target {116 &mut self.collection117 }118}119120impl<T: Config> CollectionHandle<T> {121 pub fn check_is_owner(&self, subject: &T::CrossAccountId) -> DispatchResult {122 ensure!(*subject.as_sub() == self.owner, <Error<T>>::NoPermission);123 Ok(())124 }125 pub fn is_owner_or_admin(&self, subject: &T::CrossAccountId) -> bool {126 *subject.as_sub() == self.owner || <IsAdmin<T>>::get((self.id, subject))127 }128 pub fn check_is_owner_or_admin(&self, subject: &T::CrossAccountId) -> DispatchResult {129 ensure!(self.is_owner_or_admin(subject), <Error<T>>::NoPermission);130 Ok(())131 }132 pub fn ignores_allowance(&self, user: &T::CrossAccountId) -> bool {133 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)134 }135 pub fn ignores_owned_amount(&self, user: &T::CrossAccountId) -> bool {136 self.limits.owner_can_transfer() && self.is_owner_or_admin(user)137 }138 pub fn check_allowlist(&self, user: &T::CrossAccountId) -> DispatchResult {139 ensure!(140 <Allowlist<T>>::get((self.id, user)),141 <Error<T>>::AddressNotInAllowlist142 );143 Ok(())144 }145146 pub fn check_can_update_meta(147 &self,148 subject: &T::CrossAccountId,149 item_owner: &T::CrossAccountId,150 ) -> DispatchResult {151 match self.meta_update_permission {152 MetaUpdatePermission::ItemOwner => {153 ensure!(subject == item_owner, <Error<T>>::NoPermission);154 Ok(())155 }156 MetaUpdatePermission::Admin => self.check_is_owner_or_admin(subject),157 MetaUpdatePermission::None => fail!(<Error<T>>::NoPermission),158 }159 }160}161162#[frame_support::pallet]163pub mod pallet {164 use super::*;165 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key};166 use pallet_evm::account;167 use dispatch::CollectionDispatch;168 use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};169 use frame_system::pallet_prelude::*;170 use frame_support::traits::Currency;171 use up_data_structs::{TokenId, mapping::TokenAddressMapping};172 use scale_info::TypeInfo;173 use up_evm_mapping::CrossAccountId;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,355 }356357 #[pallet::storage]358 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;359 #[pallet::storage]360 pub type DestroyedCollectionCount<T> =361 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;362363 364 #[pallet::storage]365 pub type CollectionById<T> = StorageMap<366 Hasher = Blake2_128Concat,367 Key = CollectionId,368 Value = Collection<<T as frame_system::Config>::AccountId>,369 QueryKind = OptionQuery,370 >;371372 #[pallet::storage]373 pub type AdminAmount<T> = StorageMap<374 Hasher = Blake2_128Concat,375 Key = CollectionId,376 Value = u32,377 QueryKind = ValueQuery,378 >;379380 381 #[pallet::storage]382 pub type IsAdmin<T: Config> = StorageNMap<383 Key = (384 Key<Blake2_128Concat, CollectionId>,385 Key<Blake2_128Concat, T::CrossAccountId>,386 ),387 Value = bool,388 QueryKind = ValueQuery,389 >;390391 392 #[pallet::storage]393 pub type Allowlist<T: Config> = StorageNMap<394 Key = (395 Key<Blake2_128Concat, CollectionId>,396 Key<Blake2_128Concat, T::CrossAccountId>,397 ),398 Value = bool,399 QueryKind = ValueQuery,400 >;401402 403 #[pallet::storage]404 pub type DummyStorageValue<T> =405 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;406407 #[pallet::hooks]408 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {409 fn on_runtime_upgrade() -> Weight {410 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {411 use up_data_structs::{CollectionVersion1, CollectionVersion2};412 <CollectionById<T>>::translate_values::<CollectionVersion1<T::AccountId>, _>(|v| {413 Some(CollectionVersion2::from(v))414 });415 }416417 0418 }419 }420}421422impl<T: Config> Pallet<T> {423 424 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {425 ensure!(426 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,427 <Error<T>>::AddressIsZero428 );429 Ok(())430 }431 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {432 <IsAdmin<T>>::iter_prefix((collection,))433 .map(|(a, _)| a)434 .collect()435 }436 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {437 <Allowlist<T>>::iter_prefix((collection,))438 .map(|(a, _)| a)439 .collect()440 }441 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {442 <Allowlist<T>>::get((collection, user))443 }444 pub fn collection_stats() -> CollectionStats {445 let created = <CreatedCollectionCount<T>>::get();446 let destroyed = <DestroyedCollectionCount<T>>::get();447 CollectionStats {448 created: created.0,449 destroyed: destroyed.0,450 alive: created.0 - destroyed.0,451 }452 }453454 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {455 let collection = <CollectionById<T>>::get(collection);456 if collection.is_none() {457 return None;458 }459460 let collection = collection.unwrap();461 let limits = collection.limits;462 let effective_limits = CollectionLimits {463 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),464 sponsored_data_size: Some(limits.sponsored_data_size()),465 sponsored_data_rate_limit: Some(466 limits467 .sponsored_data_rate_limit468 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),469 ),470 token_limit: Some(limits.token_limit()),471 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(472 match collection.mode {473 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,474 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,475 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,476 },477 )),478 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),479 owner_can_transfer: Some(limits.owner_can_transfer()),480 owner_can_destroy: Some(limits.owner_can_destroy()),481 transfers_enabled: Some(limits.transfers_enabled()),482 };483484 Some(effective_limits)485 }486}487488impl<T: Config> Pallet<T> {489 pub fn init_collection(490 owner: T::AccountId,491 data: CreateCollectionData<T::AccountId>,492 ) -> Result<CollectionId, DispatchError> {493 {494 ensure!(495 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,496 Error::<T>::CollectionTokenPrefixLimitExceeded497 );498 }499500 let created_count = <CreatedCollectionCount<T>>::get()501 .0502 .checked_add(1)503 .ok_or(ArithmeticError::Overflow)?;504 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;505 let id = CollectionId(created_count);506507 508 ensure!(509 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,510 <Error<T>>::TotalCollectionsLimitExceeded511 );512513 514515 let collection = Collection {516 owner: owner.clone(),517 name: data.name,518 mode: data.mode.clone(),519 mint_mode: false,520 access: data.access.unwrap_or_default(),521 description: data.description,522 token_prefix: data.token_prefix,523 offchain_schema: data.offchain_schema,524 schema_version: data.schema_version.unwrap_or_default(),525 sponsorship: data526 .pending_sponsor527 .map(SponsorshipState::Unconfirmed)528 .unwrap_or_default(),529 variable_on_chain_schema: data.variable_on_chain_schema,530 const_on_chain_schema: data.const_on_chain_schema,531 limits: data532 .limits533 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))534 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,535 meta_update_permission: data.meta_update_permission.unwrap_or_default(),536 };537538 539 {540 let mut imbalance =541 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();542 imbalance.subsume(543 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(544 &T::TreasuryAccountId::get(),545 T::CollectionCreationPrice::get(),546 ),547 );548 <T as Config>::Currency::settle(549 &owner,550 imbalance,551 WithdrawReasons::TRANSFER,552 ExistenceRequirement::KeepAlive,553 )554 .map_err(|_| Error::<T>::NotSufficientFounds)?;555 }556557 <CreatedCollectionCount<T>>::put(created_count);558 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));559 <CollectionById<T>>::insert(id, collection);560 Ok(id)561 }562563 pub fn destroy_collection(564 collection: CollectionHandle<T>,565 sender: &T::CrossAccountId,566 ) -> DispatchResult {567 ensure!(568 collection.limits.owner_can_destroy(),569 <Error<T>>::NoPermission,570 );571 collection.check_is_owner(sender)?;572573 let destroyed_collections = <DestroyedCollectionCount<T>>::get()574 .0575 .checked_add(1)576 .ok_or(ArithmeticError::Overflow)?;577578 579580 <DestroyedCollectionCount<T>>::put(destroyed_collections);581 <CollectionById<T>>::remove(collection.id);582 <AdminAmount<T>>::remove(collection.id);583 <IsAdmin<T>>::remove_prefix((collection.id,), None);584 <Allowlist<T>>::remove_prefix((collection.id,), None);585586 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));587 Ok(())588 }589590 pub fn toggle_allowlist(591 collection: &CollectionHandle<T>,592 sender: &T::CrossAccountId,593 user: &T::CrossAccountId,594 allowed: bool,595 ) -> DispatchResult {596 collection.check_is_owner_or_admin(sender)?;597598 599600 if allowed {601 <Allowlist<T>>::insert((collection.id, user), true);602 } else {603 <Allowlist<T>>::remove((collection.id, user));604 }605606 Ok(())607 }608609 pub fn toggle_admin(610 collection: &CollectionHandle<T>,611 sender: &T::CrossAccountId,612 user: &T::CrossAccountId,613 admin: bool,614 ) -> DispatchResult {615 collection.check_is_owner_or_admin(sender)?;616617 let was_admin = <IsAdmin<T>>::get((collection.id, user));618 if was_admin == admin {619 return Ok(());620 }621 let amount = <AdminAmount<T>>::get(collection.id);622623 if admin {624 let amount = amount625 .checked_add(1)626 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;627 ensure!(628 amount <= Self::collection_admins_limit(),629 <Error<T>>::CollectionAdminCountExceeded,630 );631632 633634 <AdminAmount<T>>::insert(collection.id, amount);635 <IsAdmin<T>>::insert((collection.id, user), true);636 } else {637 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));638 <IsAdmin<T>>::remove((collection.id, user));639 }640641 Ok(())642 }643644 pub fn clamp_limits(645 mode: CollectionMode,646 old_limit: &CollectionLimits,647 mut new_limit: CollectionLimits,648 ) -> Result<CollectionLimits, DispatchError> {649 macro_rules! limit_default {650 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{651 $(652 if let Some($new) = $new.$field {653 let $old = $old.$field($($arg)?);654 let _ = $new;655 let _ = $old;656 $check657 } else {658 $new.$field = $old.$field659 }660 )*661 }};662 }663664 limit_default!(old_limit, new_limit,665 account_token_ownership_limit => ensure!(666 new_limit <= MAX_TOKEN_OWNERSHIP,667 <Error<T>>::CollectionLimitBoundsExceeded,668 ),669 sponsor_transfer_timeout(match mode {670 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,671 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,672 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,673 }) => ensure!(674 new_limit <= MAX_SPONSOR_TIMEOUT,675 <Error<T>>::CollectionLimitBoundsExceeded,676 ),677 sponsored_data_size => ensure!(678 new_limit <= CUSTOM_DATA_LIMIT,679 <Error<T>>::CollectionLimitBoundsExceeded,680 ),681 token_limit => ensure!(682 old_limit >= new_limit && new_limit > 0,683 <Error<T>>::CollectionTokenLimitExceeded684 ),685 owner_can_transfer => ensure!(686 old_limit || !new_limit,687 <Error<T>>::OwnerPermissionsCantBeReverted,688 ),689 owner_can_destroy => ensure!(690 old_limit || !new_limit,691 <Error<T>>::OwnerPermissionsCantBeReverted,692 ),693 sponsored_data_rate_limit => {},694 transfers_enabled => {},695 );696 Ok(new_limit)697 }698}699700#[macro_export]701macro_rules! unsupported {702 () => {703 Err(<Error<T>>::UnsupportedOperation.into())704 };705}706707708pub trait CommonWeightInfo<CrossAccountId> {709 fn create_item() -> Weight;710 fn create_multiple_items(amount: u32) -> Weight;711 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;712 fn burn_item() -> Weight;713 fn transfer() -> Weight;714 fn approve() -> Weight;715 fn transfer_from() -> Weight;716 fn burn_from() -> Weight;717 fn set_variable_metadata(bytes: u32) -> Weight;718}719720pub trait CommonCollectionOperations<T: Config> {721 fn create_item(722 &self,723 sender: T::CrossAccountId,724 to: T::CrossAccountId,725 data: CreateItemData,726 nesting_budget: &dyn Budget,727 ) -> DispatchResultWithPostInfo;728 fn create_multiple_items(729 &self,730 sender: T::CrossAccountId,731 to: T::CrossAccountId,732 data: Vec<CreateItemData>,733 nesting_budget: &dyn Budget,734 ) -> DispatchResultWithPostInfo;735 fn create_multiple_items_ex(736 &self,737 sender: T::CrossAccountId,738 data: CreateItemExData<T::CrossAccountId>,739 nesting_budget: &dyn Budget,740 ) -> DispatchResultWithPostInfo;741 fn burn_item(742 &self,743 sender: T::CrossAccountId,744 token: TokenId,745 amount: u128,746 ) -> DispatchResultWithPostInfo;747748 fn transfer(749 &self,750 sender: T::CrossAccountId,751 to: T::CrossAccountId,752 token: TokenId,753 amount: u128,754 nesting_budget: &dyn Budget,755 ) -> DispatchResultWithPostInfo;756 fn approve(757 &self,758 sender: T::CrossAccountId,759 spender: T::CrossAccountId,760 token: TokenId,761 amount: u128,762 ) -> DispatchResultWithPostInfo;763 fn transfer_from(764 &self,765 sender: T::CrossAccountId,766 from: T::CrossAccountId,767 to: T::CrossAccountId,768 token: TokenId,769 amount: u128,770 nesting_budget: &dyn Budget,771 ) -> DispatchResultWithPostInfo;772 fn burn_from(773 &self,774 sender: T::CrossAccountId,775 from: T::CrossAccountId,776 token: TokenId,777 amount: u128,778 nesting_budget: &dyn Budget,779 ) -> DispatchResultWithPostInfo;780781 fn set_variable_metadata(782 &self,783 sender: T::CrossAccountId,784 token: TokenId,785 data: BoundedVec<u8, CustomDataLimit>,786 ) -> DispatchResultWithPostInfo;787788 fn check_nesting(789 &self,790 sender: T::CrossAccountId,791 from: CollectionId,792 under: TokenId,793 budget: &dyn Budget,794 ) -> DispatchResult;795796 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;797 fn token_exists(&self, token: TokenId) -> bool;798 fn last_token_id(&self) -> TokenId;799800 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;801 fn const_metadata(&self, token: TokenId) -> Vec<u8>;802 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;803804 805 fn collection_tokens(&self) -> u32;806 807 fn account_balance(&self, account: T::CrossAccountId) -> u32;808 809 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;810 fn allowance(811 &self,812 sender: T::CrossAccountId,813 spender: T::CrossAccountId,814 token: TokenId,815 ) -> u128;816}817818819pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {820 let post_info = PostDispatchInfo {821 actual_weight: Some(weight),822 pays_fee: Pays::Yes,823 };824 match res {825 Ok(()) => Ok(post_info),826 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),827 }828}