Ensure xcm versions over bridge (on sending chains) (#2481)

## Summary

This pull request proposes a solution for improved control of the
versioned XCM flow over the bridge (across different consensus chains)
and resolves the situation where the sending chain/consensus has already
migrated to a higher XCM version than the receiving chain/consensus.

## Problem/Motivation

The current flow over the bridge involves a transfer from AssetHubRococo
(AHR) to BridgeHubRococo (BHR) to BridgeHubWestend (BHW) and finally to
AssetHubWestend (AHW), beginning with a reserve-backed transfer on AHR.

In this process:
1. AHR sends XCM `ExportMessage` through `XcmpQueue`, incorporating XCM
version checks using the `WrapVersion` feature, influenced by
`pallet_xcm::SupportedVersion` (managed by
`pallet_xcm::force_xcm_version` or version discovery).

2. BHR handles the `ExportMessage` instruction, utilizing the latest XCM
version. The `HaulBlobExporter` converts the inner XCM to
[`VersionedXcm::from`](https://github.com/paritytech/polkadot-sdk/blob/63ac2471aa0210f0ac9903bdd7d8f9351f9a635f/polkadot/xcm/xcm-builder/src/universal_exports.rs#L465-L467),
also using the latest XCM version.

However, challenges arise:
- Incompatibility when BHW uses a different version than BHR. For
instance, if BHR migrates to **XCMv4** while BHW remains on **XCMv3**,
BHR's `VersionedXcm::from` uses `VersionedXcm::V4` variant, causing
encoding issues for BHW.
  ```
	/// Just a simulation of possible error, which could happen on BHW
	/// (this code is based on actual master without XCMv4)
	let encoded = hex_literal::hex!("0400");
	println!("{:?}", VersionedXcm::<()>::decode(&mut &encoded[..]));

Err(Error { cause: None, desc: "Could not decode `VersionedXcm`, variant
doesn't exist" })
  ``` 
- Similar compatibility issues exist between AHR and AHW.

## Solution

This pull request introduces the following solutions:

1. **New trait `CheckVersion`** - added to the `xcm` module and exposing
`pallet_xcm::SupportedVersion`. This enhancement allows checking the
actual XCM version for desired destinations outside of the `pallet_xcm`
module.

2. **Version Check in `HaulBlobExporter`** uses `CheckVersion` to check
known/configured destination versions, ensuring compatibility. For
example, in the scenario mentioned, BHR can store the version `3` for
BHW. If BHR is on XCMv4, it will attempt to downgrade the message to
version `3` instead of using the latest version `4`.

3. **Version Check in `pallet-xcm-bridge-hub-router`** - this check
ensures compatibility with the real destination's XCM version,
preventing the unnecessary sending of messages to the local bridge hub
if versions are incompatible.

These additions aim to improve the control and compatibility of XCM
flows over the bridge and addressing issues related to version
mismatches.

## Possible alternative solution

_(More investigation is needed, and at the very least, it should extend
to XCMv4/5. If this proves to be a viable option, I can open an RFC for
XCM.)._

Add the `XcmVersion` attribute to the `ExportMessage` so that the
sending chain can determine, based on what is stored in
`pallet_xcm::SupportedVersion`, the version the destination is using.
This way, we may not need to handle the version in `HaulBlobExporter`.

```
ExportMessage {
	network: NetworkId,
	destination: InteriorMultiLocation,
	xcm: Xcm<()>
	destination_xcm_version: Version, // <- new attritbute
},
```

```
pub trait ExportXcm {
        fn validate(
		network: NetworkId,
		channel: u32,
		universal_source: &mut Option<InteriorMultiLocation>,
		destination: &mut Option<InteriorMultiLocation>,
		message: &mut Option<Xcm<()>>,
                destination_xcm_version: Version, , // <- new attritbute
	) -> SendResult<Self::Ticket>;
```

## Future Directions

This PR does not fix version discovery over bridge, further
investigation will be conducted here:
https://github.com/paritytech/polkadot-sdk/issues/2417.

## TODO

- [x] `pallet_xcm` mock for tests uses hard-coded XCM version `2` -
change to 3 or lastest?
- [x] fix `pallet-xcm-bridge-hub-router`
- [x] fix HaulBlobExporter with version determination
[here](https://github.com/paritytech/polkadot-sdk/blob/2183669d05f9b510f979a0cc3c7847707bacba2e/polkadot/xcm/xcm-builder/src/universal_exports.rs#L465)
- [x] add unit-tests to the runtimes
- [x] run benchmarks for `ExportMessage`
- [x] extend local run scripts about `force_xcm_version(dest, version)`
- [ ] when merged, prepare governance calls for Rococo/Westend
- [ ] add PRDoc

Part of: https://github.com/paritytech/parity-bridges-common/issues/2719

---------

Co-authored-by: command-bot <>
This commit is contained in:
Branislav Kontur
2023-12-12 16:04:26 +01:00
committed by GitHub
parent 42a3afba94
commit 575b8f8d15
55 changed files with 1277 additions and 488 deletions
@@ -15,6 +15,7 @@ frame-support = { path = "../../../../../../../substrate/frame/support", default
pallet-assets = { path = "../../../../../../../substrate/frame/assets", default-features = false }
pallet-balances = { path = "../../../../../../../substrate/frame/balances", default-features = false }
pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue" }
sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false }
# Polkadot
xcm = { package = "staging-xcm", path = "../../../../../../../polkadot/xcm", default-features = false }
@@ -14,10 +14,12 @@
// limitations under the License.
// Substrate
pub use frame_support::assert_ok;
pub use frame_support::{assert_err, assert_ok, pallet_prelude::DispatchResult};
pub use sp_runtime::DispatchError;
// Polkadot
pub use xcm::{
latest::ParentThen,
prelude::{AccountId32 as AccountId32Junction, *},
v3::{
Error,
@@ -13,70 +13,22 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::*;
use crate::tests::*;
fn send_asset_from_asset_hub_rococo_to_asset_hub_westend(id: MultiLocation, amount: u128) {
let signed_origin =
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get().into());
let asset_hub_westend_para_id = AssetHubWestend::para_id().into();
let destination = MultiLocation {
parents: 2,
interior: X2(GlobalConsensus(NetworkId::Westend), Parachain(asset_hub_westend_para_id)),
};
let beneficiary_id = AssetHubWestendReceiver::get();
let beneficiary: MultiLocation =
AccountId32Junction { network: None, id: beneficiary_id.into() }.into();
let assets: MultiAssets = (id, amount).into();
let fee_asset_item = 0;
let destination = asset_hub_westend_location();
// fund the AHR's SA on BHR for paying bridge transport fees
let ahr_as_seen_by_bhr = BridgeHubRococo::sibling_location_of(AssetHubRococo::para_id());
let sov_ahr_on_bhr = BridgeHubRococo::sovereign_account_id_of(ahr_as_seen_by_bhr);
BridgeHubRococo::fund_accounts(vec![(sov_ahr_on_bhr.into(), 10_000_000_000_000u128)]);
BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128);
AssetHubRococo::execute_with(|| {
assert_ok!(
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::limited_reserve_transfer_assets(
signed_origin,
bx!(destination.into()),
bx!(beneficiary.into()),
bx!(assets.into()),
fee_asset_item,
WeightLimit::Unlimited,
)
);
});
// set XCM versions
AssetHubRococo::force_xcm_version(destination, XCM_VERSION);
BridgeHubRococo::force_xcm_version(bridge_hub_westend_location(), XCM_VERSION);
BridgeHubRococo::execute_with(|| {
type RuntimeEvent = <BridgeHubRococo as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubRococo,
vec![
// pay for bridge fees
RuntimeEvent::Balances(pallet_balances::Event::Withdraw { .. }) => {},
// message exported
RuntimeEvent::BridgeWestendMessages(
pallet_bridge_messages::Event::MessageAccepted { .. }
) => {},
// message processed successfully
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
});
BridgeHubWestend::execute_with(|| {
type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubWestend,
vec![
// message dispatched successfully
RuntimeEvent::XcmpQueue(
cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }
) => {},
]
);
});
// send message over bridge
assert_ok!(send_asset_from_asset_hub_rococo(destination, (id, amount)));
assert_bridge_hub_rococo_message_accepted(true);
assert_bridge_hub_westend_message_received();
}
#[test]
@@ -13,6 +13,102 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::*;
mod asset_transfers;
mod send_xcm;
mod teleport;
pub(crate) fn asset_hub_westend_location() -> MultiLocation {
MultiLocation {
parents: 2,
interior: X2(
GlobalConsensus(NetworkId::Westend),
Parachain(AssetHubWestend::para_id().into()),
),
}
}
pub(crate) fn bridge_hub_westend_location() -> MultiLocation {
MultiLocation {
parents: 2,
interior: X2(
GlobalConsensus(NetworkId::Westend),
Parachain(BridgeHubWestend::para_id().into()),
),
}
}
pub(crate) fn send_asset_from_asset_hub_rococo(
destination: MultiLocation,
(id, amount): (MultiLocation, u128),
) -> DispatchResult {
let signed_origin =
<AssetHubRococo as Chain>::RuntimeOrigin::signed(AssetHubRococoSender::get().into());
let beneficiary: MultiLocation =
AccountId32Junction { network: None, id: AssetHubWestendReceiver::get().into() }.into();
let assets: MultiAssets = (id, amount).into();
let fee_asset_item = 0;
AssetHubRococo::execute_with(|| {
<AssetHubRococo as AssetHubRococoPallet>::PolkadotXcm::limited_reserve_transfer_assets(
signed_origin,
bx!(destination.into()),
bx!(beneficiary.into()),
bx!(assets.into()),
fee_asset_item,
WeightLimit::Unlimited,
)
})
}
pub(crate) fn assert_bridge_hub_rococo_message_accepted(expected_processed: bool) {
BridgeHubRococo::execute_with(|| {
type RuntimeEvent = <BridgeHubRococo as Chain>::RuntimeEvent;
if expected_processed {
assert_expected_events!(
BridgeHubRococo,
vec![
// pay for bridge fees
RuntimeEvent::Balances(pallet_balances::Event::Withdraw { .. }) => {},
// message exported
RuntimeEvent::BridgeWestendMessages(
pallet_bridge_messages::Event::MessageAccepted { .. }
) => {},
// message processed successfully
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
} else {
assert_expected_events!(
BridgeHubRococo,
vec![
RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed {
success: false,
..
}) => {},
]
);
}
});
}
pub(crate) fn assert_bridge_hub_westend_message_received() {
BridgeHubWestend::execute_with(|| {
type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubWestend,
vec![
// message sent to destination
RuntimeEvent::XcmpQueue(
cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }
) => {},
]
);
})
}
@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::*;
use crate::tests::*;
#[test]
fn send_xcm_from_rococo_relay_to_westend_asset_hub_should_fail_on_not_applicable() {
@@ -55,17 +55,123 @@ fn send_xcm_from_rococo_relay_to_westend_asset_hub_should_fail_on_not_applicable
});
// Receive XCM message in Bridge Hub source Parachain, it should fail, because we don't have
// opened bridge/lane.
BridgeHubRococo::execute_with(|| {
type RuntimeEvent = <BridgeHubRococo as Chain>::RuntimeEvent;
assert_bridge_hub_rococo_message_accepted(false);
}
#[test]
fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() {
// Initially set only default version on all runtimes
AssetHubRococo::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION));
BridgeHubRococo::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION));
BridgeHubWestend::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION));
AssetHubWestend::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION));
// prepare data
let destination = asset_hub_westend_location();
let native_token = MultiLocation::parent();
let amount = ASSET_HUB_ROCOCO_ED * 1_000;
// fund the AHR's SA on BHR for paying bridge transport fees
BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128);
// fund sender
AssetHubRococo::fund_accounts(vec![(AssetHubRococoSender::get().into(), amount * 10)]);
// send XCM from AssetHubRococo - fails - destination version not known
assert_err!(
send_asset_from_asset_hub_rococo(destination, (native_token, amount)),
DispatchError::Module(sp_runtime::ModuleError {
index: 31,
error: [1, 0, 0, 0],
message: Some("SendFailure")
})
);
// set destination version
AssetHubRococo::force_xcm_version(destination, xcm::v3::prelude::XCM_VERSION);
// TODO: remove this block, when removing `xcm:v2`
{
// send XCM from AssetHubRococo - fails - AssetHubRococo is set to the default/safe `2`
// version, which does not have the `ExportMessage` instruction. If the default `2` is
// changed to `3`, then this assert can go away"
assert_err!(
send_asset_from_asset_hub_rococo(destination, (native_token, amount)),
DispatchError::Module(sp_runtime::ModuleError {
index: 31,
error: [1, 0, 0, 0],
message: Some("SendFailure")
})
);
// set exact version for BridgeHubWestend to `2` without `ExportMessage` instruction
AssetHubRococo::force_xcm_version(
ParentThen(Parachain(BridgeHubRococo::para_id().into()).into()).into(),
xcm::v2::prelude::XCM_VERSION,
);
// send XCM from AssetHubRococo - fails - `ExportMessage` is not in `2`
assert_err!(
send_asset_from_asset_hub_rococo(destination, (native_token, amount)),
DispatchError::Module(sp_runtime::ModuleError {
index: 31,
error: [1, 0, 0, 0],
message: Some("SendFailure")
})
);
}
// set version with `ExportMessage` for BridgeHubRococo
AssetHubRococo::force_xcm_version(
ParentThen(Parachain(BridgeHubRococo::para_id().into()).into()).into(),
xcm::v3::prelude::XCM_VERSION,
);
// send XCM from AssetHubRococo - ok
assert_ok!(send_asset_from_asset_hub_rococo(destination, (native_token, amount)));
// `ExportMessage` on local BridgeHub - fails - remote BridgeHub version not known
assert_bridge_hub_rococo_message_accepted(false);
// set version for remote BridgeHub on BridgeHubRococo
BridgeHubRococo::force_xcm_version(
bridge_hub_westend_location(),
xcm::v3::prelude::XCM_VERSION,
);
// set version for AssetHubWestend on BridgeHubWestend
BridgeHubWestend::force_xcm_version(
ParentThen(Parachain(AssetHubWestend::para_id().into()).into()).into(),
xcm::v3::prelude::XCM_VERSION,
);
// send XCM from AssetHubRococo - ok
assert_ok!(send_asset_from_asset_hub_rococo(destination, (native_token, amount)));
assert_bridge_hub_rococo_message_accepted(true);
assert_bridge_hub_westend_message_received();
// message delivered and processed at destination
AssetHubWestend::execute_with(|| {
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubRococo,
AssetHubWestend,
vec![
RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed {
success: false,
..
}) => {},
// message processed with failure, but for this scenario it is ok, important is that was delivered
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: false, .. }
) => {},
]
);
});
// TODO: remove this block, when removing `xcm:v2`
{
// set `2` version for remote BridgeHub on BridgeHubRococo, which does not have
// `UniversalOrigin` and `DescendOrigin`
BridgeHubRococo::force_xcm_version(
bridge_hub_westend_location(),
xcm::v2::prelude::XCM_VERSION,
);
// send XCM from AssetHubRococo - ok
assert_ok!(send_asset_from_asset_hub_rococo(destination, (native_token, amount)));
// message is not accepted on the local BridgeHub (`DestinationUnsupported`) because we
// cannot add `UniversalOrigin` and `DescendOrigin`
assert_bridge_hub_rococo_message_accepted(false);
}
}
@@ -15,6 +15,7 @@ frame-support = { path = "../../../../../../../substrate/frame/support", default
pallet-assets = { path = "../../../../../../../substrate/frame/assets", default-features = false }
pallet-balances = { path = "../../../../../../../substrate/frame/balances", default-features = false }
pallet-message-queue = { path = "../../../../../../../substrate/frame/message-queue" }
sp-runtime = { path = "../../../../../../../substrate/primitives/runtime", default-features = false }
# Polkadot
xcm = { package = "staging-xcm", path = "../../../../../../../polkadot/xcm", default-features = false }
@@ -14,10 +14,12 @@
// limitations under the License.
// Substrate
pub use frame_support::assert_ok;
pub use frame_support::{assert_err, assert_ok, pallet_prelude::DispatchResult};
pub use sp_runtime::DispatchError;
// Polkadot
pub use xcm::{
latest::ParentThen,
prelude::{AccountId32 as AccountId32Junction, *},
v3::{
Error,
@@ -12,70 +12,22 @@
// 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 crate::*;
use crate::tests::*;
fn send_asset_from_asset_hub_westend_to_asset_hub_rococo(id: MultiLocation, amount: u128) {
let signed_origin =
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get().into());
let asset_hub_rococo_para_id = AssetHubRococo::para_id().into();
let destination = MultiLocation {
parents: 2,
interior: X2(GlobalConsensus(NetworkId::Rococo), Parachain(asset_hub_rococo_para_id)),
};
let beneficiary_id = AssetHubRococoReceiver::get();
let beneficiary: MultiLocation =
AccountId32Junction { network: None, id: beneficiary_id.into() }.into();
let assets: MultiAssets = (id, amount).into();
let fee_asset_item = 0;
let destination = asset_hub_rococo_location();
// fund the AHW's SA on BHW for paying bridge transport fees
let ahw_as_seen_by_bhw = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id());
let sov_ahw_on_bhw = BridgeHubWestend::sovereign_account_id_of(ahw_as_seen_by_bhw);
BridgeHubWestend::fund_accounts(vec![(sov_ahw_on_bhw.into(), 10_000_000_000_000u128)]);
BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128);
AssetHubWestend::execute_with(|| {
assert_ok!(
<AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::limited_reserve_transfer_assets(
signed_origin,
bx!(destination.into()),
bx!(beneficiary.into()),
bx!(assets.into()),
fee_asset_item,
WeightLimit::Unlimited,
)
);
});
// set XCM versions
AssetHubWestend::force_xcm_version(destination, XCM_VERSION);
BridgeHubWestend::force_xcm_version(bridge_hub_rococo_location(), XCM_VERSION);
BridgeHubWestend::execute_with(|| {
type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubWestend,
vec![
// pay for bridge fees
RuntimeEvent::Balances(pallet_balances::Event::Withdraw { .. }) => {},
// message exported
RuntimeEvent::BridgeRococoMessages(
pallet_bridge_messages::Event::MessageAccepted { .. }
) => {},
// message processed successfully
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
});
BridgeHubRococo::execute_with(|| {
type RuntimeEvent = <BridgeHubRococo as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubRococo,
vec![
// message dispatched successfully
RuntimeEvent::XcmpQueue(
cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }
) => {},
]
);
});
// send message over bridge
assert_ok!(send_asset_from_asset_hub_westend(destination, (id, amount)));
assert_bridge_hub_westend_message_accepted(true);
assert_bridge_hub_rococo_message_received();
}
#[test]
@@ -13,6 +13,102 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::*;
mod asset_transfers;
mod send_xcm;
mod teleport;
pub(crate) fn asset_hub_rococo_location() -> MultiLocation {
MultiLocation {
parents: 2,
interior: X2(
GlobalConsensus(NetworkId::Rococo),
Parachain(AssetHubRococo::para_id().into()),
),
}
}
pub(crate) fn bridge_hub_rococo_location() -> MultiLocation {
MultiLocation {
parents: 2,
interior: X2(
GlobalConsensus(NetworkId::Rococo),
Parachain(BridgeHubRococo::para_id().into()),
),
}
}
pub(crate) fn send_asset_from_asset_hub_westend(
destination: MultiLocation,
(id, amount): (MultiLocation, u128),
) -> DispatchResult {
let signed_origin =
<AssetHubWestend as Chain>::RuntimeOrigin::signed(AssetHubWestendSender::get().into());
let beneficiary: MultiLocation =
AccountId32Junction { network: None, id: AssetHubRococoReceiver::get().into() }.into();
let assets: MultiAssets = (id, amount).into();
let fee_asset_item = 0;
AssetHubWestend::execute_with(|| {
<AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::limited_reserve_transfer_assets(
signed_origin,
bx!(destination.into()),
bx!(beneficiary.into()),
bx!(assets.into()),
fee_asset_item,
WeightLimit::Unlimited,
)
})
}
pub(crate) fn assert_bridge_hub_westend_message_accepted(expected_processed: bool) {
BridgeHubWestend::execute_with(|| {
type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent;
if expected_processed {
assert_expected_events!(
BridgeHubWestend,
vec![
// pay for bridge fees
RuntimeEvent::Balances(pallet_balances::Event::Withdraw { .. }) => {},
// message exported
RuntimeEvent::BridgeRococoMessages(
pallet_bridge_messages::Event::MessageAccepted { .. }
) => {},
// message processed successfully
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: true, .. }
) => {},
]
);
} else {
assert_expected_events!(
BridgeHubWestend,
vec![
RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed {
success: false,
..
}) => {},
]
);
}
});
}
pub(crate) fn assert_bridge_hub_rococo_message_received() {
BridgeHubRococo::execute_with(|| {
type RuntimeEvent = <BridgeHubRococo as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubRococo,
vec![
// message sent to destination
RuntimeEvent::XcmpQueue(
cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }
) => {},
]
);
})
}
@@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::*;
use crate::tests::*;
#[test]
fn send_xcm_from_westend_relay_to_rococo_asset_hub_should_fail_on_not_applicable() {
@@ -55,17 +55,123 @@ fn send_xcm_from_westend_relay_to_rococo_asset_hub_should_fail_on_not_applicable
});
// Receive XCM message in Bridge Hub source Parachain, it should fail, because we don't have
// opened bridge/lane.
BridgeHubWestend::execute_with(|| {
type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent;
assert_bridge_hub_westend_message_accepted(false);
}
#[test]
fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() {
// Initially set only default version on all runtimes
AssetHubRococo::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION));
BridgeHubRococo::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION));
BridgeHubWestend::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION));
AssetHubWestend::force_default_xcm_version(Some(xcm::v2::prelude::XCM_VERSION));
// prepare data
let destination = asset_hub_rococo_location();
let native_token = MultiLocation::parent();
let amount = ASSET_HUB_WESTEND_ED * 1_000;
// fund the AHR's SA on BHR for paying bridge transport fees
BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128);
// fund sender
AssetHubWestend::fund_accounts(vec![(AssetHubWestendSender::get().into(), amount * 10)]);
// send XCM from AssetHubWestend - fails - destination version not known
assert_err!(
send_asset_from_asset_hub_westend(destination, (native_token, amount)),
DispatchError::Module(sp_runtime::ModuleError {
index: 31,
error: [1, 0, 0, 0],
message: Some("SendFailure")
})
);
// set destination version
AssetHubWestend::force_xcm_version(destination, xcm::v3::prelude::XCM_VERSION);
// TODO: remove this block, when removing `xcm:v2`
{
// send XCM from AssetHubRococo - fails - AssetHubRococo is set to the default/safe `2`
// version, which does not have the `ExportMessage` instruction. If the default `2` is
// changed to `3`, then this assert can go away"
assert_err!(
send_asset_from_asset_hub_westend(destination, (native_token, amount)),
DispatchError::Module(sp_runtime::ModuleError {
index: 31,
error: [1, 0, 0, 0],
message: Some("SendFailure")
})
);
// set exact version for BridgeHubWestend to `2` without `ExportMessage` instruction
AssetHubWestend::force_xcm_version(
ParentThen(Parachain(BridgeHubWestend::para_id().into()).into()).into(),
xcm::v2::prelude::XCM_VERSION,
);
// send XCM from AssetHubWestend - fails - `ExportMessage` is not in `2`
assert_err!(
send_asset_from_asset_hub_westend(destination, (native_token, amount)),
DispatchError::Module(sp_runtime::ModuleError {
index: 31,
error: [1, 0, 0, 0],
message: Some("SendFailure")
})
);
}
// set version with `ExportMessage` for BridgeHubWestend
AssetHubWestend::force_xcm_version(
ParentThen(Parachain(BridgeHubWestend::para_id().into()).into()).into(),
xcm::v3::prelude::XCM_VERSION,
);
// send XCM from AssetHubWestend - ok
assert_ok!(send_asset_from_asset_hub_westend(destination, (native_token, amount)));
// `ExportMessage` on local BridgeHub - fails - remote BridgeHub version not known
assert_bridge_hub_westend_message_accepted(false);
// set version for remote BridgeHub on BridgeHubWestend
BridgeHubWestend::force_xcm_version(
bridge_hub_rococo_location(),
xcm::v3::prelude::XCM_VERSION,
);
// set version for AssetHubRococo on BridgeHubRococo
BridgeHubRococo::force_xcm_version(
ParentThen(Parachain(AssetHubRococo::para_id().into()).into()).into(),
xcm::v3::prelude::XCM_VERSION,
);
// send XCM from AssetHubWestend - ok
assert_ok!(send_asset_from_asset_hub_westend(destination, (native_token, amount)));
assert_bridge_hub_westend_message_accepted(true);
assert_bridge_hub_rococo_message_received();
// message delivered and processed at destination
AssetHubRococo::execute_with(|| {
type RuntimeEvent = <AssetHubRococo as Chain>::RuntimeEvent;
assert_expected_events!(
BridgeHubWestend,
AssetHubRococo,
vec![
RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed {
success: false,
..
}) => {},
// message processed with failure, but for this scenario it is ok, important is that was delivered
RuntimeEvent::MessageQueue(
pallet_message_queue::Event::Processed { success: false, .. }
) => {},
]
);
});
// TODO: remove this block, when removing `xcm:v2`
{
// set `2` version for remote BridgeHub on BridgeHubRococo, which does not have
// `UniversalOrigin` and `DescendOrigin`
BridgeHubWestend::force_xcm_version(
bridge_hub_rococo_location(),
xcm::v2::prelude::XCM_VERSION,
);
// send XCM from AssetHubWestend - ok
assert_ok!(send_asset_from_asset_hub_westend(destination, (native_token, amount)));
// message is not accepted on the local BridgeHub (`DestinationUnsupported`) because we
// cannot add `UniversalOrigin` and `DescendOrigin`
assert_bridge_hub_westend_message_accepted(false);
}
}