Skip to content

Commit

Permalink
pallet-asset-conversion: Swap Credit (paritytech#1677)
Browse files Browse the repository at this point in the history
Introduces a swap implementation that allows the exchange of a credit
(aka Negative Imbalance) of one asset for a credit of another asset.

This is particularly useful when a credit swap is required but may not
have sufficient value to meet the ED constraint, hence cannot be
deposited to temp account before. An example use case is when XCM fees
are paid using an asset held in the XCM executor registry and has to be
swapped for native currency.

Additional Updates:
- encapsulates the existing `Swap` trait impl within a transactional
context, since partial storage mutation is possible when an error
occurs;
- supplied `Currency` and `Assets` impls must be implemented over the
same `Balance` type, the `AssetBalance` generic type is dropped. This
helps to avoid numerous type conversion and overflow cases. If those
types are different it should be handled outside of the pallet;
- `Box` asset kind on a pallet level, unbox on a runtime level - here
[why](https://substrate.stackexchange.com/questions/10039/boxed-argument-of-a-dispatchable/10103#10103);
- `path` uses `Vec` now, instead of `BoundedVec` since it is never used
in PoV;
- removes the `Transfer` event due to it's redundancy with the events
emitted by `fungible/s` implementations;
- modifies the `SwapExecuted` event type;

related issue: 
- paritytech#105

related PRs:
- (required for) paritytech#1845
- (caused) paritytech#1717

// DONE make the pallet work only with `fungibles` trait and make it
free from the concept of a `native` asset -
paritytech#1842

---------

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
  • Loading branch information
muharem and joepetrowski authored Dec 19, 2023
1 parent 4f49519 commit 9842601
Show file tree
Hide file tree
Showing 10 changed files with 1,706 additions and 546 deletions.
6 changes: 2 additions & 4 deletions substrate/bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1653,7 +1653,6 @@ parameter_types! {
impl pallet_asset_conversion::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type AssetBalance = <Self as pallet_balances::Config>::Balance;
type HigherPrecisionBalance = sp_core::U256;
type Assets = Assets;
type Balance = u128;
Expand Down Expand Up @@ -2580,15 +2579,14 @@ impl_runtime_apis! {
impl pallet_asset_conversion::AssetConversionApi<
Block,
Balance,
u128,
NativeOrAssetId<u32>
> for Runtime
{
fn quote_price_exact_tokens_for_tokens(asset1: NativeOrAssetId<u32>, asset2: NativeOrAssetId<u32>, amount: u128, include_fee: bool) -> Option<Balance> {
fn quote_price_exact_tokens_for_tokens(asset1: NativeOrAssetId<u32>, asset2: NativeOrAssetId<u32>, amount: Balance, include_fee: bool) -> Option<Balance> {
AssetConversion::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee)
}

fn quote_price_tokens_for_exact_tokens(asset1: NativeOrAssetId<u32>, asset2: NativeOrAssetId<u32>, amount: u128, include_fee: bool) -> Option<Balance> {
fn quote_price_tokens_for_exact_tokens(asset1: NativeOrAssetId<u32>, asset2: NativeOrAssetId<u32>, amount: Balance, include_fee: bool) -> Option<Balance> {
AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee)
}

Expand Down
117 changes: 77 additions & 40 deletions substrate/frame/asset-conversion/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use super::*;
use frame_benchmarking::{benchmarks, whitelisted_caller};
use frame_support::{
assert_ok,
storage::bounded_vec::BoundedVec,
traits::{
fungible::{Inspect as InspectFungible, Mutate as MutateFungible, Unbalanced},
fungibles::{Create, Inspect, Mutate},
Expand Down Expand Up @@ -49,7 +48,7 @@ where

fn create_asset<T: Config>(asset: &T::MultiAssetId) -> (T::AccountId, AccountIdLookupOf<T>)
where
T::AssetBalance: From<u128>,
T::Balance: From<u128>,
T::Currency: Unbalanced<T::AccountId>,
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
{
Expand All @@ -70,7 +69,7 @@ fn create_asset_and_pool<T: Config>(
asset2: &T::MultiAssetId,
) -> (T::PoolAssetId, T::AccountId, AccountIdLookupOf<T>)
where
T::AssetBalance: From<u128>,
T::Balance: From<u128>,
T::Currency: Unbalanced<T::AccountId>,
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
T::PoolAssetId: Into<u32>,
Expand All @@ -80,8 +79,8 @@ where

assert_ok!(AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
asset1.clone(),
asset2.clone()
Box::new(asset1.clone()),
Box::new(asset2.clone())
));
let lp_token = get_lp_token_id::<T>();

Expand All @@ -99,7 +98,6 @@ fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
benchmarks! {
where_clause {
where
T::AssetBalance: From<u128> + Into<u128>,
T::Currency: Unbalanced<T::AccountId>,
T::Balance: From<u128> + Into<u128>,
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
Expand All @@ -110,7 +108,7 @@ benchmarks! {
let asset1 = T::MultiAssetIdConverter::get_native();
let asset2 = T::BenchmarkHelper::multiasset_id(0);
let (caller, _) = create_asset::<T>(&asset2);
}: _(SystemOrigin::Signed(caller.clone()), asset1.clone(), asset2.clone())
}: _(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone()))
verify {
let lp_token = get_lp_token_id::<T>();
let pool_id = (asset1.clone(), asset2.clone());
Expand All @@ -128,7 +126,7 @@ benchmarks! {
let (lp_token, caller, _) = create_asset_and_pool::<T>(&asset1, &asset2);
let ed: u128 = T::Currency::minimum_balance().into();
let add_amount = 1000 + ed;
}: _(SystemOrigin::Signed(caller.clone()), asset1.clone(), asset2.clone(), add_amount.into(), 1000.into(), 0.into(), 0.into(), caller.clone())
}: _(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone()), add_amount.into(), 1000.into(), 0.into(), 0.into(), caller.clone())
verify {
let pool_id = (asset1.clone(), asset2.clone());
let lp_minted = AssetConversion::<T>::calc_lp_amount_for_zero_supply(&add_amount.into(), &1000.into()).unwrap().into();
Expand Down Expand Up @@ -157,16 +155,16 @@ benchmarks! {

AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
asset1.clone(),
asset2.clone(),
Box::new(asset1.clone()),
Box::new(asset2.clone()),
add_amount.into(),
1000.into(),
0.into(),
0.into(),
caller.clone(),
)?;
let total_supply = <T::PoolAssets as Inspect<T::AccountId>>::total_issuance(lp_token.clone());
}: _(SystemOrigin::Signed(caller.clone()), asset1, asset2, remove_lp_amount.into(), 0.into(), 0.into(), caller.clone())
}: _(SystemOrigin::Signed(caller.clone()), Box::new(asset1), Box::new(asset2), remove_lp_amount.into(), 0.into(), 0.into(), caller.clone())
verify {
let new_total_supply = <T::PoolAssets as Inspect<T::AccountId>>::total_issuance(lp_token.clone());
assert_eq!(
Expand All @@ -185,8 +183,8 @@ benchmarks! {

AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
native.clone(),
asset1.clone(),
Box::new(native.clone()),
Box::new(asset1.clone()),
(100 * ed).into(),
200.into(),
0.into(),
Expand All @@ -199,29 +197,45 @@ benchmarks! {
// if we only allow the native-asset pools, then the worst case scenario would be to swap
// asset1-native-asset2
if !T::AllowMultiAssetPools::get() {
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), native.clone(), asset2.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(native.clone()),
Box::new(asset2.clone())
)?;
AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
native.clone(),
asset2.clone(),
Box::new(native.clone()),
Box::new(asset2.clone()),
(500 * ed).into(),
1000.into(),
0.into(),
0.into(),
caller.clone(),
)?;
path = vec![asset1.clone(), native.clone(), asset2.clone()];
path = vec![
Box::new(asset1.clone()),
Box::new(native.clone()),
Box::new(asset2.clone())
];
swap_amount = 100.into();
} else {
let asset3 = T::BenchmarkHelper::multiasset_id(3);
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset1.clone(), asset2.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(asset1.clone()),
Box::new(asset2.clone())
)?;
let (_, _) = create_asset::<T>(&asset3);
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset2.clone(), asset3.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(asset2.clone()),
Box::new(asset3.clone())
)?;

AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
asset1.clone(),
asset2.clone(),
Box::new(asset1.clone()),
Box::new(asset2.clone()),
200.into(),
2000.into(),
0.into(),
Expand All @@ -230,19 +244,22 @@ benchmarks! {
)?;
AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
asset2.clone(),
asset3.clone(),
Box::new(asset2.clone()),
Box::new(asset3.clone()),
2000.into(),
2000.into(),
0.into(),
0.into(),
caller.clone(),
)?;
path = vec![native.clone(), asset1.clone(), asset2.clone(), asset3.clone()];
path = vec![
Box::new(native.clone()),
Box::new(asset1.clone()),
Box::new(asset2.clone()),
Box::new(asset3.clone())
];
swap_amount = ed.into();
}

let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap();
let native_balance = T::Currency::balance(&caller);
let asset1_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(1), &caller);
}: _(SystemOrigin::Signed(caller.clone()), path, swap_amount, 1.into(), caller.clone(), false)
Expand All @@ -266,8 +283,8 @@ benchmarks! {

AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
native.clone(),
asset1.clone(),
Box::new(native.clone()),
Box::new(asset1.clone()),
(1000 * ed).into(),
500.into(),
0.into(),
Expand All @@ -279,28 +296,44 @@ benchmarks! {
// if we only allow the native-asset pools, then the worst case scenario would be to swap
// asset1-native-asset2
if !T::AllowMultiAssetPools::get() {
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), native.clone(), asset2.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(native.clone()),
Box::new(asset2.clone())
)?;
AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
native.clone(),
asset2.clone(),
Box::new(native.clone()),
Box::new(asset2.clone()),
(500 * ed).into(),
1000.into(),
0.into(),
0.into(),
caller.clone(),
)?;
path = vec![asset1.clone(), native.clone(), asset2.clone()];
path = vec![
Box::new(asset1.clone()),
Box::new(native.clone()),
Box::new(asset2.clone())
];
} else {
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset1.clone(), asset2.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(asset1.clone()),
Box::new(asset2.clone())
)?;
let asset3 = T::BenchmarkHelper::multiasset_id(3);
let (_, _) = create_asset::<T>(&asset3);
AssetConversion::<T>::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset2.clone(), asset3.clone())?;
AssetConversion::<T>::create_pool(
SystemOrigin::Signed(caller.clone()).into(),
Box::new(asset2.clone()),
Box::new(asset3.clone())
)?;

AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
asset1.clone(),
asset2.clone(),
Box::new(asset1.clone()),
Box::new(asset2.clone()),
2000.into(),
2000.into(),
0.into(),
Expand All @@ -309,18 +342,22 @@ benchmarks! {
)?;
AssetConversion::<T>::add_liquidity(
SystemOrigin::Signed(caller.clone()).into(),
asset2.clone(),
asset3.clone(),
Box::new(asset2.clone()),
Box::new(asset3.clone()),
2000.into(),
2000.into(),
0.into(),
0.into(),
caller.clone(),
)?;
path = vec![native.clone(), asset1.clone(), asset2.clone(), asset3.clone()];
path = vec![
Box::new(native.clone()),
Box::new(asset1.clone()),
Box::new(asset2.clone()),
Box::new(asset3.clone())
];
}

let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap();
let asset2_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(2), &caller);
let asset3_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(3), &caller);
}: _(SystemOrigin::Signed(caller.clone()), path.clone(), 100.into(), (1000 * ed).into(), caller.clone(), false)
Expand Down
Loading

0 comments on commit 9842601

Please sign in to comment.