Skip to content

Commit

Permalink
pallet-xcm: add new extrinsic for asset transfers using explicit XCM …
Browse files Browse the repository at this point in the history
…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 <[email protected]>
Co-authored-by: command-bot <>
  • Loading branch information
acatangiu and bkontur authored Apr 12, 2024
1 parent 2dfe5f7 commit 1e971b8
Show file tree
Hide file tree
Showing 28 changed files with 2,217 additions and 387 deletions.
16 changes: 12 additions & 4 deletions bridges/modules/xcm-bridge-hub-router/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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,
Expand All @@ -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)?;
Expand Down Expand Up @@ -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)
}
}
Expand Down
5 changes: 4 additions & 1 deletion cumulus/pallets/xcmp-queue/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
},
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = {
Expand All @@ -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 = {
Expand All @@ -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);
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -48,13 +48,13 @@ decl_test_networks! {
PenpalA,
],
bridge = RococoWestendMockBridge

},
pub struct WestendMockNet {
relay_chain = Westend,
parachains = vec![
AssetHubWestend,
BridgeHubWestend,
PenpalB,
],
bridge = WestendRococoMockBridge
},
Expand Down Expand Up @@ -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 }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)]
Expand Down
Loading

0 comments on commit 1e971b8

Please sign in to comment.