1use crate::test_cases::{bridges_prelude::*, run_test, RuntimeHelper};
20
21use asset_test_utils::BasicParachainRuntime;
22use bp_messages::MessageNonce;
23use bp_polkadot_core::parachains::{ParaHash, ParaId};
24use bp_runtime::Chain;
25use codec::Decode;
26use core::marker::PhantomData;
27use frame_support::{
28 assert_ok,
29 dispatch::GetDispatchInfo,
30 traits::{fungible::Mutate, Contains, OnFinalize, OnInitialize, PalletInfoAccess},
31};
32use frame_system::pallet_prelude::BlockNumberFor;
33use pallet_bridge_grandpa::{BridgedBlockHash, BridgedHeader};
34use pallet_bridge_messages::{BridgedChainOf, LaneIdOf};
35use parachains_common::AccountId;
36use parachains_runtimes_test_utils::{
37 mock_open_hrmp_channel, AccountIdOf, CollatorSessionKeys, RuntimeCallOf, SlotDurations,
38};
39use sp_core::Get;
40use sp_keyring::Sr25519Keyring::*;
41use sp_runtime::{traits::TrailingZeroInput, AccountId32};
42use xcm::latest::prelude::*;
43use xcm_executor::traits::ConvertLocation;
44
45#[impl_trait_for_tuples::impl_for_tuples(30)]
47pub trait VerifyTransactionOutcome {
48 fn verify_outcome(&self);
49}
50
51impl VerifyTransactionOutcome for Box<dyn VerifyTransactionOutcome> {
52 fn verify_outcome(&self) {
53 VerifyTransactionOutcome::verify_outcome(&**self)
54 }
55}
56
57pub struct VerifySubmitGrandpaFinalityProofOutcome<Runtime, GPI>
59where
60 Runtime: BridgeGrandpaConfig<GPI>,
61 GPI: 'static,
62{
63 expected_best_hash: BridgedBlockHash<Runtime, GPI>,
64}
65
66impl<Runtime, GPI> VerifySubmitGrandpaFinalityProofOutcome<Runtime, GPI>
67where
68 Runtime: BridgeGrandpaConfig<GPI>,
69 GPI: 'static,
70{
71 pub fn expect_best_header_hash(
73 expected_best_hash: BridgedBlockHash<Runtime, GPI>,
74 ) -> Box<dyn VerifyTransactionOutcome> {
75 Box::new(Self { expected_best_hash })
76 }
77}
78
79impl<Runtime, GPI> VerifyTransactionOutcome
80 for VerifySubmitGrandpaFinalityProofOutcome<Runtime, GPI>
81where
82 Runtime: BridgeGrandpaConfig<GPI>,
83 GPI: 'static,
84{
85 fn verify_outcome(&self) {
86 assert_eq!(
87 pallet_bridge_grandpa::BestFinalized::<Runtime, GPI>::get().unwrap().1,
88 self.expected_best_hash
89 );
90 assert!(pallet_bridge_grandpa::ImportedHeaders::<Runtime, GPI>::contains_key(
91 self.expected_best_hash
92 ));
93 }
94}
95
96pub struct VerifySubmitParachainHeaderProofOutcome<Runtime, PPI> {
98 bridged_para_id: u32,
99 expected_best_hash: ParaHash,
100 _marker: PhantomData<(Runtime, PPI)>,
101}
102
103impl<Runtime, PPI> VerifySubmitParachainHeaderProofOutcome<Runtime, PPI>
104where
105 Runtime: BridgeParachainsConfig<PPI>,
106 PPI: 'static,
107{
108 pub fn expect_best_header_hash(
110 bridged_para_id: u32,
111 expected_best_hash: ParaHash,
112 ) -> Box<dyn VerifyTransactionOutcome> {
113 Box::new(Self { bridged_para_id, expected_best_hash, _marker: PhantomData })
114 }
115}
116
117impl<Runtime, PPI> VerifyTransactionOutcome
118 for VerifySubmitParachainHeaderProofOutcome<Runtime, PPI>
119where
120 Runtime: BridgeParachainsConfig<PPI>,
121 PPI: 'static,
122{
123 fn verify_outcome(&self) {
124 assert_eq!(
125 pallet_bridge_parachains::ParasInfo::<Runtime, PPI>::get(ParaId(self.bridged_para_id))
126 .map(|info| info.best_head_hash.head_hash),
127 Some(self.expected_best_hash),
128 );
129 }
130}
131
132pub struct VerifySubmitMessagesProofOutcome<Runtime: BridgeMessagesConfig<MPI>, MPI: 'static> {
134 lane: LaneIdOf<Runtime, MPI>,
135 expected_nonce: MessageNonce,
136 _marker: PhantomData<(Runtime, MPI)>,
137}
138
139impl<Runtime, MPI> VerifySubmitMessagesProofOutcome<Runtime, MPI>
140where
141 Runtime: BridgeMessagesConfig<MPI>,
142 MPI: 'static,
143{
144 pub fn expect_last_delivered_nonce(
146 lane: LaneIdOf<Runtime, MPI>,
147 expected_nonce: MessageNonce,
148 ) -> Box<dyn VerifyTransactionOutcome> {
149 Box::new(Self { lane, expected_nonce, _marker: PhantomData })
150 }
151}
152
153impl<Runtime, MPI> VerifyTransactionOutcome for VerifySubmitMessagesProofOutcome<Runtime, MPI>
154where
155 Runtime: BridgeMessagesConfig<MPI>,
156 MPI: 'static,
157{
158 fn verify_outcome(&self) {
159 assert_eq!(
160 pallet_bridge_messages::InboundLanes::<Runtime, MPI>::get(self.lane)
161 .map(|d| d.last_delivered_nonce()),
162 Some(self.expected_nonce),
163 );
164 }
165}
166
167pub struct VerifyRelayerRewarded<Runtime: pallet_bridge_relayers::Config<RPI>, RPI: 'static> {
169 relayer: Runtime::AccountId,
170 reward_params: Runtime::Reward,
171}
172
173impl<Runtime, RPI> VerifyRelayerRewarded<Runtime, RPI>
174where
175 Runtime: pallet_bridge_relayers::Config<RPI>,
176 RPI: 'static,
177{
178 pub fn expect_relayer_reward(
180 relayer: Runtime::AccountId,
181 reward_params: impl Into<Runtime::Reward>,
182 ) -> Box<dyn VerifyTransactionOutcome> {
183 Box::new(Self { relayer, reward_params: reward_params.into() })
184 }
185}
186
187impl<Runtime, RPI> VerifyTransactionOutcome for VerifyRelayerRewarded<Runtime, RPI>
188where
189 Runtime: pallet_bridge_relayers::Config<RPI>,
190 RPI: 'static,
191{
192 fn verify_outcome(&self) {
193 assert!(pallet_bridge_relayers::RelayerRewards::<Runtime, RPI>::get(
194 &self.relayer,
195 &self.reward_params,
196 )
197 .is_some());
198 }
199}
200
201pub struct VerifyRelayerBalance<Runtime: pallet_balances::Config> {
203 relayer: Runtime::AccountId,
204 balance: Runtime::Balance,
205}
206
207impl<Runtime> VerifyRelayerBalance<Runtime>
208where
209 Runtime: pallet_balances::Config,
210{
211 pub fn expect_relayer_balance(
213 relayer: Runtime::AccountId,
214 balance: Runtime::Balance,
215 ) -> Box<dyn VerifyTransactionOutcome> {
216 Box::new(Self { relayer, balance })
217 }
218}
219
220impl<Runtime> VerifyTransactionOutcome for VerifyRelayerBalance<Runtime>
221where
222 Runtime: pallet_balances::Config,
223{
224 fn verify_outcome(&self) {
225 assert_eq!(pallet_balances::Pallet::<Runtime>::free_balance(&self.relayer), self.balance,);
226 }
227}
228
229pub(crate) fn initialize_bridge_grandpa_pallet<Runtime, GPI>(
231 init_data: bp_header_chain::InitializationData<BridgedHeader<Runtime, GPI>>,
232) where
233 Runtime: BridgeGrandpaConfig<GPI>
234 + cumulus_pallet_parachain_system::Config
235 + pallet_timestamp::Config,
236{
237 pallet_bridge_grandpa::Pallet::<Runtime, GPI>::initialize(
238 RuntimeHelper::<Runtime>::root_origin(),
239 init_data,
240 )
241 .unwrap();
242}
243
244pub type CallsAndVerifiers<Runtime> =
246 Vec<(RuntimeCallOf<Runtime>, Box<dyn VerifyTransactionOutcome>)>;
247
248pub type InboundRelayerId<Runtime, MPI> = bp_runtime::AccountIdOf<BridgedChainOf<Runtime, MPI>>;
249
250pub fn relayer_id_at_bridged_chain<Runtime: pallet_bridge_messages::Config<MPI>, MPI>(
252) -> InboundRelayerId<Runtime, MPI> {
253 Decode::decode(&mut TrailingZeroInput::zeroes()).unwrap()
254}
255
256pub fn relayed_incoming_message_works<Runtime, AllPalletsWithoutSystem, MPI>(
259 collator_session_key: CollatorSessionKeys<Runtime>,
260 slot_durations: SlotDurations,
261 runtime_para_id: u32,
262 sibling_parachain_id: u32,
263 local_relay_chain_id: NetworkId,
264 construct_and_apply_extrinsic: fn(
265 sp_keyring::Sr25519Keyring,
266 RuntimeCallOf<Runtime>,
267 ) -> sp_runtime::DispatchOutcome,
268 prepare_message_proof_import: impl FnOnce(
269 Runtime::AccountId,
270 InboundRelayerId<Runtime, MPI>,
271 InteriorLocation,
272 MessageNonce,
273 Xcm<()>,
274 bp_runtime::ChainId,
275 ) -> CallsAndVerifiers<Runtime>,
276) where
277 Runtime: BasicParachainRuntime + cumulus_pallet_xcmp_queue::Config + BridgeMessagesConfig<MPI>,
278 AllPalletsWithoutSystem:
279 OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
280 MPI: 'static,
281 AccountIdOf<Runtime>: From<AccountId32>,
282{
283 let relayer_at_target = Bob;
284 let relayer_id_on_target: AccountId32 = relayer_at_target.public().into();
285 let relayer_id_on_source = relayer_id_at_bridged_chain::<Runtime, MPI>();
286 let bridged_chain_id = Runtime::BridgedChain::ID;
287
288 assert_ne!(runtime_para_id, sibling_parachain_id);
289
290 run_test::<Runtime, _>(
291 collator_session_key,
292 runtime_para_id,
293 vec![(
294 relayer_id_on_target.clone().into(),
295 core::cmp::max::<Runtime::Balance>(Runtime::ExistentialDeposit::get(), 1u32.into()) *
300 100_000_000u32.into(),
301 )],
302 || {
303 let mut alice = [0u8; 32];
304 alice[0] = 1;
305
306 let included_head = RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::run_to_block(
307 2,
308 AccountId::from(alice).into(),
309 );
310 mock_open_hrmp_channel::<Runtime, cumulus_pallet_parachain_system::Pallet<Runtime>>(
311 runtime_para_id.into(),
312 sibling_parachain_id.into(),
313 included_head,
314 &alice,
315 &slot_durations,
316 );
317
318 let message_destination: InteriorLocation =
321 [GlobalConsensus(local_relay_chain_id), Parachain(sibling_parachain_id)].into();
322 let message_nonce = 1;
324
325 let xcm = vec![Instruction::<()>::ClearOrigin; 42];
326 let expected_dispatch = xcm::latest::Xcm::<()>({
327 let mut expected_instructions = xcm.clone();
328 expected_instructions.insert(
330 0,
331 DescendOrigin([PalletInstance(
332 <pallet_bridge_messages::Pallet<Runtime, MPI> as PalletInfoAccess>::index()
333 as u8,
334 )].into()),
335 );
336 expected_instructions
337 });
338
339 execute_and_verify_calls::<Runtime>(
340 relayer_at_target,
341 construct_and_apply_extrinsic,
342 prepare_message_proof_import(
343 relayer_id_on_target.clone().into(),
344 relayer_id_on_source.clone().into(),
345 message_destination,
346 message_nonce,
347 xcm.clone().into(),
348 bridged_chain_id,
349 ),
350 );
351
352 let imported_xcm =
354 RuntimeHelper::<cumulus_pallet_xcmp_queue::Pallet<Runtime>>::take_xcm(
355 sibling_parachain_id.into(),
356 )
357 .unwrap();
358 let dispatched = xcm::latest::Xcm::<()>::try_from(imported_xcm).unwrap();
359 let mut dispatched_clone = dispatched.clone();
360 for (idx, expected_instr) in expected_dispatch.0.iter().enumerate() {
361 assert_eq!(expected_instr, &dispatched.0[idx]);
362 assert_eq!(expected_instr, &dispatched_clone.0.remove(0));
363 }
364 match dispatched_clone.0.len() {
365 0 => (),
366 1 => assert!(matches!(dispatched_clone.0[0], SetTopic(_))),
367 count => assert!(false, "Unexpected messages count: {:?}", count),
368 }
369 },
370 )
371}
372
373fn execute_and_verify_calls<Runtime: frame_system::Config>(
375 submitter: sp_keyring::Sr25519Keyring,
376 construct_and_apply_extrinsic: fn(
377 sp_keyring::Sr25519Keyring,
378 RuntimeCallOf<Runtime>,
379 ) -> sp_runtime::DispatchOutcome,
380 calls_and_verifiers: CallsAndVerifiers<Runtime>,
381) {
382 for (call, verifier) in calls_and_verifiers {
383 let dispatch_outcome = construct_and_apply_extrinsic(submitter, call);
384 assert_ok!(dispatch_outcome);
385 verifier.verify_outcome();
386 }
387}
388
389pub(crate) mod for_pallet_xcm_bridge_hub {
390 use super::{super::for_pallet_xcm_bridge_hub::*, *};
391
392 pub fn ensure_opened_bridge<
395 Runtime,
396 XcmOverBridgePalletInstance,
397 LocationToAccountId,
398 TokenLocation>
399 (source: Location, destination: InteriorLocation, is_paid_xcm_execution: bool, bridge_opener: impl Fn(pallet_xcm_bridge_hub::BridgeLocations, Option<Asset>)) -> (pallet_xcm_bridge_hub::BridgeLocations, pallet_xcm_bridge_hub::LaneIdOf<Runtime, XcmOverBridgePalletInstance>)
400 where
401 Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig<XcmOverBridgePalletInstance>,
402 XcmOverBridgePalletInstance: 'static,
403 <Runtime as frame_system::Config>::RuntimeCall: GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>,
404 <Runtime as pallet_balances::Config>::Balance: From<<<Runtime as pallet_bridge_messages::Config<<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeMessagesPalletInstance>>::ThisChain as bp_runtime::Chain>::Balance>,
405 <Runtime as pallet_balances::Config>::Balance: From<u128>,
406 LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>,
407 TokenLocation: Get<Location>
408 {
409 let locations =
411 pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::bridge_locations(
412 source.clone().into(),
413 destination.clone().into(),
414 )
415 .expect("valid bridge locations");
416 assert!(pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get(
417 locations.bridge_id()
418 )
419 .is_none());
420
421 if !<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::AllowWithoutBridgeDeposit::contains(&source) {
423 let bridge_deposit =
425 <Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeDeposit::get();
426 let balance_needed = <Runtime as pallet_balances::Config>::ExistentialDeposit::get() + bridge_deposit.into();
427
428 let source_account_id = LocationToAccountId::convert_location(&source).expect("valid location");
429 let _ = <pallet_balances::Pallet<Runtime>>::mint_into(&source_account_id, balance_needed)
430 .expect("mint_into passes");
431 };
432
433 let maybe_paid_execution = if is_paid_xcm_execution {
434 let buy_execution_fee_amount = 5_000_000_000_000_u128;
436 let buy_execution_fee = (TokenLocation::get(), buy_execution_fee_amount).into();
437
438 let balance_needed = <Runtime as pallet_balances::Config>::ExistentialDeposit::get() +
439 buy_execution_fee_amount.into();
440 let source_account_id =
441 LocationToAccountId::convert_location(&source).expect("valid location");
442 let _ =
443 <pallet_balances::Pallet<Runtime>>::mint_into(&source_account_id, balance_needed)
444 .expect("mint_into passes");
445 Some(buy_execution_fee)
446 } else {
447 None
448 };
449
450 bridge_opener(*locations.clone(), maybe_paid_execution);
452
453 let bridge = pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get(
455 locations.bridge_id(),
456 )
457 .expect("opened bridge");
458
459 assert_ok!(
461 pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::do_try_state()
462 );
463
464 (*locations, bridge.lane_id)
466 }
467
468 pub fn open_bridge_with_extrinsic<Runtime, XcmOverBridgePalletInstance>(
470 (origin, origin_kind): (Location, OriginKind),
471 bridge_destination_universal_location: InteriorLocation,
472 maybe_paid_execution: Option<Asset>,
473 ) where
474 Runtime: frame_system::Config
475 + pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>
476 + cumulus_pallet_parachain_system::Config
477 + pallet_xcm::Config,
478 XcmOverBridgePalletInstance: 'static,
479 <Runtime as frame_system::Config>::RuntimeCall:
480 GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>,
481 {
482 let open_bridge_call = RuntimeCallOf::<Runtime>::from(BridgeXcmOverBridgeCall::<
484 Runtime,
485 XcmOverBridgePalletInstance,
486 >::open_bridge {
487 bridge_destination_universal_location: Box::new(
488 bridge_destination_universal_location.clone().into(),
489 ),
490 });
491
492 assert_ok!(RuntimeHelper::<Runtime>::execute_as_origin(
494 (origin, origin_kind),
495 open_bridge_call,
496 maybe_paid_execution
497 )
498 .ensure_complete());
499 }
500
501 pub fn open_bridge_with_storage<Runtime, XcmOverBridgePalletInstance>(
504 locations: pallet_xcm_bridge_hub::BridgeLocations,
505 lane_id: pallet_xcm_bridge_hub::LaneIdOf<Runtime, XcmOverBridgePalletInstance>,
506 ) where
507 Runtime: pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>,
508 XcmOverBridgePalletInstance: 'static,
509 {
510 assert_ok!(
512 pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::do_open_bridge(
513 Box::new(locations),
514 lane_id,
515 true
516 )
517 );
518 }
519
520 pub fn close_bridge<Runtime, XcmOverBridgePalletInstance, LocationToAccountId, TokenLocation>(
522 expected_source: Location,
523 bridge_destination_universal_location: InteriorLocation,
524 (origin, origin_kind): (Location, OriginKind),
525 is_paid_xcm_execution: bool
526 ) where
527 Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig<XcmOverBridgePalletInstance>,
528 XcmOverBridgePalletInstance: 'static,
529 <Runtime as frame_system::Config>::RuntimeCall: GetDispatchInfo + From<BridgeXcmOverBridgeCall<Runtime, XcmOverBridgePalletInstance>>,
530 <Runtime as pallet_balances::Config>::Balance: From<<<Runtime as pallet_bridge_messages::Config<<Runtime as pallet_xcm_bridge_hub::Config<XcmOverBridgePalletInstance>>::BridgeMessagesPalletInstance>>::ThisChain as bp_runtime::Chain>::Balance>,
531 <Runtime as pallet_balances::Config>::Balance: From<u128>,
532 LocationToAccountId: ConvertLocation<AccountIdOf<Runtime>>,
533 TokenLocation: Get<Location>
534 {
535 let locations =
537 pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::bridge_locations(
538 expected_source.clone().into(),
539 bridge_destination_universal_location.clone().into(),
540 )
541 .expect("valid bridge locations");
542 assert!(pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get(
543 locations.bridge_id()
544 )
545 .is_some());
546
547 let maybe_paid_execution = if is_paid_xcm_execution {
549 let buy_execution_fee_amount = 2_500_000_000_000_u128;
551 let buy_execution_fee = (TokenLocation::get(), buy_execution_fee_amount).into();
552
553 let balance_needed = <Runtime as pallet_balances::Config>::ExistentialDeposit::get() +
554 buy_execution_fee_amount.into();
555 let source_account_id =
556 LocationToAccountId::convert_location(&expected_source).expect("valid location");
557 let _ =
558 <pallet_balances::Pallet<Runtime>>::mint_into(&source_account_id, balance_needed)
559 .expect("mint_into passes");
560 Some(buy_execution_fee)
561 } else {
562 None
563 };
564
565 let close_bridge_call = RuntimeCallOf::<Runtime>::from(BridgeXcmOverBridgeCall::<
567 Runtime,
568 XcmOverBridgePalletInstance,
569 >::close_bridge {
570 bridge_destination_universal_location: Box::new(
571 bridge_destination_universal_location.into(),
572 ),
573 may_prune_messages: 16,
574 });
575
576 assert_ok!(RuntimeHelper::<Runtime>::execute_as_origin(
578 (origin, origin_kind),
579 close_bridge_call,
580 maybe_paid_execution
581 )
582 .ensure_complete());
583
584 assert!(pallet_xcm_bridge_hub::Bridges::<Runtime, XcmOverBridgePalletInstance>::get(
586 locations.bridge_id()
587 )
588 .is_none());
589
590 assert_ok!(
592 pallet_xcm_bridge_hub::Pallet::<Runtime, XcmOverBridgePalletInstance>::do_try_state()
593 );
594 }
595}