From 049d46a0bc3b8750915ad733e8d62253185d04ba Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Sat, 26 Oct 2024 22:23:56 +0200 Subject: [PATCH] Add tests that `pallet-xcm-bridge-hub-router` can work locally with `pallet-xcm-bridge-hub` for `ExportXcm` (needed for AH deploymend) --- .../modules/xcm-bridge-hub/src/exporter.rs | 133 ++++++++++-------- bridges/modules/xcm-bridge-hub/src/lib.rs | 15 +- bridges/modules/xcm-bridge-hub/src/mock.rs | 72 +++++++--- 3 files changed, 130 insertions(+), 90 deletions(-) diff --git a/bridges/modules/xcm-bridge-hub/src/exporter.rs b/bridges/modules/xcm-bridge-hub/src/exporter.rs index 0dcd3a8fccdc..3a2f811ac772 100644 --- a/bridges/modules/xcm-bridge-hub/src/exporter.rs +++ b/bridges/modules/xcm-bridge-hub/src/exporter.rs @@ -133,8 +133,9 @@ where let bridge = Self::bridge(locations.bridge_id()).ok_or_else(|| { log::error!( target: LOG_TARGET, - "No opened bridge for requested bridge_origin_relative_location: {:?} and bridge_destination_universal_location: {:?}", + "No opened bridge for requested bridge_origin_relative_location: {:?} (bridge_origin_universal_location: {:?}) and bridge_destination_universal_location: {:?}", locations.bridge_origin_relative_location(), + locations.bridge_origin_universal_location(), locations.bridge_destination_universal_location(), ); SendError::NotApplicable @@ -360,12 +361,12 @@ impl HaulBlob for DummyHaulBlob { #[cfg(test)] mod tests { use super::*; - use crate::{mock::*, Bridges, LaneToBridge, LanesManagerOf}; + use crate::{mock::*, Bridges, LanesManagerOf}; use bp_runtime::RangeInclusiveExt; use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState}; use frame_support::assert_ok; - use pallet_bridge_messages::InboundLaneStorage; + use frame_support::traits::{Contains, EnsureOrigin}; use xcm_builder::{NetworkExportTable, UnpaidRemoteExporter}; use xcm_executor::traits::export_xcm; @@ -381,61 +382,31 @@ mod tests { BridgedUniversalDestination::get() } - fn open_lane() -> (BridgeLocations, TestLaneIdType) { + fn open_lane(origin: RuntimeOrigin) -> (BridgeLocations, TestLaneIdType) { // open expected outbound lane - let origin = OpenBridgeOrigin::sibling_parachain_origin(); let with = bridged_asset_hub_universal_location(); let locations = - XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap(); + XcmOverBridge::bridge_locations_from_origin(origin.clone(), Box::new(with.into())).unwrap(); let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap(); if !Bridges::::contains_key(locations.bridge_id()) { - // insert bridge - Bridges::::insert( - locations.bridge_id(), - Bridge { - bridge_origin_relative_location: Box::new(SiblingLocation::get().into()), - bridge_origin_universal_location: Box::new( - locations.bridge_origin_universal_location().clone().into(), - ), - bridge_destination_universal_location: Box::new( - locations.bridge_destination_universal_location().clone().into(), - ), - state: BridgeState::Opened, - deposit: None, - lane_id, - }, - ); - LaneToBridge::::insert(lane_id, locations.bridge_id()); - - // create lanes - let lanes_manager = LanesManagerOf::::new(); - if lanes_manager.create_inbound_lane(lane_id).is_ok() { - assert_eq!( - 0, - lanes_manager - .active_inbound_lane(lane_id) - .unwrap() - .storage() - .data() - .last_confirmed_nonce - ); - } - if lanes_manager.create_outbound_lane(lane_id).is_ok() { - assert!(lanes_manager - .active_outbound_lane(lane_id) - .unwrap() - .queued_messages() - .is_empty()); + // fund origin (if needed) + if !>::AllowWithoutBridgeDeposit::contains( + locations.bridge_origin_relative_location() + ) { + fund_origin_sovereign_account(&locations, BridgeDeposit::get() + ExistentialDeposit::get()); } + + // open bridge + assert_ok!(XcmOverBridge::do_open_bridge(locations.clone(), lane_id, true)); } assert_ok!(XcmOverBridge::do_try_state()); (*locations, lane_id) } - fn open_lane_and_send_regular_message() -> (BridgeId, TestLaneIdType) { - let (locations, lane_id) = open_lane(); + fn open_lane_and_send_regular_message(source_origin: RuntimeOrigin) -> (BridgeId, TestLaneIdType) { + let (locations, lane_id) = open_lane(source_origin); // now let's try to enqueue message using our `ExportXcm` implementation export_xcm::( @@ -453,7 +424,7 @@ mod tests { #[test] fn exporter_works() { run_test(|| { - let (_, lane_id) = open_lane_and_send_regular_message(); + let (_, lane_id) = open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin()); // double check that the message has been pushed to the expected lane // (it should already been checked during `send_message` call) @@ -468,7 +439,7 @@ mod tests { #[test] fn exporter_does_not_suspend_the_bridge_if_outbound_bridge_queue_is_not_congested() { run_test(|| { - let (bridge_id, _) = open_lane_and_send_regular_message(); + let (bridge_id, _) = open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin()); assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); @@ -477,15 +448,15 @@ mod tests { #[test] fn exporter_does_not_suspend_the_bridge_if_it_is_already_suspended() { run_test(|| { - let (bridge_id, _) = open_lane_and_send_regular_message(); + let (bridge_id, _) = open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin()); Bridges::::mutate_extant(bridge_id, |bridge| { bridge.state = BridgeState::Suspended; }); for _ in 1..OUTBOUND_LANE_CONGESTED_THRESHOLD { - open_lane_and_send_regular_message(); + open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin()); } - open_lane_and_send_regular_message(); + open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin()); assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); }); } @@ -493,15 +464,15 @@ mod tests { #[test] fn exporter_suspends_the_bridge_if_outbound_bridge_queue_is_congested() { run_test(|| { - let (bridge_id, _) = open_lane_and_send_regular_message(); + let (bridge_id, _) = open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin()); for _ in 1..OUTBOUND_LANE_CONGESTED_THRESHOLD { - open_lane_and_send_regular_message(); + open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin()); } assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); - open_lane_and_send_regular_message(); + open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin()); assert!(TestLocalXcmChannelManager::is_bridge_suspened()); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended); }); @@ -510,7 +481,7 @@ mod tests { #[test] fn bridge_is_not_resumed_if_outbound_bridge_queue_is_still_congested() { run_test(|| { - let (bridge_id, lane_id) = open_lane_and_send_regular_message(); + let (bridge_id, lane_id) = open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin()); Bridges::::mutate_extant(bridge_id, |bridge| { bridge.state = BridgeState::Suspended; }); @@ -527,7 +498,7 @@ mod tests { #[test] fn bridge_is_not_resumed_if_it_was_not_suspended_before() { run_test(|| { - let (bridge_id, lane_id) = open_lane_and_send_regular_message(); + let (bridge_id, lane_id) = open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin()); XcmOverBridge::on_bridge_messages_delivered( lane_id, OUTBOUND_LANE_UNCONGESTED_THRESHOLD, @@ -541,7 +512,7 @@ mod tests { #[test] fn bridge_is_resumed_when_enough_messages_are_delivered() { run_test(|| { - let (bridge_id, lane_id) = open_lane_and_send_regular_message(); + let (bridge_id, lane_id) = open_lane_and_send_regular_message(OpenBridgeOrigin::sibling_parachain_origin()); Bridges::::mutate_extant(bridge_id, |bridge| { bridge.state = BridgeState::Suspended; }); @@ -637,13 +608,15 @@ mod tests { } #[test] - fn exporter_is_compatible_with_pallet_xcm_bridge_hub_router() { + fn pallet_as_exporter_is_compatible_with_pallet_xcm_bridge_hub_router_for_export_message() { run_test(|| { // valid routable destination let dest = Location::new(2, BridgedUniversalDestination::get()); // open bridge - let (_, expected_lane_id) = open_lane(); + let origin = OpenBridgeOrigin::sibling_parachain_origin(); + let origin_as_location = OpenBridgeOriginOf::::try_origin(origin.clone()).unwrap(); + let (_, expected_lane_id) = open_lane(origin); // check before - no messages assert_eq!( @@ -657,7 +630,7 @@ mod tests { ); // send `ExportMessage(message)` by `UnpaidRemoteExporter`. - ExecuteXcmOverSendXcm::set_origin_for_execute(SiblingLocation::get()); + ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location.clone()); assert_ok!(send_xcm::< UnpaidRemoteExporter< NetworkExportTable, @@ -667,8 +640,8 @@ mod tests { >(dest.clone(), Xcm::<()>::default())); // send `ExportMessage(message)` by `pallet_xcm_bridge_hub_router`. - ExecuteXcmOverSendXcm::set_origin_for_execute(SiblingLocation::get()); - assert_ok!(send_xcm::(dest.clone(), Xcm::<()>::default())); + ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location); + assert_ok!(send_xcm::(dest, Xcm::<()>::default())); // check after - a message ready to be relayed assert_eq!( @@ -683,6 +656,42 @@ mod tests { }) } + #[test] + fn pallet_as_exporter_is_compatible_with_pallet_xcm_bridge_hub_router_for_export_xcm() { + run_test(|| { + // valid routable destination + let dest = Location::new(2, BridgedUniversalDestination::get()); + + // open bridge as a root on the local chain, which should be converted as `Location::here()` + let (_, expected_lane_id) = open_lane(RuntimeOrigin::root()); + + // check before - no messages + assert_eq!( + pallet_bridge_messages::Pallet::::outbound_lane_data( + expected_lane_id + ) + .unwrap() + .queued_messages() + .saturating_len(), + 0 + ); + + // trigger `ExportXcm` by `pallet_xcm_bridge_hub_router`. + assert_ok!(send_xcm::(dest, Xcm::<()>::default())); + + // check after - a message ready to be relayed + assert_eq!( + pallet_bridge_messages::Pallet::::outbound_lane_data( + expected_lane_id + ) + .unwrap() + .queued_messages() + .saturating_len(), + 1 + ); + }) + } + #[test] fn validate_works() { run_test(|| { @@ -760,7 +769,7 @@ mod tests { ); // ok - let _ = open_lane(); + let _ = open_lane(OpenBridgeOrigin::sibling_parachain_origin()); let mut dest_wrapper = Some(bridged_relative_destination()); assert_ok!(XcmOverBridge::validate( BridgedRelayNetwork::get(), diff --git a/bridges/modules/xcm-bridge-hub/src/lib.rs b/bridges/modules/xcm-bridge-hub/src/lib.rs index aa3c755bd348..7429ff6da455 100644 --- a/bridges/modules/xcm-bridge-hub/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub/src/lib.rs @@ -841,19 +841,11 @@ mod tests { use bp_messages::LaneIdType; use mock::*; - use frame_support::{assert_err, assert_noop, assert_ok, traits::fungible::Mutate, BoundedVec}; + use frame_support::{assert_err, assert_noop, assert_ok, BoundedVec}; use frame_system::{EventRecord, Phase}; use sp_runtime::traits::Zero; use sp_runtime::TryRuntimeError; - fn fund_origin_sovereign_account(locations: &BridgeLocations, balance: Balance) -> AccountId { - let bridge_owner_account = - LocationToAccountId::convert_location(locations.bridge_origin_relative_location()) - .unwrap(); - assert_ok!(Balances::mint_into(&bridge_owner_account, balance)); - bridge_owner_account - } - fn mock_open_bridge_from_with( origin: RuntimeOrigin, deposit: Option, @@ -1094,11 +1086,12 @@ mod tests { #[test] fn open_bridge_works() { run_test(|| { - // in our test runtime, we expect that bridge may be opened by parent relay chain - // and any sibling parachain + // in our test runtime, we expect that bridge may be opened by parent relay chain, + // any sibling parachain or local root let origins = [ (OpenBridgeOrigin::parent_relay_chain_origin(), None), (OpenBridgeOrigin::sibling_parachain_origin(), Some(BridgeDeposit::get())), + (RuntimeOrigin::root(), None), ]; // check that every origin may open the bridge diff --git a/bridges/modules/xcm-bridge-hub/src/mock.rs b/bridges/modules/xcm-bridge-hub/src/mock.rs index 858561b450ba..e2460cd26631 100644 --- a/bridges/modules/xcm-bridge-hub/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub/src/mock.rs @@ -17,19 +17,23 @@ #![cfg(test)] use crate as pallet_xcm_bridge_hub; +use std::marker::PhantomData; use bp_messages::{ target_chain::{DispatchMessage, MessageDispatch}, ChainWithMessages, HashedLaneId, MessageNonce, }; use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, HashOf}; -use bp_xcm_bridge_hub::{BridgeId, LocalXcmChannelManager}; +use bp_xcm_bridge_hub::{BridgeId, BridgeLocations, LocalXcmChannelManager}; use codec::Encode; use frame_support::{ assert_ok, derive_impl, parameter_types, traits::{EnsureOrigin, Equals, Everything, OriginTrait}, weights::RuntimeDbWeight, }; +use frame_support::traits::EitherOf; +use frame_support::traits::fungible::Mutate; +use frame_system::{EnsureRoot, EnsureRootWithSuccess}; use polkadot_parachain_primitives::primitives::Sibling; use sp_core::H256; use sp_runtime::{ @@ -39,11 +43,8 @@ use sp_runtime::{ }; use sp_std::cell::RefCell; use xcm::prelude::*; -use xcm_builder::{ - AllowUnpaidExecutionFrom, DispatchBlob, DispatchBlobError, FixedWeightBounds, - InspectMessageQueues, NetworkExportTable, NetworkExportTableItem, ParentIsPreset, - SiblingParachainConvertsVia, SovereignPaidRemoteExporter, -}; +use xcm_builder::{AllowUnpaidExecutionFrom, DispatchBlob, DispatchBlobError, FixedWeightBounds, InspectMessageQueues, NetworkExportTable, NetworkExportTableItem, ParentIsPreset, SiblingParachainConvertsVia, SovereignPaidRemoteExporter, UnpaidLocalExporter}; +use xcm_executor::traits::{ConvertLocation, ConvertOrigin}; use xcm_executor::XcmExecutor; pub type AccountId = AccountId32; @@ -63,7 +64,8 @@ frame_support::construct_runtime! { Balances: pallet_balances, Messages: pallet_bridge_messages, XcmOverBridge: pallet_xcm_bridge_hub, - XcmOverBridgeRouter: pallet_xcm_bridge_hub_router = 57, + XcmOverBridgeWrappedWithExportMessageRouter: pallet_xcm_bridge_hub_router = 57, + XcmOverBridgeByExportXcmRouter: pallet_xcm_bridge_hub_router:: = 69, } } @@ -148,6 +150,7 @@ impl pallet_bridge_messages::WeightInfoExt for TestMessagesWeights { } parameter_types! { + pub const HereLocation: Location = Location::here(); pub const RelayNetwork: NetworkId = NetworkId::Kusama; pub UniversalLocation: InteriorLocation = [ GlobalConsensus(RelayNetwork::get()), @@ -195,13 +198,20 @@ impl pallet_xcm_bridge_hub::Config for TestRuntime { type DestinationVersion = AlwaysLatest; type ForceOrigin = frame_system::EnsureNever<()>; - type OpenBridgeOrigin = OpenBridgeOrigin; + type OpenBridgeOrigin = EitherOf< + // We want to translate `RuntimeOrigin::root()` to the `Location::here()` + EnsureRootWithSuccess, + OpenBridgeOrigin, + >; type BridgeOriginAccountIdConverter = LocationToAccountId; type BridgeDeposit = BridgeDeposit; type Currency = Balances; type RuntimeHoldReason = RuntimeHoldReason; - type AllowWithoutBridgeDeposit = Equals; + type AllowWithoutBridgeDeposit = ( + Equals, + Equals + ); type LocalXcmChannelManager = TestLocalXcmChannelManager; @@ -224,7 +234,7 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { // produced by `pallet_xcm_bridge_hub_router` is compatible with the `ExportXcm` implementation // of `pallet_xcm_bridge_hub`. type ToBridgeHubSender = SovereignPaidRemoteExporter< - XcmOverBridgeRouter, + XcmOverBridgeWrappedWithExportMessageRouter, // **Note**: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which // calls the `ExportXcm` implementation of `pallet_xcm_bridge_hub` as the // `MessageExporter`. @@ -237,6 +247,28 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { type FeeAsset = BridgeFeeAsset; } +/// A router instance simulates a scenario where the router is deployed on the same chain than the `MessageExporter`. This means that the router triggers `ExportXcm` trait directly. +impl pallet_xcm_bridge_hub_router::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + + type UniversalLocation = UniversalLocation; + type SiblingBridgeHubLocation = BridgeHubLocation; + type BridgedNetworkId = BridgedRelayNetwork; + type Bridges = NetworkExportTable; + type DestinationVersion = AlwaysLatest; + + // We use `UnpaidLocalExporter` here to test and ensure that `pallet_xcm_bridge_hub_router` can trigger directly `pallet_xcm_bridge_hub` as exporter. + type ToBridgeHubSender = UnpaidLocalExporter< + XcmOverBridge, + Self::UniversalLocation, + >; + type LocalXcmChannelManager = TestLocalXcmChannelManager; + + type ByteFee = ConstU128<0>; + type FeeAsset = BridgeFeeAsset; +} + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -384,13 +416,9 @@ impl EnsureOrigin for OpenBridgeOrigin { return Ok(Location { parents: 1, interior: [Parachain(SIBLING_ASSET_HUB_ID), OnlyChild].into(), - }) - } - - let mut sibling_account = [0u8; 32]; - sibling_account[..4].copy_from_slice(&SIBLING_ASSET_HUB_ID.encode()[..4]); - if signer == Some(sibling_account.into()) { - return Ok(Location { parents: 1, interior: Parachain(SIBLING_ASSET_HUB_ID).into() }) + }); + } else if signer == Self::sibling_parachain_origin().into_signer() { + return Ok(SiblingLocation::get()); } Err(o) @@ -402,6 +430,16 @@ impl EnsureOrigin for OpenBridgeOrigin { } } +pub(crate) type OpenBridgeOriginOf = >::OpenBridgeOrigin; + +pub(crate) fn fund_origin_sovereign_account(locations: &BridgeLocations, balance: Balance) -> AccountId { + let bridge_owner_account = + LocationToAccountId::convert_location(locations.bridge_origin_relative_location()) + .unwrap(); + assert_ok!(Balances::mint_into(&bridge_owner_account, balance)); + bridge_owner_account +} + pub struct TestLocalXcmChannelManager; impl TestLocalXcmChannelManager {