Skip to content

Commit

Permalink
pallet-xcm: add new flexible transfer_assets() call/extrinsic (pari…
Browse files Browse the repository at this point in the history
…tytech#2388)

We had just previously added capabilities to teleport fees during
reserve-based transfers, but what about reserve-transferring fees when
needing to teleport some non-fee asset?

This PR aligns everything under either explicit reserve-transfer,
explicit teleport, or this new flexible `transfer_assets()` which can
mix and match as needed with fewer artificial constraints imposed to the
user.

This will enable, for example, a (non-system) parachain to teleport
their `ForeignAssets` assets to AssetHub while using DOT to pay fees.
(the assets are teleported - as foreign assets should from their owner
chain - while DOT used for fees can only be reserve-based transferred
between said parachain and AssetHub).

Added `xcm-emulator` tests for this scenario ^.

Reverts `(limited_)reserve_transfer_assets` to only allow reserve-based
transfers for all `assets` including fees.

Similarly `(limited_)teleport_assets` only allows teleports for all
`assets` including fees.

For complex combinations of asset transfers where assets and fees may
have different reserves or different reserve/teleport trust
configurations, users can use the newly added `transfer_assets()`
extrinsic which is more flexible in allowing more complex scenarios.

`assets` (excluding `fees`) must have same reserve location or otherwise
be teleportable to `dest`.
No limitations imposed on `fees`.

- for local reserve: transfer assets to sovereign account of destination
chain and forward a notification XCM to `dest` to mint and deposit
reserve-based assets to `beneficiary`.
- for destination reserve: burn local assets and forward a notification
to `dest` chain to withdraw the reserve assets from this chain's
sovereign account and deposit them to `beneficiary`.
- for remote reserve: burn local assets, forward XCM to reserve chain to
move reserves from this chain's SA to `dest` chain's SA, and forward
another XCM to `dest` to mint and deposit reserve-based assets to
`beneficiary`.
- for teleports: burn local assets and forward XCM to `dest` chain to
mint/teleport assets and deposit them to `beneficiary`.

Only around 500 lines are prod code (see `pallet_xcm/src/lib.rs`), the
rest of the PR is new tests and improving existing tests.

---------

Co-authored-by: command-bot <>
  • Loading branch information
acatangiu authored and Agusrodri committed Jan 11, 2024
1 parent 997cdcd commit f4d1edb
Show file tree
Hide file tree
Showing 37 changed files with 4,204 additions and 958 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,11 @@ mod send;
mod set_xcm_versions;
mod swap;
mod teleport;

use crate::*;
emulated_integration_tests_common::include_penpal_create_foreign_asset_on_asset_hub!(
PenpalA,
AssetHubRococo,
ROCOCO_ED,
parachains_common::rococo::fee::WeightToFee
);
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@

use crate::*;
use asset_hub_rococo_runtime::xcm_config::XcmConfig as AssetHubRococoXcmConfig;
use penpal_runtime::xcm_config::XcmConfig as PenpalRococoXcmConfig;
use rococo_runtime::xcm_config::XcmConfig as RococoXcmConfig;
use rococo_system_emulated_network::penpal_emulated_chain::XcmConfig as PenpalRococoXcmConfig;

fn relay_to_para_sender_assertions(t: RelayToParaTest) {
type RuntimeEvent = <Rococo as Chain>::RuntimeEvent;

Rococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799)));

assert_expected_events!(
Rococo,
vec![
Expand Down Expand Up @@ -55,12 +53,10 @@ fn relay_to_para_receiver_assertions<Test>(_: Test) {

fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;

AssetHubRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(
630_092_000,
6_196,
)));

assert_expected_events!(
AssetHubRococo,
vec![
Expand Down Expand Up @@ -93,9 +89,7 @@ fn system_para_to_para_receiver_assertions<Test>(_: Test) {

fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;

PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8_799)));

assert_expected_events!(
PenpalA,
vec![
Expand All @@ -112,15 +106,13 @@ fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) {

fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;

let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(
AssetHubRococo::sibling_location_of(PenpalA::para_id()),
);

assert_expected_events!(
AssetHubRococo,
vec![
// Amount to reserve transfer is transferred to Parachain's Sovereign account
// Amount to reserve transfer is withdrawn from Parachain's Sovereign account
RuntimeEvent::Balances(
pallet_balances::Event::Withdraw { who, amount }
) => {
Expand All @@ -137,12 +129,10 @@ fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) {

fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;

AssetHubRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(
676_119_000,
6196,
)));

assert_expected_events!(
AssetHubRococo,
vec![
Expand Down Expand Up @@ -175,7 +165,7 @@ fn system_para_to_para_assets_receiver_assertions<Test>(_: Test) {
);
}

fn relay_to_para_limited_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult {
fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult {
<Rococo as RococoPallet>::XcmPallet::limited_reserve_transfer_assets(
t.signed_origin,
bx!(t.args.dest.into()),
Expand All @@ -186,7 +176,7 @@ fn relay_to_para_limited_reserve_transfer_assets(t: RelayToParaTest) -> Dispatch
)
}

fn system_para_to_para_limited_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
fn system_para_to_para_reserve_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::limited_reserve_transfer_assets(
t.signed_origin,
bx!(t.args.dest.into()),
Expand All @@ -197,7 +187,7 @@ fn system_para_to_para_limited_reserve_transfer_assets(t: SystemParaToParaTest)
)
}

fn para_to_system_para_limited_reserve_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult {
fn para_to_system_para_reserve_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult {
<PenpalA as PenpalAPallet>::PolkadotXcm::limited_reserve_transfer_assets(
t.signed_origin,
bx!(t.args.dest.into()),
Expand Down Expand Up @@ -297,8 +287,8 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
let receiver_balance_before = test.receiver.balance;

test.set_assertion::<Rococo>(relay_to_para_sender_assertions);
test.set_assertion::<PenpalA>(relay_to_para_receiver_assertions);
test.set_dispatchable::<Rococo>(relay_to_para_limited_reserve_transfer_assets);
test.set_assertion::<PenpalA>(para_receiver_assertions);
test.set_dispatchable::<Rococo>(relay_to_para_reserve_transfer_assets);
test.assert();

let delivery_fees = Rococo::execute_with(|| {
Expand Down Expand Up @@ -337,8 +327,8 @@ fn reserve_transfer_native_asset_from_system_para_to_para() {
let receiver_balance_before = test.receiver.balance;

test.set_assertion::<AssetHubRococo>(system_para_to_para_sender_assertions);
test.set_assertion::<PenpalA>(system_para_to_para_receiver_assertions);
test.set_dispatchable::<AssetHubRococo>(system_para_to_para_limited_reserve_transfer_assets);
test.set_assertion::<PenpalA>(para_receiver_assertions);
test.set_dispatchable::<AssetHubRococo>(system_para_to_para_reserve_transfer_assets);
test.assert();

let sender_balance_after = test.sender.balance;
Expand Down Expand Up @@ -384,7 +374,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {

test.set_assertion::<PenpalA>(para_to_system_para_sender_assertions);
test.set_assertion::<AssetHubRococo>(para_to_system_para_receiver_assertions);
test.set_dispatchable::<PenpalA>(para_to_system_para_limited_reserve_transfer_assets);
test.set_dispatchable::<PenpalA>(para_to_system_para_reserve_transfer_assets);
test.assert();

let sender_balance_after = test.sender.balance;
Expand Down Expand Up @@ -475,7 +465,7 @@ fn reserve_transfer_assets_from_system_para_to_para() {

test.set_assertion::<AssetHubRococo>(system_para_to_para_assets_sender_assertions);
test.set_assertion::<PenpalA>(system_para_to_para_assets_receiver_assertions);
test.set_dispatchable::<AssetHubRococo>(system_para_to_para_limited_reserve_transfer_assets);
test.set_dispatchable::<AssetHubRococo>(system_para_to_para_reserve_transfer_assets);
test.assert();

let sender_balance_after = test.sender.balance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
// limitations under the License.

use crate::*;
use frame_support::{instances::Instance2, BoundedVec};
use frame_support::BoundedVec;
use parachains_common::rococo::currency::EXISTENTIAL_DEPOSIT;
use sp_runtime::{DispatchError, ModuleError};
use rococo_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub;
use sp_runtime::ModuleError;

#[test]
fn swap_locally_on_chain_using_local_assets() {
Expand Down Expand Up @@ -118,114 +119,37 @@ fn swap_locally_on_chain_using_local_assets() {

#[test]
fn swap_locally_on_chain_using_foreign_assets() {
use frame_support::weights::WeightToFee;

let asset_native = Box::new(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() {
Some(GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
let asset_owner_on_penpal = PenpalASender::get();
let foreign_asset_at_asset_hub_rococo =
MultiLocation { parents: 1, interior: X1(Parachain(PenpalA::para_id().into())) }
.appended_with(asset_location_on_penpal)
.unwrap();

// 1. Create asset on penpal and, 2. Create foreign asset on asset_hub_rococo
super::penpal_create_foreign_asset_on_asset_hub(
asset_id_on_penpal,
foreign_asset_at_asset_hub_rococo,
ah_as_seen_by_penpal,
true,
asset_owner_on_penpal,
ASSET_MIN_BALANCE * 1_000_000,
);

let foreign_asset1_at_asset_hub_rococo = Box::new(MultiLocation {
parents: 1,
interior: X3(
Parachain(PenpalRococoA::para_id().into()),
PalletInstance(ASSETS_PALLET_ID),
GeneralIndex(ASSET_ID.into()),
),
});

let assets_para_destination: VersionedMultiLocation =
MultiLocation { parents: 1, interior: X1(Parachain(AssetHubRococo::para_id().into())) }
.into();

let penpal_location =
MultiLocation { parents: 1, interior: X1(Parachain(PenpalRococoA::para_id().into())) };

// 1. Create asset on penpal:
PenpalRococoA::execute_with(|| {
assert_ok!(<PenpalRococoA as PenpalRococoAPallet>::Assets::create(
<PenpalRococoA as Chain>::RuntimeOrigin::signed(PenpalRococoASender::get()),
ASSET_ID.into(),
PenpalRococoASender::get().into(),
1000,
));

assert!(<PenpalRococoA as PenpalRococoAPallet>::Assets::asset_exists(ASSET_ID));
});

// 2. Create foreign asset on asset_hub_rococo:

let require_weight_at_most = Weight::from_parts(1_100_000_000_000, 30_000);
let origin_kind = OriginKind::Xcm;
let sov_penpal_on_asset_hub_rococo = AssetHubRococo::sovereign_account_id_of(penpal_location);

let penpal_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_as_seen_by_ah);
AssetHubRococo::fund_accounts(vec![
(AssetHubRococoSender::get().into(), 5_000_000 * ROCOCO_ED), /* An account to swap dot
* for something else. */
(sov_penpal_on_asset_hub_rococo.clone().into(), 1000_000_000_000_000_000 * ROCOCO_ED),
]);

let sov_penpal_on_asset_hub_rococo_as_location: MultiLocation = MultiLocation {
parents: 0,
interior: X1(AccountId32Junction {
network: None,
id: sov_penpal_on_asset_hub_rococo.clone().into(),
}),
};

let call_foreign_assets_create =
<AssetHubRococo as Chain>::RuntimeCall::ForeignAssets(pallet_assets::Call::<
<AssetHubRococo as Chain>::Runtime,
Instance2,
>::create {
id: *foreign_asset1_at_asset_hub_rococo,
min_balance: 1000,
admin: sov_penpal_on_asset_hub_rococo.clone().into(),
})
.encode()
.into();

let buy_execution_fee_amount = parachains_common::rococo::fee::WeightToFee::weight_to_fee(
&Weight::from_parts(10_100_000_000_000, 300_000),
);
let buy_execution_fee = MultiAsset {
id: Concrete(MultiLocation { parents: 1, interior: Here }),
fun: Fungible(buy_execution_fee_amount),
};

let xcm = VersionedXcm::from(Xcm(vec![
WithdrawAsset { 0: vec![buy_execution_fee.clone()].into() },
BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited },
Transact { require_weight_at_most, origin_kind, call: call_foreign_assets_create },
RefundSurplus,
DepositAsset {
assets: All.into(),
beneficiary: sov_penpal_on_asset_hub_rococo_as_location,
},
]));

// Send XCM message from penpal => asset_hub_rococo
let sudo_penpal_origin = <PenpalRococoA as Chain>::RuntimeOrigin::root();
PenpalRococoA::execute_with(|| {
assert_ok!(<PenpalRococoA as PenpalRococoAPallet>::PolkadotXcm::send(
sudo_penpal_origin.clone(),
bx!(assets_para_destination.clone()),
bx!(xcm),
));

type RuntimeEvent = <PenpalRococoA as Chain>::RuntimeEvent;

assert_expected_events!(
PenpalRococoA,
vec![
RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {},
]
);
});

// Receive XCM message in Assets Parachain
AssetHubRococo::execute_with(|| {
assert!(<AssetHubRococo as AssetHubRococoPallet>::ForeignAssets::asset_exists(
*foreign_asset1_at_asset_hub_rococo
));

// 3: Mint foreign asset on asset_hub_rococo:
//
// (While it might be nice to use batch,
Expand All @@ -234,11 +158,9 @@ fn swap_locally_on_chain_using_foreign_assets() {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
// 3. Mint foreign asset (in reality this should be a teleport or some such)
assert_ok!(<AssetHubRococo as AssetHubRococoPallet>::ForeignAssets::mint(
<AssetHubRococo as Chain>::RuntimeOrigin::signed(
sov_penpal_on_asset_hub_rococo.clone().into()
),
*foreign_asset1_at_asset_hub_rococo,
sov_penpal_on_asset_hub_rococo.clone().into(),
<AssetHubRococo as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahr.clone().into()),
foreign_asset_at_asset_hub_rococo,
sov_penpal_on_ahr.clone().into(),
3_000_000_000_000,
));

Expand All @@ -249,11 +171,12 @@ 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_asset1_at_asset_hub_rococo.clone(),
foreign_asset_at_asset_hub_rococo.clone(),
));

assert_expected_events!(
Expand All @@ -265,16 +188,14 @@ 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_asset_hub_rococo.clone()
),
<AssetHubRococo as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()),
asset_native.clone(),
foreign_asset1_at_asset_hub_rococo.clone(),
foreign_asset_at_asset_hub_rococo.clone(),
1_000_000_000_000,
2_000_000_000_000,
0,
0,
sov_penpal_on_asset_hub_rococo.clone().into()
sov_penpal_on_ahr.clone().into()
));

assert_expected_events!(
Expand All @@ -289,7 +210,7 @@ fn swap_locally_on_chain_using_foreign_assets() {
// 6. Swap!
let path = BoundedVec::<_, _>::truncate_from(vec![
asset_native.clone(),
foreign_asset1_at_asset_hub_rococo.clone(),
foreign_asset_at_asset_hub_rococo.clone(),
]);

assert_ok!(
Expand All @@ -315,15 +236,13 @@ 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_asset_hub_rococo.clone()
),
<AssetHubRococo as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahr.clone()),
asset_native,
foreign_asset1_at_asset_hub_rococo,
foreign_asset_at_asset_hub_rococo,
1414213562273 - 2_000_000_000, // all but the 2 EDs can't be retrieved.
0,
0,
sov_penpal_on_asset_hub_rococo.clone().into(),
sov_penpal_on_ahr.clone().into(),
));
});
}
Expand Down
Loading

0 comments on commit f4d1edb

Please sign in to comment.