pallet-xcm: add new extrinsic for asset transfers using explicit XCM transfer types (#3695)

# Description

Add `transfer_assets_using()` for transferring assets from local chain
to destination chain using explicit XCM transfer types such as:
- `TransferType::LocalReserve`: transfer assets to sovereign account of
destination chain and forward a notification XCM to `dest` to mint and
deposit reserve-based assets to `beneficiary`.
- `TransferType::DestinationReserve`: 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`.
- `TransferType::RemoteReserve(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`. Typically the remote `reserve` is
Asset Hub.
- `TransferType::Teleport`: burn local assets and forward XCM to `dest`
chain to mint/teleport assets and deposit them to `beneficiary`.

By default, an asset's reserve is its origin chain. But sometimes we may
want to explicitly use another chain as reserve (as long as allowed by
runtime `IsReserve` filter).

This is very helpful for transferring assets with multiple configured
reserves (such as Asset Hub ForeignAssets), when the transfer strictly
depends on the used reserve.

E.g. For transferring Foreign Assets over a bridge, Asset Hub must be
used as the reserve location.

# Example usage scenarios

## Transfer bridged ethereum ERC20-tokenX between ecosystem parachains.

ERC20-tokenX is registered on AssetHub as a ForeignAsset by the
Polkadot<>Ethereum bridge (Snowbridge). Its asset_id is something like
`(parents:2, (GlobalConsensus(Ethereum), Address(tokenX_contract)))`.
Its _original_ reserve is Ethereum (only we can't use Ethereum as a
reserve in local transfers); but, since tokenX is also registered on
AssetHub as a ForeignAsset, we can use AssetHub as a reserve.

With this PR we can transfer tokenX from ParaA to ParaB while using
AssetHub as a reserve.

## Transfer AssetHub ForeignAssets between parachains

AssetA created on ParaA but also registered as foreign asset on Asset
Hub. Can use AssetHub as a reserve.

And all of the above can be done while still controlling transfer type
for `fees` so mixing assets in same transfer is supported.

# Tests

Added integration tests for showcasing:
- transferring local (not bridged) assets from parachain over bridge
using local Asset Hub reserve,
- transferring foreign assets from parachain to Asset Hub,
- transferring foreign assets from Asset Hub to parachain,
- transferring foreign assets from parachain to parachain using local
Asset Hub reserve.

---------

Co-authored-by: Branislav Kontur <bkontur@gmail.com>
Co-authored-by: command-bot <>
This commit is contained in:
Adrian Catangiu
2024-04-12 16:53:12 +03:00
committed by GitHub
parent 2dfe5f745c
commit 1e971b8d2a
28 changed files with 2215 additions and 385 deletions
@@ -191,6 +191,10 @@ pub mod pallet {
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Called when new message is sent (queued to local outbound XCM queue) over the bridge.
pub(crate) fn on_message_sent_to_bridge(message_size: u32) {
log::trace!(
target: LOG_TARGET,
"on_message_sent_to_bridge - message_size: {message_size:?}",
);
let _ = Bridge::<T, I>::try_mutate(|bridge| {
let is_channel_with_bridge_hub_congested = T::WithBridgeHubChannel::is_congested();
let is_bridge_congested = bridge.is_congested;
@@ -238,14 +242,16 @@ impl<T: Config<I>, I: 'static> ExporterFor for Pallet<T, I> {
remote_location: &InteriorLocation,
message: &Xcm<()>,
) -> Option<(Location, Option<Asset>)> {
log::trace!(
target: LOG_TARGET,
"exporter_for - network: {network:?}, remote_location: {remote_location:?}, msg: {message:?}",
);
// ensure that the message is sent to the expected bridged network (if specified).
if let Some(bridged_network) = T::BridgedNetworkId::get() {
if *network != bridged_network {
log::trace!(
target: LOG_TARGET,
"Router with bridged_network_id {:?} does not support bridging to network {:?}!",
bridged_network,
network,
"Router with bridged_network_id {bridged_network:?} does not support bridging to network {network:?}!",
);
return None
}
@@ -300,7 +306,7 @@ impl<T: Config<I>, I: 'static> ExporterFor for Pallet<T, I> {
log::info!(
target: LOG_TARGET,
"Going to send message to {:?} ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}",
"Validate send message to {:?} ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}",
(network, remote_location),
message_size,
fee,
@@ -321,6 +327,7 @@ impl<T: Config<I>, I: 'static> SendXcm for Pallet<T, I> {
dest: &mut Option<Location>,
xcm: &mut Option<Xcm<()>>,
) -> SendResult<Self::Ticket> {
log::trace!(target: LOG_TARGET, "validate - msg: {xcm:?}, destination: {dest:?}");
// `dest` and `xcm` are required here
let dest_ref = dest.as_ref().ok_or(SendError::MissingArgument)?;
let xcm_ref = xcm.as_ref().ok_or(SendError::MissingArgument)?;
@@ -366,6 +373,7 @@ impl<T: Config<I>, I: 'static> SendXcm for Pallet<T, I> {
// increase delivery fee factor if required
Self::on_message_sent_to_bridge(message_size);
log::trace!(target: LOG_TARGET, "deliver - message sent, xcm_hash: {xcm_hash:?}");
Ok(xcm_hash)
}
}
+4 -1
View File
@@ -942,7 +942,10 @@ impl<T: Config> SendXcm for Pallet<T> {
Self::deposit_event(Event::XcmpMessageSent { message_hash: hash });
Ok(hash)
},
Err(e) => Err(SendError::Transport(e.into())),
Err(e) => {
log::error!(target: LOG_TARGET, "Deliver error: {e:?}");
Err(SendError::Transport(e.into()))
},
}
}
}
@@ -80,15 +80,14 @@ pub fn genesis(para_id: u32) -> Storage {
assets: vec![
// Relay Native asset representation
(
Location::try_from(RelayLocation::get()).expect("conversion works"),
Location::try_from(RelayLocation::get()).unwrap(),
PenpalAssetOwner::get(),
true,
ED,
),
// Sufficient AssetHub asset representation
(
Location::try_from(LocalReservableFromAssetHub::get())
.expect("conversion works"),
Location::try_from(LocalReservableFromAssetHub::get()).unwrap(),
PenpalAssetOwner::get(),
true,
ED,
@@ -16,16 +16,19 @@
mod genesis;
pub use genesis::{genesis, PenpalAssetOwner, PenpalSudoAccount, ED, PARA_ID_A, PARA_ID_B};
pub use penpal_runtime::xcm_config::{
CustomizableAssetFromSystemAssetHub, LocalTeleportableToAssetHub, XcmConfig,
CustomizableAssetFromSystemAssetHub, RelayNetworkId as PenpalRelayNetworkId,
};
// Substrate
use frame_support::traits::OnInitialize;
use sp_core::Encode;
// Cumulus
use emulated_integration_tests_common::{
impl_accounts_helpers_for_parachain, impl_assert_events_helpers_for_parachain,
impl_assets_helpers_for_parachain, impls::Parachain, xcm_emulator::decl_test_parachains,
impl_assets_helpers_for_parachain, impl_xcm_helpers_for_parachain,
impls::{NetworkId, Parachain},
xcm_emulator::decl_test_parachains,
};
// Penpal Parachain declaration
@@ -34,6 +37,10 @@ decl_test_parachains! {
genesis = genesis(PARA_ID_A),
on_init = {
penpal_runtime::AuraExt::on_initialize(1);
frame_support::assert_ok!(penpal_runtime::System::set_storage(
penpal_runtime::RuntimeOrigin::root(),
vec![(PenpalRelayNetworkId::key().to_vec(), NetworkId::Rococo.encode())],
));
},
runtime = penpal_runtime,
core = {
@@ -53,6 +60,10 @@ decl_test_parachains! {
genesis = genesis(PARA_ID_B),
on_init = {
penpal_runtime::AuraExt::on_initialize(1);
frame_support::assert_ok!(penpal_runtime::System::set_storage(
penpal_runtime::RuntimeOrigin::root(),
vec![(PenpalRelayNetworkId::key().to_vec(), NetworkId::Westend.encode())],
));
},
runtime = penpal_runtime,
core = {
@@ -77,3 +88,5 @@ impl_assert_events_helpers_for_parachain!(PenpalA);
impl_assert_events_helpers_for_parachain!(PenpalB);
impl_assets_helpers_for_parachain!(PenpalA);
impl_assets_helpers_for_parachain!(PenpalB);
impl_xcm_helpers_for_parachain!(PenpalA);
impl_xcm_helpers_for_parachain!(PenpalB);
@@ -25,7 +25,7 @@ use asset_hub_rococo_emulated_chain::AssetHubRococo;
use asset_hub_westend_emulated_chain::AssetHubWestend;
use bridge_hub_rococo_emulated_chain::BridgeHubRococo;
use bridge_hub_westend_emulated_chain::BridgeHubWestend;
use penpal_emulated_chain::PenpalA;
use penpal_emulated_chain::{PenpalA, PenpalB};
use rococo_emulated_chain::Rococo;
use westend_emulated_chain::Westend;
@@ -48,13 +48,13 @@ decl_test_networks! {
PenpalA,
],
bridge = RococoWestendMockBridge
},
pub struct WestendMockNet {
relay_chain = Westend,
parachains = vec![
AssetHubWestend,
BridgeHubWestend,
PenpalB,
],
bridge = WestendRococoMockBridge
},
@@ -96,5 +96,6 @@ decl_test_sender_receiver_accounts_parameter_types! {
WestendRelay { sender: ALICE, receiver: BOB },
AssetHubWestendPara { sender: ALICE, receiver: BOB },
BridgeHubWestendPara { sender: ALICE, receiver: BOB },
PenpalAPara { sender: ALICE, receiver: BOB }
PenpalAPara { sender: ALICE, receiver: BOB },
PenpalBPara { sender: ALICE, receiver: BOB }
}
@@ -30,6 +30,7 @@ mod imports {
prelude::{AccountId32 as AccountId32Junction, *},
v3,
};
pub use xcm_executor::traits::TransferType;
// Cumulus
pub use asset_test_utils::xcm_helpers;
@@ -81,6 +82,7 @@ mod imports {
pub type SystemParaToParaTest = Test<AssetHubRococo, PenpalA>;
pub type ParaToSystemParaTest = Test<PenpalA, AssetHubRococo>;
pub type ParaToParaThroughRelayTest = Test<PenpalA, PenpalB, Rococo>;
pub type ParaToParaThroughAHTest = Test<PenpalA, PenpalB, AssetHubRococo>;
}
#[cfg(test)]
@@ -0,0 +1,608 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::reserve_transfer::*;
use crate::{
imports::*,
tests::teleport::do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt,
};
fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
let sov_penpal_a_on_ah = AssetHubRococo::sovereign_account_id_of(
AssetHubRococo::sibling_location_of(PenpalA::para_id()),
);
let sov_penpal_b_on_ah = AssetHubRococo::sovereign_account_id_of(
AssetHubRococo::sibling_location_of(PenpalB::para_id()),
);
assert_expected_events!(
AssetHubRococo,
vec![
// Withdrawn from sender parachain SA
RuntimeEvent::Balances(
pallet_balances::Event::Burned { who, amount }
) => {
who: *who == sov_penpal_a_on_ah,
amount: *amount == t.args.amount,
},
// Deposited to receiver parachain SA
RuntimeEvent::Balances(
pallet_balances::Event::Minted { who, .. }
) => {
who: *who == sov_penpal_b_on_ah,
},
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
}
fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
let fee_idx = t.args.fee_asset_item as usize;
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(TransferType::LocalReserve),
bx!(fee.id.into()),
bx!(TransferType::LocalReserve),
t.args.weight_limit,
)
}
fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult {
let fee_idx = t.args.fee_asset_item as usize;
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(TransferType::DestinationReserve),
bx!(fee.id.into()),
bx!(TransferType::DestinationReserve),
t.args.weight_limit,
)
}
fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> DispatchResult {
let fee_idx = t.args.fee_asset_item as usize;
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubRococo::para_id());
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())),
bx!(fee.id.into()),
bx!(TransferType::RemoteReserve(asset_hub_location.into())),
t.args.weight_limit,
)
}
fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult {
let fee_idx = t.args.fee_asset_item as usize;
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(TransferType::Teleport),
bx!(fee.id.into()),
bx!(TransferType::DestinationReserve),
t.args.weight_limit,
)
}
fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult {
let fee_idx = t.args.fee_asset_item as usize;
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::transfer_assets_using_type(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(TransferType::Teleport),
bx!(fee.id.into()),
bx!(TransferType::LocalReserve),
t.args.weight_limit,
)
}
// ===========================================================================
// ======= Transfer - Native + Bridged Assets - AssetHub->Parachain ==========
// ===========================================================================
/// Transfers of native asset plus bridged asset from AssetHub to some Parachain
/// while paying fees using native asset.
#[test]
fn transfer_foreign_assets_from_asset_hub_to_para() {
let destination = AssetHubRococo::sibling_location_of(PenpalA::para_id());
let sender = AssetHubRococoSender::get();
let native_amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000;
let native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let receiver = PenpalAReceiver::get();
let assets_owner = PenpalAssetOwner::get();
// Foreign asset used: bridged WND
let foreign_amount_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000;
let wnd_at_rococo_parachains =
v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]);
let wnd_at_rococo_parachains_latest: Location = wnd_at_rococo_parachains.try_into().unwrap();
// Configure destination chain to trust AH as reserve of WND
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as Chain>::System::set_storage(
<PenpalA as Chain>::RuntimeOrigin::root(),
vec![(
penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(),
Location::new(2, [GlobalConsensus(Westend)]).encode(),
)],
));
});
PenpalA::force_create_foreign_asset(
wnd_at_rococo_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
AssetHubRococo::force_create_foreign_asset(
wnd_at_rococo_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
AssetHubRococo::mint_foreign_asset(
<AssetHubRococo as Chain>::RuntimeOrigin::signed(assets_owner),
wnd_at_rococo_parachains,
sender.clone(),
foreign_amount_to_send * 2,
);
// Assets to send
let assets: Vec<Asset> = vec![
(Parent, native_amount_to_send).into(),
(wnd_at_rococo_parachains_latest, foreign_amount_to_send).into(),
];
let fee_asset_id = AssetId(Parent.into());
let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32;
// Init Test
let test_args = TestContext {
sender: sender.clone(),
receiver: receiver.clone(),
args: TestArgs::new_para(
destination.clone(),
receiver.clone(),
native_amount_to_send,
assets.into(),
None,
fee_asset_item,
),
};
let mut test = SystemParaToParaTest::new(test_args);
// Query initial balances
let sender_balance_before = test.sender.balance;
let sender_wnds_before = AssetHubRococo::execute_with(|| {
type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &sender)
});
let receiver_assets_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(native_asset_location.into(), &receiver)
});
let receiver_wnds_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &receiver)
});
// Set assertions and dispatchables
test.set_assertion::<AssetHubRococo>(system_para_to_para_sender_assertions);
test.set_assertion::<PenpalA>(system_para_to_para_receiver_assertions);
test.set_dispatchable::<AssetHubRococo>(ah_to_para_transfer_assets);
test.assert();
// Query final balances
let sender_balance_after = test.sender.balance;
let sender_wnds_after = AssetHubRococo::execute_with(|| {
type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &sender)
});
let receiver_assets_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(native_asset_location, &receiver)
});
let receiver_wnds_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &receiver)
});
// Sender's balance is reduced by amount sent plus delivery fees
assert!(sender_balance_after < sender_balance_before - native_amount_to_send);
// Sender's balance is reduced by foreign amount sent
assert_eq!(sender_wnds_after, sender_wnds_before - foreign_amount_to_send);
// Receiver's assets is increased
assert!(receiver_assets_after > receiver_assets_before);
// Receiver's assets 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!(receiver_assets_after < receiver_assets_before + native_amount_to_send);
// Receiver's balance is increased by foreign amount sent
assert_eq!(receiver_wnds_after, receiver_wnds_before + foreign_amount_to_send);
}
/// Reserve Transfers of native asset from Parachain to System Parachain should work
// ===========================================================================
// ======= Transfer - Native + Bridged Assets - Parachain->AssetHub ==========
// ===========================================================================
/// Transfers of native asset plus bridged asset from some Parachain to AssetHub
/// while paying fees using native asset.
#[test]
fn transfer_foreign_assets_from_para_to_asset_hub() {
// Init values for Parachain
let destination = PenpalA::sibling_location_of(AssetHubRococo::para_id());
let sender = PenpalASender::get();
let native_amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000;
let native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let assets_owner = PenpalAssetOwner::get();
// Foreign asset used: bridged WND
let foreign_amount_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000;
let wnd_at_rococo_parachains =
v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]);
let wnd_at_rococo_parachains_latest: Location = wnd_at_rococo_parachains.try_into().unwrap();
// Configure destination chain to trust AH as reserve of WND
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as Chain>::System::set_storage(
<PenpalA as Chain>::RuntimeOrigin::root(),
vec![(
penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(),
Location::new(2, [GlobalConsensus(Westend)]).encode(),
)],
));
});
PenpalA::force_create_foreign_asset(
wnd_at_rococo_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
AssetHubRococo::force_create_foreign_asset(
wnd_at_rococo_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
// fund Parachain's sender account
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
native_asset_location,
sender.clone(),
native_amount_to_send * 2,
);
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
wnd_at_rococo_parachains,
sender.clone(),
foreign_amount_to_send * 2,
);
// Init values for System Parachain
let receiver = AssetHubRococoReceiver::get();
let penpal_location_as_seen_by_ahr = AssetHubRococo::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location_as_seen_by_ahr);
// fund Parachain's SA on AssetHub with the assets held in reserve
AssetHubRococo::fund_accounts(vec![(
sov_penpal_on_ahr.clone().into(),
native_amount_to_send * 2,
)]);
AssetHubRococo::mint_foreign_asset(
<AssetHubRococo as Chain>::RuntimeOrigin::signed(assets_owner),
wnd_at_rococo_parachains,
sov_penpal_on_ahr,
foreign_amount_to_send * 2,
);
// Assets to send
let assets: Vec<Asset> = vec![
(Parent, native_amount_to_send).into(),
(wnd_at_rococo_parachains_latest, foreign_amount_to_send).into(),
];
let fee_asset_id = AssetId(Parent.into());
let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32;
// Init Test
let test_args = TestContext {
sender: sender.clone(),
receiver: receiver.clone(),
args: TestArgs::new_para(
destination.clone(),
receiver.clone(),
native_amount_to_send,
assets.into(),
None,
fee_asset_item,
),
};
let mut test = ParaToSystemParaTest::new(test_args);
// Query initial balances
let sender_native_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(native_asset_location, &sender)
});
let sender_wnds_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &sender)
});
let receiver_native_before = test.receiver.balance;
let receiver_wnds_before = AssetHubRococo::execute_with(|| {
type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &receiver)
});
// Set assertions and dispatchables
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_ah_transfer_assets);
test.assert();
// Query final balances
let sender_native_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(native_asset_location, &sender)
});
let sender_wnds_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &sender)
});
let receiver_native_after = test.receiver.balance;
let receiver_wnds_after = AssetHubRococo::execute_with(|| {
type ForeignAssets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &receiver)
});
// Sender's balance is reduced by amount sent plus delivery fees
assert!(sender_native_after < sender_native_before - native_amount_to_send);
// Sender's balance is reduced by foreign amount sent
assert_eq!(sender_wnds_after, sender_wnds_before - foreign_amount_to_send);
// Receiver's balance is increased
assert!(receiver_native_after > receiver_native_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!(receiver_native_after < receiver_native_before + native_amount_to_send);
// Receiver's balance is increased by foreign amount sent
assert_eq!(receiver_wnds_after, receiver_wnds_before + foreign_amount_to_send);
}
// ==============================================================================
// ===== Transfer - Native + Bridged Assets - Parachain->AssetHub->Parachain ====
// ==============================================================================
/// Transfers of native asset plus bridged asset from Parachain to Parachain
/// (through AssetHub reserve) with fees paid using native asset.
#[test]
fn transfer_foreign_assets_from_para_to_para_through_asset_hub() {
// Init values for Parachain Origin
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let sender = PenpalASender::get();
let roc_to_send: Balance = ROCOCO_ED * 10000;
let assets_owner = PenpalAssetOwner::get();
let roc_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let roc_location_latest: Location = roc_location.try_into().unwrap();
let sender_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalA::para_id());
let sov_of_sender_on_ah = AssetHubRococo::sovereign_account_id_of(sender_as_seen_by_ah);
let receiver_as_seen_by_ah = AssetHubRococo::sibling_location_of(PenpalB::para_id());
let sov_of_receiver_on_ah = AssetHubRococo::sovereign_account_id_of(receiver_as_seen_by_ah);
let wnd_to_send = ASSET_HUB_ROCOCO_ED * 10_000_000;
// Configure destination chain to trust AH as reserve of WND
PenpalB::execute_with(|| {
assert_ok!(<PenpalB as Chain>::System::set_storage(
<PenpalB as Chain>::RuntimeOrigin::root(),
vec![(
penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(),
Location::new(2, [GlobalConsensus(Westend)]).encode(),
)],
));
});
// Register WND as foreign asset and transfer it around the Rococo ecosystem
let wnd_at_rococo_parachains =
v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]);
let wnd_at_rococo_parachains_latest: Location = wnd_at_rococo_parachains.try_into().unwrap();
AssetHubRococo::force_create_foreign_asset(
wnd_at_rococo_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
PenpalA::force_create_foreign_asset(
wnd_at_rococo_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
PenpalB::force_create_foreign_asset(
wnd_at_rococo_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
// fund Parachain's sender account
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
roc_location,
sender.clone(),
roc_to_send * 2,
);
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
wnd_at_rococo_parachains,
sender.clone(),
wnd_to_send * 2,
);
// fund the Parachain Origin's SA on Asset Hub with the assets held in reserve
AssetHubRococo::fund_accounts(vec![(sov_of_sender_on_ah.clone().into(), roc_to_send * 2)]);
AssetHubRococo::mint_foreign_asset(
<AssetHubRococo as Chain>::RuntimeOrigin::signed(assets_owner),
wnd_at_rococo_parachains,
sov_of_sender_on_ah.clone(),
wnd_to_send * 2,
);
// Init values for Parachain Destination
let receiver = PenpalBReceiver::get();
// Assets to send
let assets: Vec<Asset> = vec![
(roc_location_latest.clone(), roc_to_send).into(),
(wnd_at_rococo_parachains_latest, wnd_to_send).into(),
];
let fee_asset_id: AssetId = roc_location_latest.into();
let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32;
// Init Test
let test_args = TestContext {
sender: sender.clone(),
receiver: receiver.clone(),
args: TestArgs::new_para(
destination,
receiver.clone(),
roc_to_send,
assets.into(),
None,
fee_asset_item,
),
};
let mut test = ParaToParaThroughAHTest::new(test_args);
// Query initial balances
let sender_rocs_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_location, &sender)
});
let sender_wnds_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &sender)
});
let rocs_in_sender_reserve_on_ahr_before =
<AssetHubRococo as Chain>::account_data_of(sov_of_sender_on_ah.clone()).free;
let wnds_in_sender_reserve_on_ahr_before = AssetHubRococo::execute_with(|| {
type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(wnd_at_rococo_parachains, &sov_of_sender_on_ah)
});
let rocs_in_receiver_reserve_on_ahr_before =
<AssetHubRococo as Chain>::account_data_of(sov_of_receiver_on_ah.clone()).free;
let wnds_in_receiver_reserve_on_ahr_before = AssetHubRococo::execute_with(|| {
type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(wnd_at_rococo_parachains, &sov_of_receiver_on_ah)
});
let receiver_rocs_before = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_location, &receiver)
});
let receiver_wnds_before = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &receiver)
});
// Set assertions and dispatchables
test.set_assertion::<PenpalA>(para_to_para_through_hop_sender_assertions);
test.set_assertion::<AssetHubRococo>(para_to_para_assethub_hop_assertions);
test.set_assertion::<PenpalB>(para_to_para_through_hop_receiver_assertions);
test.set_dispatchable::<PenpalA>(para_to_para_transfer_assets_through_ah);
test.assert();
// Query final balances
let sender_rocs_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_location, &sender)
});
let sender_wnds_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &sender)
});
let wnds_in_sender_reserve_on_ahr_after = AssetHubRococo::execute_with(|| {
type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(wnd_at_rococo_parachains, &sov_of_sender_on_ah)
});
let rocs_in_sender_reserve_on_ahr_after =
<AssetHubRococo as Chain>::account_data_of(sov_of_sender_on_ah).free;
let wnds_in_receiver_reserve_on_ahr_after = AssetHubRococo::execute_with(|| {
type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(wnd_at_rococo_parachains, &sov_of_receiver_on_ah)
});
let rocs_in_receiver_reserve_on_ahr_after =
<AssetHubRococo as Chain>::account_data_of(sov_of_receiver_on_ah).free;
let receiver_rocs_after = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_location, &receiver)
});
let receiver_wnds_after = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_at_rococo_parachains, &receiver)
});
// Sender's balance is reduced by amount sent plus delivery fees
assert!(sender_rocs_after < sender_rocs_before - roc_to_send);
assert_eq!(sender_wnds_after, sender_wnds_before - wnd_to_send);
// Sovereign accounts on reserve are changed accordingly
assert_eq!(
rocs_in_sender_reserve_on_ahr_after,
rocs_in_sender_reserve_on_ahr_before - roc_to_send
);
assert_eq!(
wnds_in_sender_reserve_on_ahr_after,
wnds_in_sender_reserve_on_ahr_before - wnd_to_send
);
assert!(rocs_in_receiver_reserve_on_ahr_after > rocs_in_receiver_reserve_on_ahr_before);
assert_eq!(
wnds_in_receiver_reserve_on_ahr_after,
wnds_in_receiver_reserve_on_ahr_before + wnd_to_send
);
// Receiver's balance is increased
assert!(receiver_rocs_after > receiver_rocs_before);
assert_eq!(receiver_wnds_after, receiver_wnds_before + wnd_to_send);
}
// ==============================================================================================
// ==== Bidirectional Transfer - Native + Teleportable Foreign Assets - Parachain<->AssetHub ====
// ==============================================================================================
/// Transfers of native asset plus teleportable foreign asset from Parachain to AssetHub and back
/// with fees paid using native asset.
#[test]
fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explicit_transfer_types() {
do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
para_to_asset_hub_teleport_foreign_assets,
asset_hub_to_para_teleport_foreign_assets,
);
}
@@ -13,6 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
mod foreign_assets_transfers;
mod reserve_transfer;
mod send;
mod set_xcm_versions;
@@ -47,7 +47,7 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) {
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Burned { asset_id, owner, balance, .. }
) => {
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).unwrap(),
owner: *owner == t.sender.account_id,
balance: *balance == t.args.amount,
},
@@ -55,70 +55,91 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) {
);
}
fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) {
pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
AssetHubRococo::assert_xcm_pallet_attempted_complete(None);
AssetHubRococo::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(
864_610_000,
8_799,
)));
let sov_acc_of_dest = AssetHubRococo::sovereign_account_id_of(t.args.dest.clone());
for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() {
let expected_id = asset.id.0.clone().try_into().unwrap();
let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
if idx == t.args.fee_asset_item as usize {
assert_expected_events!(
AssetHubRococo,
vec![
// Amount of native asset is transferred to Parachain's Sovereign account
RuntimeEvent::Balances(
pallet_balances::Event::Transfer { from, to, amount }
) => {
from: *from == t.sender.account_id,
to: *to == sov_acc_of_dest,
amount: *amount == asset_amount,
},
]
);
} else {
assert_expected_events!(
AssetHubRococo,
vec![
// Amount of foreign asset is transferred to Parachain's Sovereign account
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Transferred { asset_id, from, to, amount },
) => {
asset_id: *asset_id == expected_id,
from: *from == t.sender.account_id,
to: *to == sov_acc_of_dest,
amount: *amount == asset_amount,
},
]
);
}
}
assert_expected_events!(
AssetHubRococo,
vec![
// Amount to reserve transfer is transferred to Parachain's Sovereign account
RuntimeEvent::Balances(
pallet_balances::Event::Transfer { from, to, amount }
) => {
from: *from == t.sender.account_id,
to: *to == AssetHubRococo::sovereign_account_id_of(
t.args.dest.clone()
),
amount: *amount == t.args.amount,
},
// Transport fees are paid
RuntimeEvent::PolkadotXcm(
pallet_xcm::Event::FeesPaid { .. }
) => {},
RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {},
]
);
AssetHubRococo::assert_xcm_pallet_sent();
}
fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) {
pub fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
PenpalA::assert_xcmp_queue_success(None);
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == system_para_native_asset_location,
owner: *owner == t.receiver.account_id,
},
]
);
for asset in t.args.assets.into_inner().into_iter() {
let expected_id = asset.id.0.try_into().unwrap();
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == expected_id,
owner: *owner == t.receiver.account_id,
},
]
);
}
}
fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) {
pub 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![
// Amount to reserve transfer is transferred to Parachain's Sovereign account
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Burned { asset_id, owner, balance, .. }
) => {
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
owner: *owner == t.sender.account_id,
balance: *balance == t.args.amount,
},
]
);
PenpalA::assert_xcm_pallet_attempted_complete(None);
for asset in t.args.assets.into_inner().into_iter() {
let expected_id = asset.id.0.try_into().unwrap();
let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Burned { asset_id, owner, balance }
) => {
asset_id: *asset_id == expected_id,
owner: *owner == t.sender.account_id,
balance: *balance == asset_amount,
},
]
);
}
}
fn para_to_relay_receiver_assertions(t: ParaToRelayTest) {
@@ -150,25 +171,57 @@ fn para_to_relay_receiver_assertions(t: ParaToRelayTest) {
);
}
fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) {
pub 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()),
);
AssetHubRococo::assert_xcmp_queue_success(None);
let sov_acc_of_penpal = AssetHubRococo::sovereign_account_id_of(t.args.dest.clone());
for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() {
let expected_id = asset.id.0.clone().try_into().unwrap();
let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
if idx == t.args.fee_asset_item as usize {
assert_expected_events!(
AssetHubRococo,
vec![
// Amount of native is withdrawn from Parachain's Sovereign account
RuntimeEvent::Balances(
pallet_balances::Event::Burned { who, amount }
) => {
who: *who == sov_acc_of_penpal.clone().into(),
amount: *amount == asset_amount,
},
RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => {
who: *who == t.receiver.account_id,
},
]
);
} else {
assert_expected_events!(
AssetHubRococo,
vec![
// Amount of foreign asset is transferred from Parachain's Sovereign account
// to Receiver's account
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Burned { asset_id, owner, balance },
) => {
asset_id: *asset_id == expected_id,
owner: *owner == sov_acc_of_penpal,
balance: *balance == asset_amount,
},
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Issued { asset_id, owner, amount },
) => {
asset_id: *asset_id == expected_id,
owner: *owner == t.receiver.account_id,
amount: *amount == asset_amount,
},
]
);
}
}
assert_expected_events!(
AssetHubRococo,
vec![
// Amount to reserve transfer is withdrawn from Parachain's Sovereign account
RuntimeEvent::Balances(
pallet_balances::Event::Burned { who, amount }
) => {
who: *who == sov_penpal_on_ahr.clone().into(),
amount: *amount == t.args.amount,
},
RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {},
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: true, .. }
) => {},
@@ -212,10 +265,9 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) {
fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let reservable_asset_location =
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap();
PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8799)));
assert_expected_events!(
PenpalA,
@@ -246,13 +298,13 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) {
fn system_para_to_para_assets_receiver_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let system_para_asset_location =
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap();
PenpalA::assert_xcmp_queue_success(None);
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).unwrap(),
owner: *owner == t.receiver.account_id,
},
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => {
@@ -304,7 +356,7 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) {
PenpalA,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).unwrap(),
owner: *owner == t.receiver.account_id,
},
RuntimeEvent::MessageQueue(
@@ -314,29 +366,27 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) {
);
}
fn para_to_para_through_relay_sender_assertions(t: ParaToParaThroughRelayTest) {
pub fn para_to_para_through_hop_sender_assertions<Hop: Clone>(t: Test<PenpalA, PenpalB, Hop>) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let relay_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
PenpalA::assert_xcm_pallet_attempted_complete(None);
// XCM sent to relay reserve
PenpalA::assert_parachain_system_ump_sent();
assert_expected_events!(
PenpalA,
vec![
// Amount to reserve transfer is transferred to Parachain's Sovereign account
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Burned { asset_id, owner, balance },
) => {
asset_id: *asset_id == relay_asset_location,
owner: *owner == t.sender.account_id,
balance: *balance == t.args.amount,
},
]
);
for asset in t.args.assets.into_inner() {
let expected_id = asset.id.0.clone().try_into().unwrap();
let amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
assert_expected_events!(
PenpalA,
vec![
// Amount to reserve transfer is transferred to Parachain's Sovereign account
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Burned { asset_id, owner, balance },
) => {
asset_id: *asset_id == expected_id,
owner: *owner == t.sender.account_id,
balance: *balance == amount,
},
]
);
}
}
fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) {
@@ -369,22 +419,22 @@ fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) {
);
}
fn para_to_para_through_relay_receiver_assertions(t: ParaToParaThroughRelayTest) {
pub fn para_to_para_through_hop_receiver_assertions<Hop: Clone>(t: Test<PenpalA, PenpalB, Hop>) {
type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent;
let relay_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
PenpalB::assert_xcmp_queue_success(None);
assert_expected_events!(
PenpalB,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == relay_asset_location,
owner: *owner == t.receiver.account_id,
},
]
);
for asset in t.args.assets.into_inner().into_iter() {
let expected_id = asset.id.0.try_into().unwrap();
assert_expected_events!(
PenpalB,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == expected_id,
owner: *owner == t.receiver.account_id,
},
]
);
}
}
fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult {
@@ -526,8 +576,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
let amount_to_send: Balance = ROCOCO_ED * 1000;
// Init values fot Parachain
let relay_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let relay_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let receiver = PenpalAReceiver::get();
// Init Test
@@ -577,8 +626,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
let amount_to_send: Balance = ROCOCO_ED * 1000;
let assets: Assets = (Parent, amount_to_send).into();
let asset_owner = PenpalAssetOwner::get();
let relay_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let relay_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
// fund Parachain's sender account
PenpalA::mint_foreign_asset(
@@ -654,8 +702,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() {
let assets: Assets = (Parent, amount_to_send).into();
// Init values for Parachain
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let receiver = PenpalAReceiver::get();
// Init Test
@@ -711,8 +758,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
let sender = PenpalASender::get();
let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000;
let assets: Assets = (Parent, amount_to_send).into();
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let asset_owner = PenpalAssetOwner::get();
// fund Parachain's sender account
@@ -776,9 +822,9 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
assert!(receiver_balance_after < receiver_balance_before + amount_to_send);
}
// =========================================================================
// ======= Reserve Transfers - Non-system Asset - AssetHub<>Parachain ======
// =========================================================================
// ==================================================================================
// ======= Reserve Transfers - Native + Non-system Asset - AssetHub<>Parachain ======
// ==================================================================================
/// Reserve Transfers of a local asset and native asset from System Parachain to Parachain should
/// work
#[test]
@@ -817,10 +863,9 @@ fn reserve_transfer_assets_from_system_para_to_para() {
// Init values for Parachain
let receiver = PenpalAReceiver::get();
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let system_para_foreign_asset_location =
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap();
// Init Test
let para_test_args = TestContext {
@@ -905,10 +950,9 @@ fn reserve_transfer_assets_from_para_to_system_para() {
let penpal_asset_owner = PenpalAssetOwner::get();
let penpal_asset_owner_signer = <PenpalA as Chain>::RuntimeOrigin::signed(penpal_asset_owner);
let asset_location_on_penpal =
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap();
let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap();
let system_asset_location_on_penpal =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_asset_location_on_penpal = v3::Location::try_from(RelayLocation::get()).unwrap();
let assets: Assets = vec![
(Parent, fee_amount_to_send).into(),
(asset_location_on_penpal_latest, asset_amount_to_send).into(),
@@ -938,10 +982,9 @@ fn reserve_transfer_assets_from_para_to_system_para() {
let receiver = AssetHubRococoReceiver::get();
let penpal_location_as_seen_by_ahr = AssetHubRococo::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location_as_seen_by_ahr);
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let system_para_foreign_asset_location =
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap();
let ah_asset_owner = AssetHubRococoAssetOwner::get();
let ah_asset_owner_signer = <AssetHubRococo as Chain>::RuntimeOrigin::signed(ah_asset_owner);
@@ -1029,15 +1072,14 @@ fn reserve_transfer_assets_from_para_to_system_para() {
/// Reserve Transfers of native asset from Parachain to Parachain (through Relay reserve) should
/// work
#[test]
fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
fn reserve_transfer_native_asset_from_para_to_para_through_relay() {
// Init values for Parachain Origin
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let sender = PenpalASender::get();
let amount_to_send: Balance = ROCOCO_ED * 10000;
let asset_owner = PenpalAssetOwner::get();
let assets = (Parent, amount_to_send).into();
let relay_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let relay_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let sender_as_seen_by_relay = Rococo::child_location_of(PenpalA::para_id());
let sov_of_sender_on_relay = Rococo::sovereign_account_id_of(sender_as_seen_by_relay);
@@ -1074,9 +1116,9 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
});
// Set assertions and dispatchables
test.set_assertion::<PenpalA>(para_to_para_through_relay_sender_assertions);
test.set_assertion::<PenpalA>(para_to_para_through_hop_sender_assertions);
test.set_assertion::<Rococo>(para_to_para_relay_hop_assertions);
test.set_assertion::<PenpalB>(para_to_para_through_relay_receiver_assertions);
test.set_assertion::<PenpalB>(para_to_para_through_hop_receiver_assertions);
test.set_dispatchable::<PenpalA>(para_to_para_through_relay_limited_reserve_transfer_assets);
test.assert();
@@ -112,10 +112,9 @@ fn swap_locally_on_chain_using_local_assets() {
#[test]
fn swap_locally_on_chain_using_foreign_assets() {
let asset_native =
Box::new(v3::Location::try_from(RelayLocation::get()).expect("conversion works"));
let asset_native = Box::new(v3::Location::try_from(RelayLocation::get()).unwrap());
let asset_location_on_penpal =
v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works");
v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).unwrap();
let foreign_asset_at_asset_hub_rococo =
v3::Location::new(1, [v3::Junction::Parachain(PenpalA::para_id().into())])
.appended_with(asset_location_on_penpal)
@@ -110,8 +110,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) {
fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
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();
@@ -204,8 +203,7 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) {
let (_, expected_asset_amount) =
non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap();
let checking_account = <PenpalA as PenpalAPallet>::PolkadotXcm::check_account();
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
PenpalA::assert_xcmp_queue_success(None);
@@ -414,22 +412,23 @@ fn teleport_to_other_system_parachains_works() {
);
}
/// 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() {
/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets while paying
/// fees using (reserve transferred) native asset.
pub fn do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
para_to_ah_dispatchable: fn(ParaToSystemParaTest) -> DispatchResult,
ah_to_para_dispatchable: fn(SystemParaToParaTest) -> DispatchResult,
) {
// Init values for Parachain
let fee_amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 10000;
let asset_location_on_penpal =
v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works");
v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).unwrap();
let asset_id_on_penpal = match asset_location_on_penpal.last() {
Some(v3::Junction::GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
let asset_amount_to_send = ASSET_HUB_ROCOCO_ED * 1000;
let asset_owner = PenpalAssetOwner::get();
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let sender = PenpalASender::get();
let penpal_check_account = <PenpalA as PenpalAPallet>::PolkadotXcm::check_account();
let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubRococo::para_id());
@@ -515,7 +514,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
penpal_to_ah.set_assertion::<PenpalA>(penpal_to_ah_foreign_assets_sender_assertions);
penpal_to_ah.set_assertion::<AssetHubRococo>(penpal_to_ah_foreign_assets_receiver_assertions);
penpal_to_ah.set_dispatchable::<PenpalA>(para_to_system_para_transfer_assets);
penpal_to_ah.set_dispatchable::<PenpalA>(para_to_ah_dispatchable);
penpal_to_ah.assert();
let penpal_sender_balance_after = PenpalA::execute_with(|| {
@@ -622,7 +621,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
ah_to_penpal.set_assertion::<AssetHubRococo>(ah_to_penpal_foreign_assets_sender_assertions);
ah_to_penpal.set_assertion::<PenpalA>(ah_to_penpal_foreign_assets_receiver_assertions);
ah_to_penpal.set_dispatchable::<AssetHubRococo>(system_para_to_para_transfer_assets);
ah_to_penpal.set_dispatchable::<AssetHubRococo>(ah_to_para_dispatchable);
ah_to_penpal.assert();
let ah_sender_balance_after = ah_to_penpal.sender.balance;
@@ -660,3 +659,13 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
// Receiver's balance is increased by exact amount
assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send);
}
/// 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() {
do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
para_to_system_para_transfer_assets,
system_para_to_para_transfer_assets,
);
}
@@ -30,6 +30,7 @@ mod imports {
prelude::{AccountId32 as AccountId32Junction, *},
v3,
};
pub use xcm_executor::traits::TransferType;
// Cumulus
pub use asset_test_utils::xcm_helpers;
@@ -85,6 +86,7 @@ mod imports {
pub type SystemParaToParaTest = Test<AssetHubWestend, PenpalA>;
pub type ParaToSystemParaTest = Test<PenpalA, AssetHubWestend>;
pub type ParaToParaThroughRelayTest = Test<PenpalA, PenpalB, Westend>;
pub type ParaToParaThroughAHTest = Test<PenpalA, PenpalB, AssetHubWestend>;
}
#[cfg(test)]
@@ -0,0 +1,609 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::reserve_transfer::*;
use crate::{
imports::*,
tests::teleport::do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt,
};
fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) {
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
let sov_penpal_a_on_ah = AssetHubWestend::sovereign_account_id_of(
AssetHubWestend::sibling_location_of(PenpalA::para_id()),
);
let sov_penpal_b_on_ah = AssetHubWestend::sovereign_account_id_of(
AssetHubWestend::sibling_location_of(PenpalB::para_id()),
);
assert_expected_events!(
AssetHubWestend,
vec![
// Withdrawn from sender parachain SA
RuntimeEvent::Balances(
pallet_balances::Event::Burned { who, amount }
) => {
who: *who == sov_penpal_a_on_ah,
amount: *amount == t.args.amount,
},
// Deposited to receiver parachain SA
RuntimeEvent::Balances(
pallet_balances::Event::Minted { who, .. }
) => {
who: *who == sov_penpal_b_on_ah,
},
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
}
fn ah_to_para_transfer_assets(t: SystemParaToParaTest) -> DispatchResult {
let fee_idx = t.args.fee_asset_item as usize;
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
<AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::transfer_assets_using_type(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(TransferType::LocalReserve),
bx!(fee.id.into()),
bx!(TransferType::LocalReserve),
t.args.weight_limit,
)
}
fn para_to_ah_transfer_assets(t: ParaToSystemParaTest) -> DispatchResult {
let fee_idx = t.args.fee_asset_item as usize;
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(TransferType::DestinationReserve),
bx!(fee.id.into()),
bx!(TransferType::DestinationReserve),
t.args.weight_limit,
)
}
fn para_to_para_transfer_assets_through_ah(t: ParaToParaThroughAHTest) -> DispatchResult {
let fee_idx = t.args.fee_asset_item as usize;
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
let asset_hub_location: Location = PenpalA::sibling_location_of(AssetHubWestend::para_id());
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(TransferType::RemoteReserve(asset_hub_location.clone().into())),
bx!(fee.id.into()),
bx!(TransferType::RemoteReserve(asset_hub_location.into())),
t.args.weight_limit,
)
}
fn para_to_asset_hub_teleport_foreign_assets(t: ParaToSystemParaTest) -> DispatchResult {
let fee_idx = t.args.fee_asset_item as usize;
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(TransferType::Teleport),
bx!(fee.id.into()),
bx!(TransferType::DestinationReserve),
t.args.weight_limit,
)
}
fn asset_hub_to_para_teleport_foreign_assets(t: SystemParaToParaTest) -> DispatchResult {
let fee_idx = t.args.fee_asset_item as usize;
let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap();
<AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::transfer_assets_using_type(
t.signed_origin,
bx!(t.args.dest.into()),
bx!(t.args.beneficiary.into()),
bx!(t.args.assets.into()),
bx!(TransferType::Teleport),
bx!(fee.id.into()),
bx!(TransferType::LocalReserve),
t.args.weight_limit,
)
}
// ===========================================================================
// ======= Transfer - Native + Bridged Assets - AssetHub->Parachain ==========
// ===========================================================================
/// Transfers of native asset plus bridged asset from AssetHub to some Parachain
/// while paying fees using native asset.
#[test]
fn transfer_foreign_assets_from_asset_hub_to_para() {
let destination = AssetHubWestend::sibling_location_of(PenpalA::para_id());
let sender = AssetHubWestendSender::get();
let native_amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000;
let native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let receiver = PenpalAReceiver::get();
let assets_owner = PenpalAssetOwner::get();
// Foreign asset used: bridged ROC
let foreign_amount_to_send = ASSET_HUB_WESTEND_ED * 10_000_000;
let roc_at_westend_parachains =
v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Rococo)]);
let roc_at_westend_parachains_latest: Location = roc_at_westend_parachains.try_into().unwrap();
// Configure destination chain to trust AH as reserve of ROC
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as Chain>::System::set_storage(
<PenpalA as Chain>::RuntimeOrigin::root(),
vec![(
penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(),
Location::new(2, [GlobalConsensus(Rococo)]).encode(),
)],
));
});
PenpalA::force_create_foreign_asset(
roc_at_westend_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
AssetHubWestend::force_create_foreign_asset(
roc_at_westend_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
AssetHubWestend::mint_foreign_asset(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(assets_owner),
roc_at_westend_parachains,
sender.clone(),
foreign_amount_to_send * 2,
);
// Assets to send
let assets: Vec<Asset> = vec![
(Parent, native_amount_to_send).into(),
(roc_at_westend_parachains_latest, foreign_amount_to_send).into(),
];
let fee_asset_id = AssetId(Parent.into());
let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32;
// Init Test
let test_args = TestContext {
sender: sender.clone(),
receiver: receiver.clone(),
args: TestArgs::new_para(
destination.clone(),
receiver.clone(),
native_amount_to_send,
assets.into(),
None,
fee_asset_item,
),
};
let mut test = SystemParaToParaTest::new(test_args);
// Query initial balances
let sender_balance_before = test.sender.balance;
let sender_rocs_before = AssetHubWestend::execute_with(|| {
type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &sender)
});
let receiver_assets_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(native_asset_location.into(), &receiver)
});
let receiver_rocs_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &receiver)
});
// Set assertions and dispatchables
test.set_assertion::<AssetHubWestend>(system_para_to_para_sender_assertions);
test.set_assertion::<PenpalA>(system_para_to_para_receiver_assertions);
test.set_dispatchable::<AssetHubWestend>(ah_to_para_transfer_assets);
test.assert();
// Query final balances
let sender_balance_after = test.sender.balance;
let sender_rocs_after = AssetHubWestend::execute_with(|| {
type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &sender)
});
let receiver_assets_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(native_asset_location, &receiver)
});
let receiver_rocs_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &receiver)
});
// Sender's balance is reduced by amount sent plus delivery fees
assert!(sender_balance_after < sender_balance_before - native_amount_to_send);
// Sender's balance is reduced by foreign amount sent
assert_eq!(sender_rocs_after, sender_rocs_before - foreign_amount_to_send);
// Receiver's assets is increased
assert!(receiver_assets_after > receiver_assets_before);
// Receiver's assets 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!(receiver_assets_after < receiver_assets_before + native_amount_to_send);
// Receiver's balance is increased by foreign amount sent
assert_eq!(receiver_rocs_after, receiver_rocs_before + foreign_amount_to_send);
}
/// Reserve Transfers of native asset from Parachain to System Parachain should work
// ===========================================================================
// ======= Transfer - Native + Bridged Assets - Parachain->AssetHub ==========
// ===========================================================================
/// Transfers of native asset plus bridged asset from some Parachain to AssetHub
/// while paying fees using native asset.
#[test]
fn transfer_foreign_assets_from_para_to_asset_hub() {
// Init values for Parachain
let destination = PenpalA::sibling_location_of(AssetHubWestend::para_id());
let sender = PenpalASender::get();
let native_amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 10000;
let native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let assets_owner = PenpalAssetOwner::get();
// Foreign asset used: bridged ROC
let foreign_amount_to_send = ASSET_HUB_WESTEND_ED * 10_000_000;
let roc_at_westend_parachains =
v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Rococo)]);
let roc_at_westend_parachains_latest: Location = roc_at_westend_parachains.try_into().unwrap();
// Configure destination chain to trust AH as reserve of ROC
PenpalA::execute_with(|| {
assert_ok!(<PenpalA as Chain>::System::set_storage(
<PenpalA as Chain>::RuntimeOrigin::root(),
vec![(
penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(),
Location::new(2, [GlobalConsensus(Rococo)]).encode(),
)],
));
});
PenpalA::force_create_foreign_asset(
roc_at_westend_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
AssetHubWestend::force_create_foreign_asset(
roc_at_westend_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
// fund Parachain's sender account
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
native_asset_location,
sender.clone(),
native_amount_to_send * 2,
);
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
roc_at_westend_parachains,
sender.clone(),
foreign_amount_to_send * 2,
);
// Init values for System Parachain
let receiver = AssetHubWestendReceiver::get();
let penpal_location_as_seen_by_ahr = AssetHubWestend::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ahr =
AssetHubWestend::sovereign_account_id_of(penpal_location_as_seen_by_ahr);
// fund Parachain's SA on AssetHub with the assets held in reserve
AssetHubWestend::fund_accounts(vec![(
sov_penpal_on_ahr.clone().into(),
native_amount_to_send * 2,
)]);
AssetHubWestend::mint_foreign_asset(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(assets_owner),
roc_at_westend_parachains,
sov_penpal_on_ahr,
foreign_amount_to_send * 2,
);
// Assets to send
let assets: Vec<Asset> = vec![
(Parent, native_amount_to_send).into(),
(roc_at_westend_parachains_latest, foreign_amount_to_send).into(),
];
let fee_asset_id = AssetId(Parent.into());
let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32;
// Init Test
let test_args = TestContext {
sender: sender.clone(),
receiver: receiver.clone(),
args: TestArgs::new_para(
destination.clone(),
receiver.clone(),
native_amount_to_send,
assets.into(),
None,
fee_asset_item,
),
};
let mut test = ParaToSystemParaTest::new(test_args);
// Query initial balances
let sender_native_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(native_asset_location, &sender)
});
let sender_rocs_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &sender)
});
let receiver_native_before = test.receiver.balance;
let receiver_rocs_before = AssetHubWestend::execute_with(|| {
type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &receiver)
});
// Set assertions and dispatchables
test.set_assertion::<PenpalA>(para_to_system_para_sender_assertions);
test.set_assertion::<AssetHubWestend>(para_to_system_para_receiver_assertions);
test.set_dispatchable::<PenpalA>(para_to_ah_transfer_assets);
test.assert();
// Query final balances
let sender_native_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(native_asset_location, &sender)
});
let sender_rocs_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &sender)
});
let receiver_native_after = test.receiver.balance;
let receiver_rocs_after = AssetHubWestend::execute_with(|| {
type ForeignAssets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &receiver)
});
// Sender's balance is reduced by amount sent plus delivery fees
assert!(sender_native_after < sender_native_before - native_amount_to_send);
// Sender's balance is reduced by foreign amount sent
assert_eq!(sender_rocs_after, sender_rocs_before - foreign_amount_to_send);
// Receiver's balance is increased
assert!(receiver_native_after > receiver_native_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!(receiver_native_after < receiver_native_before + native_amount_to_send);
// Receiver's balance is increased by foreign amount sent
assert_eq!(receiver_rocs_after, receiver_rocs_before + foreign_amount_to_send);
}
// ==============================================================================
// ===== Transfer - Native + Bridged Assets - Parachain->AssetHub->Parachain ====
// ==============================================================================
/// Transfers of native asset plus bridged asset from Parachain to Parachain
/// (through AssetHub reserve) with fees paid using native asset.
#[test]
fn transfer_foreign_assets_from_para_to_para_through_asset_hub() {
// Init values for Parachain Origin
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let sender = PenpalASender::get();
let wnd_to_send: Balance = WESTEND_ED * 10000;
let assets_owner = PenpalAssetOwner::get();
let wnd_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let wnd_location_latest: Location = wnd_location.try_into().unwrap();
let sender_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalA::para_id());
let sov_of_sender_on_ah = AssetHubWestend::sovereign_account_id_of(sender_as_seen_by_ah);
let receiver_as_seen_by_ah = AssetHubWestend::sibling_location_of(PenpalB::para_id());
let sov_of_receiver_on_ah = AssetHubWestend::sovereign_account_id_of(receiver_as_seen_by_ah);
let roc_to_send = ASSET_HUB_WESTEND_ED * 10_000_000;
// Configure destination chain to trust AH as reserve of ROC
PenpalB::execute_with(|| {
assert_ok!(<PenpalB as Chain>::System::set_storage(
<PenpalB as Chain>::RuntimeOrigin::root(),
vec![(
penpal_runtime::xcm_config::CustomizableAssetFromSystemAssetHub::key().to_vec(),
Location::new(2, [GlobalConsensus(Rococo)]).encode(),
)],
));
});
// Register ROC as foreign asset and transfer it around the Westend ecosystem
let roc_at_westend_parachains =
v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Rococo)]);
let roc_at_westend_parachains_latest: Location = roc_at_westend_parachains.try_into().unwrap();
AssetHubWestend::force_create_foreign_asset(
roc_at_westend_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
PenpalA::force_create_foreign_asset(
roc_at_westend_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
PenpalB::force_create_foreign_asset(
roc_at_westend_parachains,
assets_owner.clone(),
false,
ASSET_MIN_BALANCE,
vec![],
);
// fund Parachain's sender account
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
wnd_location,
sender.clone(),
wnd_to_send * 2,
);
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(assets_owner.clone()),
roc_at_westend_parachains,
sender.clone(),
roc_to_send * 2,
);
// fund the Parachain Origin's SA on Asset Hub with the assets held in reserve
AssetHubWestend::fund_accounts(vec![(sov_of_sender_on_ah.clone().into(), wnd_to_send * 2)]);
AssetHubWestend::mint_foreign_asset(
<AssetHubWestend as Chain>::RuntimeOrigin::signed(assets_owner),
roc_at_westend_parachains,
sov_of_sender_on_ah.clone(),
roc_to_send * 2,
);
// Init values for Parachain Destination
let receiver = PenpalBReceiver::get();
// Assets to send
let assets: Vec<Asset> = vec![
(wnd_location_latest.clone(), wnd_to_send).into(),
(roc_at_westend_parachains_latest, roc_to_send).into(),
];
let fee_asset_id: AssetId = wnd_location_latest.into();
let fee_asset_item = assets.iter().position(|a| a.id == fee_asset_id).unwrap() as u32;
// Init Test
let test_args = TestContext {
sender: sender.clone(),
receiver: receiver.clone(),
args: TestArgs::new_para(
destination,
receiver.clone(),
wnd_to_send,
assets.into(),
None,
fee_asset_item,
),
};
let mut test = ParaToParaThroughAHTest::new(test_args);
// Query initial balances
let sender_wnds_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_location, &sender)
});
let sender_rocs_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &sender)
});
let wnds_in_sender_reserve_on_ah_before =
<AssetHubWestend as Chain>::account_data_of(sov_of_sender_on_ah.clone()).free;
let rocs_in_sender_reserve_on_ah_before = AssetHubWestend::execute_with(|| {
type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(roc_at_westend_parachains, &sov_of_sender_on_ah)
});
let wnds_in_receiver_reserve_on_ah_before =
<AssetHubWestend as Chain>::account_data_of(sov_of_receiver_on_ah.clone()).free;
let rocs_in_receiver_reserve_on_ah_before = AssetHubWestend::execute_with(|| {
type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(roc_at_westend_parachains, &sov_of_receiver_on_ah)
});
let receiver_wnds_before = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_location, &receiver)
});
let receiver_rocs_before = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &receiver)
});
// Set assertions and dispatchables
test.set_assertion::<PenpalA>(para_to_para_through_hop_sender_assertions);
test.set_assertion::<AssetHubWestend>(para_to_para_assethub_hop_assertions);
test.set_assertion::<PenpalB>(para_to_para_through_hop_receiver_assertions);
test.set_dispatchable::<PenpalA>(para_to_para_transfer_assets_through_ah);
test.assert();
// Query final balances
let sender_wnds_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_location, &sender)
});
let sender_rocs_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &sender)
});
let rocs_in_sender_reserve_on_ah_after = AssetHubWestend::execute_with(|| {
type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(roc_at_westend_parachains, &sov_of_sender_on_ah)
});
let wnds_in_sender_reserve_on_ah_after =
<AssetHubWestend as Chain>::account_data_of(sov_of_sender_on_ah).free;
let rocs_in_receiver_reserve_on_ah_after = AssetHubWestend::execute_with(|| {
type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(roc_at_westend_parachains, &sov_of_receiver_on_ah)
});
let wnds_in_receiver_reserve_on_ah_after =
<AssetHubWestend as Chain>::account_data_of(sov_of_receiver_on_ah).free;
let receiver_wnds_after = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(wnd_location, &receiver)
});
let receiver_rocs_after = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(roc_at_westend_parachains, &receiver)
});
// Sender's balance is reduced by amount sent plus delivery fees
assert!(sender_wnds_after < sender_wnds_before - wnd_to_send);
assert_eq!(sender_rocs_after, sender_rocs_before - roc_to_send);
// Sovereign accounts on reserve are changed accordingly
assert_eq!(
wnds_in_sender_reserve_on_ah_after,
wnds_in_sender_reserve_on_ah_before - wnd_to_send
);
assert_eq!(
rocs_in_sender_reserve_on_ah_after,
rocs_in_sender_reserve_on_ah_before - roc_to_send
);
assert!(wnds_in_receiver_reserve_on_ah_after > wnds_in_receiver_reserve_on_ah_before);
assert_eq!(
rocs_in_receiver_reserve_on_ah_after,
rocs_in_receiver_reserve_on_ah_before + roc_to_send
);
// Receiver's balance is increased
assert!(receiver_wnds_after > receiver_wnds_before);
assert_eq!(receiver_rocs_after, receiver_rocs_before + roc_to_send);
}
// ==============================================================================================
// ==== Bidirectional Transfer - Native + Teleportable Foreign Assets - Parachain<->AssetHub ====
// ==============================================================================================
/// Transfers of native asset plus teleportable foreign asset from Parachain to AssetHub and back
/// with fees paid using native asset.
#[test]
fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explicit_transfer_types() {
do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
para_to_asset_hub_teleport_foreign_assets,
asset_hub_to_para_teleport_foreign_assets,
);
}
@@ -14,6 +14,7 @@
// limitations under the License.
mod fellowship_treasury;
mod foreign_assets_transfers;
mod reserve_transfer;
mod send;
mod set_xcm_versions;
@@ -47,7 +47,7 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) {
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Burned { asset_id, owner, balance, .. }
) => {
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).unwrap(),
owner: *owner == t.sender.account_id,
balance: *balance == t.args.amount,
},
@@ -55,70 +55,91 @@ fn para_to_relay_sender_assertions(t: ParaToRelayTest) {
);
}
fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) {
pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
AssetHubWestend::assert_xcm_pallet_attempted_complete(None);
AssetHubWestend::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(
864_610_000,
8_799,
)));
let sov_acc_of_dest = AssetHubWestend::sovereign_account_id_of(t.args.dest.clone());
for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() {
let expected_id = asset.id.0.clone().try_into().unwrap();
let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
if idx == t.args.fee_asset_item as usize {
assert_expected_events!(
AssetHubWestend,
vec![
// Amount of native asset is transferred to Parachain's Sovereign account
RuntimeEvent::Balances(
pallet_balances::Event::Transfer { from, to, amount }
) => {
from: *from == t.sender.account_id,
to: *to == sov_acc_of_dest,
amount: *amount == asset_amount,
},
]
);
} else {
assert_expected_events!(
AssetHubWestend,
vec![
// Amount of foreign asset is transferred to Parachain's Sovereign account
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Transferred { asset_id, from, to, amount },
) => {
asset_id: *asset_id == expected_id,
from: *from == t.sender.account_id,
to: *to == sov_acc_of_dest,
amount: *amount == asset_amount,
},
]
);
}
}
assert_expected_events!(
AssetHubWestend,
vec![
// Amount to reserve transfer is transferred to Parachain's Sovereign account
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.clone()
),
amount: *amount == t.args.amount,
},
// Transport fees are paid
RuntimeEvent::PolkadotXcm(
pallet_xcm::Event::FeesPaid { .. }
) => {},
RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {},
]
);
AssetHubWestend::assert_xcm_pallet_sent();
}
fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) {
pub fn system_para_to_para_receiver_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
PenpalA::assert_xcmp_queue_success(None);
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == system_para_native_asset_location,
owner: *owner == t.receiver.account_id,
},
]
);
for asset in t.args.assets.into_inner().into_iter() {
let expected_id = asset.id.0.try_into().unwrap();
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == expected_id,
owner: *owner == t.receiver.account_id,
},
]
);
}
}
fn para_to_system_para_sender_assertions(t: ParaToSystemParaTest) {
pub 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![
// Amount to reserve transfer is transferred to Parachain's Sovereign account
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Burned { asset_id, owner, balance, .. }
) => {
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
owner: *owner == t.sender.account_id,
balance: *balance == t.args.amount,
},
]
);
PenpalA::assert_xcm_pallet_attempted_complete(None);
for asset in t.args.assets.into_inner().into_iter() {
let expected_id = asset.id.0.try_into().unwrap();
let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Burned { asset_id, owner, balance }
) => {
asset_id: *asset_id == expected_id,
owner: *owner == t.sender.account_id,
balance: *balance == asset_amount,
},
]
);
}
}
fn para_to_relay_receiver_assertions(t: ParaToRelayTest) {
@@ -150,25 +171,57 @@ fn para_to_relay_receiver_assertions(t: ParaToRelayTest) {
);
}
fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) {
pub fn para_to_system_para_receiver_assertions(t: ParaToSystemParaTest) {
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
let sov_penpal_on_ahr = AssetHubWestend::sovereign_account_id_of(
AssetHubWestend::sibling_location_of(PenpalA::para_id()),
);
AssetHubWestend::assert_xcmp_queue_success(None);
let sov_acc_of_penpal = AssetHubWestend::sovereign_account_id_of(t.args.dest.clone());
for (idx, asset) in t.args.assets.into_inner().into_iter().enumerate() {
let expected_id = asset.id.0.clone().try_into().unwrap();
let asset_amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
if idx == t.args.fee_asset_item as usize {
assert_expected_events!(
AssetHubWestend,
vec![
// Amount of native is withdrawn from Parachain's Sovereign account
RuntimeEvent::Balances(
pallet_balances::Event::Burned { who, amount }
) => {
who: *who == sov_acc_of_penpal.clone().into(),
amount: *amount == asset_amount,
},
RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => {
who: *who == t.receiver.account_id,
},
]
);
} else {
assert_expected_events!(
AssetHubWestend,
vec![
// Amount of foreign asset is transferred from Parachain's Sovereign account
// to Receiver's account
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Burned { asset_id, owner, balance },
) => {
asset_id: *asset_id == expected_id,
owner: *owner == sov_acc_of_penpal,
balance: *balance == asset_amount,
},
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Issued { asset_id, owner, amount },
) => {
asset_id: *asset_id == expected_id,
owner: *owner == t.receiver.account_id,
amount: *amount == asset_amount,
},
]
);
}
}
assert_expected_events!(
AssetHubWestend,
vec![
// Amount to reserve transfer is withdrawn from Parachain's Sovereign account
RuntimeEvent::Balances(
pallet_balances::Event::Burned { who, amount }
) => {
who: *who == sov_penpal_on_ahr.clone().into(),
amount: *amount == t.args.amount,
},
RuntimeEvent::Balances(pallet_balances::Event::Minted { .. }) => {},
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: true, .. }
) => {},
@@ -212,10 +265,9 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) {
fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let reservable_asset_location =
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap();
PenpalA::assert_xcm_pallet_attempted_complete(Some(Weight::from_parts(864_610_000, 8799)));
assert_expected_events!(
PenpalA,
@@ -246,13 +298,13 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) {
fn system_para_to_para_assets_receiver_assertions(t: SystemParaToParaTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let system_para_asset_location =
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap();
PenpalA::assert_xcmp_queue_success(None);
assert_expected_events!(
PenpalA,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).unwrap(),
owner: *owner == t.receiver.account_id,
},
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, amount }) => {
@@ -304,7 +356,7 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) {
PenpalA,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).expect("conversion works"),
asset_id: *asset_id == v3::Location::try_from(RelayLocation::get()).unwrap(),
owner: *owner == t.receiver.account_id,
},
RuntimeEvent::MessageQueue(
@@ -314,29 +366,27 @@ fn relay_to_para_assets_receiver_assertions(t: RelayToParaTest) {
);
}
fn para_to_para_through_relay_sender_assertions(t: ParaToParaThroughRelayTest) {
pub fn para_to_para_through_hop_sender_assertions<Hop: Clone>(t: Test<PenpalA, PenpalB, Hop>) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let relay_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
PenpalA::assert_xcm_pallet_attempted_complete(None);
// XCM sent to relay reserve
PenpalA::assert_parachain_system_ump_sent();
assert_expected_events!(
PenpalA,
vec![
// Amount to reserve transfer is transferred to Parachain's Sovereign account
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Burned { asset_id, owner, balance },
) => {
asset_id: *asset_id == relay_asset_location,
owner: *owner == t.sender.account_id,
balance: *balance == t.args.amount,
},
]
);
for asset in t.args.assets.into_inner() {
let expected_id = asset.id.0.clone().try_into().unwrap();
let amount = if let Fungible(a) = asset.fun { Some(a) } else { None }.unwrap();
assert_expected_events!(
PenpalA,
vec![
// Amount to reserve transfer is transferred to Parachain's Sovereign account
RuntimeEvent::ForeignAssets(
pallet_assets::Event::Burned { asset_id, owner, balance },
) => {
asset_id: *asset_id == expected_id,
owner: *owner == t.sender.account_id,
balance: *balance == amount,
},
]
);
}
}
fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) {
@@ -369,22 +419,22 @@ fn para_to_para_relay_hop_assertions(t: ParaToParaThroughRelayTest) {
);
}
fn para_to_para_through_relay_receiver_assertions(t: ParaToParaThroughRelayTest) {
pub fn para_to_para_through_hop_receiver_assertions<Hop: Clone>(t: Test<PenpalA, PenpalB, Hop>) {
type RuntimeEvent = <PenpalB as Chain>::RuntimeEvent;
let relay_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
PenpalB::assert_xcmp_queue_success(None);
assert_expected_events!(
PenpalB,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == relay_asset_location,
owner: *owner == t.receiver.account_id,
},
]
);
for asset in t.args.assets.into_inner().into_iter() {
let expected_id = asset.id.0.try_into().unwrap();
assert_expected_events!(
PenpalB,
vec![
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == expected_id,
owner: *owner == t.receiver.account_id,
},
]
);
}
}
fn relay_to_para_reserve_transfer_assets(t: RelayToParaTest) -> DispatchResult {
@@ -526,8 +576,7 @@ fn reserve_transfer_native_asset_from_relay_to_para() {
let amount_to_send: Balance = WESTEND_ED * 1000;
// Init values fot Parachain
let relay_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let relay_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let receiver = PenpalAReceiver::get();
// Init Test
@@ -577,8 +626,7 @@ fn reserve_transfer_native_asset_from_para_to_relay() {
let amount_to_send: Balance = WESTEND_ED * 1000;
let assets: Assets = (Parent, amount_to_send).into();
let asset_owner = PenpalAssetOwner::get();
let relay_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let relay_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
// fund Parachain's sender account
PenpalA::mint_foreign_asset(
@@ -654,8 +702,7 @@ fn reserve_transfer_native_asset_from_system_para_to_para() {
let assets: Assets = (Parent, amount_to_send).into();
// Init values for Parachain
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let receiver = PenpalAReceiver::get();
// Init Test
@@ -711,8 +758,7 @@ fn reserve_transfer_native_asset_from_para_to_system_para() {
let sender = PenpalASender::get();
let amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 1000;
let assets: Assets = (Parent, amount_to_send).into();
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let asset_owner = PenpalAssetOwner::get();
// fund Parachain's sender account
@@ -818,10 +864,9 @@ fn reserve_transfer_assets_from_system_para_to_para() {
// Init values for Parachain
let receiver = PenpalAReceiver::get();
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let system_para_foreign_asset_location =
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap();
// Init Test
let para_test_args = TestContext {
@@ -906,10 +951,9 @@ fn reserve_transfer_assets_from_para_to_system_para() {
let penpal_asset_owner = PenpalAssetOwner::get();
let penpal_asset_owner_signer = <PenpalA as Chain>::RuntimeOrigin::signed(penpal_asset_owner);
let asset_location_on_penpal =
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap();
let asset_location_on_penpal_latest: Location = asset_location_on_penpal.try_into().unwrap();
let system_asset_location_on_penpal =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_asset_location_on_penpal = v3::Location::try_from(RelayLocation::get()).unwrap();
let assets: Assets = vec![
(Parent, fee_amount_to_send).into(),
(asset_location_on_penpal_latest, asset_amount_to_send).into(),
@@ -940,10 +984,9 @@ fn reserve_transfer_assets_from_para_to_system_para() {
let penpal_location_as_seen_by_ahr = AssetHubWestend::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ahr =
AssetHubWestend::sovereign_account_id_of(penpal_location_as_seen_by_ahr);
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let system_para_foreign_asset_location =
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).expect("conversion works");
v3::Location::try_from(PenpalLocalReservableFromAssetHub::get()).unwrap();
let ah_asset_owner = AssetHubWestendAssetOwner::get();
let ah_asset_owner_signer = <AssetHubWestend as Chain>::RuntimeOrigin::signed(ah_asset_owner);
@@ -1031,15 +1074,14 @@ fn reserve_transfer_assets_from_para_to_system_para() {
/// Reserve Transfers of native asset from Parachain to Parachain (through Relay reserve) should
/// work
#[test]
fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
fn reserve_transfer_native_asset_from_para_to_para_through_relay() {
// Init values for Parachain Origin
let destination = PenpalA::sibling_location_of(PenpalB::para_id());
let sender = PenpalASender::get();
let amount_to_send: Balance = WESTEND_ED * 10000;
let asset_owner = PenpalAssetOwner::get();
let assets = (Parent, amount_to_send).into();
let relay_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let relay_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let sender_as_seen_by_relay = Westend::child_location_of(PenpalA::para_id());
let sov_of_sender_on_relay = Westend::sovereign_account_id_of(sender_as_seen_by_relay);
@@ -1076,9 +1118,9 @@ fn reserve_transfer_native_asset_from_para_to_para_trough_relay() {
});
// Set assertions and dispatchables
test.set_assertion::<PenpalA>(para_to_para_through_relay_sender_assertions);
test.set_assertion::<PenpalA>(para_to_para_through_hop_sender_assertions);
test.set_assertion::<Westend>(para_to_para_relay_hop_assertions);
test.set_assertion::<PenpalB>(para_to_para_through_relay_receiver_assertions);
test.set_assertion::<PenpalB>(para_to_para_through_hop_receiver_assertions);
test.set_dispatchable::<PenpalA>(para_to_para_through_relay_limited_reserve_transfer_assets);
test.assert();
@@ -111,8 +111,7 @@ fn swap_locally_on_chain_using_local_assets() {
#[test]
fn swap_locally_on_chain_using_foreign_assets() {
let asset_native =
Box::new(v3::Location::try_from(RelayLocation::get()).expect("conversion works"));
let asset_native = Box::new(v3::Location::try_from(RelayLocation::get()).unwrap());
let asset_location_on_penpal =
v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion_works");
let foreign_asset_at_asset_hub_westend =
@@ -110,8 +110,7 @@ fn para_dest_assertions(t: RelayToSystemParaTest) {
fn penpal_to_ah_foreign_assets_sender_assertions(t: ParaToSystemParaTest) {
type RuntimeEvent = <PenpalA as Chain>::RuntimeEvent;
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
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();
@@ -204,8 +203,7 @@ fn ah_to_penpal_foreign_assets_receiver_assertions(t: SystemParaToParaTest) {
let (_, expected_asset_amount) =
non_fee_asset(&t.args.assets, t.args.fee_asset_item as usize).unwrap();
let checking_account = <PenpalA as PenpalAPallet>::PolkadotXcm::check_account();
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
PenpalA::assert_xcmp_queue_success(None);
@@ -414,22 +412,23 @@ fn teleport_to_other_system_parachains_works() {
);
}
/// 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() {
/// Bidirectional teleports of local Penpal assets to Asset Hub as foreign assets while paying
/// fees using (reserve transferred) native asset.
pub fn do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
para_to_ah_dispatchable: fn(ParaToSystemParaTest) -> DispatchResult,
ah_to_para_dispatchable: fn(SystemParaToParaTest) -> DispatchResult,
) {
// Init values for Parachain
let fee_amount_to_send: Balance = ASSET_HUB_WESTEND_ED * 100;
let asset_location_on_penpal =
v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).expect("conversion works");
v3::Location::try_from(PenpalLocalTeleportableToAssetHub::get()).unwrap();
let asset_id_on_penpal = match asset_location_on_penpal.last() {
Some(v3::Junction::GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
let asset_amount_to_send = ASSET_HUB_WESTEND_ED * 100;
let asset_owner = PenpalAssetOwner::get();
let system_para_native_asset_location =
v3::Location::try_from(RelayLocation::get()).expect("conversion works");
let system_para_native_asset_location = v3::Location::try_from(RelayLocation::get()).unwrap();
let sender = PenpalASender::get();
let penpal_check_account = <PenpalA as PenpalAPallet>::PolkadotXcm::check_account();
let ah_as_seen_by_penpal = PenpalA::sibling_location_of(AssetHubWestend::para_id());
@@ -518,7 +517,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
penpal_to_ah.set_assertion::<PenpalA>(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::<PenpalA>(para_to_system_para_transfer_assets);
penpal_to_ah.set_dispatchable::<PenpalA>(para_to_ah_dispatchable);
penpal_to_ah.assert();
let penpal_sender_balance_after = PenpalA::execute_with(|| {
@@ -625,7 +624,7 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
ah_to_penpal.set_assertion::<AssetHubWestend>(ah_to_penpal_foreign_assets_sender_assertions);
ah_to_penpal.set_assertion::<PenpalA>(ah_to_penpal_foreign_assets_receiver_assertions);
ah_to_penpal.set_dispatchable::<AssetHubWestend>(system_para_to_para_transfer_assets);
ah_to_penpal.set_dispatchable::<AssetHubWestend>(ah_to_para_dispatchable);
ah_to_penpal.assert();
let ah_sender_balance_after = ah_to_penpal.sender.balance;
@@ -663,3 +662,13 @@ fn bidirectional_teleport_foreign_assets_between_para_and_asset_hub() {
// Receiver's balance is increased by exact amount
assert_eq!(penpal_receiver_assets_after, penpal_receiver_assets_before + asset_amount_to_send);
}
/// 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() {
do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt(
para_to_system_para_transfer_assets,
system_para_to_para_transfer_assets,
);
}
@@ -25,6 +25,7 @@ mod imports {
prelude::{AccountId32 as AccountId32Junction, *},
v3::{self, NetworkId::Westend as WestendId},
};
pub use xcm_executor::traits::TransferType;
// Cumulus
pub use emulated_integration_tests_common::{
@@ -46,7 +47,7 @@ mod imports {
bridge_hub_rococo_emulated_chain::{
genesis::ED as BRIDGE_HUB_ROCOCO_ED, BridgeHubRococoParaPallet as BridgeHubRococoPallet,
},
penpal_emulated_chain::PenpalAParaPallet as PenpalAPallet,
penpal_emulated_chain::{PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner},
rococo_emulated_chain::{genesis::ED as ROCOCO_ED, RococoRelayPallet as RococoPallet},
AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver,
AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend,
@@ -31,6 +31,73 @@ fn send_asset_from_asset_hub_rococo_to_asset_hub_westend(id: Location, amount: u
assert_bridge_hub_westend_message_received();
}
fn send_asset_from_penpal_rococo_through_local_asset_hub_to_westend_asset_hub(
id: Location,
transfer_amount: u128,
) {
let destination = asset_hub_westend_location();
let local_asset_hub: Location = PenpalA::sibling_location_of(AssetHubRococo::para_id());
let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(
AssetHubRococo::sibling_location_of(PenpalA::para_id()),
);
let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus(
Westend,
AssetHubWestend::para_id(),
);
// fund the AHR's SA on BHR for paying bridge transport fees
BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128);
// set XCM versions
PenpalA::force_xcm_version(local_asset_hub.clone(), XCM_VERSION);
AssetHubRococo::force_xcm_version(destination.clone(), XCM_VERSION);
BridgeHubRococo::force_xcm_version(bridge_hub_westend_location(), XCM_VERSION);
// send message over bridge
assert_ok!(PenpalA::execute_with(|| {
let signed_origin = <PenpalA as Chain>::RuntimeOrigin::signed(PenpalASender::get());
let beneficiary: Location =
AccountId32Junction { network: None, id: AssetHubWestendReceiver::get().into() }.into();
let assets: Assets = (id.clone(), transfer_amount).into();
let fees_id: AssetId = id.into();
<PenpalA as PenpalAPallet>::PolkadotXcm::transfer_assets_using_type(
signed_origin,
bx!(destination.into()),
bx!(beneficiary.into()),
bx!(assets.clone().into()),
bx!(TransferType::RemoteReserve(local_asset_hub.clone().into())),
bx!(fees_id.into()),
bx!(TransferType::RemoteReserve(local_asset_hub.into())),
WeightLimit::Unlimited,
)
}));
AssetHubRococo::execute_with(|| {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubRococo,
vec![
// Amount to reserve transfer is withdrawn from Penpal's sovereign account
RuntimeEvent::Balances(
pallet_balances::Event::Burned { who, amount }
) => {
who: *who == sov_penpal_on_ahr.clone().into(),
amount: *amount == transfer_amount,
},
// Amount deposited in AHW's sovereign account
RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => {
who: *who == sov_ahw_on_ahr.clone().into(),
},
RuntimeEvent::XcmpQueue(
cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }
) => {},
]
);
});
assert_bridge_hub_rococo_message_accepted(true);
assert_bridge_hub_westend_message_received();
}
#[test]
fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() {
let roc_at_asset_hub_rococo: v3::Location = v3::Parent.into();
@@ -45,7 +112,7 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() {
vec![],
);
let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus(
NetworkId::Westend,
Westend,
AssetHubWestend::para_id(),
);
@@ -135,7 +202,7 @@ fn send_rocs_from_asset_hub_rococo_to_asset_hub_westend() {
assert!(sender_rocs_before > sender_rocs_after);
// Receiver's balance is increased
assert!(receiver_rocs_after > receiver_rocs_before);
// Reserve balance is reduced by sent amount
// Reserve balance is increased by sent amount
assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before + amount);
}
@@ -144,7 +211,7 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() {
let prefund_amount = 10_000_000_000_000u128;
let wnd_at_asset_hub_rococo =
v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]);
let owner: AccountId = AssetHubWestend::account_id_of(ALICE);
let owner: AccountId = AssetHubRococo::account_id_of(ALICE);
AssetHubRococo::force_create_foreign_asset(
wnd_at_asset_hub_rococo,
owner,
@@ -155,7 +222,7 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() {
// fund the AHR's SA on AHW with the WND tokens held in reserve
let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus(
NetworkId::Rococo,
Rococo,
AssetHubRococo::para_id(),
);
AssetHubWestend::fund_accounts(vec![(sov_ahr_on_ahw.clone(), prefund_amount)]);
@@ -217,3 +284,94 @@ fn send_wnds_from_asset_hub_rococo_to_asset_hub_westend() {
// Reserve balance is reduced by sent amount
assert_eq!(wnds_in_reserve_on_ahw_after, wnds_in_reserve_on_ahw_before - amount_to_send);
}
#[test]
fn send_rocs_from_penpal_rococo_through_asset_hub_rococo_to_asset_hub_westend() {
let roc_at_rococo_parachains: v3::Location = v3::Parent.into();
let roc_at_asset_hub_westend =
v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Rococo)]);
let roc_at_rococo_parachains_latest: Location = roc_at_rococo_parachains.try_into().unwrap();
let owner: AccountId = AssetHubWestend::account_id_of(ALICE);
AssetHubWestend::force_create_foreign_asset(
roc_at_asset_hub_westend,
owner,
true,
ASSET_MIN_BALANCE,
vec![],
);
let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus(
Westend,
AssetHubWestend::para_id(),
);
let amount = ASSET_HUB_ROCOCO_ED * 10_000_000;
let penpal_location = AssetHubRococo::sibling_location_of(PenpalA::para_id());
let sov_penpal_on_ahr = AssetHubRococo::sovereign_account_id_of(penpal_location);
// fund Penpal's sovereign account on AssetHub
AssetHubRococo::fund_accounts(vec![(sov_penpal_on_ahr.into(), amount * 2)]);
// fund Penpal's sender account
PenpalA::mint_foreign_asset(
<PenpalA as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
roc_at_rococo_parachains,
PenpalASender::get(),
amount * 2,
);
let rocs_in_reserve_on_ahr_before =
<AssetHubRococo as Chain>::account_data_of(sov_ahw_on_ahr.clone()).free;
let sender_rocs_before = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
roc_at_rococo_parachains.into(),
&PenpalASender::get(),
)
});
let receiver_rocs_before = AssetHubWestend::execute_with(|| {
type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(roc_at_asset_hub_westend, &AssetHubWestendReceiver::get())
});
send_asset_from_penpal_rococo_through_local_asset_hub_to_westend_asset_hub(
roc_at_rococo_parachains_latest,
amount,
);
AssetHubWestend::execute_with(|| {
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubWestend,
vec![
// issue ROCs on AHW
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == roc_at_rococo_parachains,
owner: *owner == AssetHubWestendReceiver::get(),
},
// message processed successfully
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
});
let sender_rocs_after = PenpalA::execute_with(|| {
type ForeignAssets = <PenpalA as PenpalAPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
roc_at_rococo_parachains.into(),
&PenpalASender::get(),
)
});
let receiver_rocs_after = AssetHubWestend::execute_with(|| {
type Assets = <AssetHubWestend as AssetHubWestendPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(roc_at_asset_hub_westend, &AssetHubWestendReceiver::get())
});
let rocs_in_reserve_on_ahr_after =
<AssetHubRococo as Chain>::account_data_of(sov_ahw_on_ahr.clone()).free;
// Sender's balance is reduced
assert!(sender_rocs_after < sender_rocs_before);
// Receiver's balance is increased
assert!(receiver_rocs_after > receiver_rocs_before);
// Reserve balance is increased by sent amount (less fess)
assert!(rocs_in_reserve_on_ahr_after > rocs_in_reserve_on_ahr_before);
assert!(rocs_in_reserve_on_ahr_after <= rocs_in_reserve_on_ahr_before + amount);
}
@@ -381,10 +381,8 @@ fn send_token_from_ethereum_to_penpal() {
#[test]
fn send_weth_asset_from_asset_hub_to_ethereum() {
use asset_hub_rococo_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee;
let assethub_sovereign = BridgeHubRococo::sovereign_account_id_of(Location::new(
1,
[Parachain(AssetHubRococo::para_id().into())],
));
let assethub_location = BridgeHubRococo::sibling_location_of(AssetHubRococo::para_id());
let assethub_sovereign = BridgeHubRococo::sovereign_account_id_of(assethub_location);
AssetHubRococo::force_default_xcm_version(Some(XCM_VERSION));
BridgeHubRococo::force_default_xcm_version(Some(XCM_VERSION));
@@ -26,6 +26,7 @@ mod imports {
v3,
v4::NetworkId::Rococo as RococoId,
};
pub use xcm_executor::traits::TransferType;
// Cumulus
pub use emulated_integration_tests_common::{
@@ -48,13 +49,15 @@ mod imports {
genesis::ED as BRIDGE_HUB_WESTEND_ED,
BridgeHubWestendParaPallet as BridgeHubWestendPallet,
},
penpal_emulated_chain::{PenpalAssetOwner, PenpalBParaPallet as PenpalBPallet},
westend_emulated_chain::WestendRelayPallet as WestendPallet,
AssetHubRococoPara as AssetHubRococo, AssetHubRococoParaReceiver as AssetHubRococoReceiver,
AssetHubRococoParaSender as AssetHubRococoSender, AssetHubWestendPara as AssetHubWestend,
AssetHubWestendParaReceiver as AssetHubWestendReceiver,
AssetHubWestendParaSender as AssetHubWestendSender, BridgeHubRococoPara as BridgeHubRococo,
BridgeHubWestendPara as BridgeHubWestend,
BridgeHubWestendParaSender as BridgeHubWestendSender, WestendRelay as Westend,
BridgeHubWestendParaSender as BridgeHubWestendSender, PenpalBPara as PenpalB,
PenpalBParaSender as PenpalBSender, WestendRelay as Westend,
};
pub const ASSET_MIN_BALANCE: u128 = 1000;
@@ -30,6 +30,73 @@ fn send_asset_from_asset_hub_westend_to_asset_hub_rococo(id: Location, amount: u
assert_bridge_hub_rococo_message_received();
}
fn send_asset_from_penpal_westend_through_local_asset_hub_to_rococo_asset_hub(
id: Location,
transfer_amount: u128,
) {
let destination = asset_hub_rococo_location();
let local_asset_hub: Location = PenpalB::sibling_location_of(AssetHubWestend::para_id());
let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(
AssetHubWestend::sibling_location_of(PenpalB::para_id()),
);
let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus(
Rococo,
AssetHubRococo::para_id(),
);
// fund the AHW's SA on BHW for paying bridge transport fees
BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128);
// set XCM versions
PenpalB::force_xcm_version(local_asset_hub.clone(), XCM_VERSION);
AssetHubWestend::force_xcm_version(destination.clone(), XCM_VERSION);
BridgeHubWestend::force_xcm_version(bridge_hub_rococo_location(), XCM_VERSION);
// send message over bridge
assert_ok!(PenpalB::execute_with(|| {
let signed_origin = <PenpalB as Chain>::RuntimeOrigin::signed(PenpalBSender::get());
let beneficiary: Location =
AccountId32Junction { network: None, id: AssetHubRococoReceiver::get().into() }.into();
let assets: Assets = (id.clone(), transfer_amount).into();
let fees_id: AssetId = id.into();
<PenpalB as PenpalBPallet>::PolkadotXcm::transfer_assets_using_type(
signed_origin,
bx!(destination.into()),
bx!(beneficiary.into()),
bx!(assets.into()),
bx!(TransferType::RemoteReserve(local_asset_hub.clone().into())),
bx!(fees_id.into()),
bx!(TransferType::RemoteReserve(local_asset_hub.into())),
WeightLimit::Unlimited,
)
}));
AssetHubWestend::execute_with(|| {
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubWestend,
vec![
// Amount to reserve transfer is withdrawn from Penpal's sovereign account
RuntimeEvent::Balances(
pallet_balances::Event::Burned { who, amount }
) => {
who: *who == sov_penpal_on_ahw.clone().into(),
amount: *amount == transfer_amount,
},
// Amount deposited in AHR's sovereign account
RuntimeEvent::Balances(pallet_balances::Event::Minted { who, .. }) => {
who: *who == sov_ahr_on_ahw.clone().into(),
},
RuntimeEvent::XcmpQueue(
cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }
) => {},
]
);
});
assert_bridge_hub_westend_message_accepted(true);
assert_bridge_hub_rococo_message_received();
}
#[test]
fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() {
let wnd_at_asset_hub_westend: Location = Parent.into();
@@ -44,7 +111,7 @@ fn send_wnds_from_asset_hub_westend_to_asset_hub_rococo() {
vec![],
);
let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus(
NetworkId::Rococo,
Rococo,
AssetHubRococo::para_id(),
);
@@ -153,7 +220,7 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() {
// fund the AHW's SA on AHR with the ROC tokens held in reserve
let sov_ahw_on_ahr = AssetHubRococo::sovereign_account_of_parachain_on_other_global_consensus(
NetworkId::Westend,
Westend,
AssetHubWestend::para_id(),
);
AssetHubRococo::fund_accounts(vec![(sov_ahw_on_ahr.clone(), prefund_amount)]);
@@ -215,3 +282,94 @@ fn send_rocs_from_asset_hub_westend_to_asset_hub_rococo() {
// Reserve balance is reduced by sent amount
assert_eq!(rocs_in_reserve_on_ahr_after, rocs_in_reserve_on_ahr_before - amount_to_send);
}
#[test]
fn send_wnds_from_penpal_westend_through_asset_hub_westend_to_asset_hub_rococo() {
let wnd_at_westend_parachains: v3::Location = v3::Parent.into();
let wnd_at_asset_hub_rococo =
v3::Location::new(2, [v3::Junction::GlobalConsensus(v3::NetworkId::Westend)]);
let wnd_at_westend_parachains_latest: Location = wnd_at_westend_parachains.try_into().unwrap();
let owner: AccountId = AssetHubRococo::account_id_of(ALICE);
AssetHubRococo::force_create_foreign_asset(
wnd_at_asset_hub_rococo,
owner,
true,
ASSET_MIN_BALANCE,
vec![],
);
let sov_ahr_on_ahw = AssetHubWestend::sovereign_account_of_parachain_on_other_global_consensus(
Rococo,
AssetHubRococo::para_id(),
);
let amount = ASSET_HUB_WESTEND_ED * 10_000_000;
let penpal_location = AssetHubWestend::sibling_location_of(PenpalB::para_id());
let sov_penpal_on_ahw = AssetHubWestend::sovereign_account_id_of(penpal_location);
// fund Penpal's sovereign account on AssetHub
AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ahw.into(), amount * 2)]);
// fund Penpal's sender account
PenpalB::mint_foreign_asset(
<PenpalB as Chain>::RuntimeOrigin::signed(PenpalAssetOwner::get()),
wnd_at_westend_parachains,
PenpalBSender::get(),
amount * 2,
);
let wnds_in_reserve_on_ahw_before =
<AssetHubWestend as Chain>::account_data_of(sov_ahr_on_ahw.clone()).free;
let sender_wnds_before = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
wnd_at_westend_parachains.into(),
&PenpalBSender::get(),
)
});
let receiver_wnds_before = AssetHubRococo::execute_with(|| {
type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(wnd_at_asset_hub_rococo, &AssetHubRococoReceiver::get())
});
send_asset_from_penpal_westend_through_local_asset_hub_to_rococo_asset_hub(
wnd_at_westend_parachains_latest,
amount,
);
AssetHubRococo::execute_with(|| {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
assert_expected_events!(
AssetHubRococo,
vec![
// issue WNDs on AHR
RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { asset_id, owner, .. }) => {
asset_id: *asset_id == wnd_at_westend_parachains,
owner: *owner == AssetHubRococoReceiver::get(),
},
// message processed successfully
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
});
let sender_wnds_after = PenpalB::execute_with(|| {
type ForeignAssets = <PenpalB as PenpalBPallet>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance(
wnd_at_westend_parachains.into(),
&PenpalBSender::get(),
)
});
let receiver_wnds_after = AssetHubRococo::execute_with(|| {
type Assets = <AssetHubRococo as AssetHubRococoPallet>::ForeignAssets;
<Assets as Inspect<_>>::balance(wnd_at_asset_hub_rococo, &AssetHubRococoReceiver::get())
});
let wnds_in_reserve_on_ahw_after =
<AssetHubWestend as Chain>::account_data_of(sov_ahr_on_ahw.clone()).free;
// Sender's balance is reduced
assert!(sender_wnds_after < sender_wnds_before);
// Receiver's balance is increased
assert!(receiver_wnds_after > receiver_wnds_before);
// Reserve balance is increased by sent amount (less fess)
assert!(wnds_in_reserve_on_ahw_after > wnds_in_reserve_on_ahw_before);
assert!(wnds_in_reserve_on_ahw_after <= wnds_in_reserve_on_ahw_before + amount);
}
@@ -113,17 +113,14 @@ impl<UniversalLocation: Get<InteriorLocation>, Reserves: ContainsPair<Asset, Loc
);
// check remote origin
let _ = match ensure_is_remote(universal_source.clone(), origin.clone()) {
Ok(devolved) => devolved,
Err(_) => {
log::trace!(
target: "xcm::contains",
"IsTrustedBridgedReserveLocationForConcreteAsset origin: {:?} is not remote to the universal_source: {:?}",
origin, universal_source
);
return false
},
};
if ensure_is_remote(universal_source.clone(), origin.clone()).is_err() {
log::trace!(
target: "xcm::contains",
"IsTrustedBridgedReserveLocationForConcreteAsset origin: {:?} is not remote to the universal_source: {:?}",
origin, universal_source
);
return false
}
// check asset according to the configured reserve locations
Reserves::contains(asset, origin)
@@ -58,9 +58,16 @@ parameter_types! {
pub const RelayLocation: Location = Location::parent();
// Local native currency which is stored in `pallet_balances``
pub const PenpalNativeCurrency: Location = Location::here();
pub const RelayNetwork: Option<NetworkId> = None;
// The Penpal runtime is utilized for testing with various environment setups.
// This storage item allows us to customize the `NetworkId` where Penpal is deployed.
// By default, it is set to `NetworkId::Rococo` and can be changed using `System::set_storage`.
pub storage RelayNetworkId: NetworkId = NetworkId::Westend;
pub RelayNetwork: Option<NetworkId> = Some(RelayNetworkId::get());
pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into();
pub UniversalLocation: InteriorLocation = [Parachain(ParachainInfo::parachain_id().into())].into();
pub UniversalLocation: InteriorLocation = [
GlobalConsensus(RelayNetworkId::get()),
Parachain(ParachainInfo::parachain_id().into())
].into();
pub TreasuryAccount: AccountId = TREASURY_PALLET_ID.into_account_truncating();
}
+184 -64
View File
@@ -1299,64 +1299,20 @@ pub mod pallet {
);
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let mut assets = assets.into_inner();
let assets = assets.into_inner();
let fee_asset_item = fee_asset_item as usize;
let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
// Find transfer types for fee and non-fee assets.
let (fees_transfer_type, assets_transfer_type) =
Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
// local and remote XCM programs to potentially handle fees separately
let fees = if fees_transfer_type == assets_transfer_type {
// no need for custom fees instructions, fees are batched with assets
FeesHandling::Batched { fees }
} else {
// Disallow _remote reserves_ unless assets & fees have same remote reserve (covered
// by branch above). The reason for this is that we'd need to send XCMs to separate
// chains with no guarantee of delivery order on final destination; therefore we
// cannot guarantee to have fees in place on final destination chain to pay for
// assets transfer.
ensure!(
!matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
Error::<T>::InvalidAssetUnsupportedReserve
);
let weight_limit = weight_limit.clone();
// remove `fees` from `assets` and build separate fees transfer instructions to be
// added to assets transfers XCM programs
let fees = assets.remove(fee_asset_item);
let (local_xcm, remote_xcm) = match fees_transfer_type {
TransferType::LocalReserve => Self::local_reserve_fees_instructions(
origin.clone(),
dest.clone(),
fees,
weight_limit,
)?,
TransferType::DestinationReserve =>
Self::destination_reserve_fees_instructions(
origin.clone(),
dest.clone(),
fees,
weight_limit,
)?,
TransferType::Teleport => Self::teleport_fees_instructions(
origin.clone(),
dest.clone(),
fees,
weight_limit,
)?,
TransferType::RemoteReserve(_) =>
return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
};
FeesHandling::Separate { local_xcm, remote_xcm }
};
Self::build_and_execute_xcm_transfer_type(
Self::do_transfer_assets(
origin,
dest,
beneficiary,
assets,
assets_transfer_type,
fees,
fee_asset_item,
fees_transfer_type,
weight_limit,
)
}
@@ -1443,6 +1399,87 @@ pub mod pallet {
<Self as SendController<_>>::send_blob(origin, dest, encoded_message)?;
Ok(())
}
/// Transfer assets from the local chain to the destination chain using explicit transfer
/// types for assets and fees.
///
/// `assets` must have same reserve location or may be teleportable to `dest`. Caller must
/// provide the `assets_transfer_type` to be used for `assets`:
/// - `TransferType::LocalReserve`: transfer assets to sovereign account of destination
/// chain and forward a notification XCM to `dest` to mint and deposit reserve-based
/// assets to `beneficiary`.
/// - `TransferType::DestinationReserve`: 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`.
/// - `TransferType::RemoteReserve(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`. Typically
/// the remote `reserve` is Asset Hub.
/// - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to
/// mint/teleport assets and deposit them to `beneficiary`.
///
/// Fee payment on the source, destination and all intermediary hops, is specified through
/// `fees_id`, but make sure enough of the specified `fees_id` asset is included in the
/// given list of `assets`. `fees_id` should be enough to pay for `weight_limit`. If more
/// weight is needed than `weight_limit`, then the operation will fail and the sent assets
/// may be at risk.
///
/// `fees_id` may use different transfer type than rest of `assets` and can be specified
/// through `fees_transfer_type`.
///
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
/// - `dest`: Destination context for the assets. Will typically be `[Parent,
/// Parachain(..)]` to send from parachain to parachain, or `[Parachain(..)]` to send from
/// relay to parachain, or `(parents: 2, (GlobalConsensus(..), ..))` to send from
/// parachain across a bridge to another ecosystem destination.
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
/// generally be an `AccountId32` value.
/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
/// fee on the `dest` (and possibly reserve) chains.
/// - `assets_transfer_type`: The XCM `TransferType` used to transfer the `assets`.
/// - `fees_id`: One of the included `assets` to be be used to pay fees.
/// - `fees_transfer_type`: The XCM `TransferType` used to transfer the `fees` assets.
/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
#[pallet::call_index(15)]
#[pallet::weight(T::WeightInfo::transfer_assets())]
pub fn transfer_assets_using_type(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
assets_transfer_type: Box<TransferType>,
fees_id: Box<VersionedAssetId>,
fees_transfer_type: Box<TransferType>,
weight_limit: WeightLimit,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
let beneficiary: Location =
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
let fees_id: AssetId = (*fees_id).try_into().map_err(|()| Error::<T>::BadVersion)?;
log::debug!(
target: "xcm::pallet_xcm::transfer_assets_using_type",
"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?} through {:?}, fees-id {:?} through {:?}",
origin_location, dest, beneficiary, assets, assets_transfer_type, fees_id, fees_transfer_type,
);
let assets = assets.into_inner();
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let fee_asset_index =
assets.iter().position(|a| a.id == fees_id).ok_or(Error::<T>::FeesNotMet)?;
Self::do_transfer_assets(
origin_location,
dest,
beneficiary,
assets,
*assets_transfer_type,
fee_asset_index,
*fees_transfer_type,
weight_limit,
)
}
}
}
@@ -1607,15 +1644,16 @@ impl<T: Config> Pallet<T> {
// Ensure all assets (including fees) have same reserve location.
ensure!(assets_transfer_type == fees_transfer_type, Error::<T>::TooManyReserves);
Self::build_and_execute_xcm_transfer_type(
origin,
dest,
let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
origin.clone(),
dest.clone(),
beneficiary,
assets,
assets_transfer_type,
FeesHandling::Batched { fees },
weight_limit,
)
)?;
Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
}
fn do_teleport_assets(
@@ -1648,18 +1686,85 @@ impl<T: Config> Pallet<T> {
}
let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
Self::build_and_execute_xcm_transfer_type(
origin_location,
dest,
let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
origin_location.clone(),
dest.clone(),
beneficiary,
assets,
TransferType::Teleport,
FeesHandling::Batched { fees },
weight_limit,
)
)?;
Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm)
}
fn build_and_execute_xcm_transfer_type(
fn do_transfer_assets(
origin: Location,
dest: Location,
beneficiary: Location,
mut assets: Vec<Asset>,
assets_transfer_type: TransferType,
fee_asset_index: usize,
fees_transfer_type: TransferType,
weight_limit: WeightLimit,
) -> DispatchResult {
// local and remote XCM programs to potentially handle fees separately
let fees = if fees_transfer_type == assets_transfer_type {
let fees = assets.get(fee_asset_index).ok_or(Error::<T>::Empty)?.clone();
// no need for custom fees instructions, fees are batched with assets
FeesHandling::Batched { fees }
} else {
// Disallow _remote reserves_ unless assets & fees have same remote reserve (covered
// by branch above). The reason for this is that we'd need to send XCMs to separate
// chains with no guarantee of delivery order on final destination; therefore we
// cannot guarantee to have fees in place on final destination chain to pay for
// assets transfer.
ensure!(
!matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
Error::<T>::InvalidAssetUnsupportedReserve
);
let weight_limit = weight_limit.clone();
// remove `fees` from `assets` and build separate fees transfer instructions to be
// added to assets transfers XCM programs
let fees = assets.remove(fee_asset_index);
let (local_xcm, remote_xcm) = match fees_transfer_type {
TransferType::LocalReserve => Self::local_reserve_fees_instructions(
origin.clone(),
dest.clone(),
fees,
weight_limit,
)?,
TransferType::DestinationReserve => Self::destination_reserve_fees_instructions(
origin.clone(),
dest.clone(),
fees,
weight_limit,
)?,
TransferType::Teleport => Self::teleport_fees_instructions(
origin.clone(),
dest.clone(),
fees,
weight_limit,
)?,
TransferType::RemoteReserve(_) =>
return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
};
FeesHandling::Separate { local_xcm, remote_xcm }
};
let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
origin.clone(),
dest.clone(),
beneficiary,
assets,
assets_transfer_type,
fees,
weight_limit,
)?;
Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
}
fn build_xcm_transfer_type(
origin: Location,
dest: Location,
beneficiary: Location,
@@ -1667,14 +1772,14 @@ impl<T: Config> Pallet<T> {
transfer_type: TransferType,
fees: FeesHandling<T>,
weight_limit: WeightLimit,
) -> DispatchResult {
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Option<Xcm<()>>), Error<T>> {
log::debug!(
target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
target: "xcm::pallet_xcm::build_xcm_transfer_type",
"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \
fees_handling {:?}, weight_limit: {:?}",
origin, dest, beneficiary, assets, transfer_type, fees, weight_limit,
);
let (mut local_xcm, remote_xcm) = match transfer_type {
Ok(match transfer_type {
TransferType::LocalReserve => {
let (local, remote) = Self::local_reserve_transfer_programs(
origin.clone(),
@@ -1704,7 +1809,7 @@ impl<T: Config> Pallet<T> {
};
let local = Self::remote_reserve_transfer_program(
origin.clone(),
reserve,
reserve.try_into().map_err(|()| Error::<T>::BadVersion)?,
dest.clone(),
beneficiary,
assets,
@@ -1724,7 +1829,21 @@ impl<T: Config> Pallet<T> {
)?;
(local, Some(remote))
},
};
})
}
fn execute_xcm_transfer(
origin: Location,
dest: Location,
mut local_xcm: Xcm<<T as Config>::RuntimeCall>,
remote_xcm: Option<Xcm<()>>,
) -> DispatchResult {
log::debug!(
target: "xcm::pallet_xcm::execute_xcm_transfer",
"origin {:?}, dest {:?}, local_xcm {:?}, remote_xcm {:?}",
origin, dest, local_xcm, remote_xcm,
);
let weight =
T::Weigher::weight(&mut local_xcm).map_err(|()| Error::<T>::UnweighableMessage)?;
let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
@@ -1738,7 +1857,7 @@ impl<T: Config> Pallet<T> {
Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
outcome.ensure_complete().map_err(|error| {
log::error!(
target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
target: "xcm::pallet_xcm::execute_xcm_transfer",
"XCM execution failed with error {:?}", error
);
Error::<T>::LocalExecutionIncomplete
@@ -1750,7 +1869,7 @@ impl<T: Config> Pallet<T> {
if origin != Here.into_location() {
Self::charge_fees(origin.clone(), price).map_err(|error| {
log::error!(
target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
target: "xcm::pallet_xcm::execute_xcm_transfer",
"Unable to charge fee with error {:?}", error
);
Error::<T>::FeesNotMet
@@ -1935,6 +2054,7 @@ impl<T: Config> Pallet<T> {
]);
// handle fees
Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
// deposit all remaining assets in holding to `beneficiary` location
xcm_on_dest
.inner_mut()
+3
View File
@@ -347,6 +347,9 @@ impl<Config: config::Config> XcmExecutor<Config> {
msg: Xcm<()>,
reason: FeeReason,
) -> Result<XcmHash, XcmError> {
log::trace!(
target: "xcm::send", "Sending msg: {msg:?}, to destination: {dest:?}, (reason: {reason:?})"
);
let (ticket, fee) = validate_send::<Config::XcmSender>(dest, msg)?;
self.take_fee(fee, reason)?;
Config::XcmSender::deliver(ticket).map_err(Into::into)
@@ -30,7 +30,7 @@ pub enum Error {
}
/// Specify which type of asset transfer is required for a particular `(asset, dest)` combination.
#[derive(Clone, PartialEq, Debug)]
#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo)]
pub enum TransferType {
/// should teleport `asset` to `dest`
Teleport,
@@ -39,7 +39,7 @@ pub enum TransferType {
/// should reserve-transfer `asset` to `dest`, using `dest` as reserve
DestinationReserve,
/// should reserve-transfer `asset` to `dest`, using remote chain `Location` as reserve
RemoteReserve(Location),
RemoteReserve(VersionedLocation),
}
/// A trait for identifying asset transfer type based on `IsTeleporter` and `IsReserve`
@@ -77,7 +77,7 @@ pub trait XcmAssetTransfers {
Ok(TransferType::LocalReserve)
} else if Self::IsReserve::contains(asset, &asset_location) {
// remote location that is recognized as reserve location for asset
Ok(TransferType::RemoteReserve(asset_location))
Ok(TransferType::RemoteReserve(asset_location.into()))
} else {
// remote location that is not configured either as teleporter or reserve => cannot
// determine asset reserve
+38
View File
@@ -0,0 +1,38 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
title: "pallet-xcm: add new extrinsic for asset transfers using explicit reserve"
doc:
- audience: Runtime User
description: |
pallet-xcm has a new extrinsic `transfer_assets_using_type` for transferring
assets from local chain to destination chain using an explicit XCM transfer
types for transferring the assets and the fees:
- `TransferType::LocalReserve`: transfer assets to sovereign account of destination
chain and forward a notification XCM to `dest` to mint and deposit reserve-based
assets to `beneficiary`.
- `TransferType::DestinationReserve`: 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`.
- `TransferType::RemoteReserve(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`. Typically
the remote `reserve` is Asset Hub.
- `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to
mint/teleport assets and deposit them to `beneficiary`.
By default, an asset's reserve is its origin chain. But sometimes we may want to
explicitly use another chain as reserve (as long as allowed by runtime IsReserve
filter).
This is very helpful for transferring assets with multiple configured reserves
(such as Asset Hub ForeignAssets), when the transfer strictly depends on the used
reserve location.
E.g. For transferring a bridged Foreign Assets between local parachains, Asset Hub
or the parachain that bridged the asset over must be used as the reserve location.
Same when transferring bridged assets back across the bridge, the local bridging
parachain must be used as the explicit reserve location.
crates:
- name: pallet-xcm
bump: minor