mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 10:01:17 +00:00
3520acc3f2
* use new pallet name based genesis config names * use custom substrate and update polkadot * add initial asset-tx-payment pallet * update cargo.toml * add (failing) tests * dispatch Calls instead of using Pallet functions * fix fee-refund split * add test for transaction payment with tip * update cargo.lock * update cargo.lock * remove mint workaround and use Mutable trait * extract fee charging logic into OnChargeAssetTransaction trait * use asset-tx-payment in statemint runtime * make extrinsics public * make extrinsics public * use ChargeAssetIdOf type alias * update deps * move back to AssetIdOf * remove extra rpc_http_threads * use different substrate branch * Update pallets/asset-tx-payment/src/payment.rs Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * Update pallets/asset-tx-payment/src/payment.rs Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * remove overrides * override substrate deps (again) * increment spec_version and transaction_version (because we change transaction signing) * remove direct dependency on pallet-balances from asset-tx-payment * remove Assets pallet visibility workaround * add docs and comments * remove unused imports * more docs * add more debug asserts to document assumptions * add test for tx payment from account with only assets * add test for missing asset case * extend test to cover non-sufficient assets * add a test for Pays::No (refunded transaction) * add type alias comments * add more doc comments * add asset-tx-payment to statemine and westmint * improve formatting * update license headers * add default implementation of HandleCredit for () * update doc comments and format imports * adjust Cargo.toml * update cargo.lock * cargo fmt * cargo fmt * cargo fmt * cargo +nightly fmt * add type alias for OnChargeTransaction * cargo +nightly fmt * convert ChargeAssetTxPayment from tuple struct to regular struct * add more comments * formatting * adjust imports and comment * cargo +nightly fmt * reformat comment * use ChargeTransactionPayment's own get_priority + update Substrate * update Substrate and Polkadot * cargo fmt * cargo fmt * add OperationalFeeMultiplier to asset tx payment tests * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * add doc links * charge a minimum converted asset fee of 1 if the input fee is greater zero * cargo +nightly fmt * bump spec and transaction version Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com> Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
296 lines
9.6 KiB
Rust
296 lines
9.6 KiB
Rust
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! # Asset Transaction Payment Pallet
|
|
//!
|
|
//! This pallet allows runtimes that include it to pay for transactions in assets other than the
|
|
//! main token of the chain.
|
|
//!
|
|
//! ## Overview
|
|
//! It does this by extending transactions to include an optional `AssetId` that specifies the asset
|
|
//! to be used for payment (defaulting to the native token on `None`). It expects an
|
|
//! [`OnChargeAssetTransaction`] implementation analogously to [`pallet-transaction-payment`]. The
|
|
//! included [`FungiblesAdapter`] (implementing [`OnChargeAssetTransaction`]) determines the fee
|
|
//! amount by converting the fee calculated by [`pallet-transaction-payment`] into the desired
|
|
//! asset.
|
|
//!
|
|
//! ## Integration
|
|
//! This pallet wraps FRAME's transaction payment pallet and functions as a replacement. This means
|
|
//! you should include both pallets in your `construct_runtime` macro, but only include this
|
|
//! pallet's [`SignedExtension`] ([`ChargeAssetTxPayment`]).
|
|
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
|
use sp_std::prelude::*;
|
|
|
|
use codec::{Decode, Encode};
|
|
use frame_support::{
|
|
dispatch::DispatchResult,
|
|
traits::{
|
|
tokens::{
|
|
fungibles::{Balanced, CreditOf, Inspect},
|
|
WithdrawConsequence,
|
|
},
|
|
IsType,
|
|
},
|
|
weights::{DispatchInfo, PostDispatchInfo},
|
|
DefaultNoBound,
|
|
};
|
|
use pallet_transaction_payment::OnChargeTransaction;
|
|
use scale_info::TypeInfo;
|
|
use sp_runtime::{
|
|
traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension, Zero},
|
|
transaction_validity::{
|
|
InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,
|
|
},
|
|
FixedPointOperand,
|
|
};
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
mod payment;
|
|
pub use payment::*;
|
|
|
|
// Type aliases used for interaction with `OnChargeTransaction`.
|
|
pub(crate) type OnChargeTransactionOf<T> =
|
|
<T as pallet_transaction_payment::Config>::OnChargeTransaction;
|
|
// Balance type alias.
|
|
pub(crate) type BalanceOf<T> = <OnChargeTransactionOf<T> as OnChargeTransaction<T>>::Balance;
|
|
// Liquity info type alias.
|
|
pub(crate) type LiquidityInfoOf<T> =
|
|
<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::LiquidityInfo;
|
|
|
|
// Type alias used for interaction with fungibles (assets).
|
|
// Balance type alias.
|
|
pub(crate) type AssetBalanceOf<T> =
|
|
<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
|
|
/// Asset id type alias.
|
|
pub(crate) type AssetIdOf<T> =
|
|
<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::AssetId;
|
|
|
|
// Type aliases used for interaction with `OnChargeAssetTransaction`.
|
|
// Balance type alias.
|
|
pub(crate) type ChargeAssetBalanceOf<T> =
|
|
<<T as Config>::OnChargeAssetTransaction as OnChargeAssetTransaction<T>>::Balance;
|
|
// Asset id type alias.
|
|
pub(crate) type ChargeAssetIdOf<T> =
|
|
<<T as Config>::OnChargeAssetTransaction as OnChargeAssetTransaction<T>>::AssetId;
|
|
// Liquity info type alias.
|
|
pub(crate) type ChargeAssetLiquidityOf<T> =
|
|
<<T as Config>::OnChargeAssetTransaction as OnChargeAssetTransaction<T>>::LiquidityInfo;
|
|
|
|
/// Used to pass the initial payment info from pre- to post-dispatch.
|
|
#[derive(Encode, Decode, DefaultNoBound, TypeInfo)]
|
|
pub enum InitialPayment<T: Config> {
|
|
/// No initial fee was payed.
|
|
Nothing,
|
|
/// The initial fee was payed in the native currency.
|
|
Native(LiquidityInfoOf<T>),
|
|
/// The initial fee was payed in an asset.
|
|
Asset(CreditOf<T::AccountId, T::Fungibles>),
|
|
}
|
|
|
|
pub use pallet::*;
|
|
|
|
#[frame_support::pallet]
|
|
pub mod pallet {
|
|
use super::*;
|
|
|
|
use frame_support::pallet_prelude::*;
|
|
use frame_system::pallet_prelude::*;
|
|
|
|
#[pallet::config]
|
|
pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
|
|
/// The fungibles instance used to pay for transactions in assets.
|
|
type Fungibles: Balanced<Self::AccountId>;
|
|
/// The actual transaction charging logic that charges the fees.
|
|
type OnChargeAssetTransaction: OnChargeAssetTransaction<Self>;
|
|
}
|
|
|
|
#[pallet::pallet]
|
|
#[pallet::generate_store(pub(super) trait Store)]
|
|
pub struct Pallet<T>(_);
|
|
|
|
#[pallet::hooks]
|
|
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
|
|
|
|
#[pallet::call]
|
|
impl<T: Config> Pallet<T> {}
|
|
}
|
|
|
|
/// Require the transactor pay for themselves and maybe include a tip to gain additional priority
|
|
/// in the queue. Allows paying via both `Currency` as well as `fungibles::Balanced`.
|
|
///
|
|
/// Wraps the transaction logic in [`pallet_transaction_payment`] and extends it with assets.
|
|
/// An asset id of `None` falls back to the underlying transaction payment via the native currency.
|
|
#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
|
|
#[scale_info(skip_type_params(T))]
|
|
pub struct ChargeAssetTxPayment<T: Config> {
|
|
#[codec(compact)]
|
|
tip: BalanceOf<T>,
|
|
asset_id: Option<ChargeAssetIdOf<T>>,
|
|
}
|
|
|
|
impl<T: Config> ChargeAssetTxPayment<T>
|
|
where
|
|
T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
|
AssetBalanceOf<T>: Send + Sync + FixedPointOperand,
|
|
BalanceOf<T>: Send + Sync + FixedPointOperand + IsType<ChargeAssetBalanceOf<T>>,
|
|
ChargeAssetIdOf<T>: Send + Sync,
|
|
CreditOf<T::AccountId, T::Fungibles>: IsType<ChargeAssetLiquidityOf<T>>,
|
|
{
|
|
/// utility constructor. Used only in client/factory code.
|
|
pub fn from(tip: BalanceOf<T>, asset_id: Option<ChargeAssetIdOf<T>>) -> Self {
|
|
Self { tip, asset_id }
|
|
}
|
|
|
|
/// Fee withdrawal logic that dispatches to either `OnChargeAssetTransaction` or `OnChargeTransaction`.
|
|
fn withdraw_fee(
|
|
&self,
|
|
who: &T::AccountId,
|
|
call: &T::Call,
|
|
info: &DispatchInfoOf<T::Call>,
|
|
len: usize,
|
|
) -> Result<(BalanceOf<T>, InitialPayment<T>), TransactionValidityError> {
|
|
let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(len as u32, info, self.tip);
|
|
debug_assert!(self.tip <= fee, "tip should be included in the computed fee");
|
|
if fee.is_zero() {
|
|
Ok((fee, InitialPayment::Nothing))
|
|
} else if let Some(asset_id) = self.asset_id {
|
|
T::OnChargeAssetTransaction::withdraw_fee(
|
|
who,
|
|
call,
|
|
info,
|
|
asset_id,
|
|
fee.into(),
|
|
self.tip.into(),
|
|
)
|
|
.map(|i| (fee, InitialPayment::Asset(i.into())))
|
|
} else {
|
|
<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::withdraw_fee(
|
|
who, call, info, fee, self.tip,
|
|
)
|
|
.map(|i| (fee, InitialPayment::Native(i)))
|
|
.map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Config> sp_std::fmt::Debug for ChargeAssetTxPayment<T> {
|
|
#[cfg(feature = "std")]
|
|
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
|
|
write!(f, "ChargeAssetTxPayment<{:?}, {:?}>", self.tip, self.asset_id.encode())
|
|
}
|
|
#[cfg(not(feature = "std"))]
|
|
fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<T: Config> SignedExtension for ChargeAssetTxPayment<T>
|
|
where
|
|
T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
|
|
AssetBalanceOf<T>: Send + Sync + FixedPointOperand,
|
|
BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand + IsType<ChargeAssetBalanceOf<T>>,
|
|
ChargeAssetIdOf<T>: Send + Sync,
|
|
CreditOf<T::AccountId, T::Fungibles>: IsType<ChargeAssetLiquidityOf<T>>,
|
|
{
|
|
const IDENTIFIER: &'static str = "ChargeAssetTxPayment";
|
|
type AccountId = T::AccountId;
|
|
type Call = T::Call;
|
|
type AdditionalSigned = ();
|
|
type Pre = (
|
|
// tip
|
|
BalanceOf<T>,
|
|
// who paid the fee
|
|
Self::AccountId,
|
|
// imbalance resulting from withdrawing the fee
|
|
InitialPayment<T>,
|
|
);
|
|
|
|
fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn validate(
|
|
&self,
|
|
who: &Self::AccountId,
|
|
call: &Self::Call,
|
|
info: &DispatchInfoOf<Self::Call>,
|
|
len: usize,
|
|
) -> TransactionValidity {
|
|
use pallet_transaction_payment::ChargeTransactionPayment;
|
|
let (fee, _) = self.withdraw_fee(who, call, info, len)?;
|
|
let priority = ChargeTransactionPayment::<T>::get_priority(info, len, self.tip, fee);
|
|
Ok(ValidTransaction { priority, ..Default::default() })
|
|
}
|
|
|
|
fn pre_dispatch(
|
|
self,
|
|
who: &Self::AccountId,
|
|
call: &Self::Call,
|
|
info: &DispatchInfoOf<Self::Call>,
|
|
len: usize,
|
|
) -> Result<Self::Pre, TransactionValidityError> {
|
|
let (_fee, initial_payment) = self.withdraw_fee(who, call, info, len)?;
|
|
Ok((self.tip, who.clone(), initial_payment))
|
|
}
|
|
|
|
fn post_dispatch(
|
|
pre: Self::Pre,
|
|
info: &DispatchInfoOf<Self::Call>,
|
|
post_info: &PostDispatchInfoOf<Self::Call>,
|
|
len: usize,
|
|
_result: &DispatchResult,
|
|
) -> Result<(), TransactionValidityError> {
|
|
let (tip, who, initial_payment) = pre;
|
|
let actual_fee = pallet_transaction_payment::Pallet::<T>::compute_actual_fee(
|
|
len as u32, info, post_info, tip,
|
|
);
|
|
match initial_payment {
|
|
InitialPayment::Native(already_withdrawn) => {
|
|
<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::correct_and_deposit_fee(
|
|
&who,
|
|
info,
|
|
post_info,
|
|
actual_fee,
|
|
tip,
|
|
already_withdrawn,
|
|
)?;
|
|
},
|
|
InitialPayment::Asset(already_withdrawn) => {
|
|
T::OnChargeAssetTransaction::correct_and_deposit_fee(
|
|
&who,
|
|
info,
|
|
post_info,
|
|
actual_fee.into(),
|
|
tip.into(),
|
|
already_withdrawn.into(),
|
|
)?;
|
|
},
|
|
InitialPayment::Nothing => {
|
|
debug_assert!(
|
|
actual_fee.is_zero(),
|
|
"actual fee should be zero if initial fee was zero."
|
|
);
|
|
debug_assert!(tip.is_zero(), "tip should be zero if initial fee was zero.");
|
|
},
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|