pallet-xcm: add new flexible transfer_assets() call/extrinsic (#2388)

# Motivation (+testing)

### Enable easy `ForeignAssets` transfers using `pallet-xcm` 

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 ^.

# Description

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`.

## Review notes

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 <>
This commit is contained in:
Adrian Catangiu
2023-12-06 13:18:12 +02:00
committed by GitHub
parent 066bad6329
commit e7651cf41b
38 changed files with 3320 additions and 955 deletions
@@ -38,5 +38,4 @@ asset-test-utils = { path = "../../../../../runtimes/assets/test-utils" }
cumulus-pallet-dmp-queue = { default-features = false, path = "../../../../../../pallets/dmp-queue" }
cumulus-pallet-parachain-system = { default-features = false, path = "../../../../../../pallets/parachain-system" }
emulated-integration-tests-common = { path = "../../../common", default-features = false }
penpal-runtime = { path = "../../../../../runtimes/testing/penpal" }
westend-system-emulated-network = { path = "../../../networks/westend-system" }
@@ -19,3 +19,11 @@ mod set_xcm_versions;
mod swap;
mod teleport;
mod treasury;
use crate::*;
emulated_integration_tests_common::include_penpal_create_foreign_asset_on_asset_hub!(
PenpalB,
AssetHubWestend,
WESTEND_ED,
parachains_common::westend::fee::WeightToFee
);
@@ -15,8 +15,8 @@
use crate::*;
use asset_hub_westend_runtime::xcm_config::XcmConfig as AssetHubWestendXcmConfig;
use penpal_runtime::xcm_config::XcmConfig as PenpalWestendXcmConfig;
use westend_runtime::xcm_config::XcmConfig as WestendXcmConfig;
use westend_system_emulated_network::penpal_emulated_chain::XcmConfig as PenpalWestendXcmConfig;
fn relay_to_para_sender_assertions(t: RelayToParaTest) {
type RuntimeEvent = <Westend as Chain>::RuntimeEvent;
@@ -162,7 +162,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 {
<Westend as WestendPallet>::XcmPallet::limited_reserve_transfer_assets(
t.signed_origin,
bx!(t.args.dest.into()),
@@ -173,7 +173,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 {
<AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::limited_reserve_transfer_assets(
t.signed_origin,
bx!(t.args.dest.into()),
@@ -184,7 +184,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 {
<PenpalB as PenpalBPallet>::PolkadotXcm::limited_reserve_transfer_assets(
t.signed_origin,
bx!(t.args.dest.into()),
@@ -284,7 +284,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
test.set_assertion::<Westend>(relay_to_para_sender_assertions);
test.set_assertion::<PenpalB>(para_receiver_assertions);
test.set_dispatchable::<Westend>(relay_to_para_limited_reserve_transfer_assets);
test.set_dispatchable::<Westend>(relay_to_para_reserve_transfer_assets);
test.assert();
let delivery_fees = Westend::execute_with(|| {
@@ -328,7 +328,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() {
test.set_assertion::<AssetHubWestend>(system_para_to_para_sender_assertions);
test.set_assertion::<PenpalB>(para_receiver_assertions);
test.set_dispatchable::<AssetHubWestend>(system_para_to_para_limited_reserve_transfer_assets);
test.set_dispatchable::<AssetHubWestend>(system_para_to_para_reserve_transfer_assets);
test.assert();
let sender_balance_after = test.sender.balance;
@@ -379,7 +379,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
test.set_assertion::<PenpalB>(para_to_system_para_sender_assertions);
test.set_assertion::<AssetHubWestend>(para_to_system_para_receiver_assertions);
test.set_dispatchable::<PenpalB>(para_to_system_para_limited_reserve_transfer_assets);
test.set_dispatchable::<PenpalB>(para_to_system_para_reserve_transfer_assets);
test.assert();
let sender_balance_after = test.sender.balance;
@@ -474,7 +474,7 @@ fn reserve_transfer_assets_from_system_para_to_para() {
test.set_assertion::<AssetHubWestend>(system_para_to_para_assets_sender_assertions);
test.set_assertion::<PenpalB>(system_para_to_para_assets_receiver_assertions);
test.set_dispatchable::<AssetHubWestend>(system_para_to_para_limited_reserve_transfer_assets);
test.set_dispatchable::<AssetHubWestend>(system_para_to_para_reserve_transfer_assets);
test.assert();
let sender_balance_after = test.sender.balance;
@@ -14,6 +14,7 @@
// limitations under the License.
use crate::*;
use westend_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub;
#[test]
fn swap_locally_on_chain_using_local_assets() {
@@ -107,113 +108,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_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() {
Some(GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
let asset_owner_on_penpal = PenpalBSender::get();
let foreign_asset_at_asset_hub_westend =
MultiLocation { parents: 1, interior: X1(Parachain(PenpalB::para_id().into())) }
.appended_with(asset_location_on_penpal)
.unwrap();
let foreign_asset1_at_asset_hub_westend = Box::new(MultiLocation {
parents: 1,
interior: X3(
Parachain(PenpalB::para_id().into()),
PalletInstance(ASSETS_PALLET_ID),
GeneralIndex(ASSET_ID.into()),
),
});
let assets_para_destination: VersionedMultiLocation =
MultiLocation { parents: 1, interior: X1(Parachain(AssetHubWestend::para_id().into())) }
.into();
let penpal_location =
MultiLocation { parents: 1, interior: X1(Parachain(PenpalB::para_id().into())) };
// 1. Create asset on penpal:
PenpalB::execute_with(|| {
assert_ok!(<PenpalB as PenpalBPallet>::Assets::create(
<PenpalB as Chain>::RuntimeOrigin::signed(PenpalBSender::get()),
ASSET_ID.into(),
PenpalBSender::get().into(),
1000,
));
assert!(<PenpalB as PenpalBPallet>::Assets::asset_exists(ASSET_ID));
});
// 2. Create foreign asset on asset_hub_westend:
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_westend = AssetHubWestend::sovereign_account_id_of(penpal_location);
// 1. Create asset on penpal and, 2. Create foreign asset on asset_hub_westend
super::penpal_create_foreign_asset_on_asset_hub(
asset_id_on_penpal,
foreign_asset_at_asset_hub_westend,
ah_as_seen_by_penpal,
true,
asset_owner_on_penpal,
ASSET_MIN_BALANCE * 1_000_000,
);
let penpal_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalB::para_id());
let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(penpal_as_seen_by_ah);
AssetHubWestend::fund_accounts(vec![
(AssetHubWestendSender::get().into(), 5_000_000 * WESTEND_ED),
(sov_penpal_on_asset_hub_westend.clone().into(), 1000_000_000_000_000_000 * WESTEND_ED),
(AssetHubWestendSender::get().into(), 5_000_000 * WESTEND_ED), /* An account to swap dot
* for something else. */
]);
let sov_penpal_on_asset_hub_westend_as_location: MultiLocation = MultiLocation {
parents: 0,
interior: X1(AccountId32Junction {
network: None,
id: sov_penpal_on_asset_hub_westend.clone().into(),
}),
};
let call_foreign_assets_create =
<AssetHubWestend as Chain>::RuntimeCall::ForeignAssets(pallet_assets::Call::<
<AssetHubWestend as Chain>::Runtime,
Instance2,
>::create {
id: *foreign_asset1_at_asset_hub_westend,
min_balance: 1000,
admin: sov_penpal_on_asset_hub_westend.clone().into(),
})
.encode()
.into();
let buy_execution_fee_amount = parachains_common::westend::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_westend_as_location,
},
]));
// Send XCM message from penpal => asset_hub_westend
let sudo_penpal_origin = <PenpalB as Chain>::RuntimeOrigin::root();
PenpalB::execute_with(|| {
assert_ok!(<PenpalB as PenpalBPallet>::PolkadotXcm::send(
sudo_penpal_origin.clone(),
bx!(assets_para_destination.clone()),
bx!(xcm),
));
type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent;
assert_expected_events!(
PenpalB,
vec![
RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {},
]
);
});
// Receive XCM message in Assets Parachain in the next block.
AssetHubWestend::execute_with(|| {
assert!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::asset_exists(
*foreign_asset1_at_asset_hub_westend
));
// 3: Mint foreign asset on asset_hub_westend:
//
// (While it might be nice to use batch,
@@ -222,11 +147,9 @@ fn swap_locally_on_chain_using_foreign_assets() {
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
// 3. Mint foreign asset (in reality this should be a teleport or some such)
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::mint(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(
sov_penpal_on_asset_hub_westend.clone().into()
),
*foreign_asset1_at_asset_hub_westend,
sov_penpal_on_asset_hub_westend.clone().into(),
<AssetHubWestend as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahw.clone().into()),
foreign_asset_at_asset_hub_westend,
sov_penpal_on_ahw.clone().into(),
3_000_000_000_000,
));
@@ -237,11 +160,12 @@ 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_asset1_at_asset_hub_westend.clone(),
foreign_asset_at_asset_hub_westend.clone(),
));
assert_expected_events!(
@@ -253,16 +177,14 @@ 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_asset_hub_westend.clone()
),
<AssetHubWestend as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahw.clone()),
asset_native.clone(),
foreign_asset1_at_asset_hub_westend.clone(),
foreign_asset_at_asset_hub_westend.clone(),
1_000_000_000_000,
2_000_000_000_000,
0,
0,
sov_penpal_on_asset_hub_westend.clone().into()
sov_penpal_on_ahw.clone().into()
));
assert_expected_events!(
@@ -277,7 +199,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_westend.clone(),
foreign_asset_at_asset_hub_westend.clone(),
]);
assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::AssetConversion::swap_exact_tokens_for_tokens(
@@ -301,15 +223,13 @@ 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_asset_hub_westend.clone()
),
<AssetHubWestend as Chain>::RuntimeOrigin::signed(sov_penpal_on_ahw.clone()),
asset_native,
foreign_asset1_at_asset_hub_westend,
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_asset_hub_westend.clone().into(),
sov_penpal_on_ahw.clone().into(),
));
});
}
@@ -15,7 +15,9 @@
use crate::*;
use asset_hub_westend_runtime::xcm_config::XcmConfig as AssetHubWestendXcmConfig;
use emulated_integration_tests_common::xcm_helpers::non_fee_asset;
use westend_runtime::xcm_config::XcmConfig as WestendXcmConfig;
use westend_system_emulated_network::penpal_emulated_chain::LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub;
fn relay_origin_assertions(t: RelayToSystemParaTest) {
type RuntimeEvent = <Westend as Chain>::RuntimeEvent;
@@ -110,6 +112,123 @@ fn para_dest_assertions(t: RelayToSystemParaTest) {
);
}
fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) {
type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent;
PenpalB::assert_xcm_pallet_attempted_complete(None);
let expected_asset_id = t.args.asset_id.unwrap();
let (_, expected_asset_amount) =
non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap();
assert_expected_events!(
PenpalB,
vec![
RuntimeEvent::Balances(
pallet_balances::Event::Withdraw { who, amount }
) => {
who: *who == t.sender.account_id,
amount: *amount == t.args.amount,
},
RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => {
asset_id: *asset_id == expected_asset_id,
owner: *owner == t.sender.account_id,
balance: *balance == expected_asset_amount,
},
]
);
}
fn penpal_to_ah_foreign_assets_receiver_assertions(t: ParaToSystemParaTest) {
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(
AssetHubWestend::sibling_location_of(PenpalB::para_id()),
);
let (expected_foreign_asset_id, expected_foreign_asset_amount) =
non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap();
assert_expected_events!(
AssetHubWestend,
vec![
// native asset reserve transfer for paying fees, withdrawn from Penpal's sov account
RuntimeEvent::Balances(
pallet_balances::Event::Withdraw { who, amount }
) => {
who: *who == sov_penpal_on_ahr.clone().into(),
amount: *amount == t.args.amount,
},
RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => {
who: *who == t.receiver.account_id,
},
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => {
asset_id: *asset_id == expected_foreign_asset_id,
owner: *owner == t.receiver.account_id,
amount: *amount == expected_foreign_asset_amount,
},
RuntimeEvent::Balances(pallet_balances::Event::Deposit { .. }) => {},
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
}
fn ah_to_penpal_foreign_assets_sender_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
AssetHubWestend::assert_xcm_pallet_attempted_complete(None);
let (expected_foreign_asset_id, expected_foreign_asset_amount) =
non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap();
assert_expected_events!(
AssetHubWestend,
vec![
// native asset used for fees is transferred to Parachain's Sovereign account as reserve
RuntimeEvent::Balances(
pallet_balances::Event::Transfer { from, to, amount }
) => {
from: *from == t.sender.account_id,
to: *to == AssetHubWestend::sovereign_account_id_of(
t.args.dest
),
amount: *amount == t.args.amount,
},
// foreign asset is burned locally as part of teleportation
RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned { asset_id, owner, balance }) => {
asset_id: *asset_id == expected_foreign_asset_id,
owner: *owner == t.sender.account_id,
balance: *balance == expected_foreign_asset_amount,
},
]
);
}
fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent;
let expected_asset_id = t.args.asset_id.unwrap();
let (_, expected_asset_amount) =
non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap();
let checking_account = <PenpalB as PenpalBPallet>::PolkadotXcm::check_account();
assert_expected_events!(
PenpalB,
vec![
// checking account burns local asset as part of incoming teleport
RuntimeEvent::Assets(pallet_assets::Event::Burned { asset_id, owner, balance }) => {
asset_id: *asset_id == expected_asset_id,
owner: *owner == checking_account,
balance: *balance == expected_asset_amount,
},
// local asset is teleported into account of receiver
RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, owner, amount }) => {
asset_id: *asset_id == expected_asset_id,
owner: *owner == t.receiver.account_id,
amount: *amount == expected_asset_amount,
},
// native asset for fee is deposited to receiver
RuntimeEvent::Balances(pallet_balances::Event::Deposit { who, .. }) => {
who: *who == t.receiver.account_id,
},
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
}
fn relay_limited_teleport_assets(t: RelayToSystemParaTest) -> DispatchResult {
<Westend as WestendPallet>::XcmPallet::limited_teleport_assets(
t.signed_origin,
@@ -152,6 +271,28 @@ fn system_para_teleport_assets(t: SystemParaToRelayTest) -> DispatchResult {
)
}
fn system_para_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
<AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::transfer_assets(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
t.args.fee_asset_item,
t.args.weight_limit,
)
}
fn para_to_system_para_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult {
<PenpalB as PenpalBPallet>::PolkadotXcm::transfer_assets(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
t.args.fee_asset_item,
t.args.weight_limit,
)
}
/// Limited Teleport of native asset from Relay Chain to the System Parachain should work
#[test]
fn limited_teleport_native_assets_from_relay_to_system_para_works() {
@@ -410,3 +551,199 @@ fn teleport_to_other_system_parachains_works() {
(native_asset, amount)
);
}
/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets should work
/// (using native reserve-based transfer for fees)
#[test]
fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
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() {
Some(GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
let asset_owner_on_penpal = PenpalBSender::get();
let foreign_asset_at_asset_hub_westend =
MultiLocation { parents: 1, interior: X1(Parachain(PenpalB::para_id().into())) }
.appended_with(asset_location_on_penpal)
.unwrap();
super::penpal_create_foreign_asset_on_asset_hub(
asset_id_on_penpal,
foreign_asset_at_asset_hub_westend,
ah_as_seen_by_penpal,
false,
asset_owner_on_penpal,
ASSET_MIN_BALANCE * 1_000_000,
);
let penpal_to_ah_beneficiary_id = AssetHubWestendReceiver::get();
let fee_amount_to_send = ASSET_HUB_WESTEND_ED * 1000;
let asset_amount_to_send = ASSET_MIN_BALANCE * 1000;
let penpal_assets: MultiAssets = vec![
(Parent, fee_amount_to_send).into(),
(asset_location_on_penpal, asset_amount_to_send).into(),
]
.into();
let fee_asset_index = penpal_assets
.inner()
.iter()
.position(|r| r == &(Parent, fee_amount_to_send).into())
.unwrap() as u32;
// Penpal to AH test args
let penpal_to_ah_test_args = TestContext {
sender: PenpalBSender::get(),
receiver: AssetHubWestendReceiver::get(),
args: para_test_args(
ah_as_seen_by_penpal,
penpal_to_ah_beneficiary_id,
asset_amount_to_send,
penpal_assets,
Some(asset_id_on_penpal),
fee_asset_index,
),
};
let mut penpal_to_ah = ParaToSystemParaTest::new(penpal_to_ah_test_args);
let penpal_sender_balance_before = penpal_to_ah.sender.balance;
let ah_receiver_balance_before = penpal_to_ah.receiver.balance;
let penpal_sender_assets_before = PenpalB::execute_with(|| {
type Assets = <PenpalB as PenpalBPallet>::Assets;
<Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalBSender::get())
});
let ah_receiver_assets_before = AssetHubWestend::execute_with(|| {
type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(
foreign_asset_at_asset_hub_westend,
&AssetHubWestendReceiver::get(),
)
});
penpal_to_ah.set_assertion::<PenpalB>(penpal_to_ah_foreign_assets_sender_assertions);
penpal_to_ah.set_assertion::<AssetHubWestend>(penpal_to_ah_foreign_assets_receiver_assertions);
penpal_to_ah.set_dispatchable::<PenpalB>(para_to_system_para_transfer_assets);
penpal_to_ah.assert();
let penpal_sender_balance_after = penpal_to_ah.sender.balance;
let ah_receiver_balance_after = penpal_to_ah.receiver.balance;
let penpal_sender_assets_after = PenpalB::execute_with(|| {
type Assets = <PenpalB as PenpalBPallet>::Assets;
<Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalBSender::get())
});
let ah_receiver_assets_after = AssetHubWestend::execute_with(|| {
type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(
foreign_asset_at_asset_hub_westend,
&AssetHubWestendReceiver::get(),
)
});
// Sender's balance is reduced
assert!(penpal_sender_balance_after < penpal_sender_balance_before);
// Receiver's balance is increased
assert!(ah_receiver_balance_after > ah_receiver_balance_before);
// Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`;
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
// should be non-zero
assert!(ah_receiver_balance_after < ah_receiver_balance_before + fee_amount_to_send);
// Sender's balance is reduced by exact amount
assert_eq!(penpal_sender_assets_before - asset_amount_to_send, penpal_sender_assets_after);
// Receiver's balance is increased by exact amount
assert_eq!(ah_receiver_assets_after, ah_receiver_assets_before + asset_amount_to_send);
///////////////////////////////////////////////////////////////////////
// Now test transferring foreign assets back from AssetHub to Penpal //
///////////////////////////////////////////////////////////////////////
// Move funds on AH from AHReceiver to AHSender
AssetHubWestend::execute_with(|| {
type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
assert_ok!(ForeignAssets::transfer(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendReceiver::get()),
foreign_asset_at_asset_hub_westend,
AssetHubWestendSender::get().into(),
asset_amount_to_send,
));
});
let ah_to_penpal_beneficiary_id = PenpalBReceiver::get();
let penpal_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalB::para_id());
let ah_assets: MultiAssets = vec![
(Parent, fee_amount_to_send).into(),
(foreign_asset_at_asset_hub_westend, asset_amount_to_send).into(),
]
.into();
let fee_asset_index = ah_assets
.inner()
.iter()
.position(|r| r == &(Parent, fee_amount_to_send).into())
.unwrap() as u32;
// AH to Penpal test args
let ah_to_penpal_test_args = TestContext {
sender: AssetHubWestendSender::get(),
receiver: PenpalBReceiver::get(),
args: para_test_args(
penpal_as_seen_by_ah,
ah_to_penpal_beneficiary_id,
asset_amount_to_send,
ah_assets,
Some(asset_id_on_penpal),
fee_asset_index,
),
};
let mut ah_to_penpal = SystemParaToParaTest::new(ah_to_penpal_test_args);
let ah_sender_balance_before = ah_to_penpal.sender.balance;
let penpal_receiver_balance_before = ah_to_penpal.receiver.balance;
let ah_sender_assets_before = AssetHubWestend::execute_with(|| {
type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
foreign_asset_at_asset_hub_westend,
&AssetHubWestendSender::get(),
)
});
let penpal_receiver_assets_before = PenpalB::execute_with(|| {
type Assets = <PenpalB as PenpalBPallet>::Assets;
<Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalBReceiver::get())
});
ah_to_penpal.set_assertion::<AssetHubWestend>(ah_to_penpal_foreign_assets_sender_assertions);
ah_to_penpal.set_assertion::<PenpalB>(ah_to_penpal_foreign_assets_receiver_assertions);
ah_to_penpal.set_dispatchable::<AssetHubWestend>(system_para_to_para_transfer_assets);
ah_to_penpal.assert();
let ah_sender_balance_after = ah_to_penpal.sender.balance;
let penpal_receiver_balance_after = ah_to_penpal.receiver.balance;
let ah_sender_assets_after = AssetHubWestend::execute_with(|| {
type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
foreign_asset_at_asset_hub_westend,
&AssetHubWestendSender::get(),
)
});
let penpal_receiver_assets_after = PenpalB::execute_with(|| {
type Assets = <PenpalB as PenpalBPallet>::Assets;
<Assets as Inspect<_>>::balance(asset_id_on_penpal, &PenpalBReceiver::get())
});
// Sender's balance is reduced
assert!(ah_sender_balance_after < ah_sender_balance_before);
// Receiver's balance is increased
assert!(penpal_receiver_balance_after > penpal_receiver_balance_before);
// Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`;
// `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but
// should be non-zero
assert!(penpal_receiver_balance_after < penpal_receiver_balance_before + fee_amount_to_send);
// Sender's balance is reduced by exact amount
assert_eq!(ah_sender_assets_before - asset_amount_to_send, ah_sender_assets_after);
// Receiver's balance is increased by exact amount
assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send);
}