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,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,348 }349350 #[pallet::storage]351 pub type CreatedCollectionCount<T> = StorageValue<Value = CollectionId, QueryKind = ValueQuery>;352 #[pallet::storage]353 pub type DestroyedCollectionCount<T> =354 StorageValue<Value = CollectionId, QueryKind = ValueQuery>;355356 357 #[pallet::storage]358 pub type CollectionById<T> = StorageMap<359 Hasher = Blake2_128Concat,360 Key = CollectionId,361 Value = Collection<<T as frame_system::Config>::AccountId>,362 QueryKind = OptionQuery,363 >;364365 #[pallet::storage]366 pub type AdminAmount<T> = StorageMap<367 Hasher = Blake2_128Concat,368 Key = CollectionId,369 Value = u32,370 QueryKind = ValueQuery,371 >;372373 374 #[pallet::storage]375 pub type IsAdmin<T: Config> = StorageNMap<376 Key = (377 Key<Blake2_128Concat, CollectionId>,378 Key<Blake2_128Concat, T::CrossAccountId>,379 ),380 Value = bool,381 QueryKind = ValueQuery,382 >;383384 385 #[pallet::storage]386 pub type Allowlist<T: Config> = StorageNMap<387 Key = (388 Key<Blake2_128Concat, CollectionId>,389 Key<Blake2_128Concat, T::CrossAccountId>,390 ),391 Value = bool,392 QueryKind = ValueQuery,393 >;394395 396 #[pallet::storage]397 pub type DummyStorageValue<T> =398 StorageValue<Value = (CollectionStats, CollectionId, TokenId), QueryKind = OptionQuery>;399400 #[pallet::hooks]401 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {402 fn on_runtime_upgrade() -> Weight {403 let mut weight = 0;404405 if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {406 use up_data_structs::{CollectionVersion1, CollectionVersion2};407 <CollectionById<T>>::translate_values::<CollectionVersion1<T::AccountId>, _>(|v| {408 Some(CollectionVersion2::from(v))409 });410 }411412 weight413 }414 }415}416417impl<T: Config> Pallet<T> {418 419 pub fn ensure_correct_receiver(receiver: &T::CrossAccountId) -> DispatchResult {420 ensure!(421 &T::CrossAccountId::from_eth(H160([0; 20])) != receiver,422 <Error<T>>::AddressIsZero423 );424 Ok(())425 }426 pub fn adminlist(collection: CollectionId) -> Vec<T::CrossAccountId> {427 <IsAdmin<T>>::iter_prefix((collection,))428 .map(|(a, _)| a)429 .collect()430 }431 pub fn allowlist(collection: CollectionId) -> Vec<T::CrossAccountId> {432 <Allowlist<T>>::iter_prefix((collection,))433 .map(|(a, _)| a)434 .collect()435 }436 pub fn allowed(collection: CollectionId, user: T::CrossAccountId) -> bool {437 <Allowlist<T>>::get((collection, user))438 }439 pub fn collection_stats() -> CollectionStats {440 let created = <CreatedCollectionCount<T>>::get();441 let destroyed = <DestroyedCollectionCount<T>>::get();442 CollectionStats {443 created: created.0,444 destroyed: destroyed.0,445 alive: created.0 - destroyed.0,446 }447 }448449 pub fn effective_collection_limits(collection: CollectionId) -> Option<CollectionLimits> {450 let collection = <CollectionById<T>>::get(collection);451 if collection.is_none() {452 return None;453 }454455 let collection = collection.unwrap();456 let limits = collection.limits;457 let effective_limits = CollectionLimits {458 account_token_ownership_limit: Some(limits.account_token_ownership_limit()),459 sponsored_data_size: Some(limits.sponsored_data_size()),460 sponsored_data_rate_limit: Some(461 limits462 .sponsored_data_rate_limit463 .unwrap_or(SponsoringRateLimit::SponsoringDisabled),464 ),465 token_limit: Some(limits.token_limit()),466 sponsor_transfer_timeout: Some(limits.sponsor_transfer_timeout(467 match collection.mode {468 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,469 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,470 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,471 },472 )),473 sponsor_approve_timeout: Some(limits.sponsor_approve_timeout()),474 owner_can_transfer: Some(limits.owner_can_transfer()),475 owner_can_destroy: Some(limits.owner_can_destroy()),476 transfers_enabled: Some(limits.transfers_enabled()),477 };478479 Some(effective_limits)480 }481}482483impl<T: Config> Pallet<T> {484 pub fn init_collection(485 owner: T::AccountId,486 data: CreateCollectionData<T::AccountId>,487 ) -> Result<CollectionId, DispatchError> {488 {489 ensure!(490 data.token_prefix.len() <= MAX_TOKEN_PREFIX_LENGTH as usize,491 Error::<T>::CollectionTokenPrefixLimitExceeded492 );493 }494495 let created_count = <CreatedCollectionCount<T>>::get()496 .0497 .checked_add(1)498 .ok_or(ArithmeticError::Overflow)?;499 let destroyed_count = <DestroyedCollectionCount<T>>::get().0;500 let id = CollectionId(created_count);501502 503 ensure!(504 created_count - destroyed_count <= COLLECTION_NUMBER_LIMIT,505 <Error<T>>::TotalCollectionsLimitExceeded506 );507508 509510 let collection = Collection {511 owner: owner.clone(),512 name: data.name,513 mode: data.mode.clone(),514 mint_mode: false,515 access: data.access.unwrap_or_default(),516 description: data.description,517 token_prefix: data.token_prefix,518 offchain_schema: data.offchain_schema,519 schema_version: data.schema_version.unwrap_or_default(),520 sponsorship: data521 .pending_sponsor522 .map(SponsorshipState::Unconfirmed)523 .unwrap_or_default(),524 variable_on_chain_schema: data.variable_on_chain_schema,525 const_on_chain_schema: data.const_on_chain_schema,526 limits: data527 .limits528 .map(|limits| Self::clamp_limits(data.mode.clone(), &Default::default(), limits))529 .unwrap_or_else(|| Ok(CollectionLimits::default()))?,530 meta_update_permission: data.meta_update_permission.unwrap_or_default(),531 };532533 534 {535 let mut imbalance =536 <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();537 imbalance.subsume(538 <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(539 &T::TreasuryAccountId::get(),540 T::CollectionCreationPrice::get(),541 ),542 );543 <T as Config>::Currency::settle(544 &owner,545 imbalance,546 WithdrawReasons::TRANSFER,547 ExistenceRequirement::KeepAlive,548 )549 .map_err(|_| Error::<T>::NotSufficientFounds)?;550 }551552 <CreatedCollectionCount<T>>::put(created_count);553 <Pallet<T>>::deposit_event(Event::CollectionCreated(id, data.mode.id(), owner.clone()));554 <CollectionById<T>>::insert(id, collection);555 Ok(id)556 }557558 pub fn destroy_collection(559 collection: CollectionHandle<T>,560 sender: &T::CrossAccountId,561 ) -> DispatchResult {562 ensure!(563 collection.limits.owner_can_destroy(),564 <Error<T>>::NoPermission,565 );566 collection.check_is_owner(sender)?;567568 let destroyed_collections = <DestroyedCollectionCount<T>>::get()569 .0570 .checked_add(1)571 .ok_or(ArithmeticError::Overflow)?;572573 574575 <DestroyedCollectionCount<T>>::put(destroyed_collections);576 <CollectionById<T>>::remove(collection.id);577 <AdminAmount<T>>::remove(collection.id);578 <IsAdmin<T>>::remove_prefix((collection.id,), None);579 <Allowlist<T>>::remove_prefix((collection.id,), None);580581 <Pallet<T>>::deposit_event(Event::CollectionDestroyed(collection.id));582 Ok(())583 }584585 pub fn toggle_allowlist(586 collection: &CollectionHandle<T>,587 sender: &T::CrossAccountId,588 user: &T::CrossAccountId,589 allowed: bool,590 ) -> DispatchResult {591 collection.check_is_owner_or_admin(sender)?;592593 594595 if allowed {596 <Allowlist<T>>::insert((collection.id, user), true);597 } else {598 <Allowlist<T>>::remove((collection.id, user));599 }600601 Ok(())602 }603604 pub fn toggle_admin(605 collection: &CollectionHandle<T>,606 sender: &T::CrossAccountId,607 user: &T::CrossAccountId,608 admin: bool,609 ) -> DispatchResult {610 collection.check_is_owner_or_admin(sender)?;611612 let was_admin = <IsAdmin<T>>::get((collection.id, user));613 if was_admin == admin {614 return Ok(());615 }616 let amount = <AdminAmount<T>>::get(collection.id);617618 if admin {619 let amount = amount620 .checked_add(1)621 .ok_or(<Error<T>>::CollectionAdminCountExceeded)?;622 ensure!(623 amount <= Self::collection_admins_limit(),624 <Error<T>>::CollectionAdminCountExceeded,625 );626627 628629 <AdminAmount<T>>::insert(collection.id, amount);630 <IsAdmin<T>>::insert((collection.id, user), true);631 } else {632 <AdminAmount<T>>::insert(collection.id, amount.saturating_sub(1));633 <IsAdmin<T>>::remove((collection.id, user));634 }635636 Ok(())637 }638639 pub fn clamp_limits(640 mode: CollectionMode,641 old_limit: &CollectionLimits,642 mut new_limit: CollectionLimits,643 ) -> Result<CollectionLimits, DispatchError> {644 macro_rules! limit_default {645 ($old:ident, $new:ident, $($field:ident $(($arg:expr))? => $check:expr),* $(,)?) => {{646 $(647 if let Some($new) = $new.$field {648 let $old = $old.$field($($arg)?);649 let _ = $new;650 let _ = $old;651 $check652 } else {653 $new.$field = $old.$field654 }655 )*656 }};657 }658659 limit_default!(old_limit, new_limit,660 account_token_ownership_limit => ensure!(661 new_limit <= MAX_TOKEN_OWNERSHIP,662 <Error<T>>::CollectionLimitBoundsExceeded,663 ),664 sponsor_transfer_timeout(match mode {665 CollectionMode::NFT => NFT_SPONSOR_TRANSFER_TIMEOUT,666 CollectionMode::Fungible(_) => FUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,667 CollectionMode::ReFungible => REFUNGIBLE_SPONSOR_TRANSFER_TIMEOUT,668 }) => ensure!(669 new_limit <= MAX_SPONSOR_TIMEOUT,670 <Error<T>>::CollectionLimitBoundsExceeded,671 ),672 sponsored_data_size => ensure!(673 new_limit <= CUSTOM_DATA_LIMIT,674 <Error<T>>::CollectionLimitBoundsExceeded,675 ),676 token_limit => ensure!(677 old_limit >= new_limit && new_limit > 0,678 <Error<T>>::CollectionTokenLimitExceeded679 ),680 owner_can_transfer => ensure!(681 old_limit || !new_limit,682 <Error<T>>::OwnerPermissionsCantBeReverted,683 ),684 owner_can_destroy => ensure!(685 old_limit || !new_limit,686 <Error<T>>::OwnerPermissionsCantBeReverted,687 ),688 sponsored_data_rate_limit => {},689 transfers_enabled => {},690 );691 Ok(new_limit)692 }693}694695#[macro_export]696macro_rules! unsupported {697 () => {698 Err(<Error<T>>::UnsupportedOperation.into())699 };700}701702703pub trait CommonWeightInfo<CrossAccountId> {704 fn create_item() -> Weight;705 fn create_multiple_items(amount: u32) -> Weight;706 fn create_multiple_items_ex(cost: &CreateItemExData<CrossAccountId>) -> Weight;707 fn burn_item() -> Weight;708 fn transfer() -> Weight;709 fn approve() -> Weight;710 fn transfer_from() -> Weight;711 fn burn_from() -> Weight;712 fn set_variable_metadata(bytes: u32) -> Weight;713}714715pub trait CommonCollectionOperations<T: Config> {716 fn create_item(717 &self,718 sender: T::CrossAccountId,719 to: T::CrossAccountId,720 data: CreateItemData,721 ) -> DispatchResultWithPostInfo;722 fn create_multiple_items(723 &self,724 sender: T::CrossAccountId,725 to: T::CrossAccountId,726 data: Vec<CreateItemData>,727 ) -> DispatchResultWithPostInfo;728 fn create_multiple_items_ex(729 &self,730 sender: T::CrossAccountId,731 data: CreateItemExData<T::CrossAccountId>,732 ) -> DispatchResultWithPostInfo;733 fn burn_item(734 &self,735 sender: T::CrossAccountId,736 token: TokenId,737 amount: u128,738 ) -> DispatchResultWithPostInfo;739740 fn transfer(741 &self,742 sender: T::CrossAccountId,743 to: T::CrossAccountId,744 token: TokenId,745 amount: u128,746 ) -> DispatchResultWithPostInfo;747 fn approve(748 &self,749 sender: T::CrossAccountId,750 spender: T::CrossAccountId,751 token: TokenId,752 amount: u128,753 ) -> DispatchResultWithPostInfo;754 fn transfer_from(755 &self,756 sender: T::CrossAccountId,757 from: T::CrossAccountId,758 to: T::CrossAccountId,759 token: TokenId,760 amount: u128,761 ) -> DispatchResultWithPostInfo;762 fn burn_from(763 &self,764 sender: T::CrossAccountId,765 from: T::CrossAccountId,766 token: TokenId,767 amount: u128,768 ) -> DispatchResultWithPostInfo;769770 fn set_variable_metadata(771 &self,772 sender: T::CrossAccountId,773 token: TokenId,774 data: BoundedVec<u8, CustomDataLimit>,775 ) -> DispatchResultWithPostInfo;776777 fn account_tokens(&self, account: T::CrossAccountId) -> Vec<TokenId>;778 fn token_exists(&self, token: TokenId) -> bool;779 fn last_token_id(&self) -> TokenId;780781 fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;782 fn const_metadata(&self, token: TokenId) -> Vec<u8>;783 fn variable_metadata(&self, token: TokenId) -> Vec<u8>;784785 786 fn collection_tokens(&self) -> u32;787 788 fn account_balance(&self, account: T::CrossAccountId) -> u32;789 790 fn balance(&self, account: T::CrossAccountId, token: TokenId) -> u128;791 fn allowance(792 &self,793 sender: T::CrossAccountId,794 spender: T::CrossAccountId,795 token: TokenId,796 ) -> u128;797}798799800pub fn with_weight(res: DispatchResult, weight: Weight) -> DispatchResultWithPostInfo {801 let post_info = PostDispatchInfo {802 actual_weight: Some(weight),803 pays_fee: Pays::Yes,804 };805 match res {806 Ok(()) => Ok(post_info),807 Err(error) => Err(DispatchErrorWithPostInfo { post_info, error }),808 }809}