mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 07:01:05 +00:00
Pay tx fee with assets by using the asset conversion pallet (#14340)
* Pay tx by swapping the assets * Change liquidity structure * Uncomment the event * Update frame/transaction-payment/asset-tx-payment/src/payment.rs Co-authored-by: Squirrel <gilescope@gmail.com> * New approach * Fix bounds * Clearer version * Change IsType with Into and From * Enable event * Check ED + fix the logic * Add temp comments * Rework the refund * Clean up * Improve readability * Getting closer * fix * Use fungible instead of Currency * Test account without ed * Final push * Fixed * Rename to pallet-asset-conversion-tx-payment * Bring back the old pallet * Update versions * Update docs * Update readme * Wrong readme updated * Revert back doc change * Fix import * Fix kitchensink * Fix * One more time.. * Wait pls * Update frame/asset-conversion/src/lib.rs Co-authored-by: Squirrel <gilescope@gmail.com> * Update frame/support/src/traits/tokens/fungibles/regular.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update docs/comments * Docs improvement * Update frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Payed -> paid * Docs * Update frame/transaction-payment/asset-conversion-tx-payment/README.md Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> * Rewrite docs * Try to clean the deps * Add debug assert * Return back frame-benchmarking * Update cargo * Update frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Rename * clearer error message * Docs for Pay by Swap (#14445) * docs * better error name * more comments * more docs on swap trait * Fix compile errors * Another fix * Refactoring * Update frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Emit an error if we fail to swap the refund back * Add integrity_test * Update frame/asset-conversion/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Fmt * Use defensive_ok_or * child PR: Tidy swap event (#14441) * Dedup raising swap event * use expect rather than unwrap * Additional checks for future defence. * cargo fmt * Update frame/asset-conversion/src/lib.rs Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> --------- Co-authored-by: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> --------- Co-authored-by: Squirrel <gilescope@gmail.com> Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
@@ -53,7 +53,7 @@
|
||||
//! (This can be run against the kitchen sync node in the `node` folder of this repo.)
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
use frame_support::traits::Incrementable;
|
||||
use frame_support::traits::{DefensiveOption, Incrementable};
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
@@ -307,7 +307,10 @@ pub mod pallet {
|
||||
WrongDesiredAmount,
|
||||
/// Provided amount should be greater than or equal to the existential deposit/asset's
|
||||
/// minimal amount.
|
||||
AmountLessThanMinimal,
|
||||
AmountOneLessThanMinimal,
|
||||
/// Provided amount should be greater than or equal to the existential deposit/asset's
|
||||
/// minimal amount.
|
||||
AmountTwoLessThanMinimal,
|
||||
/// Reserve needs to always be greater than or equal to the existential deposit/asset's
|
||||
/// minimal amount.
|
||||
ReserveLeftLessThanMinimal,
|
||||
@@ -347,6 +350,20 @@ pub mod pallet {
|
||||
PathError,
|
||||
/// The provided path must consists of unique assets.
|
||||
NonUniquePath,
|
||||
/// Unable to find an element in an array/vec that should have one-to-one correspondence
|
||||
/// with another. For example, an array of assets constituting a `path` should have a
|
||||
/// corresponding array of `amounts` along the path.
|
||||
CorrespondenceError,
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
|
||||
fn integrity_test() {
|
||||
assert!(
|
||||
T::MaxSwapPathLength::get() > 1,
|
||||
"the `MaxSwapPathLength` should be greater than 1",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pallet's callable functions.
|
||||
@@ -488,9 +505,9 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
Self::validate_minimal_amount(amount1.saturating_add(reserve1), &asset1)
|
||||
.map_err(|_| Error::<T>::AmountLessThanMinimal)?;
|
||||
.map_err(|_| Error::<T>::AmountOneLessThanMinimal)?;
|
||||
Self::validate_minimal_amount(amount2.saturating_add(reserve2), &asset2)
|
||||
.map_err(|_| Error::<T>::AmountLessThanMinimal)?;
|
||||
.map_err(|_| Error::<T>::AmountTwoLessThanMinimal)?;
|
||||
|
||||
Self::transfer(&asset1, &sender, &pool_account, amount1, true)?;
|
||||
Self::transfer(&asset2, &sender, &pool_account, amount2, true)?;
|
||||
@@ -635,16 +652,7 @@ pub mod pallet {
|
||||
let amount_out = *amounts.last().expect("Has always more than 1 element");
|
||||
ensure!(amount_out >= amount_out_min, Error::<T>::ProvidedMinimumNotSufficientForSwap);
|
||||
|
||||
Self::do_swap(&sender, &amounts, &path, &send_to, keep_alive)?;
|
||||
|
||||
Self::deposit_event(Event::SwapExecuted {
|
||||
who: sender,
|
||||
send_to,
|
||||
path,
|
||||
amount_in,
|
||||
amount_out,
|
||||
});
|
||||
|
||||
Self::do_swap(sender, &amounts, path, send_to, keep_alive)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -676,21 +684,13 @@ pub mod pallet {
|
||||
let amount_in = *amounts.first().expect("Always has more than one element");
|
||||
ensure!(amount_in <= amount_in_max, Error::<T>::ProvidedMaximumNotSufficientForSwap);
|
||||
|
||||
Self::do_swap(&sender, &amounts, &path, &send_to, keep_alive)?;
|
||||
|
||||
Self::deposit_event(Event::SwapExecuted {
|
||||
who: sender,
|
||||
send_to,
|
||||
path,
|
||||
amount_in,
|
||||
amount_out,
|
||||
});
|
||||
|
||||
Self::do_swap(sender, &amounts, path, send_to, keep_alive)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Transfer an `amount` of `asset_id`, respecting the `keep_alive` requirements.
|
||||
fn transfer(
|
||||
asset_id: &T::MultiAssetId,
|
||||
from: &T::AccountId,
|
||||
@@ -709,8 +709,13 @@ pub mod pallet {
|
||||
true => Preserve,
|
||||
false => Expendable,
|
||||
};
|
||||
let amount = Self::asset_to_native(amount)?;
|
||||
Ok(Self::native_to_asset(T::Currency::transfer(from, to, amount, preservation)?)?)
|
||||
let amount = Self::convert_asset_balance_to_native_balance(amount)?;
|
||||
Ok(Self::convert_native_balance_to_asset_balance(T::Currency::transfer(
|
||||
from,
|
||||
to,
|
||||
amount,
|
||||
preservation,
|
||||
)?)?)
|
||||
} else {
|
||||
T::Assets::transfer(
|
||||
T::MultiAssetIdConverter::try_convert(&asset_id)
|
||||
@@ -723,31 +728,40 @@ pub mod pallet {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn native_to_asset(amount: T::Balance) -> Result<T::AssetBalance, Error<T>> {
|
||||
/// Convert a `Balance` type to an `AssetBalance`.
|
||||
pub(crate) fn convert_native_balance_to_asset_balance(
|
||||
amount: T::Balance,
|
||||
) -> Result<T::AssetBalance, Error<T>> {
|
||||
T::HigherPrecisionBalance::from(amount)
|
||||
.try_into()
|
||||
.map_err(|_| Error::<T>::Overflow)
|
||||
}
|
||||
|
||||
pub(crate) fn asset_to_native(amount: T::AssetBalance) -> Result<T::Balance, Error<T>> {
|
||||
/// Convert an `AssetBalance` type to a `Balance`.
|
||||
pub(crate) fn convert_asset_balance_to_native_balance(
|
||||
amount: T::AssetBalance,
|
||||
) -> Result<T::Balance, Error<T>> {
|
||||
T::HigherPrecisionBalance::from(amount)
|
||||
.try_into()
|
||||
.map_err(|_| Error::<T>::Overflow)
|
||||
}
|
||||
|
||||
/// Swap assets along a `path`, depositing in `send_to`.
|
||||
pub(crate) fn do_swap(
|
||||
sender: &T::AccountId,
|
||||
sender: T::AccountId,
|
||||
amounts: &Vec<T::AssetBalance>,
|
||||
path: &BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
|
||||
send_to: &T::AccountId,
|
||||
path: BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
|
||||
send_to: T::AccountId,
|
||||
keep_alive: bool,
|
||||
) -> Result<(), DispatchError> {
|
||||
if let Some([asset1, asset2]) = path.get(0..2) {
|
||||
ensure!(amounts.len() > 1, Error::<T>::CorrespondenceError);
|
||||
if let Some([asset1, asset2]) = &path.get(0..2) {
|
||||
let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone());
|
||||
let pool_account = Self::get_pool_account(&pool_id);
|
||||
let first_amount = amounts.first().expect("Always has more than one element");
|
||||
// amounts should always contain a corresponding element to path.
|
||||
let first_amount = amounts.first().ok_or(Error::<T>::CorrespondenceError)?;
|
||||
|
||||
Self::transfer(asset1, sender, &pool_account, *first_amount, keep_alive)?;
|
||||
Self::transfer(asset1, &sender, &pool_account, *first_amount, keep_alive)?;
|
||||
|
||||
let mut i = 0;
|
||||
let path_len = path.len() as u32;
|
||||
@@ -757,7 +771,7 @@ pub mod pallet {
|
||||
let pool_account = Self::get_pool_account(&pool_id);
|
||||
|
||||
let amount_out =
|
||||
amounts.get((i + 1) as usize).ok_or(Error::<T>::PathError)?;
|
||||
amounts.get((i + 1) as usize).ok_or(Error::<T>::CorrespondenceError)?;
|
||||
|
||||
let to = if i < path_len - 2 {
|
||||
let asset3 = path.get((i + 2) as usize).ok_or(Error::<T>::PathError)?;
|
||||
@@ -778,6 +792,15 @@ pub mod pallet {
|
||||
}
|
||||
i.saturating_inc();
|
||||
}
|
||||
Self::deposit_event(Event::SwapExecuted {
|
||||
who: sender,
|
||||
send_to,
|
||||
path,
|
||||
amount_in: *first_amount,
|
||||
amount_out: *amounts.last().expect("Always has more than 1 element"),
|
||||
});
|
||||
} else {
|
||||
return Err(Error::<T>::InvalidPath.into())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -793,14 +816,16 @@ pub mod pallet {
|
||||
.expect("infinite length input; no invalid inputs for type; qed")
|
||||
}
|
||||
|
||||
/// Get the `owner`'s balance of `asset`, which could be the chain's native asset or another
|
||||
/// fungible. Returns a value in the form of an `AssetBalance`.
|
||||
fn get_balance(
|
||||
owner: &T::AccountId,
|
||||
asset: &T::MultiAssetId,
|
||||
) -> Result<T::AssetBalance, Error<T>> {
|
||||
if T::MultiAssetIdConverter::is_native(asset) {
|
||||
Self::native_to_asset(<<T as Config>::Currency>::reducible_balance(
|
||||
owner, Expendable, Polite,
|
||||
))
|
||||
Self::convert_native_balance_to_asset_balance(
|
||||
<<T as Config>::Currency>::reducible_balance(owner, Expendable, Polite),
|
||||
)
|
||||
} else {
|
||||
Ok(<<T as Config>::Assets>::reducible_balance(
|
||||
T::MultiAssetIdConverter::try_convert(asset)
|
||||
@@ -841,6 +866,7 @@ pub mod pallet {
|
||||
Ok((balance1, balance2))
|
||||
}
|
||||
|
||||
/// Leading to an amount at the end of a `path`, get the required amounts in.
|
||||
pub(crate) fn get_amounts_in(
|
||||
amount_out: &T::AssetBalance,
|
||||
path: &BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
|
||||
@@ -860,6 +886,7 @@ pub mod pallet {
|
||||
Ok(amounts)
|
||||
}
|
||||
|
||||
/// Following an amount into a `path`, get the corresponding amounts out.
|
||||
pub(crate) fn get_amounts_out(
|
||||
amount_in: &T::AssetBalance,
|
||||
path: &BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
|
||||
@@ -969,10 +996,10 @@ pub mod pallet {
|
||||
result.try_into().map_err(|_| Error::<T>::Overflow)
|
||||
}
|
||||
|
||||
/// Calculates amount out
|
||||
/// Calculates amount out.
|
||||
///
|
||||
/// Given an input amount of an asset and pair reserves, returns the maximum output amount
|
||||
/// of the other asset
|
||||
/// of the other asset.
|
||||
pub fn get_amount_out(
|
||||
amount_in: &T::AssetBalance,
|
||||
reserve_in: &T::AssetBalance,
|
||||
@@ -1004,10 +1031,10 @@ pub mod pallet {
|
||||
result.try_into().map_err(|_| Error::<T>::Overflow)
|
||||
}
|
||||
|
||||
/// Calculates amount in
|
||||
/// Calculates amount in.
|
||||
///
|
||||
/// Given an output amount of an asset and pair reserves, returns a required input amount
|
||||
/// of the other asset
|
||||
/// of the other asset.
|
||||
pub fn get_amount_in(
|
||||
amount_out: &T::AssetBalance,
|
||||
reserve_in: &T::AssetBalance,
|
||||
@@ -1046,6 +1073,7 @@ pub mod pallet {
|
||||
result.try_into().map_err(|_| Error::<T>::Overflow)
|
||||
}
|
||||
|
||||
/// Ensure that a `value` meets the minimum balance requirements of an `asset` class.
|
||||
fn validate_minimal_amount(
|
||||
value: T::AssetBalance,
|
||||
asset: &T::MultiAssetId,
|
||||
@@ -1064,6 +1092,7 @@ pub mod pallet {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure that a path is valid.
|
||||
fn validate_swap_path(
|
||||
path: &BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
|
||||
) -> Result<(), DispatchError> {
|
||||
@@ -1092,7 +1121,7 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
impl<T: Config>
|
||||
frame_support::traits::tokens::fungibles::SwapForNative<
|
||||
frame_support::traits::tokens::fungibles::SwapNative<
|
||||
T::RuntimeOrigin,
|
||||
T::AccountId,
|
||||
T::Balance,
|
||||
@@ -1103,7 +1132,11 @@ where
|
||||
<T as pallet::Config>::Currency:
|
||||
frame_support::traits::tokens::fungible::Inspect<<T as frame_system::Config>::AccountId>,
|
||||
{
|
||||
// If successful returns the amount in.
|
||||
/// Take an `asset_id` and swap some amount for `amount_out` of the chain's native asset. If an
|
||||
/// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be
|
||||
/// too costly.
|
||||
///
|
||||
/// If successful returns the amount of the `asset_id` taken to provide `amount_out`.
|
||||
fn swap_tokens_for_exact_native(
|
||||
sender: T::AccountId,
|
||||
asset_id: T::AssetId,
|
||||
@@ -1121,26 +1154,59 @@ where
|
||||
path.push(T::MultiAssetIdConverter::get_native());
|
||||
let path = path.try_into().unwrap();
|
||||
|
||||
let amount_out = Self::native_to_asset(amount_out)?;
|
||||
// convert `amount_out` from native balance type, to asset balance type
|
||||
let amount_out = Self::convert_native_balance_to_asset_balance(amount_out)?;
|
||||
|
||||
// calculate the amount we need to provide
|
||||
let amounts = Self::get_amounts_in(&amount_out, &path)?;
|
||||
let amount_in = *amounts.first().expect("Always has more than one element");
|
||||
let amount_in =
|
||||
*amounts.first().defensive_ok_or("get_amounts_in() returned an empty result")?;
|
||||
if let Some(amount_in_max) = amount_in_max {
|
||||
ensure!(amount_in <= amount_in_max, Error::<T>::ProvidedMaximumNotSufficientForSwap);
|
||||
}
|
||||
|
||||
Self::do_swap(&sender, &amounts, &path, &send_to, keep_alive)?;
|
||||
|
||||
Self::deposit_event(Event::SwapExecuted {
|
||||
who: sender,
|
||||
send_to,
|
||||
path,
|
||||
amount_in,
|
||||
amount_out,
|
||||
});
|
||||
|
||||
Self::do_swap(sender, &amounts, path, send_to, keep_alive)?;
|
||||
Ok(amount_in)
|
||||
}
|
||||
|
||||
/// Take an `asset_id` and swap `amount_in` of the chain's native asset for it. If an
|
||||
/// `amount_out_min` is specified, it will return an error if it is unable to acquire the amount
|
||||
/// desired.
|
||||
///
|
||||
/// If successful, returns the amount of `asset_id` acquired for the `amount_in`.
|
||||
fn swap_exact_native_for_tokens(
|
||||
sender: T::AccountId,
|
||||
asset_id: T::AssetId,
|
||||
amount_in: T::Balance,
|
||||
amount_out_min: Option<T::AssetBalance>,
|
||||
send_to: T::AccountId,
|
||||
keep_alive: bool,
|
||||
) -> Result<T::AssetBalance, DispatchError> {
|
||||
ensure!(amount_in > Zero::zero(), Error::<T>::ZeroAmount);
|
||||
if let Some(amount_out_min) = amount_out_min {
|
||||
ensure!(amount_out_min > Zero::zero(), Error::<T>::ZeroAmount);
|
||||
}
|
||||
let mut path = sp_std::vec::Vec::new();
|
||||
path.push(T::MultiAssetIdConverter::get_native());
|
||||
path.push(T::MultiAssetIdConverter::into_multiasset_id(&asset_id));
|
||||
let path = path.try_into().expect(
|
||||
"`MaxSwapPathLength` is ensured by to be greater than 2; pushed only twice; qed",
|
||||
);
|
||||
|
||||
// convert `amount_in` from native balance type, to asset balance type
|
||||
let amount_in = Self::convert_native_balance_to_asset_balance(amount_in)?;
|
||||
|
||||
// calculate the amount we should receive
|
||||
let amounts = Self::get_amounts_out(&amount_in, &path)?;
|
||||
let amount_out =
|
||||
*amounts.last().defensive_ok_or("get_amounts_out() returned an empty result")?;
|
||||
if let Some(amount_out_min) = amount_out_min {
|
||||
ensure!(amount_out >= amount_out_min, Error::<T>::ProvidedMaximumNotSufficientForSwap);
|
||||
}
|
||||
|
||||
Self::do_swap(sender, &amounts, path, send_to, keep_alive)?;
|
||||
Ok(amount_out)
|
||||
}
|
||||
}
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
@@ -1167,3 +1233,5 @@ sp_api::decl_runtime_apis! {
|
||||
fn get_reserves(asset1: AssetId, asset2: AssetId) -> Option<(Balance, Balance)>;
|
||||
}
|
||||
}
|
||||
|
||||
sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);
|
||||
|
||||
@@ -356,7 +356,7 @@ fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() {
|
||||
1,
|
||||
user
|
||||
),
|
||||
Error::<Test>::AmountLessThanMinimal
|
||||
Error::<Test>::AmountOneLessThanMinimal
|
||||
);
|
||||
|
||||
assert_noop!(
|
||||
|
||||
Reference in New Issue
Block a user