1use 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 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 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 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 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 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 <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 <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 <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 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 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 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 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 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 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 let _ = RuntimeHelper::<Runtime>::run_to_block(2, alice_account.clone().into());
520
521 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 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 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!(balance_before == balance_after_checkpoint);
551 let gap =
552 <Runtime as snowbridge_pallet_ethereum_client::Config>::FreeHeadersInterval::get();
553 if update.finalized_header.slot >= initial_checkpoint.header.slot + gap as u64 {
555 assert!(balance_after_checkpoint == balance_after_update);
556 } else {
557 assert!(balance_after_checkpoint > balance_after_update);
559 }
560 assert!(balance_after_update > balance_after_invalid_update);
562 assert!(balance_after_invalid_update == balance_after_sync_com_update);
564 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}