Files
pezkuwi-subxt/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs
T
Muharem 4f832ea865 pallet-asset-conversion: Decoupling Native Currency Dependancy (#2031)
closes https://github.com/paritytech/polkadot-sdk/issues/1842

Decoupling Pallet from the Concept of Native Currency

Currently, the pallet is intrinsically linked with the concept of native
currency, requiring users to provide implementations of the
`fungible::*` and `fungibles::*` traits to interact with native and non
native assets. This incapsulates some non-related to the pallet
complexity and makes it less adaptable in contexts where the native
currency concept is absent.

With this PR, the dependence on `fungible::*` for liquidity-supplying
assets has been removed. Instead, the native and non-native currencies'
handling is now overseen by a single type that implements the
`fungibles::*` traits. To simplify this integration, types have been
introduced to facilitate the creation of a union between `fungible::*`
and `fungibles::*` implementations, producing a unified `fungibles::*`
type.

One of the reasons driving these changes is the ambition to create a
more user-friendly API for the `SwapCredit` implementation. Given that
it interacts with two distinct credit types from `fungible` and
`fungibles`, a unified type was introduced. Clients now manage potential
conversion failures for those credit types. In certain contexts, it's
vital to guarantee that operations are fail-safe, like in this impl -
[PR](https://github.com/paritytech/polkadot-sdk/pull/1845), place in
[code](https://github.com/paritytech/polkadot-sdk/blob/20b85a5fada8f55c98ba831964f5866ffeadf4da/cumulus/primitives/utility/src/lib.rs#L429).

Additional Updates:
- abstracted the pool ID and its account derivation logic via trait
bounds, along with common implementation offerings;
- removed `inc_providers` on a pool creation for the pool account;
- benchmarks:
-- swap complexity is N, not const;
-- removed `From<u128> + Into<u128>` bound from `T::Balance`;
-- removed swap/liquidity/.. amount constants, resolve them dynamically
based on pallet configuration;
-- migrated to v2 API;
- `OnUnbalanced` handler for the pool creation fee, replacing direct
transfers to a specified account ID;
- renamed `MultiAssetId` to `AssetKind` aligning with naming across
frame crates;

related PRs:
- (depends) https://github.com/paritytech/polkadot-sdk/pull/1677
- (caused) https://github.com/paritytech/polkadot-sdk/pull/2033
- (caused) https://github.com/paritytech/polkadot-sdk/pull/1876

---------

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
2023-12-20 14:57:26 +02:00

199 lines
6.8 KiB
Rust

// Copyright (C) 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 frame_support::{
ensure,
traits::{fungible::Inspect, tokens::Balance},
unsigned::TransactionValidityError,
};
use pallet_asset_conversion::Swap;
use sp_runtime::{
traits::{DispatchInfoOf, Get, PostDispatchInfoOf, Zero},
transaction_validity::InvalidTransaction,
Saturating,
};
use sp_std::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: Balance;
/// The type used to identify the assets used for transaction payment.
type AssetId: AssetId;
/// The type used to store the intermediate values between pre- and post-dispatch.
type LiquidityInfo;
/// Secure the payment of the transaction fees before the transaction is executed.
///
/// Note: The `fee` already includes the `tip`.
fn withdraw_fee(
who: &T::AccountId,
call: &T::RuntimeCall,
dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
asset_id: Self::AssetId,
fee: Self::Balance,
tip: Self::Balance,
) -> Result<
(LiquidityInfoOf<T>, Self::LiquidityInfo, AssetBalanceOf<T>),
TransactionValidityError,
>;
/// Refund any overpaid fees and deposit the corrected amount.
/// The actual fee gets calculated once the transaction is executed.
///
/// Note: The `fee` already includes the `tip`.
///
/// Returns the fee and tip in the asset used for payment as (fee, tip).
fn correct_and_deposit_fee(
who: &T::AccountId,
dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
post_info: &PostDispatchInfoOf<T::RuntimeCall>,
corrected_fee: Self::Balance,
tip: Self::Balance,
fee_paid: LiquidityInfoOf<T>,
received_exchanged: Self::LiquidityInfo,
asset_id: Self::AssetId,
initial_asset_consumed: AssetBalanceOf<T>,
) -> Result<AssetBalanceOf<T>, TransactionValidityError>;
}
/// Implements the asset transaction for a balance to asset converter (implementing [`Swap`]).
///
/// The converter is given the complete fee in terms of the asset used for the transaction.
pub struct AssetConversionAdapter<C, CON, N>(PhantomData<(C, CON, N)>);
/// Default implementation for a runtime instantiating this pallet, an asset to native swapper.
impl<T, C, CON, N> OnChargeAssetTransaction<T> for AssetConversionAdapter<C, CON, N>
where
N: Get<CON::AssetKind>,
T: Config,
C: Inspect<<T as frame_system::Config>::AccountId>,
CON: Swap<T::AccountId, Balance = BalanceOf<T>, AssetKind = T::AssetKind>,
BalanceOf<T>: Into<AssetBalanceOf<T>>,
T::AssetKind: From<AssetIdOf<T>>,
BalanceOf<T>: IsType<<C as Inspect<<T as frame_system::Config>::AccountId>>::Balance>,
{
type Balance = BalanceOf<T>;
type AssetId = AssetIdOf<T>;
type LiquidityInfo = BalanceOf<T>;
/// Swap & withdraw the predicted fee from the transaction origin.
///
/// Note: The `fee` already includes the `tip`.
///
/// Returns the total amount in native currency received by exchanging the `asset_id` and the
/// amount in native currency used to pay the fee.
fn withdraw_fee(
who: &T::AccountId,
call: &T::RuntimeCall,
info: &DispatchInfoOf<T::RuntimeCall>,
asset_id: Self::AssetId,
fee: BalanceOf<T>,
tip: BalanceOf<T>,
) -> Result<
(LiquidityInfoOf<T>, Self::LiquidityInfo, AssetBalanceOf<T>),
TransactionValidityError,
> {
// convert the asset into native currency
let ed = C::minimum_balance();
let native_asset_required =
if C::balance(&who) >= ed.saturating_add(fee.into()) { fee } else { fee + ed.into() };
let asset_consumed = CON::swap_tokens_for_exact_tokens(
who.clone(),
vec![asset_id.into(), N::get()],
native_asset_required,
None,
who.clone(),
true,
)
.map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?;
ensure!(asset_consumed > Zero::zero(), InvalidTransaction::Payment);
// charge the fee in native currency
<T::OnChargeTransaction>::withdraw_fee(who, call, info, fee, tip)
.map(|r| (r, native_asset_required, asset_consumed.into()))
}
/// Correct the fee and swap the refund back to asset.
///
/// Note: The `corrected_fee` already includes the `tip`.
/// Note: Is the ED wasn't needed, the `received_exchanged` will be equal to `fee_paid`, or
/// `fee_paid + ed` otherwise.
fn correct_and_deposit_fee(
who: &T::AccountId,
dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
post_info: &PostDispatchInfoOf<T::RuntimeCall>,
corrected_fee: BalanceOf<T>,
tip: BalanceOf<T>,
fee_paid: LiquidityInfoOf<T>,
received_exchanged: Self::LiquidityInfo,
asset_id: Self::AssetId,
initial_asset_consumed: AssetBalanceOf<T>,
) -> Result<AssetBalanceOf<T>, TransactionValidityError> {
// Refund the native asset to the account that paid the fees (`who`).
// The `who` account will receive the "fee_paid - corrected_fee" refund.
<T::OnChargeTransaction>::correct_and_deposit_fee(
who,
dispatch_info,
post_info,
corrected_fee,
tip,
fee_paid,
)?;
// calculate the refund in native asset, to swap back to the desired `asset_id`
let swap_back = received_exchanged.saturating_sub(corrected_fee);
let mut asset_refund = Zero::zero();
if !swap_back.is_zero() {
// If this fails, the account might have dropped below the existential balance or there
// is not enough liquidity left in the pool. In that case we don't throw an error and
// the account will keep the native currency.
match CON::swap_exact_tokens_for_tokens(
who.clone(), // we already deposited the native to `who`
vec![
N::get(), // we provide the native
asset_id.into(), // we want asset_id back
],
swap_back, // amount of the native asset to convert to `asset_id`
None, // no minimum amount back
who.clone(), // we will refund to `who`
false, // no need to keep alive
)
.ok()
{
Some(acquired) => {
asset_refund = acquired
.try_into()
.map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?;
},
None => {
Pallet::<T>::deposit_event(Event::<T>::AssetRefundFailed {
native_amount_kept: swap_back,
});
},
}
}
let actual_paid = initial_asset_consumed.saturating_sub(asset_refund);
Ok(actual_paid)
}
}