referrerpolicy=no-referrer-when-downgrade

snowbridge_runtime_test_common/
lib.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
3
4use codec::Encode;
5use frame_support::{
6	assert_err, assert_ok,
7	traits::{fungible::Mutate, OnFinalize, OnInitialize},
8};
9use frame_system::pallet_prelude::BlockNumberFor;
10use parachains_runtimes_test_utils::{
11	AccountIdOf, BalanceOf, CollatorSessionKeys, ExtBuilder, ValidatorIdOf, XcmReceivedFrom,
12};
13use snowbridge_core::{ChannelId, ParaId};
14use snowbridge_pallet_ethereum_client_fixtures::*;
15use sp_core::{Get, H160, U256};
16use sp_keyring::Sr25519Keyring::*;
17use sp_runtime::{traits::Header, AccountId32, DigestItem, SaturatedConversion, Saturating};
18use xcm::latest::prelude::*;
19use xcm_executor::XcmExecutor;
20
21type RuntimeHelper<Runtime, AllPalletsWithoutSystem = ()> =
22	parachains_runtimes_test_utils::RuntimeHelper<Runtime, AllPalletsWithoutSystem>;
23
24pub fn initial_fund<Runtime>(assethub_parachain_id: u32, initial_amount: u128)
25where
26	Runtime: frame_system::Config + pallet_balances::Config,
27{
28	// fund asset hub sovereign account enough so it can pay fees
29	let asset_hub_sovereign_account =
30		snowbridge_core::sibling_sovereign_account::<Runtime>(assethub_parachain_id.into());
31	<pallet_balances::Pallet<Runtime>>::mint_into(
32		&asset_hub_sovereign_account,
33		initial_amount.saturated_into::<BalanceOf<Runtime>>(),
34	)
35	.unwrap();
36}
37
38pub fn send_transfer_token_message<Runtime, XcmConfig>(
39	ethereum_chain_id: u64,
40	assethub_parachain_id: u32,
41	weth_contract_address: H160,
42	destination_address: H160,
43	fee_amount: u128,
44) -> Outcome
45where
46	Runtime: frame_system::Config
47		+ pallet_balances::Config
48		+ pallet_session::Config
49		+ pallet_xcm::Config
50		+ parachain_info::Config
51		+ pallet_collator_selection::Config
52		+ cumulus_pallet_parachain_system::Config
53		+ snowbridge_pallet_outbound_queue::Config
54		+ pallet_timestamp::Config,
55	XcmConfig: xcm_executor::Config,
56{
57	let assethub_parachain_location = Location::new(1, Parachain(assethub_parachain_id));
58	let asset = Asset {
59		id: AssetId(Location::new(
60			0,
61			[AccountKey20 { network: None, key: weth_contract_address.into() }],
62		)),
63		fun: Fungible(1000000000),
64	};
65	let assets = vec![asset.clone()];
66
67	let inner_xcm = Xcm(vec![
68		WithdrawAsset(Assets::from(assets.clone())),
69		ClearOrigin,
70		BuyExecution { fees: asset, weight_limit: Unlimited },
71		DepositAsset {
72			assets: Wild(All),
73			beneficiary: Location::new(
74				0,
75				[AccountKey20 { network: None, key: destination_address.into() }],
76			),
77		},
78		SetTopic([0; 32]),
79	]);
80
81	let fee =
82		Asset { id: AssetId(Location { parents: 1, interior: Here }), fun: Fungible(fee_amount) };
83
84	// prepare transfer token message
85	let xcm = Xcm(vec![
86		WithdrawAsset(Assets::from(vec![fee.clone()])),
87		BuyExecution { fees: fee, weight_limit: Unlimited },
88		ExportMessage {
89			network: Ethereum { chain_id: ethereum_chain_id },
90			destination: Here,
91			xcm: inner_xcm,
92		},
93	]);
94
95	// execute XCM
96	let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
97	XcmExecutor::<XcmConfig>::prepare_and_execute(
98		assethub_parachain_location,
99		xcm,
100		&mut hash,
101		RuntimeHelper::<Runtime>::xcm_max_weight(XcmReceivedFrom::Sibling),
102		Weight::zero(),
103	)
104}
105
106pub fn send_transfer_token_message_success<Runtime, XcmConfig>(
107	ethereum_chain_id: u64,
108	collator_session_key: CollatorSessionKeys<Runtime>,
109	runtime_para_id: u32,
110	assethub_parachain_id: u32,
111	weth_contract_address: H160,
112	destination_address: H160,
113	fee_amount: u128,
114	snowbridge_pallet_outbound_queue: Box<
115		dyn Fn(Vec<u8>) -> Option<snowbridge_pallet_outbound_queue::Event<Runtime>>,
116	>,
117) where
118	Runtime: frame_system::Config
119		+ pallet_balances::Config
120		+ pallet_session::Config
121		+ pallet_xcm::Config
122		+ parachain_info::Config
123		+ pallet_collator_selection::Config
124		+ pallet_message_queue::Config
125		+ cumulus_pallet_parachain_system::Config
126		+ snowbridge_pallet_outbound_queue::Config
127		+ snowbridge_pallet_system::Config
128		+ pallet_timestamp::Config,
129	XcmConfig: xcm_executor::Config,
130	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
131	<Runtime as frame_system::Config>::AccountId: From<sp_runtime::AccountId32> + AsRef<[u8]>,
132{
133	ExtBuilder::<Runtime>::default()
134		.with_collators(collator_session_key.collators())
135		.with_session_keys(collator_session_key.session_keys())
136		.with_para_id(runtime_para_id.into())
137		.with_tracing()
138		.build()
139		.execute_with(|| {
140			<snowbridge_pallet_system::Pallet<Runtime>>::initialize(
141				runtime_para_id.into(),
142				assethub_parachain_id.into(),
143			)
144			.unwrap();
145
146			// fund asset hub sovereign account enough so it can pay fees
147			initial_fund::<Runtime>(assethub_parachain_id, 5_000_000_000_000);
148
149			let outcome = send_transfer_token_message::<Runtime, XcmConfig>(
150				ethereum_chain_id,
151				assethub_parachain_id,
152				weth_contract_address,
153				destination_address,
154				fee_amount,
155			);
156
157			assert_ok!(outcome.ensure_complete());
158
159			// check events
160			let mut events = <frame_system::Pallet<Runtime>>::events()
161				.into_iter()
162				.filter_map(|e| snowbridge_pallet_outbound_queue(e.event.encode()));
163			assert!(events.any(|e| matches!(
164				e,
165				snowbridge_pallet_outbound_queue::Event::MessageQueued { .. }
166			)));
167
168			let block_number = <frame_system::Pallet<Runtime>>::block_number();
169			let next_block_number = <frame_system::Pallet<Runtime>>::block_number()
170				.saturating_add(BlockNumberFor::<Runtime>::from(1u32));
171
172			// finish current block
173			<pallet_message_queue::Pallet<Runtime>>::on_finalize(block_number);
174			<snowbridge_pallet_outbound_queue::Pallet<Runtime>>::on_finalize(block_number);
175			<frame_system::Pallet<Runtime>>::on_finalize(block_number);
176
177			// start next block
178			<frame_system::Pallet<Runtime>>::set_block_number(next_block_number);
179			<frame_system::Pallet<Runtime>>::on_initialize(next_block_number);
180			<snowbridge_pallet_outbound_queue::Pallet<Runtime>>::on_initialize(next_block_number);
181			<pallet_message_queue::Pallet<Runtime>>::on_initialize(next_block_number);
182
183			// finish next block
184			<pallet_message_queue::Pallet<Runtime>>::on_finalize(next_block_number);
185			<snowbridge_pallet_outbound_queue::Pallet<Runtime>>::on_finalize(next_block_number);
186			let included_head = <frame_system::Pallet<Runtime>>::finalize();
187
188			let origin: ParaId = assethub_parachain_id.into();
189			let channel_id: ChannelId = origin.into();
190
191			let nonce = snowbridge_pallet_outbound_queue::Nonce::<Runtime>::try_get(channel_id);
192			assert_ok!(nonce);
193			assert_eq!(nonce.unwrap(), 1);
194
195			let digest = included_head.digest();
196
197			let digest_items = digest.logs();
198			assert!(digest_items.len() == 1 && digest_items[0].as_other().is_some());
199		});
200}
201
202pub fn ethereum_outbound_queue_processes_messages_before_message_queue_works<
203	Runtime,
204	XcmConfig,
205	AllPalletsWithoutSystem,
206>(
207	ethereum_chain_id: u64,
208	collator_session_key: CollatorSessionKeys<Runtime>,
209	runtime_para_id: u32,
210	assethub_parachain_id: u32,
211	weth_contract_address: H160,
212	destination_address: H160,
213	fee_amount: u128,
214	snowbridge_pallet_outbound_queue: Box<
215		dyn Fn(Vec<u8>) -> Option<snowbridge_pallet_outbound_queue::Event<Runtime>>,
216	>,
217) where
218	Runtime: frame_system::Config
219		+ pallet_balances::Config
220		+ pallet_session::Config
221		+ pallet_xcm::Config
222		+ parachain_info::Config
223		+ pallet_collator_selection::Config
224		+ pallet_message_queue::Config
225		+ cumulus_pallet_parachain_system::Config
226		+ snowbridge_pallet_outbound_queue::Config
227		+ snowbridge_pallet_system::Config
228		+ pallet_timestamp::Config,
229	XcmConfig: xcm_executor::Config,
230	AllPalletsWithoutSystem:
231		OnInitialize<BlockNumberFor<Runtime>> + OnFinalize<BlockNumberFor<Runtime>>,
232	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
233	<Runtime as frame_system::Config>::AccountId: From<sp_runtime::AccountId32> + AsRef<[u8]>,
234{
235	ExtBuilder::<Runtime>::default()
236		.with_collators(collator_session_key.collators())
237		.with_session_keys(collator_session_key.session_keys())
238		.with_para_id(runtime_para_id.into())
239		.with_tracing()
240		.build()
241		.execute_with(|| {
242			<snowbridge_pallet_system::Pallet<Runtime>>::initialize(
243				runtime_para_id.into(),
244				assethub_parachain_id.into(),
245			)
246			.unwrap();
247
248			// fund asset hub sovereign account enough so it can pay fees
249			initial_fund::<Runtime>(assethub_parachain_id, 5_000_000_000_000);
250
251			let outcome = send_transfer_token_message::<Runtime, XcmConfig>(
252				ethereum_chain_id,
253				assethub_parachain_id,
254				weth_contract_address,
255				destination_address,
256				fee_amount,
257			);
258
259			assert_ok!(outcome.ensure_complete());
260
261			// check events
262			let mut events = <frame_system::Pallet<Runtime>>::events()
263				.into_iter()
264				.filter_map(|e| snowbridge_pallet_outbound_queue(e.event.encode()));
265			assert!(events.any(|e| matches!(
266				e,
267				snowbridge_pallet_outbound_queue::Event::MessageQueued { .. }
268			)));
269
270			let next_block_number: U256 = <frame_system::Pallet<Runtime>>::block_number()
271				.saturating_add(BlockNumberFor::<Runtime>::from(1u32))
272				.into();
273
274			let included_head =
275				RuntimeHelper::<Runtime, AllPalletsWithoutSystem>::run_to_block_with_finalize(
276					next_block_number.as_u32(),
277				);
278			let digest = included_head.digest();
279			let digest_items = digest.logs();
280
281			let mut found_outbound_digest = false;
282			for digest_item in digest_items {
283				match digest_item {
284					DigestItem::Other(_) => found_outbound_digest = true,
285					_ => {},
286				}
287			}
288
289			assert_eq!(found_outbound_digest, true);
290		});
291}
292
293pub fn send_unpaid_transfer_token_message<Runtime, XcmConfig>(
294	ethereum_chain_id: u64,
295	collator_session_key: CollatorSessionKeys<Runtime>,
296	runtime_para_id: u32,
297	assethub_parachain_id: u32,
298	weth_contract_address: H160,
299	destination_contract: H160,
300) where
301	Runtime: frame_system::Config
302		+ pallet_balances::Config
303		+ pallet_session::Config
304		+ pallet_xcm::Config
305		+ parachain_info::Config
306		+ pallet_collator_selection::Config
307		+ cumulus_pallet_parachain_system::Config
308		+ snowbridge_pallet_outbound_queue::Config
309		+ snowbridge_pallet_system::Config
310		+ pallet_timestamp::Config,
311	XcmConfig: xcm_executor::Config,
312	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
313{
314	let assethub_parachain_location = Location::new(1, Parachain(assethub_parachain_id));
315
316	ExtBuilder::<Runtime>::default()
317		.with_collators(collator_session_key.collators())
318		.with_session_keys(collator_session_key.session_keys())
319		.with_para_id(runtime_para_id.into())
320		.with_tracing()
321		.build()
322		.execute_with(|| {
323			<snowbridge_pallet_system::Pallet<Runtime>>::initialize(
324				runtime_para_id.into(),
325				assethub_parachain_id.into(),
326			)
327			.unwrap();
328
329			let asset = Asset {
330				id: AssetId(Location::new(
331					0,
332					[AccountKey20 { network: None, key: weth_contract_address.into() }],
333				)),
334				fun: Fungible(1000000000),
335			};
336			let assets = vec![asset.clone()];
337
338			let inner_xcm = Xcm(vec![
339				WithdrawAsset(Assets::from(assets.clone())),
340				ClearOrigin,
341				BuyExecution { fees: asset, weight_limit: Unlimited },
342				DepositAsset {
343					assets: Wild(AllCounted(1)),
344					beneficiary: Location::new(
345						0,
346						[AccountKey20 { network: None, key: destination_contract.into() }],
347					),
348				},
349				SetTopic([0; 32]),
350			]);
351
352			// prepare transfer token message
353			let xcm = Xcm(vec![
354				UnpaidExecution { weight_limit: Unlimited, check_origin: None },
355				ExportMessage {
356					network: Ethereum { chain_id: ethereum_chain_id },
357					destination: Here,
358					xcm: inner_xcm,
359				},
360			]);
361
362			// execute XCM
363			let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
364			let outcome = XcmExecutor::<XcmConfig>::prepare_and_execute(
365				assethub_parachain_location,
366				xcm,
367				&mut hash,
368				RuntimeHelper::<Runtime>::xcm_max_weight(XcmReceivedFrom::Sibling),
369				Weight::zero(),
370			);
371			assert_ok!(outcome.ensure_complete());
372		});
373}
374
375#[allow(clippy::too_many_arguments)]
376pub fn send_transfer_token_message_failure<Runtime, XcmConfig>(
377	ethereum_chain_id: u64,
378	collator_session_key: CollatorSessionKeys<Runtime>,
379	runtime_para_id: u32,
380	assethub_parachain_id: u32,
381	initial_amount: u128,
382	weth_contract_address: H160,
383	destination_address: H160,
384	fee_amount: u128,
385	expected_error: XcmError,
386) where
387	Runtime: frame_system::Config
388		+ pallet_balances::Config
389		+ pallet_session::Config
390		+ pallet_xcm::Config
391		+ parachain_info::Config
392		+ pallet_collator_selection::Config
393		+ cumulus_pallet_parachain_system::Config
394		+ snowbridge_pallet_outbound_queue::Config
395		+ snowbridge_pallet_system::Config
396		+ pallet_timestamp::Config,
397	XcmConfig: xcm_executor::Config,
398	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
399{
400	ExtBuilder::<Runtime>::default()
401		.with_collators(collator_session_key.collators())
402		.with_session_keys(collator_session_key.session_keys())
403		.with_para_id(runtime_para_id.into())
404		.with_tracing()
405		.build()
406		.execute_with(|| {
407			<snowbridge_pallet_system::Pallet<Runtime>>::initialize(
408				runtime_para_id.into(),
409				assethub_parachain_id.into(),
410			)
411			.unwrap();
412
413			// fund asset hub sovereign account enough so it can pay fees
414			initial_fund::<Runtime>(assethub_parachain_id, initial_amount);
415
416			let outcome = send_transfer_token_message::<Runtime, XcmConfig>(
417				ethereum_chain_id,
418				assethub_parachain_id,
419				weth_contract_address,
420				destination_address,
421				fee_amount,
422			);
423			assert_err!(
424				outcome.ensure_complete(),
425				InstructionError { index: 0, error: expected_error }
426			);
427		});
428}
429
430pub fn ethereum_extrinsic<Runtime>(
431	collator_session_key: CollatorSessionKeys<Runtime>,
432	runtime_para_id: u32,
433	construct_and_apply_extrinsic: fn(
434		sp_keyring::Sr25519Keyring,
435		<Runtime as frame_system::Config>::RuntimeCall,
436	) -> sp_runtime::DispatchOutcome,
437) where
438	Runtime: frame_system::Config
439		+ pallet_balances::Config
440		+ pallet_session::Config
441		+ pallet_xcm::Config
442		+ pallet_utility::Config
443		+ parachain_info::Config
444		+ pallet_collator_selection::Config
445		+ cumulus_pallet_parachain_system::Config
446		+ snowbridge_pallet_outbound_queue::Config
447		+ snowbridge_pallet_system::Config
448		+ snowbridge_pallet_ethereum_client::Config
449		+ pallet_timestamp::Config,
450	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
451	<Runtime as pallet_utility::Config>::RuntimeCall:
452		From<snowbridge_pallet_ethereum_client::Call<Runtime>>,
453	<Runtime as frame_system::Config>::RuntimeCall: From<pallet_utility::Call<Runtime>>,
454	AccountIdOf<Runtime>: From<AccountId32>,
455{
456	ExtBuilder::<Runtime>::default()
457		.with_collators(collator_session_key.collators())
458		.with_session_keys(collator_session_key.session_keys())
459		.with_para_id(runtime_para_id.into())
460		.with_tracing()
461		.build()
462		.execute_with(|| {
463			let initial_checkpoint = make_checkpoint();
464			let update = make_finalized_header_update();
465			let sync_committee_update = make_sync_committee_update();
466			let mut invalid_update = make_finalized_header_update();
467			let mut invalid_sync_committee_update = make_sync_committee_update();
468			invalid_update.finalized_header.slot = 4354;
469			invalid_sync_committee_update.finalized_header.slot = 4354;
470
471			let alice = Alice;
472			let alice_account = alice.to_account_id();
473			<pallet_balances::Pallet<Runtime>>::mint_into(
474				&alice_account.clone().into(),
475				10_000_000_000_000_u128.saturated_into::<BalanceOf<Runtime>>(),
476			)
477			.unwrap();
478			let balance_before =
479				<pallet_balances::Pallet<Runtime>>::free_balance(&alice_account.clone().into());
480
481			assert_ok!(<snowbridge_pallet_ethereum_client::Pallet<Runtime>>::force_checkpoint(
482				RuntimeHelper::<Runtime>::root_origin(),
483				initial_checkpoint.clone(),
484			));
485			let balance_after_checkpoint =
486				<pallet_balances::Pallet<Runtime>>::free_balance(&alice_account.clone().into());
487
488			let update_call: <Runtime as pallet_utility::Config>::RuntimeCall =
489				snowbridge_pallet_ethereum_client::Call::<Runtime>::submit {
490					update: Box::new(*update.clone()),
491				}
492				.into();
493
494			let invalid_update_call: <Runtime as pallet_utility::Config>::RuntimeCall =
495				snowbridge_pallet_ethereum_client::Call::<Runtime>::submit {
496					update: Box::new(*invalid_update),
497				}
498				.into();
499
500			let update_sync_committee_call: <Runtime as pallet_utility::Config>::RuntimeCall =
501				snowbridge_pallet_ethereum_client::Call::<Runtime>::submit {
502					update: Box::new(*sync_committee_update),
503				}
504				.into();
505
506			let invalid_update_sync_committee_call: <Runtime as pallet_utility::Config>::RuntimeCall =
507				snowbridge_pallet_ethereum_client::Call::<Runtime>::submit {
508					update: Box::new(*invalid_sync_committee_update),
509				}
510					.into();
511
512			// Finalized header update
513			let update_outcome = construct_and_apply_extrinsic(alice, update_call.into());
514			assert_ok!(update_outcome);
515			let balance_after_update =
516				<pallet_balances::Pallet<Runtime>>::free_balance(&alice_account.clone().into());
517
518			// All the extrinsics in this test do no fit into 1 block
519			let _ = RuntimeHelper::<Runtime>::run_to_block(2, alice_account.clone().into());
520
521			// Invalid finalized header update
522			let invalid_update_outcome =
523				construct_and_apply_extrinsic(alice, invalid_update_call.into());
524			assert_err!(
525				invalid_update_outcome,
526				snowbridge_pallet_ethereum_client::Error::<Runtime>::InvalidUpdateSlot
527			);
528			let balance_after_invalid_update =
529				<pallet_balances::Pallet<Runtime>>::free_balance(&alice_account.clone().into());
530
531			// Sync committee update
532			let sync_committee_outcome =
533				construct_and_apply_extrinsic(alice, update_sync_committee_call.into());
534			assert_ok!(sync_committee_outcome);
535			let balance_after_sync_com_update =
536				<pallet_balances::Pallet<Runtime>>::free_balance(&alice_account.clone().into());
537
538			// Invalid sync committee update
539			let invalid_sync_committee_outcome =
540				construct_and_apply_extrinsic(alice, invalid_update_sync_committee_call.into());
541			assert_err!(
542				invalid_sync_committee_outcome,
543				snowbridge_pallet_ethereum_client::Error::<Runtime>::InvalidUpdateSlot
544			);
545			let balance_after_invalid_sync_com_update =
546				<pallet_balances::Pallet<Runtime>>::free_balance(&alice_account.clone().into());
547
548			// Assert paid operations are charged and free operations are free
549			// Checkpoint is a free operation
550			assert!(balance_before == balance_after_checkpoint);
551			let gap =
552				<Runtime as snowbridge_pallet_ethereum_client::Config>::FreeHeadersInterval::get();
553			// Large enough header gap is free
554			if update.finalized_header.slot >= initial_checkpoint.header.slot + gap as u64 {
555				assert!(balance_after_checkpoint == balance_after_update);
556			} else {
557				// Otherwise paid
558				assert!(balance_after_checkpoint > balance_after_update);
559			}
560			// An invalid update is paid
561			assert!(balance_after_update > balance_after_invalid_update);
562			// A successful sync committee update is free
563			assert!(balance_after_invalid_update == balance_after_sync_com_update);
564			// An invalid sync committee update is paid
565			assert!(balance_after_sync_com_update > balance_after_invalid_sync_com_update);
566		});
567}
568
569pub fn ethereum_to_polkadot_message_extrinsics_work<Runtime>(
570	collator_session_key: CollatorSessionKeys<Runtime>,
571	runtime_para_id: u32,
572	construct_and_apply_extrinsic: fn(
573		sp_keyring::Sr25519Keyring,
574		<Runtime as frame_system::Config>::RuntimeCall,
575	) -> sp_runtime::DispatchOutcome,
576) where
577	Runtime: frame_system::Config
578		+ pallet_balances::Config
579		+ pallet_session::Config
580		+ pallet_xcm::Config
581		+ pallet_utility::Config
582		+ parachain_info::Config
583		+ pallet_collator_selection::Config
584		+ cumulus_pallet_parachain_system::Config
585		+ snowbridge_pallet_outbound_queue::Config
586		+ snowbridge_pallet_system::Config
587		+ snowbridge_pallet_ethereum_client::Config
588		+ pallet_timestamp::Config,
589	ValidatorIdOf<Runtime>: From<AccountIdOf<Runtime>>,
590	<Runtime as pallet_utility::Config>::RuntimeCall:
591		From<snowbridge_pallet_ethereum_client::Call<Runtime>>,
592	<Runtime as frame_system::Config>::RuntimeCall: From<pallet_utility::Call<Runtime>>,
593	AccountIdOf<Runtime>: From<AccountId32>,
594{
595	ExtBuilder::<Runtime>::default()
596		.with_collators(collator_session_key.collators())
597		.with_session_keys(collator_session_key.session_keys())
598		.with_para_id(runtime_para_id.into())
599		.with_tracing()
600		.build()
601		.execute_with(|| {
602			let initial_checkpoint = make_checkpoint();
603			let sync_committee_update = make_sync_committee_update();
604
605			let alice = Alice;
606			let alice_account = alice.to_account_id();
607			<pallet_balances::Pallet<Runtime>>::mint_into(
608				&alice_account.into(),
609				10_000_000_000_000_u128.saturated_into::<BalanceOf<Runtime>>(),
610			)
611			.unwrap();
612
613			assert_ok!(<snowbridge_pallet_ethereum_client::Pallet<Runtime>>::force_checkpoint(
614				RuntimeHelper::<Runtime>::root_origin(),
615				initial_checkpoint,
616			));
617
618			let update_sync_committee_call: <Runtime as pallet_utility::Config>::RuntimeCall =
619				snowbridge_pallet_ethereum_client::Call::<Runtime>::submit {
620					update: Box::new(*sync_committee_update),
621				}
622				.into();
623
624			let sync_committee_outcome =
625				construct_and_apply_extrinsic(alice, update_sync_committee_call.into());
626			assert_ok!(sync_committee_outcome);
627		});
628}