Skip to content

Commit

Permalink
pallet-asset-conversion: Swap Credit (#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: 
- #105

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

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

---------

Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
  • Loading branch information
muharem and joepetrowski authored Dec 19, 2023
1 parent 84d6342 commit 5ce0451
Show file tree
Hide file tree
Showing 20 changed files with 3,935 additions and 650 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,17 @@
// limitations under the License.

use crate::*;
use frame_support::BoundedVec;
use parachains_common::rococo::currency::EXISTENTIAL_DEPOSIT;
use rococo_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub;
use sp_runtime::ModuleError;

#[test]
fn swap_locally_on_chain_using_local_assets() {
let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocation::get());
let asset_one = Box::new(MultiLocation {
let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocation::get();
let asset_one = MultiLocation {
parents: 0,
interior: X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())),
});
};

AssetHubRococo::execute_with(|| {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
Expand All @@ -47,8 +46,8 @@ fn swap_locally_on_chain_using_local_assets() {

assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::create_pool(
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
asset_native.clone(),
asset_one.clone(),
Box::new(asset_native),
Box::new(asset_one),
));

assert_expected_events!(
Expand All @@ -60,8 +59,8 @@ fn swap_locally_on_chain_using_local_assets() {

assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::add_liquidity(
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
asset_native.clone(),
asset_one.clone(),
Box::new(asset_native),
Box::new(asset_one),
1_000_000_000_000,
2_000_000_000_000,
0,
Expand All @@ -76,7 +75,7 @@ fn swap_locally_on_chain_using_local_assets() {
]
);

let path = BoundedVec::<_, _>::truncate_from(vec![asset_native.clone(), asset_one.clone()]);
let path = vec![Box::new(asset_native), Box::new(asset_one)];

assert_ok!(
<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::swap_exact_tokens_for_tokens(
Expand All @@ -101,8 +100,8 @@ fn swap_locally_on_chain_using_local_assets() {

assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::remove_liquidity(
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
asset_native,
asset_one,
Box::new(asset_native),
Box::new(asset_one),
1414213562273 - EXISTENTIAL_DEPOSIT * 2, // all but the 2 EDs can't be retrieved.
0,
0,
Expand All @@ -113,7 +112,7 @@ fn swap_locally_on_chain_using_local_assets() {

#[test]
fn swap_locally_on_chain_using_foreign_assets() {
let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocation::get());
let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocation::get();
let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubRococo::para_id());
let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get();
let asset_id_on_penpal = match asset_location_on_penpal.last() {
Expand Down Expand Up @@ -165,12 +164,11 @@ fn swap_locally_on_chain_using_foreign_assets() {
]
);

let foreign_asset_at_asset_hub_rococo = Box::new(foreign_asset_at_asset_hub_rococo);
// 4. Create pool:
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::create_pool(
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
asset_native.clone(),
foreign_asset_at_asset_hub_rococo.clone(),
Box::new(asset_native),
Box::new(foreign_asset_at_asset_hub_rococo),
));

assert_expected_events!(
Expand All @@ -183,8 +181,8 @@ fn swap_locally_on_chain_using_foreign_assets() {
// 5. Add liquidity:
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::add_liquidity(
<AssetHubRococo as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()),
asset_native.clone(),
foreign_asset_at_asset_hub_rococo.clone(),
Box::new(asset_native),
Box::new(foreign_asset_at_asset_hub_rococo),
1_000_000_000_000,
2_000_000_000_000,
0,
Expand All @@ -202,10 +200,7 @@ fn swap_locally_on_chain_using_foreign_assets() {
);

// 6. Swap!
let path = BoundedVec::<_, _>::truncate_from(vec![
asset_native.clone(),
foreign_asset_at_asset_hub_rococo.clone(),
]);
let path = vec![Box::new(asset_native), Box::new(foreign_asset_at_asset_hub_rococo)];

assert_ok!(
<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::swap_exact_tokens_for_tokens(
Expand All @@ -231,8 +226,8 @@ fn swap_locally_on_chain_using_foreign_assets() {
// 7. Remove liquidity
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::remove_liquidity(
<AssetHubRococo as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()),
asset_native,
foreign_asset_at_asset_hub_rococo,
Box::new(asset_native),
Box::new(foreign_asset_at_asset_hub_rococo),
1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved.
0,
0,
Expand All @@ -243,7 +238,7 @@ fn swap_locally_on_chain_using_foreign_assets() {

#[test]
fn cannot_create_pool_from_pool_assets() {
let asset_native = Box::new(asset_hub_rococo_runtime::xcm_config::TokenLocation::get());
let asset_native = asset_hub_rococo_runtime::xcm_config::TokenLocation::get();
let mut asset_one = asset_hub_rococo_runtime::xcm_config::PoolAssetsPalletLocation::get();
asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets");

Expand All @@ -268,7 +263,7 @@ fn cannot_create_pool_from_pool_assets() {
assert_matches::assert_matches!(
<AssetHubRococo as AssetHubRococoPallet>::AssetConversion::create_pool(
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get()),
asset_native.clone(),
Box::new(asset_native),
Box::new(asset_one),
),
Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("UnsupportedAsset"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ use westend_system_emulated_network::penpal_emulated_chain::LocalTeleportableToA

#[test]
fn swap_locally_on_chain_using_local_assets() {
let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocation::get());
let asset_one = Box::new(MultiLocation {
let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocation::get();
let asset_one = MultiLocation {
parents: 0,
interior: X2(PalletInstance(ASSETS_PALLET_ID), GeneralIndex(ASSET_ID.into())),
});
};

AssetHubWestend::execute_with(|| {
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
Expand All @@ -44,8 +44,8 @@ fn swap_locally_on_chain_using_local_assets() {

assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::create_pool(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
asset_native.clone(),
asset_one.clone(),
Box::new(asset_native),
Box::new(asset_one),
));

assert_expected_events!(
Expand All @@ -57,8 +57,8 @@ fn swap_locally_on_chain_using_local_assets() {

assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::add_liquidity(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
asset_native.clone(),
asset_one.clone(),
Box::new(asset_native),
Box::new(asset_one),
1_000_000_000_000,
2_000_000_000_000,
0,
Expand All @@ -73,7 +73,7 @@ fn swap_locally_on_chain_using_local_assets() {
]
);

let path = BoundedVec::<_, _>::truncate_from(vec![asset_native.clone(), asset_one.clone()]);
let path = vec![Box::new(asset_native), Box::new(asset_one)];

assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::swap_exact_tokens_for_tokens(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
Expand All @@ -96,8 +96,8 @@ fn swap_locally_on_chain_using_local_assets() {

assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::remove_liquidity(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
asset_native,
asset_one,
Box::new(asset_native),
Box::new(asset_one),
1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved.
0,
0,
Expand All @@ -108,7 +108,7 @@ fn swap_locally_on_chain_using_local_assets() {

#[test]
fn swap_locally_on_chain_using_foreign_assets() {
let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocation::get());
let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocation::get();
let ah_as_seen_by_penpal = PenpalB::sibling_location_of(AssetHubWestend::para_id());
let asset_location_on_penpal = PenpalLocalTeleportableToAssetHub::get();
let asset_id_on_penpal = match asset_location_on_penpal.last() {
Expand Down Expand Up @@ -160,12 +160,11 @@ fn swap_locally_on_chain_using_foreign_assets() {
]
);

let foreign_asset_at_asset_hub_westend = Box::new(foreign_asset_at_asset_hub_westend);
// 4. Create pool:
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::create_pool(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
asset_native.clone(),
foreign_asset_at_asset_hub_westend.clone(),
Box::new(asset_native),
Box::new(foreign_asset_at_asset_hub_westend),
));

assert_expected_events!(
Expand All @@ -178,8 +177,8 @@ fn swap_locally_on_chain_using_foreign_assets() {
// 5. Add liquidity:
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::add_liquidity(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahw.clone()),
asset_native.clone(),
foreign_asset_at_asset_hub_westend.clone(),
Box::new(asset_native),
Box::new(foreign_asset_at_asset_hub_westend),
1_000_000_000_000,
2_000_000_000_000,
0,
Expand All @@ -197,10 +196,7 @@ fn swap_locally_on_chain_using_foreign_assets() {
);

// 6. Swap!
let path = BoundedVec::<_, _>::truncate_from(vec![
asset_native.clone(),
foreign_asset_at_asset_hub_westend.clone(),
]);
let path = vec![Box::new(asset_native), Box::new(foreign_asset_at_asset_hub_westend)];

assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::swap_exact_tokens_for_tokens(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
Expand All @@ -224,19 +220,19 @@ fn swap_locally_on_chain_using_foreign_assets() {
// 7. Remove liquidity
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::remove_liquidity(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahw.clone()),
asset_native,
foreign_asset_at_asset_hub_westend,
Box::new(asset_native),
Box::new(foreign_asset_at_asset_hub_westend),
1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved.
0,
0,
sov_penpal_on_ahw.clone().into(),
sov_penpal_on_ahw.into(),
));
});
}

#[test]
fn cannot_create_pool_from_pool_assets() {
let asset_native = Box::new(asset_hub_westend_runtime::xcm_config::WestendLocation::get());
let asset_native = asset_hub_westend_runtime::xcm_config::WestendLocation::get();
let mut asset_one = asset_hub_westend_runtime::xcm_config::PoolAssetsPalletLocation::get();
asset_one.append_with(GeneralIndex(ASSET_ID.into())).expect("pool assets");

Expand All @@ -261,7 +257,7 @@ fn cannot_create_pool_from_pool_assets() {
assert_matches::assert_matches!(
<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::create_pool(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get()),
asset_native.clone(),
Box::new(asset_native),
Box::new(asset_one),
),
Err(DispatchError::Module(ModuleError{index: _, error: _, message})) => assert_eq!(message, Some("UnsupportedAsset"))
Expand Down
Loading

0 comments on commit 5ce0451

Please sign in to comment.