mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 21:01:02 +00:00
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:
-1
@@ -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" }
|
||||
|
||||
+8
@@ -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
|
||||
);
|
||||
|
||||
+8
-8
@@ -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;
|
||||
|
||||
+37
-117
@@ -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(),
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
+337
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user