Files
pezkuwi-subxt/pallets/asset-tx-payment/src/payment.rs
T
Alexander Popiak 3520acc3f2 Asset Transaction Payment (#488)
* 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>
2021-10-11 11:57:37 +02:00

169 lines
6.5 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.
///! Traits and default implementation for paying transaction fees in assets.
use super::*;
use crate::Config;
use codec::FullCodec;
use frame_support::{
traits::{
fungibles::{Balanced, CreditOf, Inspect},
tokens::BalanceConversion,
},
unsigned::TransactionValidityError,
};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{
AtLeast32BitUnsigned, DispatchInfoOf, MaybeSerializeDeserialize, One, PostDispatchInfoOf,
},
transaction_validity::InvalidTransaction,
};
use sp_std::{fmt::Debug, marker::PhantomData};
/// Handle withdrawing, refunding and depositing of transaction fees.
pub trait OnChargeAssetTransaction<T: Config> {
/// The underlying integer type in which fees are calculated.
type Balance: AtLeast32BitUnsigned
+ FullCodec
+ Copy
+ MaybeSerializeDeserialize
+ Debug
+ Default
+ TypeInfo;
/// The type used to identify the assets used for transaction payment.
type AssetId: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo;
/// The type used to store the intermediate values between pre- and post-dispatch.
type LiquidityInfo;
/// Before the transaction is executed the payment of the transaction fees needs to be secured.
///
/// Note: The `fee` already includes the `tip`.
fn withdraw_fee(
who: &T::AccountId,
call: &T::Call,
dispatch_info: &DispatchInfoOf<T::Call>,
asset_id: Self::AssetId,
fee: Self::Balance,
tip: Self::Balance,
) -> Result<Self::LiquidityInfo, TransactionValidityError>;
/// After the transaction was executed the actual fee can be calculated.
/// This function should refund any overpaid fees and optionally deposit
/// the corrected amount.
///
/// Note: The `fee` already includes the `tip`.
fn correct_and_deposit_fee(
who: &T::AccountId,
dispatch_info: &DispatchInfoOf<T::Call>,
post_info: &PostDispatchInfoOf<T::Call>,
corrected_fee: Self::Balance,
tip: Self::Balance,
already_withdrawn: Self::LiquidityInfo,
) -> Result<(), TransactionValidityError>;
}
/// Allows specifying what to do with the withdrawn asset fees.
pub trait HandleCredit<AccountId, B: Balanced<AccountId>> {
/// Implement to determine what to do with the withdrawn asset fees.
/// Default for `CreditOf` from the assets pallet is to burn and
/// decrease total issuance.
fn handle_credit(credit: CreditOf<AccountId, B>);
}
/// Default implementation that just drops the credit according to the `OnDrop` in the underlying
/// imbalance type.
impl<A, B: Balanced<A>> HandleCredit<A, B> for () {
fn handle_credit(_credit: CreditOf<A, B>) {}
}
/// Implements the asset transaction for a balance to asset converter (implementing
/// [`BalanceConversion`]) and a credit handler (implementing [`HandleCredit`]).
///
/// The credit handler is given the complete fee in terms of the asset used for the transaction.
pub struct FungiblesAdapter<CON, HC>(PhantomData<(CON, HC)>);
/// Default implementation for a runtime instantiating this pallet, a balance to asset converter and
/// a credit handler.
impl<T, CON, HC> OnChargeAssetTransaction<T> for FungiblesAdapter<CON, HC>
where
T: Config,
CON: BalanceConversion<BalanceOf<T>, AssetIdOf<T>, AssetBalanceOf<T>>,
HC: HandleCredit<T::AccountId, T::Fungibles>,
AssetIdOf<T>: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo,
{
type Balance = BalanceOf<T>;
type AssetId = AssetIdOf<T>;
type LiquidityInfo = CreditOf<T::AccountId, T::Fungibles>;
/// Withdraw the predicted fee from the transaction origin.
///
/// Note: The `fee` already includes the `tip`.
fn withdraw_fee(
who: &T::AccountId,
_call: &T::Call,
_info: &DispatchInfoOf<T::Call>,
asset_id: Self::AssetId,
fee: Self::Balance,
_tip: Self::Balance,
) -> Result<Self::LiquidityInfo, TransactionValidityError> {
// We don't know the precision of the underlying asset. Because the converted fee could be
// less than one (e.g. 0.5) but gets rounded down by integer division we introduce a minimum
// fee.
let min_converted_fee = if fee.is_zero() { Zero::zero() } else { One::one() };
let converted_fee = CON::to_asset_balance(fee, asset_id)
.map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?
.max(min_converted_fee);
let can_withdraw = <T::Fungibles as Inspect<T::AccountId>>::can_withdraw(
asset_id.into(),
who,
converted_fee,
);
if !matches!(can_withdraw, WithdrawConsequence::Success) {
return Err(InvalidTransaction::Payment.into())
}
<T::Fungibles as Balanced<T::AccountId>>::withdraw(asset_id.into(), who, converted_fee)
.map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))
}
/// Hand the fee and the tip over to the `[HandleCredit]` implementation.
/// Since the predicted fee might have been too high, parts of the fee may be refunded.
///
/// Note: The `corrected_fee` already includes the `tip`.
fn correct_and_deposit_fee(
who: &T::AccountId,
_dispatch_info: &DispatchInfoOf<T::Call>,
_post_info: &PostDispatchInfoOf<T::Call>,
corrected_fee: Self::Balance,
_tip: Self::Balance,
paid: Self::LiquidityInfo,
) -> Result<(), TransactionValidityError> {
let min_converted_fee = if corrected_fee.is_zero() { Zero::zero() } else { One::one() };
// Convert the corrected fee into the asset used for payment.
let converted_fee = CON::to_asset_balance(corrected_fee, paid.asset().into())
.map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })?
.max(min_converted_fee);
// Calculate how much refund we should return.
let (final_fee, refund) = paid.split(converted_fee);
// Refund to the account that paid the fees. If this fails, the account might have dropped
// below the existential balance. In that case we don't refund anything.
let _ = <T::Fungibles as Balanced<T::AccountId>>::resolve(who, refund);
// Handle the final fee, e.g. by transferring to the block author or burning.
HC::handle_credit(final_fee);
Ok(())
}
}