// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Cumulus is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . use super::{BridgeHubRococo, BridgeHubWococo}; // pub use paste; use bp_messages::{ target_chain::{DispatchMessage, DispatchMessageData, MessageDispatch}, LaneId, MessageKey, OutboundLaneData, }; use bridge_runtime_common::messages_xcm_extension::XcmBlobMessageDispatchResult; use codec::Decode; pub use cumulus_primitives_core::{DmpMessageHandler, XcmpMessageHandler}; use pallet_bridge_messages::{Config, Instance1, Instance2, OutboundLanes, Pallet}; use sp_core::Get; use xcm_emulator::{BridgeMessage, BridgeMessageDispatchError, BridgeMessageHandler, Chain}; pub struct BridgeHubMessageHandler { _marker: std::marker::PhantomData<(S, T, I)>, } struct LaneIdWrapper(LaneId); impl From for u32 { fn from(lane_id: LaneIdWrapper) -> u32 { u32::from_be_bytes(lane_id.0 .0) } } impl From for LaneIdWrapper { fn from(id: u32) -> LaneIdWrapper { LaneIdWrapper(LaneId(id.to_be_bytes())) } } type BridgeHubRococoRuntime = ::Runtime; type BridgeHubWococoRuntime = ::Runtime; // TODO: uncomment when https://github.com/paritytech/cumulus/pull/2528 is merged // type BridgeHubPolkadotRuntime = ::Runtime; // type BridgeHubKusamaRuntime = ::Runtime; pub type RococoWococoMessageHandler = BridgeHubMessageHandler; pub type WococoRococoMessageHandler = BridgeHubMessageHandler; // TODO: uncomment when https://github.com/paritytech/cumulus/pull/2528 is merged // pub type PolkadotKusamaMessageHandler // = BridgeHubMessageHandler; // pub type KusamaPolkadotMessageHandler // = BridgeHubMessageHandler; impl BridgeMessageHandler for BridgeHubMessageHandler where S: Config, T: Config, I: 'static, >::InboundPayload: From>, >::MessageDispatch: MessageDispatch, { fn get_source_outbound_messages() -> Vec { // get the source active outbound lanes let active_lanes = S::ActiveOutboundLanes::get(); let mut messages: Vec = Default::default(); // collect messages from `OutboundMessages` for each active outbound lane in the source for lane in active_lanes { let latest_generated_nonce = OutboundLanes::::get(lane).latest_generated_nonce; let latest_received_nonce = OutboundLanes::::get(lane).latest_received_nonce; (latest_received_nonce + 1..=latest_generated_nonce).for_each(|nonce| { let encoded_payload: Vec = Pallet::::outbound_message_data(*lane, nonce) .expect("Bridge message does not exist") .into(); let payload = Vec::::decode(&mut &encoded_payload[..]) .expect("Decodign XCM message failed"); let id: u32 = LaneIdWrapper(*lane).into(); let message = BridgeMessage { id, nonce, payload }; messages.push(message); }); } messages } fn dispatch_target_inbound_message( message: BridgeMessage, ) -> Result<(), BridgeMessageDispatchError> { type TargetMessageDispatch = >::MessageDispatch; type InboundPayload = >::InboundPayload; let lane_id = LaneIdWrapper::from(message.id).0; let nonce = message.nonce; let payload = Ok(From::from(message.payload)); // Directly dispatch outbound messages assuming everything is correct // and bypassing the `Relayers` and `InboundLane` logic let dispatch_result = TargetMessageDispatch::::dispatch(DispatchMessage { key: MessageKey { lane_id, nonce }, data: DispatchMessageData::> { payload }, }); let result = match dispatch_result.dispatch_level_result { XcmBlobMessageDispatchResult::Dispatched => Ok(()), XcmBlobMessageDispatchResult::InvalidPayload => Err(BridgeMessageDispatchError( Box::new(XcmBlobMessageDispatchResult::InvalidPayload), )), XcmBlobMessageDispatchResult::NotDispatched(e) => Err(BridgeMessageDispatchError( Box::new(XcmBlobMessageDispatchResult::NotDispatched(e)), )), }; result } fn notify_source_message_delivery(lane_id: u32) { let data = OutboundLanes::::get(LaneIdWrapper::from(lane_id).0); let new_data = OutboundLaneData { oldest_unpruned_nonce: data.oldest_unpruned_nonce + 1, latest_received_nonce: data.latest_received_nonce + 1, ..data }; OutboundLanes::::insert(LaneIdWrapper::from(lane_id).0, new_data); } } #[macro_export] macro_rules! impl_accounts_helpers_for_relay_chain { ( $chain:ident ) => { $crate::paste::paste! { impl $chain { /// Fund a set of accounts with a balance pub fn fund_accounts(accounts: Vec<(AccountId, Balance)>) { Self::execute_with(|| { for account in accounts { assert_ok!(]>::Balances::force_set_balance( ::RuntimeOrigin::root(), account.0.into(), account.1, )); } }); } /// Fund a sovereign account based on its Parachain Id pub fn fund_para_sovereign(amount: Balance, para_id: ParaId) -> sp_runtime::AccountId32 { let sovereign_account = Self::sovereign_account_id_of_child_para(para_id); Self::fund_accounts(vec![(sovereign_account.clone(), amount)]); sovereign_account } } } }; } #[macro_export] macro_rules! impl_assert_events_helpers_for_relay_chain { ( $chain:ident ) => { $crate::paste::paste! { type [<$chain RuntimeEvent>] = <$chain as Chain>::RuntimeEvent; impl $chain { /// Asserts a dispatchable is completely executed and XCM sent pub fn assert_xcm_pallet_attempted_complete(expected_weight: Option) { assert_expected_events!( Self, vec![ [<$chain RuntimeEvent>]::XcmPallet( pallet_xcm::Event::Attempted { outcome: Outcome::Complete(weight) } ) => { weight: weight_within_threshold( (REF_TIME_THRESHOLD, PROOF_SIZE_THRESHOLD), expected_weight.unwrap_or(*weight), *weight ), }, ] ); } /// Asserts a dispatchable is incompletely executed and XCM sent pub fn assert_xcm_pallet_attempted_incomplete( expected_weight: Option, expected_error: Option, ) { assert_expected_events!( Self, vec![ // Dispatchable is properly executed and XCM message sent [<$chain RuntimeEvent>]::XcmPallet( pallet_xcm::Event::Attempted { outcome: Outcome::Incomplete(weight, error) } ) => { weight: weight_within_threshold( (REF_TIME_THRESHOLD, PROOF_SIZE_THRESHOLD), expected_weight.unwrap_or(*weight), *weight ), error: *error == expected_error.unwrap_or(*error), }, ] ); } /// Asserts a XCM message is sent pub fn assert_xcm_pallet_sent() { assert_expected_events!( Self, vec![ [<$chain RuntimeEvent>]::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, ] ); } /// Asserts a XCM from System Parachain is succesfully received and proccessed pub fn assert_ump_queue_processed( expected_success: bool, expected_id: Option, expected_weight: Option, ) { assert_expected_events!( Self, vec![ // XCM is succesfully received and proccessed [<$chain RuntimeEvent>]::MessageQueue(pallet_message_queue::Event::Processed { origin: AggregateMessageOrigin::Ump(UmpQueueId::Para(id)), weight_used, success, .. }) => { id: *id == expected_id.unwrap_or(*id), weight_used: weight_within_threshold( (REF_TIME_THRESHOLD, PROOF_SIZE_THRESHOLD), expected_weight.unwrap_or(*weight_used), *weight_used ), success: *success == expected_success, }, ] ); } } } }; } #[macro_export] macro_rules! impl_hrmp_channels_helpers_for_relay_chain { ( $chain:ident ) => { $crate::paste::paste! { impl $chain { /// Init open channel request with another Parachain pub fn init_open_channel_call( recipient_para_id: ParaId, max_capacity: u32, max_message_size: u32, ) -> DoubleEncoded<()> { ::RuntimeCall::Hrmp(polkadot_runtime_parachains::hrmp::Call::< ::Runtime, >::hrmp_init_open_channel { recipient: recipient_para_id, proposed_max_capacity: max_capacity, proposed_max_message_size: max_message_size, }) .encode() .into() } /// Recipient Parachain accept the open request from another Parachain pub fn accept_open_channel_call(sender_para_id: ParaId) -> DoubleEncoded<()> { ::RuntimeCall::Hrmp(polkadot_runtime_parachains::hrmp::Call::< ::Runtime, >::hrmp_accept_open_channel { sender: sender_para_id, }) .encode() .into() } /// A root origin force to open a channel between two Parachains pub fn force_process_hrmp_open(sender: ParaId, recipient: ParaId) { Self::execute_with(|| { let relay_root_origin = ::RuntimeOrigin::root(); // Force process HRMP open channel requests without waiting for the next session assert_ok!(]>::Hrmp::force_process_hrmp_open( relay_root_origin, 0 )); let channel_id = HrmpChannelId { sender, recipient }; let hrmp_channel_exist = polkadot_runtime_parachains::hrmp::HrmpChannels::< ::Runtime, >::contains_key(&channel_id); // Check the HRMP channel has been successfully registrered assert!(hrmp_channel_exist) }); } } } }; } #[macro_export] macro_rules! impl_accounts_helpers_for_parachain { ( $chain:ident ) => { $crate::paste::paste! { impl $chain { /// Fund a set of accounts with a balance pub fn fund_accounts(accounts: Vec<(AccountId, Balance)>) { Self::execute_with(|| { for account in accounts { assert_ok!(]>::Balances::force_set_balance( ::RuntimeOrigin::root(), account.0.into(), account.1, )); } }); } } } }; } #[macro_export] macro_rules! impl_assert_events_helpers_for_parachain { ( $chain:ident ) => { $crate::paste::paste! { type [<$chain RuntimeEvent>] = <$chain as Chain>::RuntimeEvent; impl $chain { /// Asserts a dispatchable is completely executed and XCM sent pub fn assert_xcm_pallet_attempted_complete(expected_weight: Option) { assert_expected_events!( Self, vec![ [<$chain RuntimeEvent>]::PolkadotXcm( pallet_xcm::Event::Attempted { outcome: Outcome::Complete(weight) } ) => { weight: weight_within_threshold( (REF_TIME_THRESHOLD, PROOF_SIZE_THRESHOLD), expected_weight.unwrap_or(*weight), *weight ), }, ] ); } /// Asserts a dispatchable is incompletely executed and XCM sent pub fn assert_xcm_pallet_attempted_incomplete( expected_weight: Option, expected_error: Option, ) { assert_expected_events!( Self, vec![ // Dispatchable is properly executed and XCM message sent [<$chain RuntimeEvent>]::PolkadotXcm( pallet_xcm::Event::Attempted { outcome: Outcome::Incomplete(weight, error) } ) => { weight: weight_within_threshold( (REF_TIME_THRESHOLD, PROOF_SIZE_THRESHOLD), expected_weight.unwrap_or(*weight), *weight ), error: *error == expected_error.unwrap_or(*error), }, ] ); } /// Asserts a dispatchable throws and error when trying to be sent pub fn assert_xcm_pallet_attempted_error(expected_error: Option) { assert_expected_events!( Self, vec![ // Execution fails in the origin with `Barrier` [<$chain RuntimeEvent>]::PolkadotXcm( pallet_xcm::Event::Attempted { outcome: Outcome::Error(error) } ) => { error: *error == expected_error.unwrap_or(*error), }, ] ); } /// Asserts a XCM message is sent pub fn assert_xcm_pallet_sent() { assert_expected_events!( Self, vec![ [<$chain RuntimeEvent>]::PolkadotXcm(pallet_xcm::Event::Sent { .. }) => {}, ] ); } /// Asserts a XCM message is sent to Relay Chain pub fn assert_parachain_system_ump_sent() { assert_expected_events!( Self, vec![ [<$chain RuntimeEvent>]::ParachainSystem( cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. } ) => {}, ] ); } /// Asserts a XCM from Relay Chain is completely executed pub fn assert_dmp_queue_complete(expected_weight: Option) { assert_expected_events!( Self, vec![ [<$chain RuntimeEvent>]::DmpQueue(cumulus_pallet_dmp_queue::Event::ExecutedDownward { outcome: Outcome::Complete(weight), .. }) => { weight: weight_within_threshold( (REF_TIME_THRESHOLD, PROOF_SIZE_THRESHOLD), expected_weight.unwrap_or(*weight), *weight ), }, ] ); } /// Asserts a XCM from Relay Chain is incompletely executed pub fn assert_dmp_queue_incomplete( expected_weight: Option, expected_error: Option, ) { assert_expected_events!( Self, vec![ [<$chain RuntimeEvent>]::DmpQueue(cumulus_pallet_dmp_queue::Event::ExecutedDownward { outcome: Outcome::Incomplete(weight, error), .. }) => { weight: weight_within_threshold( (REF_TIME_THRESHOLD, PROOF_SIZE_THRESHOLD), expected_weight.unwrap_or(*weight), *weight ), error: *error == expected_error.unwrap_or(*error), }, ] ); } /// Asserts a XCM from another Parachain is completely executed pub fn assert_xcmp_queue_success(expected_weight: Option) { assert_expected_events!( Self, vec![ [<$chain RuntimeEvent>]::XcmpQueue( cumulus_pallet_xcmp_queue::Event::Success { weight, .. } ) => { weight: weight_within_threshold( (REF_TIME_THRESHOLD, PROOF_SIZE_THRESHOLD), expected_weight.unwrap_or(*weight), *weight ), }, ] ); } } } }; } #[macro_export] macro_rules! impl_assets_helpers_for_parachain { ( $chain:ident, $relay_chain:ident ) => { $crate::paste::paste! { impl $chain { /// Returns the encoded call for `force_create` from the assets pallet pub fn force_create_asset_call( asset_id: u32, owner: AccountId, is_sufficient: bool, min_balance: Balance, ) -> DoubleEncoded<()> { ::RuntimeCall::Assets(pallet_assets::Call::< ::Runtime, Instance1, >::force_create { id: asset_id.into(), owner: owner.into(), is_sufficient, min_balance, }) .encode() .into() } /// Returns a `VersionedXcm` for `force_create` from the assets pallet pub fn force_create_asset_xcm( origin_kind: OriginKind, asset_id: u32, owner: AccountId, is_sufficient: bool, min_balance: Balance, ) -> VersionedXcm<()> { let call = Self::force_create_asset_call(asset_id, owner, is_sufficient, min_balance); xcm_transact_unpaid_execution(call, origin_kind) } /// Mint assets making use of the assets pallet pub fn mint_asset( signed_origin: ::RuntimeOrigin, id: u32, beneficiary: AccountId, amount_to_mint: u128, ) { Self::execute_with(|| { assert_ok!(]>::Assets::mint( signed_origin, id.into(), beneficiary.clone().into(), amount_to_mint )); type RuntimeEvent = <$chain as Chain>::RuntimeEvent; assert_expected_events!( Self, vec![ RuntimeEvent::Assets(pallet_assets::Event::Issued { asset_id, owner, amount }) => { asset_id: *asset_id == id, owner: *owner == beneficiary.clone().into(), amount: *amount == amount_to_mint, }, ] ); }); } /// Force create and mint assets making use of the assets pallet pub fn force_create_and_mint_asset( id: u32, min_balance: u128, is_sufficient: bool, asset_owner: AccountId, amount_to_mint: u128, ) { // Init values for Relay Chain let root_origin = <$relay_chain as Chain>::RuntimeOrigin::root(); let destination = <$relay_chain>::child_location_of(<$chain>::para_id()); let xcm = Self::force_create_asset_xcm( OriginKind::Superuser, id, asset_owner.clone(), is_sufficient, min_balance, ); <$relay_chain>::execute_with(|| { assert_ok!(<$relay_chain as [<$relay_chain Pallet>]>::XcmPallet::send( root_origin, bx!(destination.into()), bx!(xcm), )); <$relay_chain>::assert_xcm_pallet_sent(); }); Self::execute_with(|| { Self::assert_dmp_queue_complete(Some(Weight::from_parts(1_019_445_000, 200_000))); type RuntimeEvent = <$chain as Chain>::RuntimeEvent; assert_expected_events!( Self, vec![ // Asset has been created RuntimeEvent::Assets(pallet_assets::Event::ForceCreated { asset_id, owner }) => { asset_id: *asset_id == id, owner: *owner == asset_owner.clone(), }, ] ); assert!(]>::Assets::asset_exists(id.into())); }); let signed_origin = ::RuntimeOrigin::signed(asset_owner.clone()); // Mint asset for System Parachain's sender Self::mint_asset(signed_origin, id, asset_owner, amount_to_mint); } } } }; }