// Copyright 2019-2021 Parity Technologies (UK) Ltd. // This file is part of Parity Bridges Common. // Parity Bridges Common 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. // Parity Bridges Common 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 Parity Bridges Common. If not, see . //! Runtime module which takes care of dispatching messages received over the bridge. //! //! The messages are interpreted directly as runtime `Call`. We attempt to decode //! them and then dispatch as usual. To prevent compatibility issues, the Calls have //! to include a `spec_version`. This will be checked before dispatch. In the case of //! a successful dispatch an event is emitted. #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] // Generated by `decl_event!` #![allow(clippy::unused_unit)] use bp_message_dispatch::{CallOrigin, MessageDispatch, MessagePayload, SpecVersion}; use bp_runtime::{ derive_account_id, messages::{DispatchFeePayment, MessageDispatchResult}, ChainId, SourceAccount, }; use codec::Encode; use frame_support::{ dispatch::Dispatchable, ensure, traits::{Filter, Get}, weights::{extract_actual_weight, GetDispatchInfo}, }; use frame_system::RawOrigin; use sp_runtime::traits::{BadOrigin, Convert, IdentifyAccount, MaybeDisplay, Verify}; use sp_std::{fmt::Debug, prelude::*}; pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. type Event: From> + IsType<::Event>; /// Id of the message. Whenever message is passed to the dispatch module, it emits /// event with this id + dispatch result. Could be e.g. (LaneId, MessageNonce) if /// it comes from the messages module. type MessageId: Parameter; /// Type of account ID on source chain. type SourceChainAccountId: Parameter + Member + MaybeSerializeDeserialize + Debug + MaybeDisplay + Ord + Default; /// Type of account public key on target chain. type TargetChainAccountPublic: Parameter + IdentifyAccount; /// Type of signature that may prove that the message has been signed by /// owner of `TargetChainAccountPublic`. type TargetChainSignature: Parameter + Verify; /// The overarching dispatch call type. type Call: Parameter + GetDispatchInfo + Dispatchable< Origin = ::Origin, PostInfo = frame_support::dispatch::PostDispatchInfo, >; /// Pre-dispatch filter for incoming calls. /// /// The pallet will filter all incoming calls right before they're dispatched. If this filter /// rejects the call, special event (`Event::MessageCallRejected`) is emitted. type CallFilter: Filter<>::Call>; /// The type that is used to wrap the `Self::Call` when it is moved over bridge. /// /// The idea behind this is to avoid `Call` conversion/decoding until we'll be sure /// that all other stuff (like `spec_version`) is ok. If we would try to decode /// `Call` which has been encoded using previous `spec_version`, then we might end /// up with decoding error, instead of `MessageVersionSpecMismatch`. type EncodedCall: Decode + Encode + Into>::Call, ()>>; /// A type which can be turned into an AccountId from a 256-bit hash. /// /// Used when deriving target chain AccountIds from source chain AccountIds. type AccountIdConverter: sp_runtime::traits::Convert; } type MessageIdOf = >::MessageId; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(PhantomData<(T, I)>); #[pallet::hooks] impl, I: 'static> Hooks> for Pallet {} #[pallet::call] impl, I: 'static> Pallet {} #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] #[pallet::metadata(::AccountId = "AccountId", MessageIdOf = "MessageId")] pub enum Event, I: 'static = ()> { /// Message has been rejected before reaching dispatch. MessageRejected(ChainId, MessageIdOf), /// Message has been rejected by dispatcher because of spec version mismatch. /// Last two arguments are: expected and passed spec version. MessageVersionSpecMismatch(ChainId, MessageIdOf, SpecVersion, SpecVersion), /// Message has been rejected by dispatcher because of weight mismatch. /// Last two arguments are: expected and passed call weight. MessageWeightMismatch(ChainId, MessageIdOf, Weight, Weight), /// Message signature mismatch. MessageSignatureMismatch(ChainId, MessageIdOf), /// We have failed to decode Call from the message. MessageCallDecodeFailed(ChainId, MessageIdOf), /// The call from the message has been rejected by the call filter. MessageCallRejected(ChainId, MessageIdOf), /// The origin account has failed to pay fee for dispatching the message. MessageDispatchPaymentFailed( ChainId, MessageIdOf, ::AccountId, Weight, ), /// Message has been dispatched with given result. MessageDispatched(ChainId, MessageIdOf, DispatchResult), /// Phantom member, never used. Needed to handle multiple pallet instances. _Dummy(PhantomData), } } impl, I: 'static> MessageDispatch for Pallet { type Message = MessagePayload; fn dispatch_weight(message: &Self::Message) -> bp_message_dispatch::Weight { message.weight } fn dispatch Result<(), ()>>( source_chain: ChainId, target_chain: ChainId, id: T::MessageId, message: Result, pay_dispatch_fee: P, ) -> MessageDispatchResult { // emit special even if message has been rejected by external component let message = match message { Ok(message) => message, Err(_) => { log::trace!( target: "runtime::bridge-dispatch", "Message {:?}/{:?}: rejected before actual dispatch", source_chain, id, ); Self::deposit_event(Event::MessageRejected(source_chain, id)); return MessageDispatchResult { dispatch_result: false, unspent_weight: 0, dispatch_fee_paid_during_dispatch: false, }; } }; // verify spec version // (we want it to be the same, because otherwise we may decode Call improperly) let mut dispatch_result = MessageDispatchResult { dispatch_result: false, unspent_weight: message.weight, dispatch_fee_paid_during_dispatch: false, }; let expected_version = ::Version::get().spec_version; if message.spec_version != expected_version { log::trace!( "Message {:?}/{:?}: spec_version mismatch. Expected {:?}, got {:?}", source_chain, id, expected_version, message.spec_version, ); Self::deposit_event(Event::MessageVersionSpecMismatch( source_chain, id, expected_version, message.spec_version, )); return dispatch_result; } // now that we have spec version checked, let's decode the call let call = match message.call.into() { Ok(call) => call, Err(_) => { log::trace!( target: "runtime::bridge-dispatch", "Failed to decode Call from message {:?}/{:?}", source_chain, id, ); Self::deposit_event(Event::MessageCallDecodeFailed(source_chain, id)); return dispatch_result; } }; // prepare dispatch origin let origin_account = match message.origin { CallOrigin::SourceRoot => { let hex_id = derive_account_id::(source_chain, SourceAccount::Root); let target_id = T::AccountIdConverter::convert(hex_id); log::trace!(target: "runtime::bridge-dispatch", "Root Account: {:?}", &target_id); target_id } CallOrigin::TargetAccount(source_account_id, target_public, target_signature) => { let digest = account_ownership_digest( &call, source_account_id, message.spec_version, source_chain, target_chain, ); let target_account = target_public.into_account(); if !target_signature.verify(&digest[..], &target_account) { log::trace!( target: "runtime::bridge-dispatch", "Message {:?}/{:?}: origin proof is invalid. Expected account: {:?} from signature: {:?}", source_chain, id, target_account, target_signature, ); Self::deposit_event(Event::MessageSignatureMismatch(source_chain, id)); return dispatch_result; } log::trace!(target: "runtime::bridge-dispatch", "Target Account: {:?}", &target_account); target_account } CallOrigin::SourceAccount(source_account_id) => { let hex_id = derive_account_id(source_chain, SourceAccount::Account(source_account_id)); let target_id = T::AccountIdConverter::convert(hex_id); log::trace!(target: "runtime::bridge-dispatch", "Source Account: {:?}", &target_id); target_id } }; // filter the call if !T::CallFilter::filter(&call) { log::trace!( target: "runtime::bridge-dispatch", "Message {:?}/{:?}: the call ({:?}) is rejected by filter", source_chain, id, call, ); Self::deposit_event(Event::MessageCallRejected(source_chain, id)); return dispatch_result; } // verify weight // (we want passed weight to be at least equal to pre-dispatch weight of the call // because otherwise Calls may be dispatched at lower price) let dispatch_info = call.get_dispatch_info(); let expected_weight = dispatch_info.weight; if message.weight < expected_weight { log::trace!( target: "runtime::bridge-dispatch", "Message {:?}/{:?}: passed weight is too low. Expected at least {:?}, got {:?}", source_chain, id, expected_weight, message.weight, ); Self::deposit_event(Event::MessageWeightMismatch( source_chain, id, expected_weight, message.weight, )); return dispatch_result; } // pay dispatch fee right before dispatch let pay_dispatch_fee_at_target_chain = message.dispatch_fee_payment == DispatchFeePayment::AtTargetChain; if pay_dispatch_fee_at_target_chain && pay_dispatch_fee(&origin_account, message.weight).is_err() { log::trace!( target: "runtime::bridge-dispatch", "Failed to pay dispatch fee for dispatching message {:?}/{:?} with weight {}", source_chain, id, message.weight, ); Self::deposit_event(Event::MessageDispatchPaymentFailed( source_chain, id, origin_account, message.weight, )); return dispatch_result; } dispatch_result.dispatch_fee_paid_during_dispatch = pay_dispatch_fee_at_target_chain; // finally dispatch message let origin = RawOrigin::Signed(origin_account).into(); log::trace!(target: "runtime::bridge-dispatch", "Message being dispatched is: {:.4096?}", &call); let result = call.dispatch(origin); let actual_call_weight = extract_actual_weight(&result, &dispatch_info); dispatch_result.dispatch_result = result.is_ok(); dispatch_result.unspent_weight = message.weight.saturating_sub(actual_call_weight); log::trace!( target: "runtime::bridge-dispatch", "Message {:?}/{:?} has been dispatched. Weight: {} of {}. Result: {:?}. Call dispatch result: {:?}", source_chain, id, actual_call_weight, message.weight, dispatch_result, result, ); Self::deposit_event(Event::MessageDispatched( source_chain, id, result.map(drop).map_err(|e| e.error), )); dispatch_result } } /// Check if the message is allowed to be dispatched on the target chain given the sender's origin /// on the source chain. /// /// For example, if a message is sent from a "regular" account on the source chain it will not be /// allowed to be dispatched as Root on the target chain. This is a useful check to do on the source /// chain _before_ sending a message whose dispatch will be rejected on the target chain. pub fn verify_message_origin( sender_origin: &RawOrigin, message: &MessagePayload, ) -> Result, BadOrigin> where SourceChainAccountId: PartialEq + Clone, { match message.origin { CallOrigin::SourceRoot => { ensure!(sender_origin == &RawOrigin::Root, BadOrigin); Ok(None) } CallOrigin::TargetAccount(ref source_account_id, _, _) => { ensure!( sender_origin == &RawOrigin::Signed(source_account_id.clone()), BadOrigin ); Ok(Some(source_account_id.clone())) } CallOrigin::SourceAccount(ref source_account_id) => { ensure!( sender_origin == &RawOrigin::Signed(source_account_id.clone()) || sender_origin == &RawOrigin::Root, BadOrigin ); Ok(Some(source_account_id.clone())) } } } /// Target account ownership digest from the source chain. /// /// The byte vector returned by this function will be signed with a target chain account /// private key. This way, the owner of `source_account_id` on the source chain proves that /// the target chain account private key is also under his control. pub fn account_ownership_digest( call: &Call, source_account_id: AccountId, target_spec_version: SpecVersion, source_chain_id: ChainId, target_chain_id: ChainId, ) -> Vec where Call: Encode, AccountId: Encode, SpecVersion: Encode, { let mut proof = Vec::new(); call.encode_to(&mut proof); source_account_id.encode_to(&mut proof); target_spec_version.encode_to(&mut proof); source_chain_id.encode_to(&mut proof); target_chain_id.encode_to(&mut proof); proof } #[cfg(test)] mod tests { // From construct_runtime macro #![allow(clippy::from_over_into)] use super::*; use codec::Decode; use frame_support::{parameter_types, weights::Weight}; use frame_system::{EventRecord, Phase}; use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, Perbill, }; type AccountId = u64; type MessageId = [u8; 4]; const SOURCE_CHAIN_ID: ChainId = *b"srce"; const TARGET_CHAIN_ID: ChainId = *b"trgt"; #[derive(Debug, Encode, Decode, Clone, PartialEq, Eq)] pub struct TestAccountPublic(AccountId); impl IdentifyAccount for TestAccountPublic { type AccountId = AccountId; fn into_account(self) -> AccountId { self.0 } } #[derive(Debug, Encode, Decode, Clone, PartialEq, Eq)] pub struct TestSignature(AccountId); impl Verify for TestSignature { type Signer = TestAccountPublic; fn verify>(&self, _msg: L, signer: &AccountId) -> bool { self.0 == *signer } } pub struct AccountIdConverter; impl sp_runtime::traits::Convert for AccountIdConverter { fn convert(hash: H256) -> AccountId { hash.to_low_u64_ne() } } type Block = frame_system::mocking::MockBlock; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; use crate as call_dispatch; frame_support::construct_runtime! { pub enum TestRuntime where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, Dispatch: call_dispatch::{Pallet, Call, Event}, } } parameter_types! { pub const BlockHashCount: u64 = 250; pub const MaximumBlockWeight: Weight = 1024; pub const MaximumBlockLength: u32 = 2 * 1024; pub const AvailableBlockRatio: Perbill = Perbill::one(); } impl frame_system::Config for TestRuntime { type Origin = Origin; type Index = u64; type Call = Call; type BlockNumber = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; type Event = Event; type BlockHashCount = BlockHashCount; type Version = (); type PalletInfo = PalletInfo; type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); type BaseCallFilter = (); type SystemWeightInfo = (); type BlockWeights = (); type BlockLength = (); type DbWeight = (); type SS58Prefix = (); type OnSetCode = (); } impl Config for TestRuntime { type Event = Event; type MessageId = MessageId; type SourceChainAccountId = AccountId; type TargetChainAccountPublic = TestAccountPublic; type TargetChainSignature = TestSignature; type Call = Call; type CallFilter = TestCallFilter; type EncodedCall = EncodedCall; type AccountIdConverter = AccountIdConverter; } #[derive(Decode, Encode)] pub struct EncodedCall(Vec); impl From for Result { fn from(call: EncodedCall) -> Result { Call::decode(&mut &call.0[..]).map_err(drop) } } pub struct TestCallFilter; impl Filter for TestCallFilter { fn filter(call: &Call) -> bool { !matches!(*call, Call::System(frame_system::Call::fill_block(_))) } } const TEST_SPEC_VERSION: SpecVersion = 0; const TEST_WEIGHT: Weight = 1_000_000_000; fn new_test_ext() -> sp_io::TestExternalities { let t = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); sp_io::TestExternalities::new(t) } fn prepare_message( origin: CallOrigin, call: Call, ) -> as MessageDispatch::MessageId>>::Message { MessagePayload { spec_version: TEST_SPEC_VERSION, weight: TEST_WEIGHT, origin, dispatch_fee_payment: DispatchFeePayment::AtSourceChain, call: EncodedCall(call.encode()), } } fn prepare_root_message( call: Call, ) -> as MessageDispatch::MessageId>>::Message { prepare_message(CallOrigin::SourceRoot, call) } fn prepare_target_message( call: Call, ) -> as MessageDispatch::MessageId>>::Message { let origin = CallOrigin::TargetAccount(1, TestAccountPublic(1), TestSignature(1)); prepare_message(origin, call) } fn prepare_source_message( call: Call, ) -> as MessageDispatch::MessageId>>::Message { let origin = CallOrigin::SourceAccount(1); prepare_message(origin, call) } #[test] fn should_fail_on_spec_version_mismatch() { new_test_ext().execute_with(|| { let id = [0; 4]; const BAD_SPEC_VERSION: SpecVersion = 99; let mut message = prepare_root_message(Call::System(>::remark(vec![1, 2, 3]))); let weight = message.weight; message.spec_version = BAD_SPEC_VERSION; System::set_block_number(1); let result = Dispatch::dispatch(SOURCE_CHAIN_ID, TARGET_CHAIN_ID, id, Ok(message), |_, _| unreachable!()); assert_eq!(result.unspent_weight, weight); assert!(!result.dispatch_result); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: Event::Dispatch(call_dispatch::Event::::MessageVersionSpecMismatch( SOURCE_CHAIN_ID, id, TEST_SPEC_VERSION, BAD_SPEC_VERSION )), topics: vec![], }], ); }); } #[test] fn should_fail_on_weight_mismatch() { new_test_ext().execute_with(|| { let id = [0; 4]; let call = Call::System(>::remark(vec![1, 2, 3])); let call_weight = call.get_dispatch_info().weight; let mut message = prepare_root_message(call); message.weight = 7; assert!( call_weight != 7, "needed for test to actually trigger a weight mismatch" ); System::set_block_number(1); let result = Dispatch::dispatch(SOURCE_CHAIN_ID, TARGET_CHAIN_ID, id, Ok(message), |_, _| unreachable!()); assert_eq!(result.unspent_weight, 7); assert!(!result.dispatch_result); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: Event::Dispatch(call_dispatch::Event::::MessageWeightMismatch( SOURCE_CHAIN_ID, id, call_weight, 7, )), topics: vec![], }], ); }); } #[test] fn should_fail_on_signature_mismatch() { new_test_ext().execute_with(|| { let id = [0; 4]; let call_origin = CallOrigin::TargetAccount(1, TestAccountPublic(1), TestSignature(99)); let message = prepare_message( call_origin, Call::System(>::remark(vec![1, 2, 3])), ); let weight = message.weight; System::set_block_number(1); let result = Dispatch::dispatch(SOURCE_CHAIN_ID, TARGET_CHAIN_ID, id, Ok(message), |_, _| unreachable!()); assert_eq!(result.unspent_weight, weight); assert!(!result.dispatch_result); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: Event::Dispatch(call_dispatch::Event::::MessageSignatureMismatch( SOURCE_CHAIN_ID, id )), topics: vec![], }], ); }); } #[test] fn should_emit_event_for_rejected_messages() { new_test_ext().execute_with(|| { let id = [0; 4]; System::set_block_number(1); Dispatch::dispatch(SOURCE_CHAIN_ID, TARGET_CHAIN_ID, id, Err(()), |_, _| unreachable!()); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: Event::Dispatch(call_dispatch::Event::::MessageRejected( SOURCE_CHAIN_ID, id )), topics: vec![], }], ); }); } #[test] fn should_fail_on_call_decode() { new_test_ext().execute_with(|| { let id = [0; 4]; let mut message = prepare_root_message(Call::System(>::remark(vec![1, 2, 3]))); let weight = message.weight; message.call.0 = vec![]; System::set_block_number(1); let result = Dispatch::dispatch(SOURCE_CHAIN_ID, TARGET_CHAIN_ID, id, Ok(message), |_, _| unreachable!()); assert_eq!(result.unspent_weight, weight); assert!(!result.dispatch_result); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: Event::Dispatch(call_dispatch::Event::::MessageCallDecodeFailed( SOURCE_CHAIN_ID, id )), topics: vec![], }], ); }); } #[test] fn should_emit_event_for_rejected_calls() { new_test_ext().execute_with(|| { let id = [0; 4]; let call = Call::System(>::fill_block(Perbill::from_percent(75))); let weight = call.get_dispatch_info().weight; let mut message = prepare_root_message(call); message.weight = weight; System::set_block_number(1); let result = Dispatch::dispatch(SOURCE_CHAIN_ID, TARGET_CHAIN_ID, id, Ok(message), |_, _| unreachable!()); assert_eq!(result.unspent_weight, weight); assert!(!result.dispatch_result); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: Event::Dispatch(call_dispatch::Event::::MessageCallRejected( SOURCE_CHAIN_ID, id )), topics: vec![], }], ); }); } #[test] fn should_emit_event_for_unpaid_calls() { new_test_ext().execute_with(|| { let id = [0; 4]; let mut message = prepare_root_message(Call::System(>::remark(vec![1, 2, 3]))); let weight = message.weight; message.dispatch_fee_payment = DispatchFeePayment::AtTargetChain; System::set_block_number(1); let result = Dispatch::dispatch(SOURCE_CHAIN_ID, TARGET_CHAIN_ID, id, Ok(message), |_, _| Err(())); assert_eq!(result.unspent_weight, weight); assert!(!result.dispatch_result); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: Event::Dispatch(call_dispatch::Event::::MessageDispatchPaymentFailed( SOURCE_CHAIN_ID, id, AccountIdConverter::convert(derive_account_id::( SOURCE_CHAIN_ID, SourceAccount::Root )), TEST_WEIGHT, )), topics: vec![], }], ); }); } #[test] fn should_dispatch_calls_paid_at_target_chain() { new_test_ext().execute_with(|| { let id = [0; 4]; let mut message = prepare_root_message(Call::System(>::remark(vec![1, 2, 3]))); message.dispatch_fee_payment = DispatchFeePayment::AtTargetChain; System::set_block_number(1); let result = Dispatch::dispatch(SOURCE_CHAIN_ID, TARGET_CHAIN_ID, id, Ok(message), |_, _| Ok(())); assert!(result.dispatch_fee_paid_during_dispatch); assert!(result.dispatch_result); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: Event::Dispatch(call_dispatch::Event::::MessageDispatched( SOURCE_CHAIN_ID, id, Ok(()) )), topics: vec![], }], ); }); } #[test] fn should_return_dispatch_failed_flag_if_dispatch_happened_but_failed() { new_test_ext().execute_with(|| { let id = [0; 4]; let call = Call::System(>::set_heap_pages(1)); let message = prepare_target_message(call); System::set_block_number(1); let result = Dispatch::dispatch(SOURCE_CHAIN_ID, TARGET_CHAIN_ID, id, Ok(message), |_, _| unreachable!()); assert!(!result.dispatch_fee_paid_during_dispatch); assert!(!result.dispatch_result); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: Event::Dispatch(call_dispatch::Event::::MessageDispatched( SOURCE_CHAIN_ID, id, Err(sp_runtime::DispatchError::BadOrigin) )), topics: vec![], }], ); }) } #[test] fn should_dispatch_bridge_message_from_root_origin() { new_test_ext().execute_with(|| { let id = [0; 4]; let message = prepare_root_message(Call::System(>::remark(vec![1, 2, 3]))); System::set_block_number(1); let result = Dispatch::dispatch(SOURCE_CHAIN_ID, TARGET_CHAIN_ID, id, Ok(message), |_, _| unreachable!()); assert!(!result.dispatch_fee_paid_during_dispatch); assert!(result.dispatch_result); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: Event::Dispatch(call_dispatch::Event::::MessageDispatched( SOURCE_CHAIN_ID, id, Ok(()) )), topics: vec![], }], ); }); } #[test] fn should_dispatch_bridge_message_from_target_origin() { new_test_ext().execute_with(|| { let id = [0; 4]; let call = Call::System(>::remark(vec![])); let message = prepare_target_message(call); System::set_block_number(1); let result = Dispatch::dispatch(SOURCE_CHAIN_ID, TARGET_CHAIN_ID, id, Ok(message), |_, _| unreachable!()); assert!(!result.dispatch_fee_paid_during_dispatch); assert!(result.dispatch_result); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: Event::Dispatch(call_dispatch::Event::::MessageDispatched( SOURCE_CHAIN_ID, id, Ok(()) )), topics: vec![], }], ); }) } #[test] fn should_dispatch_bridge_message_from_source_origin() { new_test_ext().execute_with(|| { let id = [0; 4]; let call = Call::System(>::remark(vec![])); let message = prepare_source_message(call); System::set_block_number(1); let result = Dispatch::dispatch(SOURCE_CHAIN_ID, TARGET_CHAIN_ID, id, Ok(message), |_, _| unreachable!()); assert!(!result.dispatch_fee_paid_during_dispatch); assert!(result.dispatch_result); assert_eq!( System::events(), vec![EventRecord { phase: Phase::Initialization, event: Event::Dispatch(call_dispatch::Event::::MessageDispatched( SOURCE_CHAIN_ID, id, Ok(()) )), topics: vec![], }], ); }) } #[test] fn origin_is_checked_when_verifying_sending_message_using_source_root_account() { let call = Call::System(>::remark(vec![])); let message = prepare_root_message(call); // When message is sent by Root, CallOrigin::SourceRoot is allowed assert!(matches!(verify_message_origin(&RawOrigin::Root, &message), Ok(None))); // when message is sent by some real account, CallOrigin::SourceRoot is not allowed assert!(matches!( verify_message_origin(&RawOrigin::Signed(1), &message), Err(BadOrigin) )); } #[test] fn origin_is_checked_when_verifying_sending_message_using_target_account() { let call = Call::System(>::remark(vec![])); let message = prepare_target_message(call); // When message is sent by Root, CallOrigin::TargetAccount is not allowed assert!(matches!( verify_message_origin(&RawOrigin::Root, &message), Err(BadOrigin) )); // When message is sent by some other account, it is rejected assert!(matches!( verify_message_origin(&RawOrigin::Signed(2), &message), Err(BadOrigin) )); // When message is sent by a real account, it is allowed to have origin // CallOrigin::TargetAccount assert!(matches!( verify_message_origin(&RawOrigin::Signed(1), &message), Ok(Some(1)) )); } #[test] fn origin_is_checked_when_verifying_sending_message_using_source_account() { let call = Call::System(>::remark(vec![])); let message = prepare_source_message(call); // Sending a message from the expected origin account works assert!(matches!( verify_message_origin(&RawOrigin::Signed(1), &message), Ok(Some(1)) )); // If we send a message from a different account, it is rejected assert!(matches!( verify_message_origin(&RawOrigin::Signed(2), &message), Err(BadOrigin) )); // The Root account is allowed to assume any expected origin account assert!(matches!(verify_message_origin(&RawOrigin::Root, &message), Ok(Some(1)))); } }